diff --git a/CHANGELOG.md b/CHANGELOG.md index 575d6f196..89af50970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ 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.11.1] - 2026-02-24 +### Added +- New test `TestGatewayIPNSRecordWithSubpath` for IPNS records whose Value field contains a sub-path (e.g. `/ipfs//root2`). All prior IPNS tests only covered bare `/ipfs/` in the record Value. +- New test `TestDNSLinkGatewayWithSubpath` for DNSLink TXT records pointing at a content path with a sub-path (e.g. `dnslink=/ipfs//root2`). + ## [0.11.0] - 2026-02-24 ### Added - New test `TestDNSLinkGatewayIPNS` for DNSLink TXT records pointing at `/ipns/` instead of `/ipfs/`. All prior DNSLink tests only covered the `/ipfs/` case. [#272](https://github.com/ipfs/gateway-conformance/pull/272) diff --git a/fixtures/gateway-cache/README.md b/fixtures/gateway-cache/README.md index 41ad5f4e4..6b74dd388 100644 --- a/fixtures/gateway-cache/README.md +++ b/fixtures/gateway-cache/README.md @@ -41,3 +41,16 @@ ipfs dag export ${ROOT1_CID} > ./fixtures.car # FILE_CID=bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am # ./root2/root3/root4/index.html # TEST_IPNS_ID=k51qzi5uqu5dlxdsdu5fpuu7h69wu4ohp32iwm9pdt9nq3y5rpn3ln9j12zfhe ``` + +### ipns-record with sub-path in Value + +IPNS record whose Value points at `/ipfs/ROOT1_CID/root2` instead of just +`/ipfs/ROOT1_CID`. Used to test that gateways correctly handle IPNS records +with sub-paths embedded in the Value field. + +Generated by `makeSubpathRecord()` in `../ipns_records/generator/main.go` +(run with `cd ../ipns_records && go run generator/main.go`). + +``` +# SUBPATH_IPNS_ID=k51qzi5uqu5djokp3m1keo36hoxtd6u3a1d2rg1camf6al7p3huy63dojlm57c +``` diff --git a/fixtures/gateway-cache/dnslink.yml b/fixtures/gateway-cache/dnslink.yml new file mode 100644 index 000000000..4bed1b969 --- /dev/null +++ b/fixtures/gateway-cache/dnslink.yml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../fixture.schema.json +dnslinks: + dnslink-with-subpath: + domain: dnslink-subpath.example.org + # ROOT1_CID from gateway-cache/fixtures.car with /root2 sub-path + path: /ipfs/bafybeib3ffl2teiqdncv3mkz4r23b5ctrwkzrrhctdbne6iboayxuxk5ui/root2 diff --git a/fixtures/gateway-cache/k51qzi5uqu5djokp3m1keo36hoxtd6u3a1d2rg1camf6al7p3huy63dojlm57c.ipns-record b/fixtures/gateway-cache/k51qzi5uqu5djokp3m1keo36hoxtd6u3a1d2rg1camf6al7p3huy63dojlm57c.ipns-record new file mode 100644 index 000000000..3693f9ea0 Binary files /dev/null and b/fixtures/gateway-cache/k51qzi5uqu5djokp3m1keo36hoxtd6u3a1d2rg1camf6al7p3huy63dojlm57c.ipns-record differ diff --git a/fixtures/ipns_records/generator/main.go b/fixtures/ipns_records/generator/main.go index e5a4aef85..e002231a2 100644 --- a/fixtures/ipns_records/generator/main.go +++ b/fixtures/ipns_records/generator/main.go @@ -190,6 +190,28 @@ func makeV2Only() { saveToFile(raw, name.String()+"_v2.ipns-record", v) } +// makeSubpathRecord creates a valid V1+V2 IPNS record whose Value contains a +// sub-path (e.g. /ipfs//root2) instead of a bare CID. This record is +// placed in gateway-cache/ alongside the CAR it references. +func makeSubpathRecord() { + // ROOT1_CID from gateway-cache/fixtures.car (see ../gateway-cache/README.md) + root1CID, err := cid.Decode("bafybeib3ffl2teiqdncv3mkz4r23b5ctrwkzrrhctdbne6iboayxuxk5ui") + panicOnErr(err) + + v, err := path.Join(path.FromCid(root1CID), "root2") + panicOnErr(err) + + sk, _, name := makeKeyPair() + + rec, err := ipns.NewRecord(sk, v, seq, eol, ttl, ipns.WithV1Compatibility(true)) + panicOnErr(err) + + raw, err := ipns.MarshalRecord(rec) + panicOnErr(err) + + saveToFile(raw, "../gateway-cache/"+name.String()+".ipns-record", v) +} + func main() { makeV1Only() makeV1V2() @@ -197,4 +219,5 @@ func main() { makeV1V2WithBrokenSignatureV1() makeV1V2WithBrokenSignatureV2() makeV2Only() + makeSubpathRecord() } diff --git a/tests/dnslink_gateway_test.go b/tests/dnslink_gateway_test.go index 15023b2e9..66a3536dd 100644 --- a/tests/dnslink_gateway_test.go +++ b/tests/dnslink_gateway_test.go @@ -75,3 +75,29 @@ func TestDNSLinkGatewayUnixFSDirectoryListing(t *testing.T) { RunWithSpecs(t, tests, specs.DNSLinkGateway) } + +func TestDNSLinkGatewayWithSubpath(t *testing.T) { + tooling.LogTestGroup(t, GroupDNSLink) + + dnsLinks := dnslink.MustOpenDNSLink("gateway-cache/dnslink.yml") + dnsLink := dnsLinks.MustGet("dnslink-with-subpath") + + tests := SugarTests{ + { + Name: "GET for DNSLink with dnslink=/ipfs//sub-path returns file at sub-path", + Hint: ` + When a DNSLink TXT record points to a content path with a sub-path + (e.g. /ipfs//root2), the gateway must resolve the full path and + concatenate any additional request path segments to serve the file. + `, + Request: Request(). + Header("Host", dnsLink). + Path("/root3/root4/index.html"), + Response: Expect(). + Status(200). + Body("hello\n"), + }, + } + + RunWithSpecs(t, tests, specs.DNSLinkGateway) +} diff --git a/tests/path_gateway_ipns_test.go b/tests/path_gateway_ipns_test.go index 93eeb5568..8455cc13f 100644 --- a/tests/path_gateway_ipns_test.go +++ b/tests/path_gateway_ipns_test.go @@ -165,3 +165,29 @@ func TestRedirectCanonicalIPNS(t *testing.T) { RunWithSpecs(t, tests, specs.PathGatewayIPNS) } + +func TestGatewayIPNSRecordWithSubpath(t *testing.T) { + tooling.LogTestGroup(t, GroupIPNS) + + // IPNS record whose Value = /ipfs/ROOT1_CID/root2 (note the sub-path) + // reuses the gateway-cache/fixtures.car which has root2/root3/root4/index.html + ipnsSubpath := MustOpenIPNSRecordWithKey("gateway-cache/k51qzi5uqu5djokp3m1keo36hoxtd6u3a1d2rg1camf6al7p3huy63dojlm57c.ipns-record") + + tests := SugarTests{ + { + Name: "GET /ipns/{key}/root3/root4/index.html returns file when IPNS record Value has sub-path", + Hint: ` + When an IPNS record Value contains a sub-path (e.g. /ipfs//root2), + the gateway must concatenate the request path with the IPNS-resolved + content path and serve the resulting file. + `, + Request: Request(). + Path("/ipns/{{key}}/root3/root4/index.html", ipnsSubpath.Key()), + Response: Expect(). + Status(200). + Body("hello\n"), + }, + } + + RunWithSpecs(t, tests, specs.PathGatewayIPNS) +}