@@ -11,6 +11,7 @@ import (
11
11
gopath "path"
12
12
"regexp"
13
13
"runtime/debug"
14
+ "strconv"
14
15
"strings"
15
16
"time"
16
17
@@ -203,6 +204,10 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
203
204
webError (w , "ipfs resolve -r " + escapedURLPath , err , http .StatusServiceUnavailable )
204
205
return
205
206
default :
207
+ if i .servePretty404IfPresent (w , r , parsedPath ) {
208
+ return
209
+ }
210
+
206
211
webError (w , "ipfs resolve -r " + escapedURLPath , err , http .StatusNotFound )
207
212
return
208
213
}
@@ -290,6 +295,10 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
290
295
return
291
296
}
292
297
298
+ if i .servePretty404IfPresent (w , r , parsedPath ) {
299
+ return
300
+ }
301
+
293
302
// storage for directory listing
294
303
var dirListing []directoryItem
295
304
dirit := dir .Entries ()
@@ -406,6 +415,36 @@ func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, nam
406
415
http .ServeContent (w , req , name , modtime , content )
407
416
}
408
417
418
+ func (i * gatewayHandler ) servePretty404IfPresent (w http.ResponseWriter , r * http.Request , parsedPath ipath.Path ) bool {
419
+ resolved404Path , ctype , err := i .searchUpTreeFor404 (r , parsedPath )
420
+ if err != nil {
421
+ return false
422
+ }
423
+
424
+ dr , err := i .api .Unixfs ().Get (r .Context (), resolved404Path )
425
+ if err != nil {
426
+ return false
427
+ }
428
+ defer dr .Close ()
429
+
430
+ f , ok := dr .(files.File )
431
+ if ! ok {
432
+ return false
433
+ }
434
+
435
+ size , err := f .Size ()
436
+ if err != nil {
437
+ return false
438
+ }
439
+
440
+ log .Debugf ("using pretty 404 file for %s" , parsedPath .String ())
441
+ w .Header ().Set ("Content-Type" , ctype )
442
+ w .Header ().Set ("Content-Length" , strconv .FormatInt (size , 10 ))
443
+ w .WriteHeader (http .StatusNotFound )
444
+ _ , err = io .CopyN (w , f , size )
445
+ return err == nil
446
+ }
447
+
409
448
func (i * gatewayHandler ) postHandler (w http.ResponseWriter , r * http.Request ) {
410
449
p , err := i .api .Unixfs ().Add (r .Context (), files .NewReaderFile (r .Body ))
411
450
if err != nil {
@@ -619,3 +658,45 @@ func getFilename(s string) string {
619
658
}
620
659
return gopath .Base (s )
621
660
}
661
+
662
+ func (i * gatewayHandler ) searchUpTreeFor404 (r * http.Request , parsedPath ipath.Path ) (ipath.Resolved , string , error ) {
663
+ filename404 , ctype , err := preferred404Filename (r .Header .Values ("Accept" ))
664
+ if err != nil {
665
+ return nil , "" , err
666
+ }
667
+
668
+ pathComponents := strings .Split (parsedPath .String (), "/" )
669
+
670
+ for idx := len (pathComponents ); idx >= 3 ; idx -- {
671
+ pretty404 := gopath .Join (append (pathComponents [0 :idx ], filename404 )... )
672
+ parsed404Path := ipath .New ("/" + pretty404 )
673
+ if parsed404Path .IsValid () != nil {
674
+ break
675
+ }
676
+ resolvedPath , err := i .api .ResolvePath (r .Context (), parsed404Path )
677
+ if err != nil {
678
+ continue
679
+ }
680
+ return resolvedPath , ctype , nil
681
+ }
682
+
683
+ return nil , "" , fmt .Errorf ("no pretty 404 in any parent folder" )
684
+ }
685
+
686
+ func preferred404Filename (acceptHeaders []string ) (string , string , error ) {
687
+ // If we ever want to offer a 404 file for a different content type
688
+ // then this function will need to parse q weightings, but for now
689
+ // the presence of anything matching HTML is enough.
690
+ for _ , acceptHeader := range acceptHeaders {
691
+ accepted := strings .Split (acceptHeader , "," )
692
+ for _ , spec := range accepted {
693
+ contentType := strings .SplitN (spec , ";" , 1 )[0 ]
694
+ switch contentType {
695
+ case "*/*" , "text/*" , "text/html" :
696
+ return "ipfs-404.html" , "text/html" , nil
697
+ }
698
+ }
699
+ }
700
+
701
+ return "" , "" , fmt .Errorf ("there is no 404 file for the requested content types" )
702
+ }
0 commit comments