diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go
index e47198174..0275c873d 100644
--- a/core/corehttp/gateway_handler.go
+++ b/core/corehttp/gateway_handler.go
@@ -3,6 +3,7 @@ package corehttp
import (
"context"
"fmt"
+ "html/template"
"io"
"mime"
"net/http"
@@ -36,6 +37,26 @@ const (
var onlyAscii = regexp.MustCompile("[[:^ascii:]]")
+// HTML-based redirect for errors which can be recovered from, but we want
+// to provide hint to people that they should fix things on their end.
+var redirectTemplate = template.Must(template.New("redirect").Parse(`
+
+
+
+
+
+
+
+
{{.ErrorMsg}}
(if a redirect does not happen in 10 seconds, use "{{.SuggestedPath}}" instead)
+
+`))
+
+type redirectTemplateData struct {
+ RedirectURL string
+ SuggestedPath string
+ ErrorMsg string
+}
+
// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/)
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
type gatewayHandler struct {
@@ -216,8 +237,14 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
}
parsedPath := ipath.New(urlPath)
- if err := parsedPath.IsValid(); err != nil {
- webError(w, "invalid ipfs path", err, http.StatusBadRequest)
+ if pathErr := parsedPath.IsValid(); pathErr != nil {
+ if prefix == "" && fixupSuperfluousNamespace(w, urlPath, r.URL.RawQuery) {
+ // the error was due to redundant namespace, which we were able to fix
+ // by returning error/redirect page, nothing left to do here
+ return
+ }
+ // unable to fix path, returning error
+ webError(w, "invalid ipfs path", pathErr, http.StatusBadRequest)
return
}
@@ -781,3 +808,33 @@ func preferred404Filename(acceptHeaders []string) (string, string, error) {
return "", "", fmt.Errorf("there is no 404 file for the requested content types")
}
+
+// Attempt to fix redundant /ipfs/ namespace as long as resulting
+// 'intended' path is valid. This is in case gremlins were tickled
+// wrong way and user ended up at /ipfs/ipfs/{cid} or /ipfs/ipns/{id}
+// like in bafybeien3m7mdn6imm425vc2s22erzyhbvk5n3ofzgikkhmdkh5cuqbpbq :^))
+func fixupSuperfluousNamespace(w http.ResponseWriter, urlPath string, urlQuery string) bool {
+ if !(strings.HasPrefix(urlPath, "/ipfs/ipfs/") || strings.HasPrefix(urlPath, "/ipfs/ipns/")) {
+ return false // not a superfluous namespace
+ }
+ intendedPath := ipath.New(strings.TrimPrefix(urlPath, "/ipfs"))
+ if err := intendedPath.IsValid(); err != nil {
+ return false // not a valid path
+ }
+ intendedURL := intendedPath.String()
+ if urlQuery != "" {
+ // we render HTML, so ensure query entries are properly escaped
+ q, _ := url.ParseQuery(urlQuery)
+ intendedURL = intendedURL + "?" + q.Encode()
+ }
+ // return HTTP 400 (Bad Request) with HTML error page that:
+ // - points at correct canonical path via header
+ // - displays human-readable error
+ // - redirects to intendedURL after a short delay
+ w.WriteHeader(http.StatusBadRequest)
+ return redirectTemplate.Execute(w, redirectTemplateData{
+ RedirectURL: intendedURL,
+ SuggestedPath: intendedPath.String(),
+ ErrorMsg: fmt.Sprintf("invalid path: %q should be %q", urlPath, intendedPath.String()),
+ }) == nil
+}
diff --git a/test/sharness/t0110-gateway.sh b/test/sharness/t0110-gateway.sh
index cb0b0cd67..84bc96341 100755
--- a/test/sharness/t0110-gateway.sh
+++ b/test/sharness/t0110-gateway.sh
@@ -84,6 +84,12 @@ test_expect_success "GET IPFS nonexistent file returns code expected (404)" '
test_curl_resp_http_code "http://127.0.0.1:$port/ipfs/$HASH2/pleaseDontAddMe" "HTTP/1.1 404 Not Found"
'
+test_expect_success "GET /ipfs/ipfs/{cid} returns redirect to the valid path" '
+ curl -sD - "http://127.0.0.1:$port/ipfs/ipfs/bafkqaaa?query=to-remember" > response_with_double_ipfs_ns &&
+ test_should_contain "" response_with_double_ipfs_ns &&
+ test_should_contain "" response_with_double_ipfs_ns
+'
+
test_expect_failure "GET IPNS path succeeds" '
ipfs name publish --allow-offline "$HASH" &&
PEERID=$(ipfs config Identity.PeerID) &&
@@ -95,6 +101,13 @@ test_expect_failure "GET IPNS path output looks good" '
test_cmp expected actual
'
+test_expect_success "GET /ipfs/ipns/{peerid} returns redirect to the valid path" '
+ PEERID=$(ipfs config Identity.PeerID) &&
+ curl -sD - "http://127.0.0.1:$port/ipfs/ipns/${PEERID}?query=to-remember" > response_with_ipfs_ipns_ns &&
+ test_should_contain "" response_with_ipfs_ipns_ns &&
+ test_should_contain "" response_with_ipfs_ipns_ns
+'
+
test_expect_success "GET invalid IPFS path errors" '
test_must_fail curl -sf "http://127.0.0.1:$port/ipfs/12345"
'