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 @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

## [0.14.1] - 2026-08-27
### Added
- New `TestUnixFSPBNodeFieldOrder` and `TestTrustlessRawPBNodeFieldOrder` tests covering both PBNode field orders: the streaming-friendly `Data`-before-`Links` order proposed for the `unixfs-v1-2026` profile in [IPIP-550](https://github.com/ipfs/specs/pull/550), and the legacy `Links`-before-`Data` order. Path resolution must work through UnixFS directories and HAMT shards in either encoding, and raw block responses must return the stored bytes verbatim without re-encoding to a preferred order. Backed by the new `pbnode-field-orders.car` fixture, byte-identical to the IPIP-550 fixtures table.

## [0.14.0] - 2026-08-27
### Added
- New `TestGatewayIpfsUri*` tests for the `Ipfs-Uri` response header introduced by [IPIP-548](https://github.com/ipfs/specs/pull/548), one request per failure class: canonical percent-encoded path segments (delimiters, platform-encoder gaps like `!'()*~`, 4-byte UTF-8), request-spelling independence over the existing `dir-with-percent-encoded-filename.car` (raw sub-delims, lowercase hex escapes), decode-once normalization (`%2F`, `%2E`, dot segments), nested-directory trailing slashes, query exclusion on alternate formats, redirects, and CIDv0 / legacy base58 peer ID authority normalization. Backed by the new `dir-with-tricky-filenames.car` and `dir-with-tricky-nested-filenames.car` fixtures, plus a hand-built `dir-with-slash-in-filename.car` proving that a dag-pb link with `/` in its name is not addressable by any content path (`%2F` always decodes to a separator); the full byte-level vectors live in the IPIP-0548 test fixtures table. `TestDNSLinkGatewayUnixFSDirectoryListing` also asserts the `ipns://` value for DNSLink hosts, and `TestDNSLinkGatewayIpfsUriAuthority` checks that only dotted DNSLink names appear as `ipns://` authorities.
Expand Down
Binary file added fixtures/path_gateway_unixfs/pbnode-field-orders.car
Binary file not shown.
117 changes: 117 additions & 0 deletions tests/path_gateway_unixfs_pbnode_order_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package tests

import (
"encoding/hex"
"testing"

"github.com/ipfs/gateway-conformance/tooling"
"github.com/ipfs/gateway-conformance/tooling/specs"
. "github.com/ipfs/gateway-conformance/tooling/test"
)

// PBNode field ordering fixtures from IPIP-550
// (https://github.com/ipfs/specs/pull/550): the same single-entry
// UnixFS Directory and HAMTShard encoded twice, once with the Data
// field before Links (streaming-friendly order written by the
// unixfs-v1-2026 profile) and once with Links before Data (legacy
// order written by unixfs-v1-2025 and earlier). Both orders are valid
// dag-pb and gateways must resolve content through either. Blocks are
// provisioned from fixtures/path_gateway_unixfs/pbnode-field-orders.car
// and asserted byte-exact against the hex in the IPIP fixtures table.
var (
pbnodeOrderLeafCID = "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am"
pbnodeOrderLeafData = mustHexDecode("68656c6c6f0a") // "hello\n"

pbnodeOrderRoots = []struct {
Name string
Cid string
Data []byte
}{
{
Name: "Directory with Data field before Links",
Cid: "bafybeigqvyloizmfcdy6scaxnyltftzptaruqa3hnnplfzsbf4sqteiwlm",
Data: mustHexDecode("0a02080112330a24015512205891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03120968656c6c6f2e7478741806"),
},
{
Name: "Directory with Links before Data field",
Cid: "bafybeigdcg7pksx2zk5336vrfsktjodlr4rbfz37qr3koc5xboxe5ekv24",
Data: mustHexDecode("12330a24015512205891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03120968656c6c6f2e74787418060a020801"),
},
{
Name: "HAMTShard with Data field before Links",
Cid: "bafybeicwgy2rlqmqqu3yy2tqvm2wbgdvy3snu4sbbv4wqpvpnoplpzxz74",
Data: mustHexDecode("0a250805121c80000000000000000000000000000000000000000000000000000000282230800212350a24015512205891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03120b444668656c6c6f2e7478741806"),
},
{
Name: "HAMTShard with Links before Data field",
Cid: "bafybeicjwkfslu7gwyywffvqgse5kiibojtktxcdqhgv7ldj5fjdacuceq",
Data: mustHexDecode("12350a24015512205891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03120b444668656c6c6f2e74787418060a250805121c800000000000000000000000000000000000000000000000000000002822308002"),
},
}
)

func mustHexDecode(s string) []byte {
data, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return data
}

// TestUnixFSPBNodeFieldOrder asserts that path resolution works through
// UnixFS directories and HAMT shards regardless of PBNode field order.
func TestUnixFSPBNodeFieldOrder(t *testing.T) {
tooling.LogTestGroup(t, GroupUnixFS)

tests := SugarTests{}
for _, root := range pbnodeOrderRoots {
tests = append(tests, SugarTest{
Name: "GET file from " + root.Name,
Hint: "both PBNode field orders decode to the same logical node, so pathing must work through either encoding",
Request: Request().
Path("/ipfs/{{cid}}/hello.txt", root.Cid),
Response: Expect().
Status(200).
Body(pbnodeOrderLeafData),
})
}

RunWithSpecs(t, tests, specs.PathGatewayUnixFS)
}

// TestTrustlessRawPBNodeFieldOrder asserts that raw block responses
// return verbatim bytes for both PBNode field orders, without
// re-encoding to a preferred order (which would change the CID).
func TestTrustlessRawPBNodeFieldOrder(t *testing.T) {
tooling.LogTestGroup(t, GroupBlockCar)

tests := SugarTests{
{
Name: "GET raw leaf block referenced by both field orders",
Request: Request().
Path("/ipfs/{{cid}}", pbnodeOrderLeafCID).
Headers(
Header("Accept", "application/vnd.ipld.raw"),
),
Response: Expect().
Status(200).
Body(pbnodeOrderLeafData),
},
}
for _, root := range pbnodeOrderRoots {
tests = append(tests, SugarTest{
Name: "GET raw block of " + root.Name,
Hint: "raw block response bytes must match the stored block exactly, preserving PBNode field order",
Request: Request().
Path("/ipfs/{{cid}}", root.Cid).
Headers(
Header("Accept", "application/vnd.ipld.raw"),
),
Response: Expect().
Status(200).
Body(root.Data),
})
}

RunWithSpecs(t, tests, specs.TrustlessGatewayRaw)
}
Loading