Skip to content

Commit d30f1e5

Browse files
authored
Merge pull request #7930 from ipfs/fix/redundant-ns-on-gateway
feat(gw): /ipfs/ipfs/{cid} → /ipfs/{cid}
2 parents c0ce56f + 763a120 commit d30f1e5

File tree

2 files changed

+72
-2
lines changed

2 files changed

+72
-2
lines changed

core/corehttp/gateway_handler.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package corehttp
33
import (
44
"context"
55
"fmt"
6+
"html/template"
67
"io"
78
"mime"
89
"net/http"
@@ -36,6 +37,26 @@ const (
3637

3738
var onlyAscii = regexp.MustCompile("[[:^ascii:]]")
3839

40+
// HTML-based redirect for errors which can be recovered from, but we want
41+
// to provide hint to people that they should fix things on their end.
42+
var redirectTemplate = template.Must(template.New("redirect").Parse(`<!DOCTYPE html>
43+
<html>
44+
<head>
45+
<meta charset="utf-8">
46+
<meta http-equiv="refresh" content="10;url={{.RedirectURL}}" />
47+
<link rel="canonical" href="{{.RedirectURL}}" />
48+
</head>
49+
<body>
50+
<pre>{{.ErrorMsg}}</pre><pre>(if a redirect does not happen in 10 seconds, use "{{.SuggestedPath}}" instead)</pre>
51+
</body>
52+
</html>`))
53+
54+
type redirectTemplateData struct {
55+
RedirectURL string
56+
SuggestedPath string
57+
ErrorMsg string
58+
}
59+
3960
// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
4061
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
4162
type gatewayHandler struct {
@@ -216,8 +237,14 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
216237
}
217238

218239
parsedPath := ipath.New(urlPath)
219-
if err := parsedPath.IsValid(); err != nil {
220-
webError(w, "invalid ipfs path", err, http.StatusBadRequest)
240+
if pathErr := parsedPath.IsValid(); pathErr != nil {
241+
if prefix == "" && fixupSuperfluousNamespace(w, urlPath, r.URL.RawQuery) {
242+
// the error was due to redundant namespace, which we were able to fix
243+
// by returning error/redirect page, nothing left to do here
244+
return
245+
}
246+
// unable to fix path, returning error
247+
webError(w, "invalid ipfs path", pathErr, http.StatusBadRequest)
221248
return
222249
}
223250

@@ -781,3 +808,33 @@ func preferred404Filename(acceptHeaders []string) (string, string, error) {
781808

782809
return "", "", fmt.Errorf("there is no 404 file for the requested content types")
783810
}
811+
812+
// Attempt to fix redundant /ipfs/ namespace as long as resulting
813+
// 'intended' path is valid. This is in case gremlins were tickled
814+
// wrong way and user ended up at /ipfs/ipfs/{cid} or /ipfs/ipns/{id}
815+
// like in bafybeien3m7mdn6imm425vc2s22erzyhbvk5n3ofzgikkhmdkh5cuqbpbq :^))
816+
func fixupSuperfluousNamespace(w http.ResponseWriter, urlPath string, urlQuery string) bool {
817+
if !(strings.HasPrefix(urlPath, "/ipfs/ipfs/") || strings.HasPrefix(urlPath, "/ipfs/ipns/")) {
818+
return false // not a superfluous namespace
819+
}
820+
intendedPath := ipath.New(strings.TrimPrefix(urlPath, "/ipfs"))
821+
if err := intendedPath.IsValid(); err != nil {
822+
return false // not a valid path
823+
}
824+
intendedURL := intendedPath.String()
825+
if urlQuery != "" {
826+
// we render HTML, so ensure query entries are properly escaped
827+
q, _ := url.ParseQuery(urlQuery)
828+
intendedURL = intendedURL + "?" + q.Encode()
829+
}
830+
// return HTTP 400 (Bad Request) with HTML error page that:
831+
// - points at correct canonical path via <link> header
832+
// - displays human-readable error
833+
// - redirects to intendedURL after a short delay
834+
w.WriteHeader(http.StatusBadRequest)
835+
return redirectTemplate.Execute(w, redirectTemplateData{
836+
RedirectURL: intendedURL,
837+
SuggestedPath: intendedPath.String(),
838+
ErrorMsg: fmt.Sprintf("invalid path: %q should be %q", urlPath, intendedPath.String()),
839+
}) == nil
840+
}

test/sharness/t0110-gateway.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ test_expect_success "GET IPFS nonexistent file returns code expected (404)" '
8484
test_curl_resp_http_code "http://127.0.0.1:$port/ipfs/$HASH2/pleaseDontAddMe" "HTTP/1.1 404 Not Found"
8585
'
8686

87+
test_expect_success "GET /ipfs/ipfs/{cid} returns redirect to the valid path" '
88+
curl -sD - "http://127.0.0.1:$port/ipfs/ipfs/bafkqaaa?query=to-remember" > response_with_double_ipfs_ns &&
89+
test_should_contain "<meta http-equiv=\"refresh\" content=\"10;url=/ipfs/bafkqaaa?query=to-remember\" />" response_with_double_ipfs_ns &&
90+
test_should_contain "<link rel=\"canonical\" href=\"/ipfs/bafkqaaa?query=to-remember\" />" response_with_double_ipfs_ns
91+
'
92+
8793
test_expect_failure "GET IPNS path succeeds" '
8894
ipfs name publish --allow-offline "$HASH" &&
8995
PEERID=$(ipfs config Identity.PeerID) &&
@@ -95,6 +101,13 @@ test_expect_failure "GET IPNS path output looks good" '
95101
test_cmp expected actual
96102
'
97103

104+
test_expect_success "GET /ipfs/ipns/{peerid} returns redirect to the valid path" '
105+
PEERID=$(ipfs config Identity.PeerID) &&
106+
curl -sD - "http://127.0.0.1:$port/ipfs/ipns/${PEERID}?query=to-remember" > response_with_ipfs_ipns_ns &&
107+
test_should_contain "<meta http-equiv=\"refresh\" content=\"10;url=/ipns/${PEERID}?query=to-remember\" />" response_with_ipfs_ipns_ns &&
108+
test_should_contain "<link rel=\"canonical\" href=\"/ipns/${PEERID}?query=to-remember\" />" response_with_ipfs_ipns_ns
109+
'
110+
98111
test_expect_success "GET invalid IPFS path errors" '
99112
test_must_fail curl -sf "http://127.0.0.1:$port/ipfs/12345"
100113
'

0 commit comments

Comments
 (0)