diff --git a/Makefile b/Makefile
index 303dea813..af272befd 100644
--- a/Makefile
+++ b/Makefile
@@ -19,7 +19,7 @@ test-cargateway: provision-cargateway fixtures.car gateway-conformance
test-kubo-subdomains: provision-kubo gateway-conformance
./kubo-config.example.sh
- ./gateway-conformance test --json reports/output.json --gateway-url http://127.0.0.1:8080 --subdomain-url http://localhost:8080
+ ./gateway-conformance test --json reports/output.json --gateway-url http://127.0.0.1:8080 --subdomain-url http://example.com:8080
test-kubo: provision-kubo gateway-conformance
./gateway-conformance test --json reports/output.json --gateway-url http://127.0.0.1:8080 --specs -subdomain-gateway
@@ -31,13 +31,16 @@ provision-kubo:
find ./fixtures -name '*.car' -exec ipfs dag import --stats --pin-roots=false {} \;
find ./fixtures -name '*.ipns-record' -exec sh -c 'ipfs routing put --allow-offline /ipns/$$(basename -s .ipns-record "{}" | cut -d'_' -f1) "{}"' \;
-start-kubo-docker: stop-kubo-docker gateway-conformance
- ./gateway-conformance extract-fixtures --dnslink=true --car=false --ipns=false --dir=.temp
- docker pull ipfs/kubo:$(KUBO_VERSION)
- docker run -d --rm --net=host --name $(KUBO_DOCKER_NAME) -e IPFS_NS_MAP="$(shell cat ./.temp/dnslinks.IPFS_NS_MAP)" -v ./fixtures:/fixtures ipfs/kubo:$(KUBO_VERSION) daemon --init --offline
- @until docker exec $(KUBO_DOCKER_NAME) ipfs --api=/ip4/127.0.0.1/tcp/5001 dag stat /ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn >/dev/null 2>&1; do sleep 0.1; done
- find ./fixtures -name '*.car' -exec docker exec $(KUBO_DOCKER_NAME) ipfs dag import --stats --pin-roots=false {} \;
- find ./fixtures -name '*.ipns-record' -exec docker exec $(KUBO_DOCKER_NAME) sh -c 'ipfs routing put --allow-offline /ipns/$$(basename -s .ipns-record "{}" | cut -d'_' -f1) "{}"' \;
+#start-kubo-docker: stop-kubo-docker gateway-conformance
+# ./gateway-conformance extract-fixtures --dir=.temp/fixtures
+# docker pull ipfs/kubo:$(KUBO_VERSION)
+# docker run -d --rm --net=host --name $(KUBO_DOCKER_NAME) -v "$(shell realpath .temp/fixtures)":/fixtures -v kubo-config.example.sh:/container-init.d/001-config.sh ipfs/kubo:$(KUBO_VERSION) daemon --init --offline
+# @until docker exec $(KUBO_DOCKER_NAME) ipfs --api=/ip4/127.0.0.1/tcp/5001 dag stat /ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn >/dev/null 2>&1; do sleep 0.1; done
+# docker exec $(KUBO_DOCKER_NAME) find /fixtures -name '*.car' -exec ipfs dag import --stats --pin-roots=false {} \;
+# docker exec $(KUBO_DOCKER_NAME) find /fixtures -name '*.ipns-record' -exec sh -c 'ipfs routing put --allow-offline /ipns/$$(basename -s .ipns-record "{}" | cut -d'_' -f1) "{}"' \;
+# TODO: provision Kubo config at Gateway.PublicGateways to have subdomain gateway on example.com and also enable inlining on localhost
+# See: https://github.com/ipfs/kubo/blob/a07852a3f0294974b802923fb136885ad077384e/.github/workflows/gateway-conformance.yml#L22-L34
+# (this is not as trivial as it sounds because Kubo does not apply config inrealtime, and a restart is required.)
stop-kubo-docker: clean
docker stop $(KUBO_DOCKER_NAME) || true
diff --git a/cmd/gateway-conformance/main.go b/cmd/gateway-conformance/main.go
index 1e234dda4..cc92ac090 100644
--- a/cmd/gateway-conformance/main.go
+++ b/cmd/gateway-conformance/main.go
@@ -91,7 +91,7 @@ func main() {
Name: "subdomain-url",
EnvVars: []string{"SUBDOMAIN_GATEWAY_URL"},
Usage: "URL of the HTTP Host that should be used when testing https://specs.ipfs.tech/http-gateways/subdomain-gateway/ functionality",
- Value: "http://example.com:8080", // TODO: ideally, make these empty by default, and opt-in
+ Value: "http://example.com:8080",
},
&cli.StringFlag{
Name: "json-output",
@@ -108,7 +108,7 @@ func main() {
&cli.StringFlag{
Name: "specs",
EnvVars: []string{"SPECS"},
- Usage: "Optional explicit scope of tests to run. Accepts a 'spec' (test only this spec), a '+spec' (test also this immature spec), or a '-spec' (do not test this mature spec). Available spec presets: " + strings.Join(getAvailableSpecPresets(), ","),
+ Usage: "Adjust the scope of tests to run. Accepts a 'spec' (test only this spec), a '+spec' (test also this immature spec), or a '-spec' (do not test this mature spec). Available spec presets: " + strings.Join(getAvailableSpecPresets(), ","),
Value: "",
},
&cli.BoolFlag{
@@ -120,13 +120,24 @@ func main() {
Action: func(cctx *cli.Context) error {
env := os.Environ()
verbose := cctx.Bool("verbose")
+
+ // Set gateway URLs
gatewayURL := cctx.String("gateway-url")
subdomainGatewayURL := cctx.String("subdomain-url")
- env = append(env, fmt.Sprintf("GATEWAY_URL=%s", gatewayURL))
+ envGwURL := fmt.Sprintf("GATEWAY_URL=%s", gatewayURL)
+ if verbose {
+ fmt.Println(envGwURL)
+ }
+ env = append(env, envGwURL)
if subdomainGatewayURL != "" {
- env = append(env, fmt.Sprintf("SUBDOMAIN_GATEWAY_URL=%s", subdomainGatewayURL))
+ envSubdomainGwURL := fmt.Sprintf("SUBDOMAIN_GATEWAY_URL=%s", subdomainGatewayURL)
+ if verbose {
+ fmt.Println(envSubdomainGwURL)
+ }
+ env = append(env, envSubdomainGwURL)
}
+ // Set other parameters
args := []string{"test", "./tests", "-test.v=test2json"}
specs := cctx.String("specs")
@@ -140,8 +151,8 @@ func main() {
args = append(args, cctx.Args().Slice()...)
fmt.Println("go " + strings.Join(args, " "))
- fmt.Println("ENV " + strings.Join(env, " "))
+ // Execute tests against URLs
output := &bytes.Buffer{}
cmd := exec.Command("go", args...)
cmd.Dir = tooling.Home()
diff --git a/kubo-config.example.sh b/kubo-config.example.sh
index b5aa74aa2..87e30da6d 100755
--- a/kubo-config.example.sh
+++ b/kubo-config.example.sh
@@ -1,4 +1,4 @@
-#! /usr/bin/env bash
+#!/usr/bin/env bash
FIXTURES_PATH=${1:-$(pwd)}
@@ -15,7 +15,7 @@ ipfs config --json Gateway.PublicGateways '{
}
}'
-export IPFS_NS_MAP="$(cat "${FIXTURES_PATH}/dnslinks.json" | jq -r '.domains | to_entries | map("\(.key):\(.value)") | join(",")')"
+export IPFS_NS_MAP="$(cat "${FIXTURES_PATH}/dnslinks.IPFS_NS_MAP")"
echo "Set the following IPFS_NS_MAP before starting the kubo daemon:"
echo "IPFS_NS_MAP=${IPFS_NS_MAP}"
diff --git a/tests/dnslink_gateway_test.go b/tests/dnslink_gateway_test.go
index bfa5ba6a4..adbc9c023 100644
--- a/tests/dnslink_gateway_test.go
+++ b/tests/dnslink_gateway_test.go
@@ -7,7 +7,6 @@ import (
"github.com/ipfs/gateway-conformance/tooling/car"
. "github.com/ipfs/gateway-conformance/tooling/check"
"github.com/ipfs/gateway-conformance/tooling/dnslink"
- "github.com/ipfs/gateway-conformance/tooling/helpers"
"github.com/ipfs/gateway-conformance/tooling/specs"
. "github.com/ipfs/gateway-conformance/tooling/test"
)
@@ -74,5 +73,5 @@ func TestDNSLinkGatewayUnixFSDirectoryListing(t *testing.T) {
},
}
- RunWithSpecs(t, helpers.UnwrapSubdomainTests(t, tests), specs.DNSLinkGateway)
+ RunWithSpecs(t, tests, specs.DNSLinkGateway)
}
diff --git a/tests/metadata_test.go b/tests/metadata_test.go
index 738d44aef..b76c55e6e 100644
--- a/tests/metadata_test.go
+++ b/tests/metadata_test.go
@@ -12,8 +12,8 @@ func logGatewayURL(t *testing.T) {
GatewayURL string `json:"gateway_url"`
SubdomainGatewayURL string `json:"subdomain_gateway_url"`
}{
- GatewayURL: test.GatewayURL,
- SubdomainGatewayURL: test.SubdomainGatewayURL,
+ GatewayURL: test.GatewayURL().String(),
+ SubdomainGatewayURL: test.SubdomainGatewayURL().String(),
})
}
diff --git a/tests/redirects_file_test.go b/tests/redirects_file_test.go
index 1a667b3d9..25c9ee3d1 100644
--- a/tests/redirects_file_test.go
+++ b/tests/redirects_file_test.go
@@ -1,14 +1,12 @@
package tests
import (
- "net/url"
"testing"
"github.com/ipfs/gateway-conformance/tooling"
"github.com/ipfs/gateway-conformance/tooling/car"
. "github.com/ipfs/gateway-conformance/tooling/check"
"github.com/ipfs/gateway-conformance/tooling/dnslink"
- "github.com/ipfs/gateway-conformance/tooling/helpers"
"github.com/ipfs/gateway-conformance/tooling/specs"
. "github.com/ipfs/gateway-conformance/tooling/test"
. "github.com/ipfs/gateway-conformance/tooling/tmpl"
@@ -28,20 +26,16 @@ func TestRedirectsFileSupport(t *testing.T) {
// Redirects require origin isolation (https://specs.ipfs.tech/http-gateways/web-redirects-file/)
// This means we only run these tests against origins explicitly passed via --subdomain-url
- u, err := url.Parse(SubdomainGatewayURL)
- if err != nil {
- t.Fatal(err)
- }
-
- redirectDirBaseURL := Fmt("{{scheme}}://{{cid}}.ipfs.{{host}}", u.Scheme, redirectDirCID, u.Host)
+ u := SubdomainGatewayURL()
- // TODO hostHdr := Fmt("{{cid}}.ipfs.{{host}}", redirectDirCID, u.Host)
+ dirCIDInSubdomain := Fmt("{{cid}}.ipfs.{{host}}", redirectDirCID, u.Host)
tests = append(tests, SugarTests{
{
- Name: "request for $REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file",
+ Name: "request for {cid}.ipfs.example.com/redirect-one redirects with default of 301, per _redirects file",
Request: Request().
- URL("{{url}}/redirect-one", redirectDirBaseURL),
+ Header("Host", dirCIDInSubdomain).
+ Path("/redirect-one"),
Response: Expect().
Status(301).
Headers(
@@ -49,9 +43,10 @@ func TestRedirectsFileSupport(t *testing.T) {
),
},
{
- Name: "request for $REDIRECTS_DIR_HOSTNAME/301-redirect-one redirects with 301, per _redirects file",
+ Name: "request for {cid}.ipfs.example.com/301-redirect-one redirects with 301, per _redirects file",
Request: Request().
- URL("{{url}}/301-redirect-one", redirectDirBaseURL),
+ Header("Host", dirCIDInSubdomain).
+ Path("/301-redirect-one"),
Response: Expect().
Status(301).
Headers(
@@ -59,9 +54,10 @@ func TestRedirectsFileSupport(t *testing.T) {
),
},
{
- Name: "request for $REDIRECTS_DIR_HOSTNAME/302-redirect-two redirects with 302, per _redirects file",
+ Name: "request for {cid}.ipfs.example.com/302-redirect-two redirects with 302, per _redirects file",
Request: Request().
- URL("{{url}}/302-redirect-two", redirectDirBaseURL),
+ Header("Host", dirCIDInSubdomain).
+ Path("/302-redirect-two"),
Response: Expect().
Status(302).
Headers(
@@ -69,17 +65,19 @@ func TestRedirectsFileSupport(t *testing.T) {
),
},
{
- Name: "request for $REDIRECTS_DIR_HOSTNAME/200-index returns 200, per _redirects file",
+ Name: "request for {cid}.ipfs.example.com/200-index returns 200, per _redirects file",
Request: Request().
- URL("{{url}}/200-index", redirectDirBaseURL),
+ Header("Host", dirCIDInSubdomain).
+ Path("/200-index"),
Response: Expect().
Status(200).
Body(Contains("my index")),
},
{
- Name: "request for $REDIRECTS_DIR_HOSTNAME/posts/:year/:month/:day/:title redirects with 301 and placeholders, per _redirects file",
+ Name: "request for {cid}.ipfs.example.com/posts/:year/:month/:day/:title redirects with 301 and placeholders, per _redirects file",
Request: Request().
- URL("{{url}}/posts/2022/01/01/hello-world", redirectDirBaseURL),
+ Header("Host", dirCIDInSubdomain).
+ Path("/posts/2022/01/01/hello-world"),
Response: Expect().
Status(301).
Headers(
@@ -87,9 +85,10 @@ func TestRedirectsFileSupport(t *testing.T) {
),
},
{
- Name: "request for $REDIRECTS_DIR_HOSTNAME/splat/one.html redirects with 301 and splat placeholder, per _redirects file",
+ Name: "request for {cid}.ipfs.example.com/splat/one.html redirects with 301 and splat placeholder, per _redirects file",
Request: Request().
- URL("{{url}}/splat/one.html", redirectDirBaseURL),
+ Header("Host", dirCIDInSubdomain).
+ Path("/splat/one.html"),
Response: Expect().
Status(301).
Headers(
@@ -97,9 +96,10 @@ func TestRedirectsFileSupport(t *testing.T) {
),
},
{
- Name: "request for $REDIRECTS_DIR_HOSTNAME/not-found/has-no-redirects-entry returns custom 404, per _redirects file",
+ Name: "request for {cid}.ipfs.example.com/not-found/has-no-redirects-entry returns custom 404, per _redirects file",
Request: Request().
- URL("{{url}}/not-found/has-no-redirects-entry", redirectDirBaseURL),
+ Header("Host", dirCIDInSubdomain).
+ Path("/not-found/has-no-redirects-entry"),
Response: Expect().
Status(404).
Headers(
@@ -109,9 +109,10 @@ func TestRedirectsFileSupport(t *testing.T) {
Body(Contains(custom404.ReadFile())),
},
{
- Name: "request for $REDIRECTS_DIR_HOSTNAME/gone/has-no-redirects-entry returns custom 410, per _redirects file",
+ Name: "request for {cid}.ipfs.example.com/gone/has-no-redirects-entry returns custom 410, per _redirects file",
Request: Request().
- URL("{{url}}/gone/has-no-redirects-entry", redirectDirBaseURL),
+ Header("Host", dirCIDInSubdomain).
+ Path("/gone/has-no-redirects-entry"),
Response: Expect().
Status(410).
Headers(
@@ -121,9 +122,10 @@ func TestRedirectsFileSupport(t *testing.T) {
Body(Contains(custom410.ReadFile())),
},
{
- Name: "request for $REDIRECTS_DIR_HOSTNAME/unavail/has-no-redirects-entry returns custom 451, per _redirects file",
+ Name: "request for {cid}.ipfs.example.com/unavail/has-no-redirects-entry returns custom 451, per _redirects file",
Request: Request().
- URL("{{url}}/unavail/has-no-redirects-entry", redirectDirBaseURL),
+ Header("Host", dirCIDInSubdomain).
+ Path("/unavail/has-no-redirects-entry"),
Response: Expect().
Status(451).
Headers(
@@ -133,9 +135,10 @@ func TestRedirectsFileSupport(t *testing.T) {
Body(Contains(custom451.ReadFile())),
},
{
- Name: "request for $REDIRECTS_DIR_HOSTNAME/catch-all returns 200, per _redirects file",
+ Name: "request for {cid}.ipfs.example.com/catch-all returns 200, per _redirects file",
Request: Request().
- URL("{{url}}/catch-all", redirectDirBaseURL),
+ Header("Host", dirCIDInSubdomain).
+ Path("/catch-all"),
Response: Expect().
Status(200).
Body(Contains("my index")),
@@ -144,17 +147,18 @@ func TestRedirectsFileSupport(t *testing.T) {
// # Invalid file, containing forced redirect
invalidRedirectsDirCID := fixture.MustGetNode("forced").Base32Cid()
- invalidDirBaseURL := Fmt("{{scheme}}://{{cid}}.ipfs.{{host}}", u.Scheme, invalidRedirectsDirCID, u.Host)
+ invalidDirSubdomain := Fmt("{{cid}}.ipfs.{{host}}", invalidRedirectsDirCID, u.Host)
tooLargeRedirectsDirCID := fixture.MustGetNode("too-large").Base32Cid()
- tooLargeDirBaseURL := Fmt("{{scheme}}://{{cid}}.ipfs.{{host}}", u.Scheme, tooLargeRedirectsDirCID, u.Host)
+ tooLargeDirSubdomain := Fmt("{{cid}}.ipfs.{{host}}", tooLargeRedirectsDirCID, u.Host)
tests = append(tests, SugarTests{
{
Name: "invalid file: request for $INVALID_REDIRECTS_DIR_HOSTNAME/not-found returns error about invalid redirects file",
Hint: `if accessing a path that doesn't exist, read _redirects and fail parsing, and return error`,
Request: Request().
- URL("{{url}}/not-found", invalidDirBaseURL),
+ Header("Host", invalidDirSubdomain).
+ Path("/not-found"),
Response: Expect().
Status(500).
Body(
@@ -169,7 +173,8 @@ func TestRedirectsFileSupport(t *testing.T) {
Name: "invalid file: request for $TOO_LARGE_REDIRECTS_DIR_HOSTNAME/not-found returns error about too large redirects file",
Hint: `if accessing a path that doesn't exist and _redirects file is too large, return error`,
Request: Request().
- URL("{{url}}/not-found", tooLargeDirBaseURL),
+ Header("Host", tooLargeDirSubdomain).
+ Path("/not-found"),
Response: Expect().
Status(500).
Body(
@@ -184,21 +189,22 @@ func TestRedirectsFileSupport(t *testing.T) {
// # With CRLF line terminator
newlineRedirectsDirCID := fixture.MustGetNode("newlines").Base32Cid()
- newlineBaseURL := Fmt("{{scheme}}://{{cid}}.ipfs.{{host}}", u.Scheme, newlineRedirectsDirCID, u.Host)
+ newlineHost := Fmt("{{cid}}.ipfs.{{host}}", newlineRedirectsDirCID, u.Host)
// # Good codes
goodRedirectDirCID := fixture.MustGetNode("good-codes").Base32Cid()
- goodRedirectDirBaseURL := Fmt("{{scheme}}://{{cid}}.ipfs.{{host}}", u.Scheme, goodRedirectDirCID, u.Host)
+ goodRedirectDirHost := Fmt("{{cid}}.ipfs.{{host}}", goodRedirectDirCID, u.Host)
// # Bad codes
badRedirectDirCID := fixture.MustGetNode("bad-codes").Base32Cid()
- badRedirectDirBaseURL := Fmt("{{scheme}}://{{cid}}.ipfs.{{host}}", u.Scheme, badRedirectDirCID, u.Host)
+ badRedirectDirHost := Fmt("{{cid}}.ipfs.{{host}}", badRedirectDirCID, u.Host)
tests = append(tests, SugarTests{
{
Name: "newline: request for $NEWLINE_REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file",
Request: Request().
- URL("{{url}}/redirect-one", newlineBaseURL),
+ Header("Host", newlineHost).
+ Path("/redirect-one"),
Response: Expect().
Status(301).
Headers(
@@ -208,7 +214,8 @@ func TestRedirectsFileSupport(t *testing.T) {
{
Name: "good codes: request for $GOOD_REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file",
Request: Request().
- URL("{{url}}/a301", goodRedirectDirBaseURL),
+ Header("Host", goodRedirectDirHost).
+ Path("/a301"),
Response: Expect().
Status(301).
Headers(
@@ -218,7 +225,8 @@ func TestRedirectsFileSupport(t *testing.T) {
{
Name: "bad codes: request for $BAD_REDIRECTS_DIR_HOSTNAME/found.html doesn't return error about bad code",
Request: Request().
- URL("{{url}}/found.html", badRedirectDirBaseURL),
+ Header("Host", badRedirectDirHost).
+ Path("/found.html"),
Response: Expect().
Status(200).
Body(
@@ -230,7 +238,7 @@ func TestRedirectsFileSupport(t *testing.T) {
},
}...)
- RunWithSpecs(t, helpers.UnwrapSubdomainTests(t, tests), specs.SubdomainGatewayIPFS, specs.RedirectsFile)
+ RunWithSpecs(t, tests, specs.SubdomainGatewayIPFS, specs.RedirectsFile)
}
func TestRedirectsFileSupportWithDNSLink(t *testing.T) {
@@ -280,18 +288,15 @@ func TestRedirectsFileWithIfNoneMatchHeader(t *testing.T) {
dnsLinks := dnslink.MustOpenDNSLink("redirects_file/dnslink.yml")
dnsLink := dnsLinks.MustGet("redirects-spa")
- u, err := url.Parse(SubdomainGatewayURL)
- if err != nil {
- t.Fatal(err)
- }
+ u := SubdomainGatewayURL()
dnslinkAtSubdomainGw := Fmt("{{dnslink}}.ipns.{{host}}", dnslink.InlineDNS(dnsLink), u.Host)
var etag string
- RunWithSpecs(t, helpers.UnwrapSubdomainTests(t, SugarTests{
+ RunWithSpecs(t, SugarTests{
{
- Name: "request for //{{dnslink}}.ipns.{{subdomain-gateway}}/missing-page returns body of index.html as per _redirects",
+ Name: "request for //{dnslink}.ipns.{subdomain-gateway}/missing-page returns body of index.html as per _redirects",
Request: Request().
Path("/missing-page").
Headers(
@@ -309,11 +314,11 @@ func TestRedirectsFileWithIfNoneMatchHeader(t *testing.T) {
).
Body(fixture.MustGetRawData("index.html")),
},
- }), specs.SubdomainGatewayIPNS, specs.RedirectsFile)
+ }, specs.SubdomainGatewayIPNS, specs.RedirectsFile)
- RunWithSpecs(t, helpers.UnwrapSubdomainTests(t, SugarTests{
+ RunWithSpecs(t, SugarTests{
{
- Name: "request for //{dnslink}.ipns.{subdomain-gw}/missing-page with If-None-Match returns 304",
+ Name: "request for //{dnslink}.ipns.{subdomain-gateway}/missing-page with If-None-Match returns 304",
Request: Request().
Path("/missing-page").
Headers(
@@ -324,9 +329,9 @@ func TestRedirectsFileWithIfNoneMatchHeader(t *testing.T) {
Response: Expect().
Status(304),
},
- }), specs.SubdomainGatewayIPNS, specs.RedirectsFile)
+ }, specs.SubdomainGatewayIPNS, specs.RedirectsFile)
- RunWithSpecs(t, helpers.UnwrapSubdomainTests(t, SugarTests{
+ RunWithSpecs(t, SugarTests{
{
Name: "request for //{dnslink}/missing-page returns body of index.html as per _redirects",
Request: Request().
@@ -346,9 +351,9 @@ func TestRedirectsFileWithIfNoneMatchHeader(t *testing.T) {
).
Body(fixture.MustGetRawData("index.html")),
},
- }), specs.DNSLinkGateway, specs.RedirectsFile)
+ }, specs.DNSLinkGateway, specs.RedirectsFile)
- RunWithSpecs(t, helpers.UnwrapSubdomainTests(t, SugarTests{
+ RunWithSpecs(t, SugarTests{
{
Name: "request for //{dnslink}/missing-page with If-None-Match returns 304",
Request: Request().
@@ -361,5 +366,5 @@ func TestRedirectsFileWithIfNoneMatchHeader(t *testing.T) {
Response: Expect().
Status(304),
},
- }), specs.DNSLinkGateway, specs.RedirectsFile)
+ }, specs.DNSLinkGateway, specs.RedirectsFile)
}
diff --git a/tests/subdomain_gateway_ipfs_test.go b/tests/subdomain_gateway_ipfs_test.go
index bca6cbedc..8d86624b6 100644
--- a/tests/subdomain_gateway_ipfs_test.go
+++ b/tests/subdomain_gateway_ipfs_test.go
@@ -1,15 +1,14 @@
package tests
import (
- "net/url"
"testing"
"github.com/ipfs/gateway-conformance/tooling"
"github.com/ipfs/gateway-conformance/tooling/car"
. "github.com/ipfs/gateway-conformance/tooling/check"
- "github.com/ipfs/gateway-conformance/tooling/helpers"
"github.com/ipfs/gateway-conformance/tooling/specs"
. "github.com/ipfs/gateway-conformance/tooling/test"
+ . "github.com/ipfs/gateway-conformance/tooling/tmpl"
)
func TestUnixFSDirectoryListingOnSubdomainGateway(t *testing.T) {
@@ -22,21 +21,14 @@ func TestUnixFSDirectoryListingOnSubdomainGateway(t *testing.T) {
tests := SugarTests{}
// run against origins explicitly passed via --subdomain-url
- u, err := url.Parse(SubdomainGatewayURL)
- if err != nil {
- t.Fatal(err)
- }
+ u := SubdomainGatewayURL()
tests = append(tests, SugarTests{
{
Name: "backlink on root CID should be hidden (TODO: cleanup Kubo-specifics)",
Request: Request().
- URL(
- "{{scheme}}://{{cid}}.ipfs.{{host}}/",
- u.Scheme,
- root.Cid(),
- u.Host,
- ),
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", root.Cid(), u.Host)).
+ Path("/"),
Response: Expect().
BodyWithHint("backlink on root CID should be hidden",
And(
@@ -47,12 +39,8 @@ func TestUnixFSDirectoryListingOnSubdomainGateway(t *testing.T) {
{
Name: "redirect dir listing to URL with trailing slash",
Request: Request().
- URL(
- "{{scheme}}://{{cid}}.ipfs.{{host}}/ą/ę",
- u.Scheme,
- root.Cid(),
- u.Host,
- ),
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", root.Cid(), u.Host)).
+ Path("/ą/ę"),
Response: Expect().
Status(301).
Headers(
@@ -61,12 +49,9 @@ func TestUnixFSDirectoryListingOnSubdomainGateway(t *testing.T) {
},
{
Name: "Regular dir listing HTML (TODO: cleanup Kubo-specifics)",
- Request: Request().URL(
- "{{scheme}}://{{cid}}.ipfs.{{host}}/ą/ę/",
- u.Scheme,
- root.Cid(),
- u.Host,
- ),
+ Request: Request().
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", root.Cid(), u.Host)).
+ Path("/ą/ę/"),
Response: Expect().
Headers(
Header("Etag").Contains(`"DirIndex-`),
@@ -97,7 +82,7 @@ func TestUnixFSDirectoryListingOnSubdomainGateway(t *testing.T) {
},
}...)
- RunWithSpecs(t, helpers.UnwrapSubdomainTests(t, tests), specs.SubdomainGatewayIPFS)
+ RunWithSpecs(t, tests, specs.SubdomainGatewayIPFS)
}
func TestGatewaySubdomains(t *testing.T) {
@@ -119,21 +104,19 @@ func TestGatewaySubdomains(t *testing.T) {
tests := SugarTests{}
// run against origins explicitly passed via --subdomain-url
- gatewayURL := SubdomainGatewayURL
- u, err := url.Parse(gatewayURL)
- if err != nil {
- t.Fatal(err)
- }
+ u := SubdomainGatewayURL()
tests = append(tests, SugarTests{
{
- Name: "request for example.com/ipfs/{CIDv1} redirects to subdomain",
+ Name: "request for example.com/ipfs/{cid} redirects to {cid}.ipfs.example.com",
Hint: `
- path requests to gateways with subdomain support
- should not return payload directly,
- but redirect to URL with proper origin isolation
+ path requests to gateways with subdomain support should not
+ return payload directly, but redirect to URL with proper
+ origin isolation
`,
- Request: Request().URL("{{url}}/ipfs/{{cid}}/", gatewayURL, CIDv1),
+ Request: Request().
+ Header("Host", u.Host).
+ Path("/ipfs/{{cid}}/", CIDv1),
Response: Expect().
Status(301).
Headers(
@@ -143,8 +126,11 @@ func TestGatewaySubdomains(t *testing.T) {
),
},
{
- Name: "request for example.com/ipfs/{CIDv1}/{filename with percent encoding} redirects to subdomain",
- Request: Request().URL("{{url}}/ipfs/{{cid}}/Portugal%252C+España=Peninsula%20Ibérica.txt", gatewayURL, dirWithPercentEncodedFilenameCID),
+ Name: "request for example.com/ipfs/{CIDv1}/{filename with percent encoding} redirects to subdomain",
+ Hint: "the path remainder MUST be preserved",
+ Request: Request().
+ Header("Host", u.Host).
+ Path("/ipfs/{{cid}}/Portugal%252C+España=Peninsula%20Ibérica.txt", dirWithPercentEncodedFilenameCID),
Response: Expect().
Status(301).
Headers(
@@ -152,13 +138,15 @@ func TestGatewaySubdomains(t *testing.T) {
),
},
{
- Name: "request for example.com/ipfs/{DirCID} redirects to subdomain",
+ Name: "request for example.com/ipfs/{DirCID}/ redirects to subdomain",
Hint: `
- path requests to gateways with subdomain support
- should not return payload directly,
- but redirect to URL with proper origin isolation
+ path requests to gateways with subdomain support should not
+ return payload directly, but redirect to URL with proper
+ origin isolation
`,
- Request: Request().URL("{{url}}/ipfs/{{cid}}/", gatewayURL, DirCID),
+ Request: Request().
+ Header("Host", u.Host).
+ Path("/ipfs/{{cid}}/", DirCID),
Response: Expect().
Status(301).
Headers(
@@ -168,8 +156,10 @@ func TestGatewaySubdomains(t *testing.T) {
),
},
{
- Name: "request for example.com/ipfs/{CIDv0} redirects to CIDv1 representation in subdomain",
- Request: Request().URL("{{url}}/ipfs/{{cid}}/", gatewayURL, CIDv0),
+ Name: "request for example.com/ipfs/{CIDv0} redirects to {CIDv1}.ipfs.example.com",
+ Request: Request().
+ Header("Host", u.Host).
+ Path("/ipfs/{{cid}}/", CIDv0),
Response: Expect().
Status(301).
Headers(
@@ -179,53 +169,67 @@ func TestGatewaySubdomains(t *testing.T) {
),
},
{
- Name: "request for {CID}.ipfs.example.com should return expected payload",
- Request: Request().URL("{{scheme}}://{{cid}}.ipfs.{{host}}", u.Scheme, CIDv1, u.Host),
+ Name: "request for {CID}.ipfs.example.com should return expected payload",
+ Request: Request().
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", CIDv1, u.Host)).
+ Path("/"),
Response: Expect().
Status(200).
Body(Contains(CIDVal)),
},
{
- Name: "request for {CID}.ipfs.example.com/ipfs/{CID} should return HTTP 404",
- Hint: "ensure /ipfs/ namespace is not mounted on subdomain",
- Request: Request().URL("{{scheme}}://{{cid}}.ipfs.{{host}}/ipfs/{{cid}}", u.Scheme, CIDv1, u.Host),
+ Name: "request for {CID}.ipfs.example.com/ipfs/{CID} should return HTTP 404",
+ Hint: "ensure /ipfs/ namespace is not mounted on subdomain",
+ Request: Request().
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", CIDv1, u.Host)).
+ Path("/ipfs/{{cid}}/", CIDv1),
Response: Expect().
Status(404),
},
{
- Name: "request for {CID}.ipfs.example.com/ipfs/file.txt should return data from a file in CID content root",
- Hint: "ensure requests to /ipfs/* are not blocked, if content root has such subdirectory",
- Request: Request().URL("{{scheme}}://{{cid}}.ipfs.{{host}}/ipfs/file.txt", u.Scheme, DirCID, u.Host),
+ Name: "request for {CID}.ipfs.example.com/ipfs/file.txt should return data from a file in CID content root",
+ Hint: "ensure requests to /ipfs/* are not blocked, if content root has such subdirectory",
+ Request: Request().
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", DirCID, u.Host)).
+ Path("/ipfs/file.txt"),
Response: Expect().
Status(200).
Body(Contains("I am a txt file")),
},
{
- Name: "valid file and subdirectory paths in directory listing at {cid}.ipfs.example.com",
- Hint: "{CID}.ipfs.example.com/sub/dir (Directory Listing)",
- Request: Request().URL("{{scheme}}://{{cid}}.ipfs.{{host}}/", u.Scheme, DirCID, u.Host),
+ Name: "valid file and subdirectory paths in directory listing at {cid}.ipfs.example.com",
+ Hint: "{CID}.ipfs.example.com (Directory Listing)",
+ Request: Request().
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", DirCID, u.Host)).
+ Path("/"),
Response: Expect().
Status(200).
Body(And(
- // TODO: implement html expectations
+ // TODO: implement html expectations https://github.com/ipfs/gateway-conformance/issues/21
Contains(`hello`),
Contains(`ipfs`),
)),
},
{
- Name: "valid parent directory path in directory listing at {cid}.ipfs.example.com/sub/dir",
- Request: Request().URL("{{scheme}}://{{cid}}.ipfs.{{host}}/ipfs/ipns/", u.Scheme, DirCID, u.Host),
+ Name: "valid parent directory path in directory listing at {cid}.ipfs.example.com/sub/dir",
+ Hint: "{CID}.ipfs.example.com/ipfs/ipns/ (if exists) should produce a valid directory listing",
+ Request: Request().
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", DirCID, u.Host)).
+ Path("/ipfs/ipns/"),
Response: Expect().
Status(200).
Body(And(
- // TODO: implement html expectations
+ // TODO: implement html expectations https://github.com/ipfs/gateway-conformance/issues/21
Contains(`..`),
Contains(`bar`),
)),
},
{
- Name: "request for deep path resource at {cid}.ipfs.example.com/sub/dir/file",
- Request: Request().URL("{{scheme}}://{{cid}}.ipfs.{{host}}/ipfs/ipns/bar", u.Scheme, DirCID, u.Host),
+ Name: "request for deep path resource at {cid}.ipfs.example.com/sub/dir/file",
+ Hint: "{CID}.ipfs.example.com/ipfs/ipns/bar (if exists) should return expected file",
+ Request: Request().
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", DirCID, u.Host)).
+ Path("/ipfs/ipns/bar"),
Response: Expect().
Status(200).
Body(Contains("text-file-content")),
@@ -236,7 +240,9 @@ func TestGatewaySubdomains(t *testing.T) {
Note 1: we test for sneaky subdir names {cid}.ipfs.example.com/ipfs/ipns/ :^)
Note 2: example.com/ipfs/.. present in HTML will be redirected to subdomain, so this is expected behavior
`,
- Request: Request().URL("{{scheme}}://{{cid}}.ipfs.{{host}}/ipfs/ipns/", u.Scheme, DirCID, u.Host),
+ Request: Request().
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", DirCID, u.Host)).
+ Path("/ipfs/ipns/"),
Response: Expect().
Status(200).
Body(
@@ -248,50 +254,47 @@ func TestGatewaySubdomains(t *testing.T) {
),
},
{
- Name: "request for example.com/ipfs/{CIDv1} produces redirect to {CIDv1}.ipfs.example.com",
- Hint: "path requests to the root hostname should redirect to a subdomain URL with proper origin isolation",
- Request: Request().URL("{{scheme}}://{{host}}/ipfs/{{cid}}/", u.Scheme, u.Host, CIDv1),
- Response: Expect().
- Headers(
- Header("Location").Equals("{{scheme}}://{{cid}}.ipfs.{{host}}/", u.Scheme, CIDv1, u.Host),
- ),
- },
- {
- Name: "request for example.com/ipfs/{InvalidCID} produces useful error before redirect",
- Hint: "error message should include original CID (and it should be case-sensitive, as we can't assume everyone uses base32)",
- Request: Request().URL("{{scheme}}://{{host}}/ipfs/QmInvalidCID", u.Scheme, u.Host),
+ Name: "request for example.com/ipfs/{InvalidCID} produces useful error before redirect",
+ Hint: "error message should include original CID (and it should be case-sensitive, as we can't assume everyone uses base32)",
+ Request: Request().
+ Header("Host", u.Host).
+ Path("/ipfs/QmInvalidCID"),
Response: Expect().
Body(Contains(`invalid path "/ipfs/QmInvalidCID"`)),
},
-
{
- Name: "request for example.com/ipfs/{CIDv0} produces redirect to {CIDv1}.ipfs.example.com",
- Request: Request().URL("{{scheme}}://{{host}}/ipfs/{{cid}}/", u.Scheme, u.Host, CIDv0),
+ Name: "request for example.com/ipfs/{CID} with X-Forwarded-Proto: https produces redirect to HTTPS URL",
+ Hint: "Support X-Forwarded-Proto",
+ Request: Request().
+ Header("Host", u.Host).
+ Header("X-Forwarded-Proto", "https").
+ Path("/ipfs/{{cid}}/", CIDv1),
Response: Expect().
Status(301).
Headers(
- Header("Location").Equals("{{scheme}}://{{cid}}.ipfs.{{host}}/", u.Scheme, CIDv0to1, u.Host),
+ Header("Location").Equals("https://{{cid}}.ipfs.{{host}}/", CIDv1, u.Host),
),
},
-
{
- Name: "request for http://example.com/ipfs/{CID} with X-Forwarded-Proto: https produces redirect to HTTPS URL",
+ Name: "request for example.com/ipfs/{CID} with X-Forwarded-Proto: http produces redirect to HTTP URL",
Hint: "Support X-Forwarded-Proto",
- Request: Request().URL("{{scheme}}://{{host}}/ipfs/{{cid}}/", u.Scheme, u.Host, CIDv1).
- Header("X-Forwarded-Proto", "https"),
+ Request: Request().
+ Header("Host", u.Host).
+ Header("X-Forwarded-Proto", "http").
+ Path("/ipfs/{{cid}}/", CIDv1),
Response: Expect().
Status(301).
Headers(
- Header("Location").Equals("https://{{cid}}.ipfs.{{host}}/", CIDv1, u.Host),
+ Header("Location").Equals("http://{{cid}}.ipfs.{{host}}/", CIDv1, u.Host),
),
},
{
Name: "request for example.com/ipfs/?uri=ipfs%3A%2F%2F.. produces redirect to /ipfs/.. content path",
Hint: "Support ipfs:// in https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler",
- Request: Request().URL("{{scheme}}://{{host}}/ipfs/", u.Scheme, u.Host).
- Query(
- "uri", "ipfs://{{host}}/wiki/Diego_Maradona.html", CIDWikipedia,
- ),
+ Request: Request().
+ Header("Host", u.Host).
+ Path("/ipfs/").
+ Query("uri", "ipfs://{{host}}/wiki/Diego_Maradona.html", CIDWikipedia),
Response: Expect().
Status(301).
Headers(
@@ -299,17 +302,21 @@ func TestGatewaySubdomains(t *testing.T) {
),
},
{
- Name: "request for a too long CID at example.com/ipfs/{CIDv1} returns human readable error",
- Hint: "router should not redirect to hostnames that could fail due to DNS limits",
- Request: Request().URL("{{url}}/ipfs/{{cid}}", gatewayURL, CIDv1_TOO_LONG),
+ Name: "request for a too long CID at example.com/ipfs/{CIDv1} returns human readable error",
+ Hint: "router should not redirect to hostnames that could fail due to DNS limits",
+ Request: Request().
+ Header("Host", u.Host).
+ Path("/ipfs/{{cid}}/", CIDv1_TOO_LONG),
Response: Expect().
Status(400).
Body(Contains("CID incompatible with DNS label length limit of 63")),
},
{
- Name: "request for a too long CID at {CIDv1}.ipfs.example.com returns expected payload",
- Hint: "direct request should also fail (provides the same UX as router and avoids confusion)",
- Request: Request().URL("{{scheme}}://{{cid}}.ipfs.{{host}}/", u.Scheme, CIDv1_TOO_LONG, u.Host),
+ Name: "request for a too long CID at {CIDv1}.ipfs.example.com returns expected payload",
+ Hint: "direct request should also fail (provides the same UX as router and avoids confusion)",
+ Request: Request().
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", CIDv1_TOO_LONG, u.Host)).
+ Path("/"),
Response: Expect().
Status(400).
Body(Contains("CID incompatible with DNS label length limit of 63")),
@@ -318,17 +325,21 @@ func TestGatewaySubdomains(t *testing.T) {
// ## Test support for X-Forwarded-Host
// ## ============================================================================
{
- Name: "request for http://fake.domain.com/ipfs/{CID} doesn't match the example.com gateway",
+ Name: "request for fake.domain.com/ipfs/{CID} doesn't match the example.com gateway",
+ Hint: "when there is no Host match, request is processed as a path gateway",
Request: Request().
- URL("{{scheme}}://{{domain}}/ipfs/{{cid}}", u.Scheme, "fake.domain.com", CIDv1),
+ Header("Host", "fake.domain.com").
+ Path("/ipfs/{{cid}}", CIDv1),
Response: Expect().
Status(200),
},
{
- Name: "request for http://fake.domain.com/ipfs/{CID} with X-Forwarded-Host: example.com match the example.com gateway",
+ Name: "request for fake.domain.com/ipfs/{CID} with X-Forwarded-Host: example.com match the example.com gateway",
+ Hint: "X-Forwarded-Host overrides Host, request should be processed as a subdomain gateway",
Request: Request().
- URL("{{scheme}}://{{domain}}/ipfs/{{cid}}", u.Scheme, "fake.domain.com", CIDv1).
- Header("X-Forwarded-Host", u.Host),
+ Header("Host", "fake.domain.com").
+ Header("X-Forwarded-Host", u.Host).
+ Path("/ipfs/{{cid}}", CIDv1),
Response: Expect().
Status(301).
Headers(
@@ -336,9 +347,10 @@ func TestGatewaySubdomains(t *testing.T) {
),
},
{
- Name: "request for http://fake.domain.com/ipfs/{CID} with X-Forwarded-Host: example.com and X-Forwarded-Proto: https match the example.com gateway, redirect with https",
+ Name: "request for fake.domain.com/ipfs/{CID} with X-Forwarded-Host: example.com and X-Forwarded-Proto: https match the example.com gateway, redirect with https",
Request: Request().
- URL("{{scheme}}://{{domain}}/ipfs/{{cid}}", u.Scheme, "fake.domain.com", CIDv1).
+ Header("Host", "fake.domain.com").
+ Path("/ipfs/{{cid}}", CIDv1).
Header("X-Forwarded-Host", u.Host).
Header("X-Forwarded-Proto", "https"),
Response: Expect().
@@ -347,7 +359,20 @@ func TestGatewaySubdomains(t *testing.T) {
Header("Location").Equals("https://{{cid}}.ipfs.{{host}}/", CIDv1, u.Host),
),
},
+ {
+ Name: "request for fake.domain.com/ipfs/{CID} with X-Forwarded-Host: example.com and X-Forwarded-Proto: http match the example.com gateway, redirect with http",
+ Request: Request().
+ Header("Host", "fake.domain.com").
+ Path("/ipfs/{{cid}}", CIDv1).
+ Header("X-Forwarded-Host", u.Host).
+ Header("X-Forwarded-Proto", "http"),
+ Response: Expect().
+ Status(301).
+ Headers(
+ Header("Location").Equals("http://{{cid}}.ipfs.{{host}}/", CIDv1, u.Host),
+ ),
+ },
}...)
- RunWithSpecs(t, helpers.UnwrapSubdomainTests(t, tests), specs.SubdomainGatewayIPFS)
+ RunWithSpecs(t, tests, specs.SubdomainGatewayIPFS)
}
diff --git a/tests/subdomain_gateway_ipns_test.go b/tests/subdomain_gateway_ipns_test.go
index 74c4ce769..997595a93 100644
--- a/tests/subdomain_gateway_ipns_test.go
+++ b/tests/subdomain_gateway_ipns_test.go
@@ -1,16 +1,15 @@
package tests
import (
- "net/url"
"testing"
"github.com/ipfs/gateway-conformance/tooling"
"github.com/ipfs/gateway-conformance/tooling/car"
"github.com/ipfs/gateway-conformance/tooling/dnslink"
- "github.com/ipfs/gateway-conformance/tooling/helpers"
"github.com/ipfs/gateway-conformance/tooling/ipns"
"github.com/ipfs/gateway-conformance/tooling/specs"
. "github.com/ipfs/gateway-conformance/tooling/test"
+ . "github.com/ipfs/gateway-conformance/tooling/tmpl"
"github.com/multiformats/go-multibase"
"github.com/multiformats/go-multicodec"
)
@@ -32,18 +31,27 @@ func TestGatewaySubdomainAndIPNS(t *testing.T) {
}
// run against origins passed via --subdomain-url (e.g. http://localhost:port)
- gatewayURL := SubdomainGatewayURL
- u, err := url.Parse(gatewayURL)
- if err != nil {
- t.Fatal(err)
- }
+ u := SubdomainGatewayURL()
for _, record := range ipnsRecords {
tests = append(tests, SugarTests{
{
Name: "request for /ipns/{CIDv0} redirects to CIDv1 with libp2p-key multicodec in subdomain",
Request: Request().
- URL("{{url}}/ipns/{{cid}}", gatewayURL, record.IdV0()),
+ Header("Host", u.Host).
+ Path("/ipns/{{id}}", record.IdV0()),
+ Response: Expect().
+ Status(301).
+ Headers(
+ Header("Location").
+ Equals("{{scheme}}://{{cid}}.ipns.{{host}}/", u.Scheme, record.IdV1(), u.Host),
+ ),
+ },
+ {
+ Name: "request for /ipns/{CIDv1} redirects to same CIDv1 on subdomain",
+ Request: Request().
+ Header("Host", u.Host).
+ Path("/ipns/{{id}}", record.IdV1()),
Response: Expect().
Status(301).
Headers(
@@ -52,9 +60,10 @@ func TestGatewaySubdomainAndIPNS(t *testing.T) {
),
},
{
- Name: "request for {CIDv1-libp2p-key}.ipns.{gateway} returns expected payload",
+ Name: "request for {CIDv1-base36-libp2p-key}.ipns.{gateway} returns expected payload",
Request: Request().
- URL("{{scheme}}://{{cid}}.ipns.{{host}}/", u.Scheme, record.IdV1(), u.Host),
+ Header("Host", Fmt("{{cid}}.ipns.{{host}}", record.IdV1(), u.Host)).
+ Path("/"),
Response: Expect().
Status(200).
BodyWithHint("Request for {{cid}}.ipns.{{host}} returns expected payload", payload),
@@ -62,7 +71,8 @@ func TestGatewaySubdomainAndIPNS(t *testing.T) {
{
Name: "request for {CIDv1-dag-pb}.ipns.{gateway} redirects to CID with libp2p-key multicodec",
Request: Request().
- URL("{{scheme}}://{{cid}}.ipns.{{host}}/", u.Scheme, record.ToCID(multicodec.DagPb, multibase.Base36), u.Host),
+ Header("Host", Fmt("{{cid}}.ipns.{{host}}", record.ToCID(multicodec.DagPb, multibase.Base36), u.Host)).
+ Path("/"),
Response: Expect().
Status(301).
Headers(
@@ -142,7 +152,8 @@ func TestGatewaySubdomainAndIPNS(t *testing.T) {
{
Name: "request for a ED25519 libp2p-key at example.com/ipns/{b58mh} returns Location HTTP header for DNS-safe subdomain redirect in browsers",
Request: Request().
- URL("{{url}}/ipns/{{cid}}", gatewayURL, ed25519Fixture.B58MH()),
+ Header("Host", u.Host).
+ Path("/ipns/{{b58mh}}", ed25519Fixture.B58MH()),
Response: Expect().
Headers(
Header("Location").
@@ -151,7 +162,7 @@ func TestGatewaySubdomainAndIPNS(t *testing.T) {
},
}...)
- RunWithSpecs(t, helpers.UnwrapSubdomainTests(t, tests), specs.SubdomainGatewayIPNS)
+ RunWithSpecs(t, tests, specs.SubdomainGatewayIPNS)
}
func TestSubdomainGatewayDNSLinkInlining(t *testing.T) {
@@ -164,27 +175,34 @@ func TestSubdomainGatewayDNSLinkInlining(t *testing.T) {
dnsLinkTest := dnsLinks.MustGet("test")
// run against origins passed via --subdomain-url
- gatewayURL := SubdomainGatewayURL
- u, err := url.Parse(gatewayURL)
- if err != nil {
- t.Fatal(err)
- }
+ u := SubdomainGatewayURL()
tests = append(tests, SugarTests{
{
- Name: "request for /ipns/{fqdn} redirects to DNSLink in subdomain",
+ Name: "request for /ipns/{dnslink}/foo/ redirects to {inlined-dnslink}.ipns.example.com",
+ Hint: "https://specs.ipfs.tech/http-gateways/subdomain-gateway/#host-request-header",
Request: Request().
- URL("{{url}}/ipns/{{fqdn}}/wiki/", gatewayURL, wikipedia),
+ Header("Host", u.Host).
+ Path("/ipns/{{dnslink}}/wiki/", wikipedia),
Response: Expect().
Headers(
Header("Location").
- Equals("{{scheme}}://{{fqdn}}.ipns.{{host}}/wiki/", u.Scheme, dnslink.InlineDNS(wikipedia), u.Host),
+ Equals("{{scheme}}://{{inlined}}.ipns.{{host}}/wiki/", u.Scheme, dnslink.InlineDNS(wikipedia), u.Host),
),
},
{
Name: "request for {dnslink}.ipns.{gateway} returns expected payload",
Request: Request().
- URL("{{scheme}}://{{fqdn}}.ipns.{{host}}", u.Scheme, dnsLinkTest, u.Host),
+ Header("Host", Fmt("{{dnslink}}.ipns.{{host}}", dnsLinkTest, u.Host)).
+ Path("/"),
+ Response: Expect().
+ Body("hello\n"),
+ },
+ {
+ Name: "request for {inlineddnslink}.ipns.{gateway} returns expected payload",
+ Request: Request().
+ Header("Host", Fmt("{{inlined}}.ipns.{{host}}", dnslink.InlineDNS(dnsLinkTest), u.Host)).
+ Path("/"),
Response: Expect().
Body("hello\n"),
},
@@ -196,7 +214,8 @@ func TestSubdomainGatewayDNSLinkInlining(t *testing.T) {
`,
Request: Request().
Header("X-Forwarded-Proto", "https").
- URL("{{url}}/ipns/{{wikipedia}}/wiki/", gatewayURL, wikipedia),
+ Header("Host", u.Host).
+ Path("/ipns/{{wikipedia}}/wiki/", wikipedia),
Response: Expect().
Headers(
Header("Location").
@@ -207,8 +226,9 @@ func TestSubdomainGatewayDNSLinkInlining(t *testing.T) {
Name: `request for example.com/ipns/?uri=ipns%3A%2F%2F.. produces redirect to /ipns/.. content path`,
Hint: "Support ipns:// in https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler",
Request: Request().
- // TODO: use Query or future QueryRaw here
- URL(`{{url}}/ipns/?uri=ipns%3A%2F%2F{{dnslink}}`, gatewayURL, wikipedia),
+ Header("Host", u.Host).
+ Path("/ipns/").
+ Query("uri", "ipns://{{dnslink}}", wikipedia),
Response: Expect().
Headers(
Header("Location").Equals("/ipns/{{wikipedia}}", wikipedia),
@@ -216,5 +236,5 @@ func TestSubdomainGatewayDNSLinkInlining(t *testing.T) {
},
}...)
- RunWithSpecs(t, helpers.UnwrapSubdomainTests(t, tests), specs.SubdomainGatewayIPNS)
+ RunWithSpecs(t, tests, specs.SubdomainGatewayIPNS)
}
diff --git a/tests/subdomain_gateway_proxy_test.go b/tests/subdomain_gateway_proxy_test.go
new file mode 100644
index 000000000..4e1f7c348
--- /dev/null
+++ b/tests/subdomain_gateway_proxy_test.go
@@ -0,0 +1,141 @@
+package tests
+
+import (
+ "testing"
+
+ "github.com/ipfs/gateway-conformance/tooling/car"
+ . "github.com/ipfs/gateway-conformance/tooling/check"
+ "github.com/ipfs/gateway-conformance/tooling/specs"
+ . "github.com/ipfs/gateway-conformance/tooling/test"
+ . "github.com/ipfs/gateway-conformance/tooling/tmpl"
+)
+
+var (
+ fixture = car.MustOpenUnixfsCar("subdomain_gateway/fixtures.car")
+
+ CIDVal = string(fixture.MustGetRawData("hello-CIDv1")) // hello
+ DirCID = fixture.MustGetCid("testdirlisting")
+ CIDv1 = fixture.MustGetCid("hello-CIDv1")
+ CIDv0 = fixture.MustGetCid("hello-CIDv0")
+ CIDv0to1 = fixture.MustGetCid("hello-CIDv0to1")
+ //CIDv1_TOO_LONG = fixture.MustGetCid("hello-CIDv1_TOO_LONG")
+
+ // the gateway endpoint is used as HTTP proxy
+ gatewayAsProxyURL = GatewayURL().String()
+
+ // run against origins explicitly passed via --subdomain-url
+ s = SubdomainGatewayURL()
+)
+
+func TestProxyGatewaySubdomains(t *testing.T) {
+ tests := SugarTests{
+ {
+ Name: "request for {CID}.ipfs.example.com should return expected payload",
+ Hint: "HTTP proxy gateway accepts requests for GETs of full URLs as Paths",
+ Request: Request().
+ Proxy(gatewayAsProxyURL).
+ Path("{{scheme}}://{{cid}}.ipfs.{{host}}", s.Scheme, CIDv1, s.Host),
+ Response: Expect().
+ Status(200).
+ Body(Contains(CIDVal)),
+ },
+ {
+ Name: "request for example.com/ipfs/{CIDv0} redirects to {CIDv1}.ipfs.example.com",
+ Hint: "HTTP proxy gateway accepts requests for GETs of full URLs as Paths",
+ Request: Request().
+ Proxy(gatewayAsProxyURL).
+ Path("{{scheme}}://{{host}}/ipfs/{{cid}}/", s.Scheme, s.Host, CIDv0),
+ Response: Expect().
+ Status(301).
+ Headers(
+ Header("Location").
+ Hint("request for example.com/ipfs/{CIDv0to1} returns Location HTTP header for subdomain redirect in browsers").
+ Contains("{{scheme}}://{{cid}}.ipfs.{{host}}/", s.Scheme, CIDv0to1, s.Host),
+ ),
+ },
+ {
+ Name: "request for {CID}.ipfs.example.com/ipfs/file.txt should return data from a file in CID content root",
+ Hint: "ensure subdomain gateway takes priority over processing /ipfs/* paths",
+ Request: Request().
+ Proxy(gatewayAsProxyURL).
+ Path("{{scheme}}://{{cid}}.ipfs.{{host}}/ipfs/file.txt", s.Scheme, DirCID, s.Host),
+ Response: Expect().
+ Status(200).
+ Body(Contains("I am a txt file")),
+ },
+ /* TODO: value added
+ {
+ Name: "request for a too long CID at {CIDv1}.ipfs.example.com returns expected payload",
+ Hint: "HTTP proxy mode allows responding to requests with 'DNS labels' longer than 63 characters",
+ Request: Request().
+ Proxy(gatewayAsProxyURL).
+ TODO turn to Path: Header("Host", Fmt("{{cid}}.ipfs.{{host}}", CIDv1_TOO_LONG, s.Host)).
+ Path("/"),
+ Response: Expect().
+ Status(400).
+ Body(Contains("TODO")),
+ },
+ */
+ }
+ RunWithSpecs(t, tests, specs.ProxyGateway, specs.SubdomainGatewayIPFS)
+}
+
+func TestProxyTunnelGatewaySubdomains(t *testing.T) {
+ tests := SugarTests{
+ {
+ Name: "request for {CID}.ipfs.example.com should return expected payload",
+ Hint: "HTTP CONNECT is how some proxy setups convert an HTTP connection into a tunnel to a remote host https://tools.ietf.org/html/rfc7231#section-4.3.6",
+ Request: Request().
+ WithProxyTunnel().
+ Proxy(gatewayAsProxyURL).
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", CIDv1, s.Host)).
+ Path("/"),
+ Response: Expect().
+ Status(200).
+ Body(Contains(CIDVal)),
+ },
+ {
+ Name: "request for example.com/ipfs/{CIDv0} redirects to {CIDv1}.ipfs.example.com",
+ Hint: "proxy tunnel follows ",
+ Request: Request().
+ WithProxyTunnel().
+ Proxy(gatewayAsProxyURL).
+ Header("Host", s.Host).
+ Path("/ipfs/{{cid}}/", CIDv0),
+ Response: Expect().
+ Status(301).
+ Headers(
+ Header("Location").
+ Hint("request for example.com/ipfs/{CIDv0to1} returns Location HTTP header for subdomain redirect in browsers").
+ Contains("{{scheme}}://{{cid}}.ipfs.{{host}}/", s.Scheme, CIDv0to1, s.Host),
+ ),
+ },
+ {
+ Name: "request for {CID}.ipfs.example.com/ipfs/file.txt should return data from a file in CID content root",
+ Hint: "ensure subdomain gateway takes priority over processing /ipfs/* paths",
+ Request: Request().
+ WithProxyTunnel().
+ Proxy(gatewayAsProxyURL).
+ Header("Host", Fmt("{{cid}}.ipfs.{{host}}", DirCID, s.Host)).
+ Path("/ipfs/file.txt"),
+ Response: Expect().
+ Status(200).
+ Body(Contains("I am a txt file")),
+ },
+ /* TODO: value added
+ {
+ Name: "request for a too long CID at {CIDv1}.ipfs.example.com returns expected payload",
+ Hint: "HTTP proxy mode allows responding to requests with 'DNS labels' longer than 63 characters",
+ Request: Request().
+ WithProxyTunnel().
+ Proxy(gatewayAsProxyURL).
+ TODO turn to Path: Header("Host", Fmt("{{cid}}.ipfs.{{host}}", CIDv1_TOO_LONG, s.Host)).
+ Path("/"),
+ Response: Expect().
+ Status(400).
+ Body(Contains("TODO")),
+ },
+ */
+ }
+ RunWithSpecs(t, tests, specs.ProxyGateway, specs.SubdomainGatewayIPFS)
+}
diff --git a/tooling/helpers/subdomain.go b/tooling/helpers/subdomain.go
deleted file mode 100644
index b1850f22e..000000000
--- a/tooling/helpers/subdomain.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package helpers
-
-import (
- "fmt"
- "net/url"
- "testing"
-
- "github.com/ipfs/gateway-conformance/tooling/test"
-)
-
-/**
- * UnwrapSubdomainTests takes a list of tests and returns a (larger) list of tests
- * that will run on the subdomain gateway.
- */
-func UnwrapSubdomainTests(t *testing.T, tests test.SugarTests) test.SugarTests {
- t.Helper()
-
- var out test.SugarTests
- for _, test := range tests {
- out = append(out, unwrapSubdomainTest(t, test)...)
- }
- return out
-}
-
-func unwrapSubdomainTest(t *testing.T, unwraped test.SugarTest) test.SugarTests {
- t.Helper()
-
- var logicalURL, httpEndpointURL string
- req := unwraped.Request
- expected := unwraped.Response
- host := req.RemoveHeader("Host")
- if host != "" {
- // when custom Host header is present we skip legacy magic
- // and use Host and Path as-is
- u, err := url.Parse(test.GatewayURL)
- if err != nil {
- panic("failed to parse GatewayURL")
- }
- u.Host = host
- // httpEndpointURL is gateway-url + Path
- u.Path = unwraped.Request.Path_
- unwraped.Request.Path_ = ""
- httpEndpointURL = u.String()
- // logicalURL is httpEndpointURL with hostname from Host header
- logicalURL = u.String()
- unwraped.Request.URL_ = logicalURL
- } else {
- // Legacy flow based on URL instead of Host header
- logicalURL := unwraped.Request.GetURL()
-
- u, err := url.Parse(logicalURL)
- if err != nil {
- t.Fatal(err)
- }
-
- // change the low level HTTP endpoint to one defined via --gateway-url
- // to allow testing Host-based logic against arbitrary gateway URL (useful on CI)
- u.Host = test.GatewayHost
-
- httpEndpointURL = u.String()
- }
-
- // TODO: we want to refactor this magic into explicit Proxy test suite.
- // Having this magic here silently modifies headers such as Host, and if a
- // test fails, it is difficult to grasp how much really is broken, because
- // number of errors is always multiplied x3. We should have standalone
- // proxy test for subdomain gateway and dnslink (simple GET should be
- // enough) and remove need for UnwrapSubdomainTests.
-
- return test.SugarTests{
- {
- Name: fmt.Sprintf("%s (direct HTTP)", unwraped.Name),
- Hint: fmt.Sprintf("%s\n%s", unwraped.Hint, "direct HTTP request (hostname in URL, raw IP in Host header)"),
- Request: req.
- URL(httpEndpointURL).
- Headers(
- test.Header("Host", host),
- ),
- Response: expected,
- },
- {
- Name: fmt.Sprintf("%s (HTTP proxy)", unwraped.Name),
- Hint: fmt.Sprintf("%s\n%s", unwraped.Hint, "HTTP proxy (hostname is passed via URL)"),
- Request: req.
- URL(logicalURL).
- Proxy(test.GatewayURL),
- Response: expected,
- },
- {
- Name: fmt.Sprintf("%s (HTTP proxy tunneling via CONNECT)", unwraped.Name),
- Hint: fmt.Sprintf("%s\n%s", unwraped.Hint, `HTTP proxy
- In HTTP/1.x, the pseudo-method CONNECT,
- can be used to convert an HTTP connection into a tunnel to a remote host
- https://tools.ietf.org/html/rfc7231#section-4.3.6
- `),
- Request: req.
- URL(logicalURL).
- Proxy(test.GatewayURL).
- WithProxyTunnel().
- Headers(
- test.Header("Host", host),
- ),
- Response: expected,
- },
- }
-}
diff --git a/tooling/specs/specs.go b/tooling/specs/specs.go
index af4d136e8..f99bd7d37 100644
--- a/tooling/specs/specs.go
+++ b/tooling/specs/specs.go
@@ -121,6 +121,7 @@ var (
SubdomainGateway = Collection{"subdomain-gateway", []Spec{SubdomainGatewayIPFS, SubdomainGatewayIPNS}}
DNSLinkGateway = Leaf{"dnslink-gateway", stable}
RedirectsFile = Leaf{"redirects-file", stable}
+ ProxyGateway = Leaf{"proxy-gateway", stable}
)
// All specs MUST be listed here.
@@ -141,6 +142,7 @@ var specs = []Spec{
SubdomainGateway,
DNSLinkGateway,
RedirectsFile,
+ ProxyGateway,
}
var specEnabled = map[Spec]bool{}
diff --git a/tooling/test/config.go b/tooling/test/config.go
index 3d501da83..ef94a7d59 100644
--- a/tooling/test/config.go
+++ b/tooling/test/config.go
@@ -10,44 +10,23 @@ import (
var log = logging.Logger("conformance")
-func GetEnv(key string, fallback string) string {
- if value, ok := os.LookupEnv(key); ok {
- return value
+func env2url(key string) *url.URL {
+ value, ok := os.LookupEnv(key)
+ if !ok {
+ panic(key + " must be set")
}
- return fallback
-}
-
-var GatewayURL = strings.TrimRight(
- GetEnv("GATEWAY_URL", "http://127.0.0.1:8080"),
- "/")
-
-var SubdomainGatewayURL = strings.TrimRight(
- GetEnv("SUBDOMAIN_GATEWAY_URL", "http://localhost:8080"),
- "/")
-
-var GatewayHost = ""
-var SubdomainGatewayHost = ""
-var SubdomainGatewayScheme = ""
-
-func init() {
- parsed, err := url.Parse(GatewayURL)
+ gatewayURL := strings.TrimRight(value, "/")
+ parsed, err := url.Parse(gatewayURL)
if err != nil {
panic(err)
}
+ return parsed
+}
- GatewayHost = parsed.Host
-
- parsed, err = url.Parse(SubdomainGatewayURL)
- if err != nil {
- panic(err)
- }
-
- SubdomainGatewayHost = parsed.Host
- SubdomainGatewayScheme = parsed.Scheme
-
- log.Debugf("GatewayURL: %s", GatewayURL)
+func GatewayURL() *url.URL {
+ return env2url("GATEWAY_URL")
+}
- log.Debugf("SubdomainGatewayURL: %s", SubdomainGatewayURL)
- log.Debugf("SubdomainGatewayHost: %s", SubdomainGatewayHost)
- log.Debugf("SubdomainGatewayScheme: %s", SubdomainGatewayScheme)
+func SubdomainGatewayURL() *url.URL {
+ return env2url("SUBDOMAIN_GATEWAY_URL")
}
diff --git a/tooling/test/proxy.go b/tooling/test/proxy.go
index 55e893e3e..d1888f68f 100644
--- a/tooling/test/proxy.go
+++ b/tooling/test/proxy.go
@@ -10,6 +10,8 @@ import (
"net/url"
)
+// NewProxyTunnelClient creates an HTTP client that routes requests through an HTTP proxy
+// using the CONNECT method, as described in RFC 7231 Section 4.3.6.
func NewProxyTunnelClient(proxyURL string) *http.Client {
proxy, err := url.Parse(proxyURL)
if err != nil {
@@ -52,6 +54,7 @@ func NewProxyTunnelClient(proxyURL string) *http.Client {
return conn, nil
},
+ // Skip TLS cert verification to make it easier to test on CI and dev envs
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
@@ -62,6 +65,7 @@ func NewProxyTunnelClient(proxyURL string) *http.Client {
return client
}
+// NewProxyClient creates an HTTP client that routes requests through an HTTP proxy.
func NewProxyClient(proxyURL string) *http.Client {
proxy, err := url.Parse(proxyURL)
if err != nil {
diff --git a/tooling/test/run.go b/tooling/test/run.go
index 8e6e91dea..d408ef270 100644
--- a/tooling/test/run.go
+++ b/tooling/test/run.go
@@ -6,7 +6,10 @@ import (
"fmt"
"io"
"net/http"
+ "strings"
"testing"
+
+ "github.com/ipfs/gateway-conformance/tooling"
)
type Reporter func(t *testing.T, msg interface{}, rest ...interface{})
@@ -18,8 +21,9 @@ func runRequest(ctx context.Context, t *testing.T, test SugarTest, builder Reque
}
// Prepare a client,
- // use proxy, deal with redirects, etc.
client := &http.Client{}
+
+ // HTTP proxy tests require additional prep
if builder.UseProxyTunnel_ {
if builder.Proxy_ == "" {
t.Fatal("ProxyTunnel requires a proxy")
@@ -30,6 +34,7 @@ func runRequest(ctx context.Context, t *testing.T, test SugarTest, builder Reque
client = NewProxyClient(builder.Proxy_)
}
+ // Handle redirect tests
if !builder.FollowRedirects_ {
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
@@ -54,21 +59,26 @@ func runRequest(ctx context.Context, t *testing.T, test SugarTest, builder Reque
}
var url string
- if builder.URL_ != "" && builder.Path_ != "" {
- localReport(t, "Both 'URL' and 'Path' are set")
- }
- if builder.URL_ == "" && builder.Path_ == "" {
- localReport(t, "Neither 'URL' nor 'Path' are set")
- }
- if builder.URL_ != "" {
- url = builder.URL_
+ if builder.Path_ == "" {
+ localReport(t, "'Path' is not set")
}
if builder.Path_ != "" {
- if builder.Path_[0] != '/' {
- localReport(t, "Path must start with '/'")
+ if builder.Proxy_ != "" && !builder.UseProxyTunnel_ {
+ // plain HTTP proxy test uses custom client, and the Path is the full URL
+ // to be used in the request
+ if !strings.HasPrefix(builder.Path_, "http") {
+ t.Fatalf("plain Proxy tests require requested Path to be full URL starting with http. builder.Path_ was %q", builder.Path_)
+ }
+ // plain proxy requests use Path as-is
+ url = builder.Path_
+ } else {
+ // no HTTP proxy, make a regular HTTP request for Path at GatewayURL (+ optional Host header)
+ if builder.Path_[0] != '/' {
+ localReport(t, "When proxy mode is not used, the Path must start with '/'")
+ }
+ // regular requests attach Path to gateway endpoint URL
+ url = fmt.Sprintf("%s%s", strings.TrimRight(GatewayURL().String(), "/"), builder.Path_)
}
-
- url = fmt.Sprintf("%s%s", GatewayURL, builder.Path_)
}
query := builder.Query_.Encode()
@@ -97,7 +107,12 @@ func runRequest(ctx context.Context, t *testing.T, test SugarTest, builder Reque
}
}
- // send request
+ // Set meaningful User-Agent, if custom one was not set by a test
+ if _, exists := builder.Headers_["User-Agent"]; !exists {
+ req.Header.Set("User-Agent", "ipfs/gateway-conformance/"+tooling.Version)
+ }
+
+ // Send request
log.Debugf("Querying %s", url)
req = req.WithContext(ctx)
diff --git a/tooling/test/sugar.go b/tooling/test/sugar.go
index 07a0f023d..cbe0228da 100644
--- a/tooling/test/sugar.go
+++ b/tooling/test/sugar.go
@@ -14,7 +14,6 @@ import (
type RequestBuilder struct {
Method_ string `json:"method,omitempty"`
Path_ string `json:"path,omitempty"`
- URL_ string `json:"url,omitempty"`
Proxy_ string `json:"proxy,omitempty"`
UseProxyTunnel_ bool `json:"useProxyTunnel,omitempty"`
Headers_ map[string]string `json:"headers,omitempty"`
@@ -40,26 +39,11 @@ func (r RequestBuilder) Path(path string, args ...any) RequestBuilder {
return r
}
-func (r RequestBuilder) URL(path string, args ...any) RequestBuilder {
- r.URL_ = tmpl.Fmt(path, args...)
- return r
-}
-
func (r RequestBuilder) Query(key, value string, args ...any) RequestBuilder {
r.Query_.Add(key, tmpl.Fmt(value, args...))
return r
}
-func (r RequestBuilder) GetURL() string {
- if r.Path_ != "" {
- // This seems to be some tech debt. Generally, we want to move away from URL,
- // and instead just provide Path + Host header
- panic("calling GetURL() is not supported when Path is set")
- }
-
- return r.URL_
-}
-
func (r RequestBuilder) RemoveHeader(hdr string) string {
if r.Headers_ != nil {
v, ok := r.Headers_[hdr]
@@ -138,7 +122,6 @@ func (r RequestBuilder) Clone() RequestBuilder {
return RequestBuilder{
Method_: r.Method_,
Path_: r.Path_,
- URL_: r.URL_,
Proxy_: r.Proxy_,
UseProxyTunnel_: r.UseProxyTunnel_,
Headers_: clonedHeaders,