Skip to content

Commit 3182986

Browse files
committed
fix(gw): ensure dir URLs have trailing slash
This fixes a regression around directory listing and index.html hosting. Seems that during one of recent refactors code changed and we no longer check for trailing slash in HTTP request path, but look at content path instead. This cleans this up and also ensures dir behavior is the same for both index.html hosting and dir-index-html (generated listing). It also adds more tests so we catch any future regressions.
1 parent c95f5c9 commit 3182986

File tree

6 files changed

+53
-32
lines changed

6 files changed

+53
-32
lines changed

core/corehttp/gateway_handler_unixfs_dir.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,28 +41,30 @@ func (i *gatewayHandler) serveDirectory(ctx context.Context, w http.ResponseWrit
4141
}
4242
originalUrlPath := requestURI.Path
4343

44-
// Check if directory has index.html, if so, serveFile
45-
idxPath := ipath.Join(contentPath, "index.html")
46-
idx, err := i.api.Unixfs().Get(ctx, idxPath)
47-
switch err.(type) {
48-
case nil:
49-
cpath := contentPath.String()
50-
dirwithoutslash := cpath[len(cpath)-1] != '/'
44+
// Ensure directory paths end with '/'
45+
if originalUrlPath[len(originalUrlPath)-1] != '/' {
46+
// don't redirect to trailing slash if it's go get
47+
// https://github.com/ipfs/kubo/pull/3963
5148
goget := r.URL.Query().Get("go-get") == "1"
52-
if dirwithoutslash && !goget {
53-
// See comment above where originalUrlPath is declared.
49+
if !goget {
5450
suffix := "/"
51+
// preserve query parameters
5552
if r.URL.RawQuery != "" {
56-
// preserve query parameters
5753
suffix = suffix + "?" + r.URL.RawQuery
5854
}
59-
55+
// /ipfs/cid/foo?bar must be redirected to /ipfs/cid/foo/?bar
6056
redirectURL := originalUrlPath + suffix
61-
logger.Debugw("serving index.html file", "to", redirectURL, "status", http.StatusFound, "path", idxPath)
62-
http.Redirect(w, r, redirectURL, http.StatusFound)
57+
logger.Debugw("directory location moved permanently", "status", http.StatusMovedPermanently)
58+
http.Redirect(w, r, redirectURL, http.StatusMovedPermanently)
6359
return
6460
}
61+
}
6562

63+
// Check if directory has index.html, if so, serveFile
64+
idxPath := ipath.Join(contentPath, "index.html")
65+
idx, err := i.api.Unixfs().Get(ctx, idxPath)
66+
switch err.(type) {
67+
case nil:
6668
f, ok := idx.(files.File)
6769
if !ok {
6870
internalWebError(w, files.ErrNotReader)
@@ -163,7 +165,7 @@ func (i *gatewayHandler) serveDirectory(ctx context.Context, w http.ResponseWrit
163165
// add the correct link depending on whether the path ends with a slash
164166
default:
165167
if strings.HasSuffix(backLink, "/") {
166-
backLink += "./.."
168+
backLink += ".."
167169
} else {
168170
backLink += "/.."
169171
}

core/corehttp/gateway_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,9 @@ func TestIPNSHostnameRedirect(t *testing.T) {
380380
t.Fatal(err)
381381
}
382382

383-
// expect 302 redirect to same path, but with trailing slash
384-
if res.StatusCode != 302 {
385-
t.Errorf("status is %d, expected 302", res.StatusCode)
383+
// expect 301 redirect to same path, but with trailing slash
384+
if res.StatusCode != 301 {
385+
t.Errorf("status is %d, expected 301", res.StatusCode)
386386
}
387387
hdr := res.Header["Location"]
388388
if len(hdr) < 1 {
@@ -403,9 +403,9 @@ func TestIPNSHostnameRedirect(t *testing.T) {
403403
t.Fatal(err)
404404
}
405405

406-
// expect 302 redirect to same path, but with prefix and trailing slash
407-
if res.StatusCode != 302 {
408-
t.Errorf("status is %d, expected 302", res.StatusCode)
406+
// expect 301 redirect to same path, but with prefix and trailing slash
407+
if res.StatusCode != 301 {
408+
t.Errorf("status is %d, expected 301", res.StatusCode)
409409
}
410410
hdr = res.Header["Location"]
411411
if len(hdr) < 1 {
@@ -492,7 +492,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) {
492492
if !matchPathOrBreadcrumbs(s, "/ipns/<a href=\"//example.net/\">example.net</a>/<a href=\"//example.net/foo%3F%20%23%3C%27\">foo? #&lt;&#39;</a>") {
493493
t.Fatalf("expected a path in directory listing")
494494
}
495-
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/./..\">") {
495+
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/..\">") {
496496
t.Fatalf("expected backlink in directory listing")
497497
}
498498
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/file.txt\">") {
@@ -566,7 +566,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) {
566566
if !matchPathOrBreadcrumbs(s, "/ipns/<a href=\"//example.net/\">example.net</a>/<a href=\"//example.net/foo%3F%20%23%3C%27\">foo? #&lt;&#39;</a>/<a href=\"//example.net/foo%3F%20%23%3C%27/bar\">bar</a>") {
567567
t.Fatalf("expected a path in directory listing")
568568
}
569-
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/bar/./..\">") {
569+
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/bar/..\">") {
570570
t.Fatalf("expected backlink in directory listing")
571571
}
572572
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/bar/file.txt\">") {

test/sharness/t0110-gateway.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ test_expect_success "GET IPFS directory file output looks good" '
7272

7373
test_expect_success "GET IPFS directory with index.html returns redirect to add trailing slash" "
7474
curl -sI -o response_without_slash \"http://127.0.0.1:$port/ipfs/$HASH2/dirwithindex?query=to-remember\" &&
75+
test_should_contain \"HTTP/1.1 301 Moved Permanently\" response_without_slash &&
7576
test_should_contain \"Location: /ipfs/$HASH2/dirwithindex/?query=to-remember\" response_without_slash
7677
"
7778

test/sharness/t0113-gateway-symlink.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ test_expect_success "Add the test directory" '
2222
'
2323

2424
test_expect_success "Test the directory listing" '
25-
curl "$GWAY_ADDR/ipfs/$HASH" > list_response &&
25+
curl "$GWAY_ADDR/ipfs/$HASH/" > list_response &&
2626
test_should_contain ">foo<" list_response &&
2727
test_should_contain ">bar<" list_response
2828
'

test/sharness/t0114-gateway-subdomains.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ test_expect_success "valid file and subdirectory paths in directory listing at {
268268

269269
test_expect_success "valid parent directory path in directory listing at {cid}.ipfs.localhost/sub/dir" '
270270
curl -s --resolve $DIR_HOSTNAME:127.0.0.1 "http://$DIR_HOSTNAME/ipfs/ipns/" > list_response &&
271-
test_should_contain "<a href=\"/ipfs/ipns/./..\">..</a>" list_response &&
271+
test_should_contain "<a href=\"/ipfs/ipns/..\">..</a>" list_response &&
272272
test_should_contain "<a href=\"/ipfs/ipns/bar\">bar</a>" list_response
273273
'
274274

@@ -441,7 +441,7 @@ test_expect_success "valid file and directory paths in directory listing at {cid
441441

442442
test_expect_success "valid parent directory path in directory listing at {cid}.ipfs.example.com/sub/dir" '
443443
curl -s -H "Host: $DIR_FQDN" http://127.0.0.1:$GWAY_PORT/ipfs/ipns/ > list_response &&
444-
test_should_contain "<a href=\"/ipfs/ipns/./..\">..</a>" list_response &&
444+
test_should_contain "<a href=\"/ipfs/ipns/..\">..</a>" list_response &&
445445
test_should_contain "<a href=\"/ipfs/ipns/bar\">bar</a>" list_response
446446
'
447447

test/sharness/t0115-gateway-dir-listing.sh

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,14 @@ test_expect_success "path gw: backlink on root CID should be hidden" '
4343
test_should_not_contain "<a href=\"/ipfs/$DIR_CID/\">..</a>" list_response
4444
'
4545

46-
test_expect_success "path gw: Etag should be present" '
46+
test_expect_success "path gw: redirect dir listing to URL with trailing slash" '
4747
curl -sD - http://127.0.0.1:$GWAY_PORT/ipfs/${DIR_CID}/ą/ę > list_response &&
48+
test_should_contain "HTTP/1.1 301 Moved Permanently" list_response &&
49+
test_should_contain "Location: /ipfs/${DIR_CID}/%c4%85/%c4%99/" list_response
50+
'
51+
52+
test_expect_success "path gw: Etag should be present" '
53+
curl -sD - http://127.0.0.1:$GWAY_PORT/ipfs/${DIR_CID}/ą/ę/ > list_response &&
4854
test_should_contain "Index of" list_response &&
4955
test_should_contain "Etag: \"DirIndex-" list_response
5056
'
@@ -72,31 +78,37 @@ test_expect_success "path gw: hash column should be a CID link with filename par
7278
DIR_HOSTNAME="${DIR_CID}.ipfs.localhost"
7379
# note: we skip DNS lookup by running curl with --resolve $DIR_HOSTNAME:127.0.0.1
7480

75-
test_expect_success "path gw: backlink on root CID should be hidden" '
81+
test_expect_success "subdomain gw: backlink on root CID should be hidden" '
7682
curl -sD - --resolve $DIR_HOSTNAME:$GWAY_PORT:127.0.0.1 http://$DIR_HOSTNAME:$GWAY_PORT/ > list_response &&
7783
test_should_contain "Index of" list_response &&
7884
test_should_not_contain "<a href=\"/\">..</a>" list_response
7985
'
8086

81-
test_expect_success "path gw: Etag should be present" '
87+
test_expect_success "subdomain gw: redirect dir listing to URL with trailing slash" '
8288
curl -sD - --resolve $DIR_HOSTNAME:$GWAY_PORT:127.0.0.1 http://$DIR_HOSTNAME:$GWAY_PORT/ą/ę > list_response &&
89+
test_should_contain "HTTP/1.1 301 Moved Permanently" list_response &&
90+
test_should_contain "Location: /%c4%85/%c4%99/" list_response
91+
'
92+
93+
test_expect_success "subdomain gw: Etag should be present" '
94+
curl -sD - --resolve $DIR_HOSTNAME:$GWAY_PORT:127.0.0.1 http://$DIR_HOSTNAME:$GWAY_PORT/ą/ę/ > list_response &&
8395
test_should_contain "Index of" list_response &&
8496
test_should_contain "Etag: \"DirIndex-" list_response
8597
'
8698

87-
test_expect_success "path gw: backlink on subdirectory should point at parent directory" '
99+
test_expect_success "subdomain gw: backlink on subdirectory should point at parent directory" '
88100
test_should_contain "<a href=\"/%C4%85/%C4%99/..\">..</a>" list_response
89101
'
90102

91103
test_expect_success "subdomain gw: breadcrumbs should leverage path-based router mounted on the parent domain" '
92104
test_should_contain "/ipfs/<a href=\"//localhost:$GWAY_PORT/ipfs/$DIR_CID\">$DIR_CID</a>/<a href=\"//localhost:$GWAY_PORT/ipfs/$DIR_CID/%C4%85\">ą</a>/<a href=\"//localhost:$GWAY_PORT/ipfs/$DIR_CID/%C4%85/%C4%99\">ę</a>" list_response
93105
'
94106

95-
test_expect_success "path gw: name column should be a link to content root mounted at subdomain origin" '
107+
test_expect_success "subdomain gw: name column should be a link to content root mounted at subdomain origin" '
96108
test_should_contain "<a href=\"/%C4%85/%C4%99/file-%C5%BA%C5%82.txt\">file-źł.txt</a>" list_response
97109
'
98110

99-
test_expect_success "path gw: hash column should be a CID link to path router with filename param" '
111+
test_expect_success "subdomain gw: hash column should be a CID link to path router with filename param" '
100112
test_should_contain "<a class=\"ipfs-hash\" translate=\"no\" href=\"//localhost:$GWAY_PORT/ipfs/$FILE_CID?filename=file-%25C5%25BA%25C5%2582.txt\">" list_response
101113
'
102114

@@ -121,8 +133,14 @@ test_expect_success "dnslink gw: backlink on root CID should be hidden" '
121133
test_should_not_contain "<a href=\"/\">..</a>" list_response
122134
'
123135

124-
test_expect_success "dnslink gw: Etag should be present" '
136+
test_expect_success "dnslink gw: redirect dir listing to URL with trailing slash" '
125137
curl -sD - --resolve $DNSLINK_HOSTNAME:$GWAY_PORT:127.0.0.1 http://$DNSLINK_HOSTNAME:$GWAY_PORT/ą/ę > list_response &&
138+
test_should_contain "HTTP/1.1 301 Moved Permanently" list_response &&
139+
test_should_contain "Location: /%c4%85/%c4%99/" list_response
140+
'
141+
142+
test_expect_success "dnslink gw: Etag should be present" '
143+
curl -sD - --resolve $DNSLINK_HOSTNAME:$GWAY_PORT:127.0.0.1 http://$DNSLINK_HOSTNAME:$GWAY_PORT/ą/ę/ > list_response &&
126144
test_should_contain "Index of" list_response &&
127145
test_should_contain "Etag: \"DirIndex-" list_response
128146
'

0 commit comments

Comments
 (0)