@@ -3,6 +3,7 @@ package corehttp
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "html/template"
6
7
"io"
7
8
"mime"
8
9
"net/http"
@@ -36,6 +37,26 @@ const (
36
37
37
38
var onlyAscii = regexp .MustCompile ("[[:^ascii:]]" )
38
39
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
+
39
60
// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
40
61
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
41
62
type gatewayHandler struct {
@@ -216,8 +237,14 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
216
237
}
217
238
218
239
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 )
221
248
return
222
249
}
223
250
@@ -781,3 +808,33 @@ func preferred404Filename(acceptHeaders []string) (string, string, error) {
781
808
782
809
return "" , "" , fmt .Errorf ("there is no 404 file for the requested content types" )
783
810
}
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
+ }
0 commit comments