Skip to content

Commit f4eb525

Browse files
committed
test: set expectations for content blocking and CAR HTTP responses
- test HTTP 200 with CAR for safe subdirectory when sibling is blocked - test HTTP 404 for non-existent paths with CAR format - test HTTP 410 for blocked root CID with CAR format - verify safe content is included and blocked content is excluded from CARs - distinguish between blocked (410) and not found (404) responses These tests document the expected behavior when content blocking interacts with CAR format responses, ensuring proper HTTP status codes and content filtering.
1 parent fea028b commit f4eb525

File tree

7 files changed

+68
-31
lines changed

7 files changed

+68
-31
lines changed

docs/examples/kubo-as-a-library/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ go 1.25
77
replace github.com/ipfs/kubo => ./../../..
88

99
require (
10-
github.com/ipfs/boxo v0.34.1-0.20250830012345-dba0bc4c8c3c
10+
github.com/ipfs/boxo v0.34.1-0.20250830232151-99cef5c5d50c
1111
github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
1212
github.com/libp2p/go-libp2p v0.43.0
1313
github.com/multiformats/go-multiaddr v0.16.1

docs/examples/kubo-as-a-library/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
287287
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
288288
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
289289
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
290-
github.com/ipfs/boxo v0.34.1-0.20250830012345-dba0bc4c8c3c h1:nEMQr6xFfVzrMl+5xNtoL3mQdTiFMVWsGKwdYtwiJtM=
291-
github.com/ipfs/boxo v0.34.1-0.20250830012345-dba0bc4c8c3c/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
290+
github.com/ipfs/boxo v0.34.1-0.20250830232151-99cef5c5d50c h1:zfwjO4+LKVCvUkj5+4u/dDmLMxyKh7DkO2rbYo3T/Wc=
291+
github.com/ipfs/boxo v0.34.1-0.20250830232151-99cef5c5d50c/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
292292
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
293293
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
294294
github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ require (
2222
github.com/hashicorp/go-version v1.7.0
2323
github.com/ipfs-shipyard/nopfs v0.0.14
2424
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0
25-
github.com/ipfs/boxo v0.34.1-0.20250830012345-dba0bc4c8c3c
25+
github.com/ipfs/boxo v0.34.1-0.20250830232151-99cef5c5d50c
2626
github.com/ipfs/go-block-format v0.2.2
2727
github.com/ipfs/go-cid v0.5.0
2828
github.com/ipfs/go-cidutil v0.1.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
354354
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
355355
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
356356
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
357-
github.com/ipfs/boxo v0.34.1-0.20250830012345-dba0bc4c8c3c h1:nEMQr6xFfVzrMl+5xNtoL3mQdTiFMVWsGKwdYtwiJtM=
358-
github.com/ipfs/boxo v0.34.1-0.20250830012345-dba0bc4c8c3c/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
357+
github.com/ipfs/boxo v0.34.1-0.20250830232151-99cef5c5d50c h1:zfwjO4+LKVCvUkj5+4u/dDmLMxyKh7DkO2rbYo3T/Wc=
358+
github.com/ipfs/boxo v0.34.1-0.20250830232151-99cef5c5d50c/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
359359
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
360360
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
361361
github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=

test/cli/content_blocking_test.go

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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

test/dependencies/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ require (
134134
github.com/huin/goupnp v1.3.0 // indirect
135135
github.com/inconshreveable/mousetrap v1.1.0 // indirect
136136
github.com/ipfs/bbloom v0.0.4 // indirect
137-
github.com/ipfs/boxo v0.34.1-0.20250830012345-dba0bc4c8c3c // indirect
137+
github.com/ipfs/boxo v0.34.1-0.20250830232151-99cef5c5d50c // indirect
138138
github.com/ipfs/go-bitfield v1.1.0 // indirect
139139
github.com/ipfs/go-block-format v0.2.2 // indirect
140140
github.com/ipfs/go-cid v0.5.0 // indirect

test/dependencies/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
332332
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
333333
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
334334
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
335-
github.com/ipfs/boxo v0.34.1-0.20250830012345-dba0bc4c8c3c h1:nEMQr6xFfVzrMl+5xNtoL3mQdTiFMVWsGKwdYtwiJtM=
336-
github.com/ipfs/boxo v0.34.1-0.20250830012345-dba0bc4c8c3c/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
335+
github.com/ipfs/boxo v0.34.1-0.20250830232151-99cef5c5d50c h1:zfwjO4+LKVCvUkj5+4u/dDmLMxyKh7DkO2rbYo3T/Wc=
336+
github.com/ipfs/boxo v0.34.1-0.20250830232151-99cef5c5d50c/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
337337
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
338338
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
339339
github.com/ipfs/go-block-format v0.2.2 h1:uecCTgRwDIXyZPgYspaLXoMiMmxQpSx2aq34eNc4YvQ=

0 commit comments

Comments
 (0)