From cd1feb3af4e42a835870aca96243b55760ab5f04 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Fri, 25 Sep 2020 22:26:44 +0200 Subject: [PATCH] fix(gateway): correct breadcrumbs on dnslink site --- core/corehttp/gateway_handler.go | 11 ++++++++--- core/corehttp/gateway_indexPage.go | 19 +++++++++++++++++-- core/corehttp/gateway_test.go | 8 +++++--- core/corehttp/hostname.go | 21 ++++++++++++++------- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index b9e7f144b..cff82fef7 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -379,8 +379,9 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request hash := resolvedPath.Cid().String() - // Storage for gateway URL to be used when linking to other rootIDs. This - // will be blank unless subdomain resolution is being used for this request. + // Gateway root URL to be used when linking to other rootIDs. + // This will be blank unless subdomain or DNSLink resolution is being used + // for this request. var gwURL string // Get gateway hostname and build gateway URL. @@ -396,11 +397,15 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request Listing: dirListing, Size: size, Path: urlPath, - Breadcrumbs: breadcrumbs(urlPath), + Breadcrumbs: breadcrumbs(urlPath, gwURL), BackLink: backLink, Hash: hash, } + // TODO: remove logging below + // tplDataJSON, _ := json.MarshalIndent(tplData, "", " ") + // fmt.Println(string(tplDataJSON)) + err = listingTemplate.Execute(w, tplData) if err != nil { internalWebError(w, err) diff --git a/core/corehttp/gateway_indexPage.go b/core/corehttp/gateway_indexPage.go index c9a948708..5cbf33bec 100644 --- a/core/corehttp/gateway_indexPage.go +++ b/core/corehttp/gateway_indexPage.go @@ -34,7 +34,7 @@ type breadcrumb struct { Path string } -func breadcrumbs(urlPath string) []breadcrumb { +func breadcrumbs(urlPath string, gwRootURL string) []breadcrumb { var ret []breadcrumb p, err := ipfspath.ParsePath(urlPath) @@ -42,8 +42,9 @@ func breadcrumbs(urlPath string) []breadcrumb { // No breadcrumbs, fallback to bare Path in template return ret } - segs := p.Segments() + ns := segs[0] + contentRoot := segs[1] for i, seg := range segs { if i == 0 { ret = append(ret, breadcrumb{Name: seg}) @@ -55,6 +56,20 @@ func breadcrumbs(urlPath string) []breadcrumb { } } + // Drop the /ipns/ prefix from breadcrumb Paths when directory listing + // on a DNSLink website (loaded due to Host header in HTTP request). + // Necessary because gwRootURL won't have a public gateway mounted. + if ns == "ipns" && (("//" + contentRoot) == gwRootURL) { + prefix := "/ipns/" + contentRoot + for i, crumb := range ret { + if strings.HasPrefix(crumb.Path, prefix) { + ret[i].Path = strings.Replace(crumb.Path, prefix, "", 1) + } + } + // Make contentRoot breadcrumb link to the website root + ret[1].Path = "/" + } + return ret } diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index f4d6d810d..46bf76a5e 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -391,6 +391,8 @@ func TestIPNSHostnameRedirect(t *testing.T) { } } +// Test directory listing on DNSLink website +// (scenario when Host header is the same as URL hostname) func TestIPNSHostnameBacklinks(t *testing.T) { ns := mockNamesys{} ts, api, ctx := newTestServerAndNode(t, ns) @@ -445,7 +447,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) { s := string(body) t.Logf("body: %s\n", string(body)) - if !matchPathOrBreadcrumbs(s, "/ipns/example.net/foo? #<'") { + if !matchPathOrBreadcrumbs(s, "/ipns/example.net/foo? #<'") { t.Fatalf("expected a path in directory listing") } if !strings.Contains(s, "") { @@ -511,7 +513,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) { s = string(body) t.Logf("body: %s\n", string(body)) - if !matchPathOrBreadcrumbs(s, "/ipns/example.net/foo? #<'/bar") { + if !matchPathOrBreadcrumbs(s, "/ipns/example.net/foo? #<'/bar") { t.Fatalf("expected a path in directory listing") } if !strings.Contains(s, "") { @@ -545,7 +547,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) { s = string(body) t.Logf("body: %s\n", string(body)) - if !matchPathOrBreadcrumbs(s, "/ipns/example.net") { + if !matchPathOrBreadcrumbs(s, "/ipns/example.net") { t.Fatalf("expected a path in directory listing") } if !strings.Contains(s, "") { diff --git a/core/corehttp/hostname.go b/core/corehttp/hostname.go index 8b2666afb..4a531a4d3 100644 --- a/core/corehttp/hostname.go +++ b/core/corehttp/hostname.go @@ -129,7 +129,7 @@ func HostnameOption() ServeOption { if !gw.NoDNSLink && isDNSLinkRequest(r.Context(), coreAPI, host) { // rewrite path and handle as DNSLink r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path - childMux.ServeHTTP(w, r) + childMux.ServeHTTP(w, withHostnameContext(r, host)) return } @@ -143,10 +143,6 @@ func HostnameOption() ServeOption { if gw, hostname, ns, rootID, ok := knownSubdomainDetails(host, knownGateways); ok { // Looks like we're using a known gateway in subdomain mode. - // Add gateway hostname context for linking to other root ids. - // Example: localhost/ipfs/{cid} - ctx := context.WithValue(r.Context(), "gw-hostname", hostname) - // Assemble original path prefix. pathPrefix := "/" + ns + "/" + rootID @@ -201,7 +197,7 @@ func HostnameOption() ServeOption { r.URL.Path = pathPrefix + r.URL.Path // Serve path request - childMux.ServeHTTP(w, r.WithContext(ctx)) + childMux.ServeHTTP(w, withHostnameContext(r, hostname)) return } // We don't have a known gateway. Fallback on DNSLink lookup @@ -213,7 +209,7 @@ func HostnameOption() ServeOption { if !cfg.Gateway.NoDNSLink && isDNSLinkRequest(r.Context(), coreAPI, host) { // rewrite path and handle as DNSLink r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path - childMux.ServeHTTP(w, r) + childMux.ServeHTTP(w, withHostnameContext(r, host)) return } @@ -234,6 +230,17 @@ type wildcardHost struct { spec *config.GatewaySpec } +// Extends request context to include hostname of a canonical gateway root +// (subdomain root or dnslink fqdn) +func withHostnameContext(r *http.Request, hostname string) *http.Request { + // This is required for links on directory listing pages to work correctly + // on subdomain and dnslink gateways. While DNSlink could read value from + // Host header, subdomain gateways have more comples rules (knownSubdomainDetails) + // More: https://github.com/ipfs/dir-index-html/issues/42 + ctx := context.WithValue(r.Context(), "gw-hostname", hostname) + return r.WithContext(ctx) +} + func prepareKnownGateways(publicGateways map[string]*config.GatewaySpec) gatewayHosts { var hosts gatewayHosts