IPIP-548: Sunset x-ipfs-path header - #548
Conversation
The `x-ipfs-path` header can contain characters outside of the US-ASCII character set which some clients will refuse to process. Adding encoding rules to this header could break existing clients, instead add an `ipfs-uri` header that performs the same task but is a URI instead of a path so follows standard percent-encoding rules found in RFC 3986. Futher discussion can be found in #547.
🚀 Build Preview on IPFS ready
|
|
(I'll be adding some extra ipip-0548-test-fixtures.zip) |
Build the Ipfs-Uri header on the ipfs-uri and ipns-uri specs instead of restating their canonical forms, and pin down what a second implementation needs to emit byte-identical values: - content path: decode request segments once, %2F is a separator - strict RFC 3986 percent-encoding, with %2E / %2E%2E for dot segments - DNSLink authority converts to A-labels or the header is omitted - header also sent on redirects and errors, capped at 8192 bytes, exposed for CORS - X-Ipfs-Path is legacy-only and MUST be omitted for non-ASCII paths
Make the IPIP lean on the URI specs and give implementers testable fixtures per AGENTS.md: - cite ipfs-uri and ipns-uri as the base of the design - fixture table covers all 15 files of the tricky-filenames dir; the CAR is attached to the PR and the root is pinned as ipip-548_test-fixtures - add gateway-conformance dir-with-percent-encoded-filename.car as a second fixture that catches raw URL path passthrough - status back to proposal, point relatedIssues at discussion #547
Implement the Ipfs-Uri response header from ipfs/specs#548: a canonical ipfs:// or ipns:// URI for the requested content path, safe in an HTTP field value for any UnixFS file name. - authority in canonical form: base32 CIDv1, base36 IPNS name keeping its multicodec, lowercase A-label DNSLink; header omitted when the root cannot be normalized - every path segment percent-encoded over the RFC 3986 unreserved set with uppercase hex; "." and ".." emitted as %2E / %2E%2E - sent on deserialized, redirect, and error responses up to 8192 bytes, and exposed via default Access-Control-Expose-Headers - X-Ipfs-Path no longer sent by default; restore via Config.DeprecatedXIpfsPath + Headers.WithDeprecatedXIpfsPath, still omitted for bytes invalid in a field value - CI runs gateway-conformance from ipfs/gateway-conformance#301 until a release with the IPIP-548 tests ships
Cover the Ipfs-Uri response header from ipfs/specs#548 and the deprecation of X-Ipfs-Path, one request per failure class: - canonical percent-encoded segments: delimiters, platform-encoder gaps (!'()*~), 4-byte UTF-8; full byte-level vectors live in the IPIP-0548 fixtures table - request-spelling independence over dir-with-percent-encoded-filename.car: raw sub-delims and lowercase hex must yield the same canonical value - decode-once normalization (%2F, %2E, dot segments), nested trailing slashes, query exclusion on alternate formats, redirects - authority normalization: CIDv0 and legacy base58 peer IDs - X-Ipfs-Path must be omitted for non-ASCII paths and unchanged when present for ASCII-safe ones; existing expectations move to Ipfs-Uri, raw block tests drop the header expectation, CORS expects Ipfs-Uri exposed - DNSLink hosts get an ipns:// assertion in the dnslink suite
Implement the Ipfs-Uri response header from ipfs/specs#548: a canonical ipfs:// or ipns:// URI for the requested content path, safe in an HTTP field value for any UnixFS file name. - authority in canonical form: base32 CIDv1, base36 IPNS name keeping its multicodec, lowercase A-label DNSLink; header omitted when the root cannot be normalized - every path segment percent-encoded over the RFC 3986 unreserved set with uppercase hex; "." and ".." emitted as %2E / %2E%2E - sent on deserialized, redirect, and error responses up to 8192 bytes, and exposed via default Access-Control-Expose-Headers - X-Ipfs-Path no longer sent by default; restore via Config.DeprecatedXIpfsPath + Headers.WithDeprecatedXIpfsPath, still omitted for bytes invalid in a field value - CI runs gateway-conformance from ipfs/gateway-conformance#301 until a release with the IPIP-548 tests ships
|
Thanks @achingbrain for kicking this off, The header hid more edge cases than expected ( Remaining before ratification:
Both implementations follow the spec as closely as possible; push back or relax if any of it feels too pedantic. I'm also leaning toward dropping |
Cover the Ipfs-Uri response header from ipfs/specs#548 and the deprecation of X-Ipfs-Path, one request per failure class: - canonical percent-encoded segments: delimiters, platform-encoder gaps (!'()*~), 4-byte UTF-8; full byte-level vectors live in the IPIP-0548 fixtures table - request-spelling independence over dir-with-percent-encoded-filename.car: raw sub-delims and lowercase hex must yield the same canonical value - decode-once normalization (%2F, %2E, dot segments), nested trailing slashes, query exclusion on alternate formats, redirects - a hand-built fixture with a link literally named "a/b.txt" proves such an entry is not path-addressable: %2F always decodes to a separator, so the nested b.txt wins under every request spelling - authority normalization: CIDv0 and legacy base58 peer IDs - X-Ipfs-Path must be omitted for non-ASCII paths and unchanged when present for ASCII-safe ones; existing expectations move to Ipfs-Uri, raw block tests drop the header expectation, CORS expects Ipfs-Uri exposed - DNSLink hosts get an ipns:// assertion in the dnslink suite
Implement the Ipfs-Uri response header from ipfs/specs#548: a canonical ipfs:// or ipns:// URI for the requested content path, safe in an HTTP field value for any UnixFS file name. - authority in canonical form: base32 CIDv1, base36 IPNS name keeping its multicodec, lowercase A-label DNSLink; header omitted when the root cannot be normalized - every path segment percent-encoded over the RFC 3986 unreserved set with uppercase hex; "." and ".." emitted as %2E / %2E%2E - sent on deserialized, redirect, and error responses up to 8192 bytes, and exposed via default Access-Control-Expose-Headers - X-Ipfs-Path no longer sent by default; restore via Config.DeprecatedXIpfsPath + Headers.WithDeprecatedXIpfsPath, still omitted for bytes invalid in a field value - CI runs gateway-conformance from ipfs/gateway-conformance#301 until a release with the IPIP-548 tests ships
Cover the Ipfs-Uri response header from ipfs/specs#548 and the deprecation of X-Ipfs-Path, one request per failure class: - canonical percent-encoded segments: delimiters, platform-encoder gaps (!'()*~), 4-byte UTF-8; full byte-level vectors live in the IPIP-0548 fixtures table - request-spelling independence over dir-with-percent-encoded-filename.car: raw sub-delims and lowercase hex must yield the same canonical value - decode-once normalization (%2F, %2E, dot segments), nested trailing slashes, query exclusion on alternate formats, redirects - a hand-built fixture with a link literally named "a/b.txt" proves such an entry is not path-addressable: %2F always decodes to a separator, so the nested b.txt wins under every request spelling - authority normalization: CIDv0 and legacy base58 peer IDs - X-Ipfs-Path must be omitted for non-ASCII paths and unchanged when present for ASCII-safe ones; existing expectations move to Ipfs-Uri, raw block tests drop the header expectation, CORS expects Ipfs-Uri exposed - DNSLink hosts get an ipns:// assertion in the dnslink suite
Implement the Ipfs-Uri response header from ipfs/specs#548: a canonical ipfs:// or ipns:// URI for the requested content path, safe in an HTTP field value for any UnixFS file name. - authority in canonical form: base32 CIDv1, base36 IPNS name keeping its multicodec, lowercase A-label DNSLink; header omitted when the root cannot be normalized - every path segment percent-encoded over the RFC 3986 unreserved set with uppercase hex; "." and ".." emitted as %2E / %2E%2E - sent on deserialized, redirect, and error responses up to 8192 bytes, and exposed via default Access-Control-Expose-Headers - X-Ipfs-Path no longer sent by default; restore via Config.DeprecatedXIpfsPath + Headers.WithDeprecatedXIpfsPath, still omitted for bytes invalid in a field value - CI runs gateway-conformance from ipfs/gateway-conformance#301 until a release with the IPIP-548 tests ships
A dag-pb link name may contain a slash at the byte level, but no content path can address it. Make every relevant spec say so: - unixfs: names containing "/" join the restricted names list, and the path escaping section defines the HTTP gateway and URI behavior while leaving other contexts unspecified - path-gateway: spell out the consequence next to the %2F rule - ipip-0548: record the previously-undefined escaping gap in the design rationale, and reference the conformance fixture that proves a slash-named link resolves to the nested file under every request spelling Refs ipfs/helia-verified-fetch#376 (comment)
8e17877 to
01ababb
Compare
In the DNSLink case a name with no dot never becomes an Ipfs-Uri authority, because on each network it can point at different content. Give the omission clause a concrete pair: examplenodot never works, example.local is fine. Matching positive and negative tests exist in gateway-conformance, boxo, and helia-verified-fetch. Refs ipfs/helia-verified-fetch#376 (comment)
* feat: tests for IPIP-548 Ipfs-Uri header Cover the Ipfs-Uri response header from ipfs/specs#548 and the deprecation of X-Ipfs-Path, one request per failure class: - canonical percent-encoded segments: delimiters, platform-encoder gaps (!'()*~), 4-byte UTF-8; full byte-level vectors live in the IPIP-0548 fixtures table - request-spelling independence over dir-with-percent-encoded-filename.car: raw sub-delims and lowercase hex must yield the same canonical value - decode-once normalization (%2F, %2E, dot segments), nested trailing slashes, query exclusion on alternate formats, redirects - a hand-built fixture with a link literally named "a/b.txt" proves such an entry is not path-addressable: %2F always decodes to a separator, so the nested b.txt wins under every request spelling - authority normalization: CIDv0 and legacy base58 peer IDs - X-Ipfs-Path must be omitted for non-ASCII paths and unchanged when present for ASCII-safe ones; existing expectations move to Ipfs-Uri, raw block tests drop the header expectation, CORS expects Ipfs-Uri exposed - DNSLink hosts get an ipns:// assertion in the dnslink suite * test: dot-less DNSLink names never in Ipfs-Uri Per IPIP-548, only dotted DNSLink names can be an ipns:// authority: a name with no dot can point at different content on each network. Add a positive and a negative test over new dir_listing dnslink entries: example.local yields ipns://example.local/, and examplenodot never yields an Ipfs-Uri header, no matter how the gateway answers. Refs ipfs/helia-verified-fetch#376 (comment) * test: rename dot-less domain to examplemissingtld * Update fixtures/path_gateway_unixfs/README.md Co-authored-by: Alex Potsides <alex@achingbrain.net> * fix: Has() checks every expected header value HeaderBuilder.Has overwrote its check in a loop, so only the last argument was ever asserted and the rest of each list in TestCors was silently ignored. Build one And() over all values instead, drop the per-callsite Contains workaround, and add a regression test for a missing first value. The kubo-specific X-Chunked-Output and X-Stream-Output entries, which no spec mandates, are removed from the Access-Control-Expose-Headers expectation instead of becoming newly enforced. --------- Co-authored-by: Alex Potsides <alex@achingbrain.net>
* feat(gateway): Ipfs-Uri response header Implement the Ipfs-Uri response header from ipfs/specs#548: a canonical ipfs:// or ipns:// URI for the requested content path, safe in an HTTP field value for any UnixFS file name. - authority in canonical form: base32 CIDv1, base36 IPNS name keeping its multicodec, lowercase A-label DNSLink; header omitted when the root cannot be normalized - every path segment percent-encoded over the RFC 3986 unreserved set with uppercase hex; "." and ".." emitted as %2E / %2E%2E - sent on deserialized, redirect, and error responses up to 8192 bytes, and exposed via default Access-Control-Expose-Headers - X-Ipfs-Path no longer sent by default; restore via Config.DeprecatedXIpfsPath + Headers.WithDeprecatedXIpfsPath, still omitted for bytes invalid in a field value - CI runs gateway-conformance from ipfs/gateway-conformance#301 until a release with the IPIP-548 tests ships * test: dotted vs dot-less DNSLink Ipfs-Uri Per IPIP-548 only dotted DNSLink names become an ipns:// authority: example.local works, examplenodot never does. Refs ipfs/helia-verified-fetch#376 (comment) * ci: bump gateway-conformance pin Use the conformance tests from ipfs/gateway-conformance#301 at its latest commit, which adds the dot-less DNSLink Ipfs-Uri tests. * test: rename dot-less name to examplemissingtld * ci: gateway-conformance v0.14 * docs: changelog for Ipfs-Uri and conformance v0.14
* feat: Ipfs-Uri gateway header (IPIP-548) Bump boxo to the IPIP-548 implementation (ipfs/boxo#1209): gateway responses carry a canonical percent-encoded Ipfs-Uri header and stop sending the deprecated X-Ipfs-Path, which cannot represent every UnixFS file name. - sharness: CORS expects Ipfs-Uri exposed, X-Ipfs-Path gone - gateway-conformance CI pinned to the IPIP-548 test suite (ipfs/gateway-conformance#301) until a release ships - reverse-proxy doc and v0.44 changelog updated Refs ipfs/specs#548 * feat: opt-in Gateway.DeprecatedXIpfsPath Expose boxo's opt-in for the legacy X-Ipfs-Path response header as a kubo config flag, default off. Unsafe: the legacy value cannot represent every UnixFS file name, so it must only be used to facilitate migration to Ipfs-Uri, and even when enabled the header is still skipped when the value would include non-ASCII byte sequences. Refs ipfs/specs#548 * ci: bump gateway-conformance pin * ci: gateway-conformance v0.14 * chore: boxo with IPIP-548 from boxo/main * docs: Ipfs-Uri changelog in v0.43.1 * docs: assemble v0.43.1 changelog Move the v0.44 highlights and dependency lines into a new v0.43.1 section, and add the missing entry for owner-only key exports (#11428). v0.44.md returns to an empty skeleton. * docs: note boxo v0.42.2 fixes in v0.43.1
There was a problem hiding this comment.
Status update:
- gateway-conformance v0.14.0 shipped with the IPIP-548 tests
- ipfs/boxo#1209 and ipfs/kubo#11437 are merged; the header will likely ship in Kubo v0.43.1 this or next week
- ipfs/helia-verified-fetch#376 covers the JS side
@achingbrain if no concerns we can flip this to ratified state and merge?
gateway-conformance v0.14.0 contains ipfs/gateway-conformance#301; both CARs are byte-identical to the previously pinned refs.
Replace the pass-through ipfs-uri value from #375 with the canonical serialization from ipfs/specs#548, so the header no longer depends on how the request spelled the path: - authority normalized: base32 CIDv1 for ipfs://, base36 libp2p-key CIDv1 for ipns:// (legacy base58 peer ids converted), lowercase A-label DNSLink names; header omitted when the root cannot be normalized - each path segment decoded once and strictly re-encoded over the RFC 3986 unreserved set with uppercase hex - x-ipfs-path kept for legacy clients but omitted when the content path cannot appear in an HTTP field value, instead of mangling it to ASCII - Ipfs-Uri added to Access-Control-Expose-Headers Per IPIP-548 only dotted DNSLink names become an ipns:// authority: a name with no dot can point at different content on each network. Use examplenodot for the negative case instead of localhost, and add example.local as the positive private-network case. Refs #376 (comment) --------- Co-authored-by: Alex Potsides <alex@achingbrain.net>
## [@helia/verified-fetch-v8.1.0](https://github.com/ipfs/helia-verified-fetch/compare/@helia/verified-fetch-8.0.4...@helia/verified-fetch-8.1.0) (2026-08-27) ### Features * canonical Ipfs-Uri header (IPIP-548) ([#376](#376)) ([0b26cc1](0b26cc1)), closes [#375](#375) [ipfs/specs#548](ipfs/specs#548) ### Trivial Changes * just return overridden fields ([#378](#378)) ([ff7c6fd](ff7c6fd))
The
x-ipfs-pathheader can contain characters outside of the US-ASCII character set which some clients will refuse to process.Adding encoding rules to this header could break existing clients, instead add an
ipfs-uriheader that performs the same task but is a URI instead of a path so follows standard percent-encoding rules found in RFC 3986.Futher discussion can be found in #547.