Replace X-Ipfs-Path header with Ipfs-Uri version that can correctly encode
any special characters likely to be found in an IPFS Path.
HTTP header values can only include characters from a limited set.
There is a gap in the existing gateway specification in that it does not say how characters from outside this set are to be treated.
Values already arrive broken: browser fetch() garbles raw UTF-8, and
Go's net/http replaces CR and LF with spaces.
The spec is implemented and consumed widely so retrospectively adding encoding rules would be disruptive, and we would have to agree on an encoding format.
URIs already have a well-defined encoding format (percent encoding, defined in
RFC 3986), so
introduce an Ipfs-Uri header to be used in preference to X-Ipfs-Path which
can handle any and all characters found in an IPFS path, and can be losslessly
converted back into an IPFS Path if the client desires it.
The Ipfs-Uri header should be added which contains the IPFS/IPNS path as a
URI (e.g. ipfs://... or ipns://...) with any special characters
percent-encoded as per RFC 3986.
The URI schemes are defined by [ipfs-uri] and [ipns-uri]; the
Ipfs-Uri section
of [path-gateway] defines the exact serialization.
This IPIP also updates [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.
Ipfs-Uri deprecates X-Ipfs-Path, and clients SHOULD prefer Ipfs-Uri
when both are present. The
X-Ipfs-Path section
says when the legacy header MUST be omitted to avoid issues with unsafe
byte ranges.
Retroactively adding encoding rules to X-Ipfs-Path would be too disruptive to
existing clients so adding a new header and deprecating the old one seems like
the least worst way forward.
Path escaping was previously undefined across the stack: the UnixFS spec
explicitly declared it out of scope, and nothing said how gateways decode
request paths or how a content path becomes a header-safe string. This IPIP
locks that behavior down: request path components are percent-decoded once
(so %2F is a component separator), Ipfs-Uri is the canonical encoded
form, and names containing / are formally not path-addressable.
Ipfs-Uri correctly encodes otherwise illegal characters so users can determine
the original IPFS Path of a resource without data corruption.
Since we are adding a new header this is a non-breaking change.
Existing deployments can keep returning both headers; new implementations
return only Ipfs-Uri.
Percent-encoding keeps raw control bytes such as CR and LF out of Ipfs-Uri
values.
A UnixFS directory under
bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae
(dir-with-tricky-filenames.car in
ipip-0548-test-fixtures.zip),
and the header returned for each file in it:
| UnixFS file name | Response header |
|---|---|
plain.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/plain.txt |
with space.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/with%20space.txt |
100% sure.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/100%25%20sure.txt |
a#b?c.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/a%23b%3Fc.txt |
łódź.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/%C5%82%C3%B3d%C5%BA.txt |
emoji🚀.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/emoji%F0%9F%9A%80.txt |
αρχείο.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/%CE%B1%CF%81%CF%87%CE%B5%CE%AF%CE%BF.txt |
файл.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/%D1%84%D0%B0%D0%B9%D0%BB.txt |
קובץ.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/%D7%A7%D7%95%D7%91%D7%A5.txt |
ملف.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/%D9%85%D9%84%D9%81.txt |
नमस्ते.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/%E0%A4%A8%E0%A4%AE%E0%A4%B8%E0%A5%8D%E0%A4%A4%E0%A5%87.txt |
ไฟล์.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/%E0%B9%84%E0%B8%9F%E0%B8%A5%E0%B9%8C.txt |
ファイル.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.txt |
你好.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/%E4%BD%A0%E5%A5%BD.txt |
파일.txt |
Ipfs-Uri: ipfs://bafybeiflhd5aimv4xavauidbetge3v3uadbqfibu5lfo4b26yyieqvnvae/%ED%8C%8C%EC%9D%BC.txt |
A second fixture, the preexisting
dir-with-percent-encoded-filename.car
directory under
bafybeig675grnxcmshiuzdaz2xalm6ef4thxxds6o6ypakpghm5kghpc34, holds a
name that already looks percent-encoded. The literal %2C is encoded
again (%252C), never decoded into a comma, and + and = do not pass
through raw:
| UnixFS file name | Response header |
|---|---|
Portugal%2C+España=Peninsula Ibérica.txt |
Ipfs-Uri: ipfs://bafybeig675grnxcmshiuzdaz2xalm6ef4thxxds6o6ypakpghm5kghpc34/Portugal%252C%2BEspa%C3%B1a%3DPeninsula%20Ib%C3%A9rica.txt |
A third fixture,
dir-with-slash-in-filename.car
under bafybeihuqitp4tzukehqfyaozl6zexd7szyzeywojuynfxopnn7dqjepv4, holds
a real subdirectory a with b.txt inside, plus a sibling link literally
named a/b.txt. Such a link is legal in dag-pb but not addressable by any
content path: %2F decodes to a separator, so every spelling resolves to
the nested file:
| Request path | Response header |
|---|---|
/ipfs/{cid}/a/b.txt |
Ipfs-Uri: ipfs://bafybeihuqitp4tzukehqfyaozl6zexd7szyzeywojuynfxopnn7dqjepv4/a/b.txt |
/ipfs/{cid}/a%2Fb.txt |
Ipfs-Uri: ipfs://bafybeihuqitp4tzukehqfyaozl6zexd7szyzeywojuynfxopnn7dqjepv4/a/b.txt |
The gateway-conformance
test suite uses these directories to test Ipfs-Uri and the legacy
X-Ipfs-Path behavior.
Copyright and related rights waived via CC0.
We gratefully acknowledge the following individuals for their valuable contributions, ranging from minor suggestions to major insights, which have shaped and improved this specification.