fix(gateway): correct breadcrumbs on dnslink site
Marcin Rataj committed
Sep 25, 2020 at 22:26 UTC
cd1feb3af4e42a835870aca96243b55760ab5f04
4 files changed
+44
-15
core/corehttp/gateway_handler.go
+8
-3
@@ -379,8 +379,9 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
379
380
hash := resolvedPath.Cid().String()
381
382
- // Storage for gateway URL to be used when linking to other rootIDs. This
383
- // will be blank unless subdomain resolution is being used for this request.
382
+ // Gateway root URL to be used when linking to other rootIDs.
383
+ // This will be blank unless subdomain or DNSLink resolution is being used
384
+ // for this request.
385
var gwURL string
386
387
// Get gateway hostname and build gateway URL.
@@ -396,11 +397,15 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
397
Listing: dirListing,
398
Size: size,
399
Path: urlPath,
399
- Breadcrumbs: breadcrumbs(urlPath),
400
+ Breadcrumbs: breadcrumbs(urlPath, gwURL),
401
BackLink: backLink,
402
Hash: hash,
403
}
404
405
+ // TODO: remove logging below
406
+ // tplDataJSON, _ := json.MarshalIndent(tplData, "", " ")
407
+ // fmt.Println(string(tplDataJSON))
408
+
409
err = listingTemplate.Execute(w, tplData)
410
if err != nil {
411
internalWebError(w, err)
core/corehttp/gateway_indexPage.go
+17
-2
@@ -34,7 +34,7 @@ type breadcrumb struct {
34
Path string
35
}
36
37
-func breadcrumbs(urlPath string) []breadcrumb {
37
+func breadcrumbs(urlPath string, gwRootURL string) []breadcrumb {
38
var ret []breadcrumb
39
40
p, err := ipfspath.ParsePath(urlPath)
@@ -42,8 +42,9 @@ func breadcrumbs(urlPath string) []breadcrumb {
42
// No breadcrumbs, fallback to bare Path in template
43
return ret
44
}
45
-
45
segs := p.Segments()
46
+ ns := segs[0]
47
+ contentRoot := segs[1]
48
for i, seg := range segs {
49
if i == 0 {
50
ret = append(ret, breadcrumb{Name: seg})
@@ -55,6 +56,20 @@ func breadcrumbs(urlPath string) []breadcrumb {
56
}
57
}
58
59
+ // Drop the /ipns/<fqdn> prefix from breadcrumb Paths when directory listing
60
+ // on a DNSLink website (loaded due to Host header in HTTP request).
61
+ // Necessary because gwRootURL won't have a public gateway mounted.
62
+ if ns == "ipns" && (("//" + contentRoot) == gwRootURL) {
63
+ prefix := "/ipns/" + contentRoot
64
+ for i, crumb := range ret {
65
+ if strings.HasPrefix(crumb.Path, prefix) {
66
+ ret[i].Path = strings.Replace(crumb.Path, prefix, "", 1)
67
+ }
68
+ }
69
+ // Make contentRoot breadcrumb link to the website root
70
+ ret[1].Path = "/"
71
+ }
72
+
73
return ret
74
}
75
core/corehttp/gateway_test.go
+5
-3
@@ -391,6 +391,8 @@ func TestIPNSHostnameRedirect(t *testing.T) {
391
}
392
}
393
394
+// Test directory listing on DNSLink website
395
+// (scenario when Host header is the same as URL hostname)
396
func TestIPNSHostnameBacklinks(t *testing.T) {
397
ns := mockNamesys{}
398
ts, api, ctx := newTestServerAndNode(t, ns)
@@ -445,7 +447,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) {
447
s := string(body)
448
t.Logf("body: %s\n", string(body))
449
448
- if !matchPathOrBreadcrumbs(s, "/ipns/<a href=\"/ipns/example.net\">example.net</a>/<a href=\"/ipns/example.net/foo%3F%20%23%3C%27\">foo? #<'</a>") {
450
+ if !matchPathOrBreadcrumbs(s, "/ipns/<a href=\"//example.net/\">example.net</a>/<a href=\"//example.net/foo%3F%20%23%3C%27\">foo? #<'</a>") {
451
t.Fatalf("expected a path in directory listing")
452
}
453
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/./..\">") {
@@ -511,7 +513,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) {
513
s = string(body)
514
t.Logf("body: %s\n", string(body))
515
514
- if !matchPathOrBreadcrumbs(s, "/ipns/<a href=\"/ipns/example.net\">example.net</a>/<a href=\"/ipns/example.net/foo%3F%20%23%3C%27\">foo? #<'</a>/<a href=\"/ipns/example.net/foo%3F%20%23%3C%27/bar\">bar</a>") {
516
+ if !matchPathOrBreadcrumbs(s, "/ipns/<a href=\"//example.net/\">example.net</a>/<a href=\"//example.net/foo%3F%20%23%3C%27\">foo? #<'</a>/<a href=\"//example.net/foo%3F%20%23%3C%27/bar\">bar</a>") {
517
t.Fatalf("expected a path in directory listing")
518
}
519
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/bar/./..\">") {
@@ -545,7 +547,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) {
547
s = string(body)
548
t.Logf("body: %s\n", string(body))
549
548
- if !matchPathOrBreadcrumbs(s, "/ipns/<a href=\"/ipns/example.net\">example.net</a>") {
550
+ if !matchPathOrBreadcrumbs(s, "/ipns/<a href=\"//example.net/\">example.net</a>") {
551
t.Fatalf("expected a path in directory listing")
552
}
553
if !strings.Contains(s, "<a href=\"/good-prefix/\">") {
core/corehttp/hostname.go
+14
-7
@@ -129,7 +129,7 @@ func HostnameOption() ServeOption {
129
if !gw.NoDNSLink && isDNSLinkRequest(r.Context(), coreAPI, host) {
130
// rewrite path and handle as DNSLink
131
r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
132
- childMux.ServeHTTP(w, r)
132
+ childMux.ServeHTTP(w, withHostnameContext(r, host))
133
return
134
}
135
@@ -143,10 +143,6 @@ func HostnameOption() ServeOption {
143
if gw, hostname, ns, rootID, ok := knownSubdomainDetails(host, knownGateways); ok {
144
// Looks like we're using a known gateway in subdomain mode.
145
146
- // Add gateway hostname context for linking to other root ids.
147
- // Example: localhost/ipfs/{cid}
148
- ctx := context.WithValue(r.Context(), "gw-hostname", hostname)
149
-
146
// Assemble original path prefix.
147
pathPrefix := "/" + ns + "/" + rootID
148
@@ -201,7 +197,7 @@ func HostnameOption() ServeOption {
197
r.URL.Path = pathPrefix + r.URL.Path
198
199
// Serve path request
204
- childMux.ServeHTTP(w, r.WithContext(ctx))
200
+ childMux.ServeHTTP(w, withHostnameContext(r, hostname))
201
return
202
}
203
// We don't have a known gateway. Fallback on DNSLink lookup
@@ -213,7 +209,7 @@ func HostnameOption() ServeOption {
209
if !cfg.Gateway.NoDNSLink && isDNSLinkRequest(r.Context(), coreAPI, host) {
210
// rewrite path and handle as DNSLink
211
r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
216
- childMux.ServeHTTP(w, r)
212
+ childMux.ServeHTTP(w, withHostnameContext(r, host))
213
return
214
}
215
@@ -234,6 +230,17 @@ type wildcardHost struct {
230
spec *config.GatewaySpec
231
}
232
233
+// Extends request context to include hostname of a canonical gateway root
234
+// (subdomain root or dnslink fqdn)
235
+func withHostnameContext(r *http.Request, hostname string) *http.Request {
236
+ // This is required for links on directory listing pages to work correctly
237
+ // on subdomain and dnslink gateways. While DNSlink could read value from
238
+ // Host header, subdomain gateways have more comples rules (knownSubdomainDetails)
239
+ // More: https://github.com/ipfs/dir-index-html/issues/42
240
+ ctx := context.WithValue(r.Context(), "gw-hostname", hostname)
241
+ return r.WithContext(ctx)
242
+}
243
+
244
func prepareKnownGateways(publicGateways map[string]*config.GatewaySpec) gatewayHosts {
245
var hosts gatewayHosts
246