Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gw): /ipfs/ipfs/{cid} → /ipfs/{cid} #7930

Merged
merged 6 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions core/corehttp/gateway_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package corehttp
import (
"context"
"fmt"
"html/template"
"io"
"mime"
"net/http"
Expand Down Expand Up @@ -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(`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="10;url={{.RedirectURL}}" />
<link rel="canonical" href="{{.RedirectURL}}" />
</head>
<body>
<pre>{{.ErrorMsg}}</pre><pre>(if a redirect does not happen in 10 seconds, use "{{.SuggestedPath}}" instead)</pre>
</body>
</html>`))

type redirectTemplateData struct {
RedirectURL string
SuggestedPath string
ErrorMsg string
}

// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
type gatewayHandler struct {
Expand Down Expand Up @@ -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 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
}

Expand Down Expand Up @@ -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 {
Stebalien marked this conversation as resolved.
Show resolved Hide resolved
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 <link> 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
}
13 changes: 13 additions & 0 deletions test/sharness/t0110-gateway.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<meta http-equiv=\"refresh\" content=\"10;url=/ipfs/bafkqaaa?query=to-remember\" />" response_with_double_ipfs_ns &&
test_should_contain "<link rel=\"canonical\" href=\"/ipfs/bafkqaaa?query=to-remember\" />" response_with_double_ipfs_ns
'

test_expect_failure "GET IPNS path succeeds" '
ipfs name publish --allow-offline "$HASH" &&
PEERID=$(ipfs config Identity.PeerID) &&
Expand All @@ -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 "<meta http-equiv=\"refresh\" content=\"10;url=/ipns/${PEERID}?query=to-remember\" />" response_with_ipfs_ipns_ns &&
test_should_contain "<link rel=\"canonical\" href=\"/ipns/${PEERID}?query=to-remember\" />" 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"
'
Expand Down