From defb1fd03df61ffd724d9e4f81da21d6927fcd0a Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Tue, 3 Mar 2020 17:55:39 +0100 Subject: [PATCH] test: /api in subdomains License: MIT Signed-off-by: Marcin Rataj --- core/corehttp/hostname.go | 54 ++++++++++++++++------- test/sharness/t0114-gateway-subdomains.sh | 48 ++++++++++++++++---- 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/core/corehttp/hostname.go b/core/corehttp/hostname.go index e08e80686f2..ed9218da92c 100644 --- a/core/corehttp/hostname.go +++ b/core/corehttp/hostname.go @@ -18,12 +18,12 @@ import ( ) var pathGatewaySpec = config.GatewaySpec{ - Paths: []string{ipfsPathPrefix, ipnsPathPrefix, "/api", "/p2p", "/version"}, + Paths: []string{"/ipfs/", "/ipns/", "/api/", "/p2p/", "/version"}, UseSubdomains: false, } var subdomainGatewaySpec = config.GatewaySpec{ - Paths: []string{ipfsPathPrefix, ipnsPathPrefix}, + Paths: []string{"/ipfs/", "/ipns/", "/api/", "/p2p/"}, UseSubdomains: true, } @@ -38,7 +38,7 @@ var defaultKnownGateways = map[string]config.GatewaySpec{ // Find content identifier, protocol, and remaining hostname (host+optional port) // of a subdomain gateway (eg. *.ipfs.foo.bar.co.uk) -var subdomainGatewayRegex = regexp.MustCompile("^(.+).(ipfs|ipns|ipld|p2p).([^/?#&]+)$") +var subdomainGatewayRegex = regexp.MustCompile("^(.+).(ipfs|ipns|ipld|p2p|api).([^/?#&]+)$") // HostnameOption rewrites an incoming request based on the Host header. func HostnameOption() ServeOption { @@ -112,7 +112,7 @@ func HostnameOption() ServeOption { if gw.UseSubdomains { // Yes, redirect if applicable (pretty much everything except `/api`). // Example: dweb.link/ipfs/{cid} → {cid}.ipfs.dweb.link - if newURL, ok := toSubdomainURL(r.Host, r.URL.Path); ok { + if newURL, ok := toSubdomainURL(r.Host, r.URL.Path, r.URL.RawQuery); ok { http.Redirect(w, r, newURL, http.StatusMovedPermanently) return } @@ -130,24 +130,24 @@ func HostnameOption() ServeOption { // HTTP Host check: is this one of our subdomain-based "known gateways"? // Example: {cid}.ipfs.localhost, {cid}.ipfs.dweb.link - if hostname, ns, rootId, ok := parseSubdomains(r.Host); ok { + if hostname, ns, rootID, ok := parseSubdomains(r.Host); ok { // Looks like we're using subdomains. // Again, is this a known gateway that supports subdomains? if gw, ok := isKnownGateway(hostname); ok { // Assemble original path prefix. - pathPrefix := "/" + ns + "/" + rootId + pathPrefix := "/" + ns + "/" + rootID // Does this gateway _handle_ this path? if gw.UseSubdomains && hasPrefix(pathPrefix, gw.Paths...) { // Do we need to fix multicodec in CID? if ns == "ipns" { - keyCid, err := cid.Decode(rootId) + keyCid, err := cid.Decode(rootID) if err == nil && keyCid.Type() != cid.Libp2pKey { - if newURL, ok := toSubdomainURL(hostname, pathPrefix+r.URL.Path); ok { + if newURL, ok := toSubdomainURL(hostname, pathPrefix+r.URL.Path, r.URL.RawQuery); ok { // Redirect to CID fixed inside of toSubdomainURL() http.Redirect(w, r, newURL, http.StatusMovedPermanently) return @@ -198,7 +198,7 @@ func HostnameOption() ServeOption { func isSubdomainNamespace(ns string) bool { switch ns { - case "ipfs", "ipns", "p2p", "ipld": + case "ipfs", "ipns", "p2p", "ipld", "api": return true default: return false @@ -207,7 +207,7 @@ func isSubdomainNamespace(ns string) bool { // Parses Host header of a subdomain-based URL and returns it's components // Note: hostname is host + optional port -func parseSubdomains(hostHeader string) (hostname, ns, rootId string, ok bool) { +func parseSubdomains(hostHeader string) (hostname, ns, rootID string, ok bool) { parts := subdomainGatewayRegex.FindStringSubmatch(hostHeader) if len(parts) < 4 || !isSubdomainNamespace(parts[2]) { return "", "", "", false @@ -216,17 +216,17 @@ func parseSubdomains(hostHeader string) (hostname, ns, rootId string, ok bool) { } // Converts a hostname/path to a subdomain-based URL, if applicable. -func toSubdomainURL(hostname, path string) (url string, ok bool) { +func toSubdomainURL(hostname, path string, query string) (url string, ok bool) { parts := strings.SplitN(path, "/", 4) - var ns, rootId, rest string + var ns, rootID, rest string switch len(parts) { case 4: rest = parts[3] fallthrough case 3: ns = parts[1] - rootId = parts[2] + rootID = parts[2] default: return "", false } @@ -235,7 +235,26 @@ func toSubdomainURL(hostname, path string) (url string, ok bool) { return "", false } - if rootCid, err := cid.Decode(rootId); err == nil { + // add prefix if query is present + if query != "" { + query = "?" + query + } + + if ns == "api" || ns == "p2p" { + // API and P2P proxy use the same paths on subdomains: + // api.hostname/api/.. and p2p.hostname/p2p/.. + return fmt.Sprintf( + "http://%s.%s/%s/%s/%s%s", + ns, + hostname, + ns, + rootID, + rest, + query, + ), true + } + + if rootCid, err := cid.Decode(rootID); err == nil { multicodec := rootCid.Type() // CIDs in IPNS are expected to have libp2p-key multicodec. @@ -248,15 +267,16 @@ func toSubdomainURL(hostname, path string) (url string, ok bool) { // if object turns out to be a valid CID, // ensure text representation used in subdomain is CIDv1 in Base32 // https://github.com/ipfs/in-web-browsers/issues/89 - rootId = cid.NewCidV1(multicodec, rootCid.Hash()).String() + rootID = cid.NewCidV1(multicodec, rootCid.Hash()).String() } return fmt.Sprintf( - "http://%s.%s.%s/%s", - rootId, + "http://%s.%s.%s/%s%s", + rootID, ns, hostname, rest, + query, ), true } diff --git a/test/sharness/t0114-gateway-subdomains.sh b/test/sharness/t0114-gateway-subdomains.sh index e3d535fdfaf..87f9b8d56f4 100755 --- a/test/sharness/t0114-gateway-subdomains.sh +++ b/test/sharness/t0114-gateway-subdomains.sh @@ -4,8 +4,13 @@ test_description="Test subdomain support on the HTTP gateway" + . lib/test-lib.sh +## ============================================================================ +## Helpers specific to subdomain tests +## ============================================================================ + # Helper that tests gateway response over direct HTTP # and in all supported HTTP proxy modes test_localhost_gateway_response_should_contain() { @@ -41,6 +46,9 @@ test_hostname_gateway_response_should_contain() { test_should_contain \"$4\" response " } +## ============================================================================ +## Start IPFS Node and prepare test CIDs +## ============================================================================ test_init_ipfs test_launch_ipfs_daemon --offline @@ -53,6 +61,13 @@ test_expect_success "Add test text file" ' CIDv0to1=$(echo "$CIDv0" | ipfs cid base32) ' +test_expect_success "Add the test directory" ' + mkdir -p testdirlisting/subdir1/subdir2 && + echo "hello" > testdirlisting/hello && + echo "subdir2-bar" > testdirlisting/subdir1/subdir2/bar && + DIR_CID=$(ipfs add -Qr --cid-version 1 testdirlisting) +' + test_expect_success "Publish test text file to IPNS" ' PEERID=$(ipfs id --format="") IPNS_IDv0=$(echo "$PEERID" | ipfs cid format -v 0) @@ -64,6 +79,7 @@ test_expect_success "Publish test text file to IPNS" ' printf "/ipfs/%s\n" "$CIDv1" > expected2 && test_cmp expected2 output ' + #test_kill_ipfs_daemon #test_launch_ipfs_daemon @@ -108,6 +124,13 @@ test_localhost_gateway_response_should_contain \ "http://localhost:$GWAY_PORT/ipns/en.wikipedia-on-ipfs.org/wiki" \ "Location: http://en.wikipedia-on-ipfs.org.ipns.localhost:$GWAY_PORT/wiki" +# /api/ → api.localhost/api + +test_localhost_gateway_response_should_contain \ + "Request for localhost/api redirect to api.localhost" \ + "http://localhost:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \ + "Location: http://api.localhost:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" + ## ============================================================================ ## Test subdomain-based requests to a local gateway with default config ## (origin per content root at http://*.localhost) @@ -127,13 +150,6 @@ test_localhost_gateway_response_should_contain \ # {CID}.ipfs.localhost/sub/dir (Directory Listing) -test_expect_success "Add the test directory" ' - mkdir -p testdirlisting/subdir1/subdir2 && - echo "hello" > testdirlisting/hello && - echo "subdir2-bar" > testdirlisting/subdir1/subdir2/bar && - DIR_CID=$(ipfs add -Qr --cid-version 1 testdirlisting) -' - test_expect_success "Valid file and subdirectory paths in directory listing at {cid}.ipfs.localhost" ' curl -s "http://${DIR_CID}.ipfs.localhost:$GWAY_PORT" > list_response && test_should_contain "hello" list_response && @@ -184,6 +200,13 @@ test_localhost_gateway_response_should_contain \ # test_cmp docs_cid_expected dnslink_response #' +# api.localhost/api + +# Note: use DIR_CID so refs -r returns some CIDs for child nodes +test_localhost_gateway_response_should_contain \ + "Request for api.localhost returns API response" \ + "http://api.localhost:$GWAY_PORT/api/v0/refs?arg=$DIR_CID&r=true" \ + "Ref" ## ============================================================================ ## Test subdomain-based requests with a custom hostname config @@ -191,7 +214,7 @@ test_localhost_gateway_response_should_contain \ ## ============================================================================ # set explicit subdomain gateway config for the hostname -ipfs config --json Gateway.PublicGateways '{"example.com": { "UseSubdomains": true, "Paths": ["/ipfs", "/ipns"] }}' +ipfs config --json Gateway.PublicGateways '{"example.com": { "UseSubdomains": true, "Paths": ["/ipfs", "/ipns", "/api"] }}' # restart daemon to apply config changes test_kill_ipfs_daemon test_launch_ipfs_daemon --offline @@ -281,7 +304,14 @@ test_hostname_gateway_response_should_contain \ "http://127.0.0.1:$GWAY_PORT" \ "Location: http://${IPNS_IDv1}.ipns.example.com/" -# TODO: api.example.com/v0/id +# api.example.com +# ============================================================================ + +test_hostname_gateway_response_should_contain \ + "Request for api.example.com/api/v0/refs returns expected payload when /api is on Paths whitelist" \ + "api.example.com" \ + "http://127.0.0.1:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \ + "Ref" # # DNSLink requests (could be moved to separate test file) #