@@ -37,8 +37,21 @@ func TestContentBlocking(t *testing.T) {
3737
3838 // Create CIDs we use in test
3939 h .WriteFile ("parent-dir/blocked-subdir/indirectly-blocked-file.txt" , "indirectly blocked file content" )
40+ h .WriteFile ("parent-dir/safe-subdir/safe-file.txt" , "safe file content" )
4041 allowedParentDirCID := node .IPFS ("add" , "--raw-leaves" , "-Q" , "-r" , "--pin=false" , filepath .Join (h .Dir , "parent-dir" )).Stdout .Trimmed ()
41- blockedSubDirCID := node .IPFS ("add" , "--raw-leaves" , "-Q" , "-r" , "--pin=false" , filepath .Join (h .Dir , "parent-dir" , "blocked-subdir" )).Stdout .Trimmed ()
42+
43+ // Get the CID of subdirectories as they exist within the parent DAG
44+ // Note: These CIDs are different from adding the directories standalone
45+ safeSubDirCID := node .IPFS ("resolve" , "-r" , "/ipfs/" + allowedParentDirCID + "/safe-subdir" ).Stdout .Trimmed ()
46+ safeSubDirCID = strings .TrimPrefix (safeSubDirCID , "/ipfs/" )
47+ blockedSubDirCID := node .IPFS ("resolve" , "-r" , "/ipfs/" + allowedParentDirCID + "/blocked-subdir" ).Stdout .Trimmed ()
48+ blockedSubDirCID = strings .TrimPrefix (blockedSubDirCID , "/ipfs/" )
49+
50+ // Get the CID of the safe file within the safe subdirectory
51+ safeFileCID := node .IPFS ("resolve" , "-r" , "/ipfs/" + allowedParentDirCID + "/safe-subdir/safe-file.txt" ).Stdout .Trimmed ()
52+ safeFileCID = strings .TrimPrefix (safeFileCID , "/ipfs/" )
53+
54+ // Remove the blocked subdirectory from blockstore
4255 node .IPFS ("block" , "rm" , blockedSubDirCID )
4356
4457 h .WriteFile ("directly-blocked-file.txt" , "directly blocked file content" )
@@ -102,34 +115,71 @@ func TestContentBlocking(t *testing.T) {
102115 assert .Equal (t , http .StatusOK , resp .StatusCode )
103116 })
104117
105- // Confirm CAR responses skip blocked subpaths
106- t .Run ("Gateway returns CAR without blocked subpath" , func (t * testing.T ) {
107- resp := client .Get ("/ipfs/" + allowedParentDirCID + "/subdir?format=car" )
118+ // Verify that when requesting a subdirectory as CAR, the response includes
119+ // the safe content even when a sibling directory is blocked
120+ t .Run ("Gateway returns 200 with CAR for safe subdir when sibling is blocked" , func (t * testing.T ) {
121+ // Request the SAFE subdirectory, verifying it's accessible even though a sibling is blocked
122+ resp := client .Get ("/ipfs/" + allowedParentDirCID + "/safe-subdir?format=car" )
108123 assert .Equal (t , http .StatusOK , resp .StatusCode )
109124
110125 bs , err := carstore .NewReadOnly (strings .NewReader (resp .Body ), nil )
111126 assert .NoError (t , err )
112127
113- has , err := bs .Has ( context . Background (), cid . MustParse ( blockedSubDirCID ) )
128+ roots , err := bs .Roots ( )
114129 assert .NoError (t , err )
115- assert .False (t , has )
130+ assert .Equal (t , 1 , len (roots ))
131+ assert .Equal (t , safeSubDirCID , roots [0 ].String ())
132+
133+ // Verify the safe content IS in the CAR
134+ ctx := context .TODO ()
135+ has , err := bs .Has (ctx , cid .MustParse (safeSubDirCID ))
136+ assert .NoError (t , err )
137+ assert .True (t , has , "CAR should include the safe subdirectory" )
138+
139+ // Verify the safe file within the subdirectory IS in the CAR
140+ has , err = bs .Has (ctx , cid .MustParse (safeFileCID ))
141+ assert .NoError (t , err )
142+ assert .True (t , has , "CAR should include the safe file within the subdirectory" )
143+
144+ // Verify the blocked content is NOT in the CAR (it shouldn't be traversed)
145+ has , err = bs .Has (ctx , cid .MustParse (blockedSubDirCID ))
146+ assert .NoError (t , err )
147+ assert .False (t , has , "CAR should not include the blocked subdirectory" )
148+ })
149+
150+ // Test that requesting non-existent path with CAR format returns 404 (not 410)
151+ // This ensures we distinguish between blocked content and missing content
152+ t .Run ("Gateway returns 404 for non-existent path with CAR format" , func (t * testing.T ) {
153+ // Request a non-existent subdirectory as CAR
154+ resp := client .Get ("/ipfs/" + allowedParentDirCID + "/safe-404?format=car" )
155+ assert .Equal (t , http .StatusNotFound , resp .StatusCode , "Non-existent path should return 404 Not Found" )
156+
157+ // Verify response body is not a CAR file but an error message
158+ // CAR files start with specific magic bytes
159+ assert .NotContains (t , resp .Body , "CAR" , "404 response should not contain CAR data" )
160+ // Verify it contains an appropriate error message
161+ assert .Contains (t , resp .Body , "no link named" , "404 response should contain IPFS path resolution error" )
116162 })
117163
118- // TODO: this was already broken in 0.26, but we should fix it
119- t .Run ("Gateway returns CAR without directly blocked CID" , func (t * testing.T ) {
164+ // Test that confirms blocking of children skips them from produced CAR.
165+ t .Run ("Gateway returns 200 with CAR without directly blocked CID" , func (t * testing.T ) {
120166 // First verify that the blocked CID is actually blocked when accessed directly
121167 directResp := client .Get ("/ipfs/" + blockedCID )
122168 assert .Equal (t , http .StatusGone , directResp .StatusCode , "Direct access to blocked CID should return 410" )
123-
169+
124170 allowedDirWithDirectlyBlockedCID := node .IPFS ("add" , "--raw-leaves" , "-Q" , "-rw" , filepath .Join (h .Dir , "directly-blocked-file.txt" )).Stdout .Trimmed ()
125171 t .Logf ("Directory CID containing blocked file: %s" , allowedDirWithDirectlyBlockedCID )
126172 t .Logf ("Blocked CID that should not appear in CAR: %s" , blockedCID )
127173 resp := client .Get ("/ipfs/" + allowedDirWithDirectlyBlockedCID + "?format=car" )
174+
175+ // We expect HTTP 200 because root cid is not blocked, so we start
176+ // streaming directory recursively
128177 assert .Equal (t , http .StatusOK , resp .StatusCode )
129178
130179 bs , err := carstore .NewReadOnly (strings .NewReader (resp .Body ), nil )
131180 assert .NoError (t , err )
132181
182+ // The blocked CID MUST be omitted from HTTP response
133183 has , err := bs .Has (context .Background (), cid .MustParse (blockedCID ))
134184 assert .NoError (t , err )
135185 assert .False (t , has , "Returned CAR should not include blockedCID" )
@@ -149,19 +199,6 @@ func TestContentBlocking(t *testing.T) {
149199 assert .Contains (t , resp .Body , blockedMsg , "Error message should indicate content is blocked" )
150200 })
151201
152- // Confirm CAR responses skip blocked subpaths
153- t .Run ("Gateway returns CAR without blocked subpath" , func (t * testing.T ) {
154- resp := client .Get ("/ipfs/" + allowedParentDirCID + "/subdir?format=car" )
155- assert .Equal (t , http .StatusOK , resp .StatusCode )
156-
157- bs , err := carstore .NewReadOnly (strings .NewReader (resp .Body ), nil )
158- assert .NoError (t , err )
159-
160- has , err := bs .Has (context .Background (), cid .MustParse (blockedSubDirCID ))
161- assert .NoError (t , err )
162- assert .False (t , has , "Returned CAR should not include blockedSubDirCID" )
163- })
164-
165202 // Ok, now the full list of test cases we want to cover in both CLI and Gateway
166203 testCases := []struct {
167204 name string
0 commit comments