Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.8.2] - 2025-08-31
### Changed
- Relaxed CAR tests to accept both HTTP 200 and 404 for non-existing paths. The response code now depends on implementation details such as locality and cost of path traversal checks. Implementations that can efficiently detect non-existing paths should return 404 (improved behavior per [ipfs/boxo#458](https://github.com/ipfs/boxo/issues/458)). Implementations focusing on stateless streaming and low latency may return 200 with partial CAR up to the missing link (legacy behavior). [#244](https://github.com/ipfs/gateway-conformance/pull/244)

## [0.8.1] - 2025-06-17
### Changed
- DAG-CBOR HTML preview pages previously had to be returned without Cache-Control headers. Now they can use Cache-Control headers similar to those used in generated UnixFS directory listings. [#241](https://github.com/ipfs/gateway-conformance/pull/241)
Expand Down
41 changes: 25 additions & 16 deletions tests/trustless_gateway_car_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,26 +94,35 @@ func TestTrustlessCarPathing(t *testing.T) {
{
Name: "GET default CAR response for non-existing file",
Hint: `
CAR stream of a non-existing path must return 200 OK and all the blocks necessary
to traverse the path up to and including the parent of the first non-existing
segment of the path, in order to allow the client to verify that the request
path does not exist.
The response code depends on implementation details such as the locality and the cost of path traversal checks,
and trade-off between latency and correctness.
Implementations that are able to efficiently detect requested content path does not exist,
should not return CAR response, but a simple 404.
Implementations that are focusing on stateless streaming and low latency are free to return
partial CAR up to the missing link (blocks necessary to traverse the path up to and including
the parent of the first non-existing segment).
`,
Request: Request().
Path("/ipfs/{{cid}}/subdir/i-do-not-exist", subdirTwoSingleBlockFilesFixture.MustGetCidWithCodec(0x70)).
Query("format", "car"),
Response: Expect().
Status(200).
Body(
IsCar().
IgnoreRoots().
HasBlocks(
subdirTwoSingleBlockFilesFixture.MustGetCid(),
subdirTwoSingleBlockFilesFixture.MustGetCid("subdir"),
).
Exactly().
InThatOrder(),
),
Response: AnyOf(
// Stateless streaming implementations: 200 with partial CAR
Expect().
Status(200).
Body(
IsCar().
IgnoreRoots().
HasBlocks(
subdirTwoSingleBlockFilesFixture.MustGetCid(),
subdirTwoSingleBlockFilesFixture.MustGetCid("subdir"),
).
Exactly().
InThatOrder(),
),
// Implementations with efficient path checks: 404 Not Found
Expect().
Status(404),
),
},
}

Expand Down
35 changes: 28 additions & 7 deletions tooling/helpers/car.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,9 @@ func StandardCARTestTransforms(t *testing.T, sts test.SugarTests) test.SugarTest
return out
}

func applyStandardCarResponseHeaders(t *testing.T, st test.SugarTest) test.SugarTest {
resp, ok := st.Response.(test.ExpectBuilder)
if !ok {
t.Fatal("can only apply test transformation on an ExpectBuilder")
}
st.Response = resp.Headers(
// carResponseHeaders returns the standard headers expected for CAR responses
func carResponseHeaders() []test.HeaderBuilder {
return []test.HeaderBuilder{
// TODO: Go always sends Content-Length and it's not possible to explicitly disable the behavior.
// For now, we ignore this check. It should be able to be resolved soon: https://github.com/ipfs/boxo/pull/177
// test.Header("Content-Length").
Expand All @@ -43,7 +40,31 @@ func applyStandardCarResponseHeaders(t *testing.T, st test.SugarTest) test.Sugar
test.Header("Etag").
Hint("Etag must be present for caching purposes").
Not().IsEmpty(),
)
}
}

func applyStandardCarResponseHeaders(t *testing.T, st test.SugarTest) test.SugarTest {
switch resp := st.Response.(type) {
case test.AnyOfExpectBuilder:
// Apply headers only to successful CAR responses (status 200)
transformedExpects := make([]test.ExpectBuilder, 0, len(resp.Expect_))
for _, expect := range resp.Expect_ {
// Only apply CAR headers to 200 responses
// 404/410 responses don't have CAR content, so no CAR headers needed
if expect.StatusCode_ == 200 {
expect = expect.Headers(carResponseHeaders()...)
}
transformedExpects = append(transformedExpects, expect)
}
st.Response = test.AnyOf(transformedExpects...)

case test.ExpectBuilder:
st.Response = resp.Headers(carResponseHeaders()...)

default:
t.Fatal("can only apply test transformation on an ExpectBuilder or AnyOfExpectBuilder")
}

return st
}

Expand Down
Loading