@@ -13,7 +13,6 @@ import (
13
13
gopath "path"
14
14
"regexp"
15
15
"runtime/debug"
16
- "strconv"
17
16
"strings"
18
17
"time"
19
18
@@ -378,30 +377,18 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
378
377
return
379
378
}
380
379
381
- // Resolve path to the final DAG node for the ETag
382
- resolvedPath , err := i .api .ResolvePath (r .Context (), contentPath )
383
- switch err {
384
- case nil :
385
- case coreiface .ErrOffline :
386
- webError (w , "ipfs resolve -r " + debugStr (contentPath .String ()), err , http .StatusServiceUnavailable )
387
- return
388
- default :
389
- // if Accept is text/html, see if ipfs-404.html is present
390
- if i .servePretty404IfPresent (w , r , contentPath ) {
391
- logger .Debugw ("serve pretty 404 if present" )
392
- return
393
- }
394
- webError (w , "ipfs resolve -r " + debugStr (contentPath .String ()), err , http .StatusBadRequest )
395
- return
396
- }
397
-
398
380
// Detect when explicit Accept header or ?format parameter are present
399
381
responseFormat , formatParams , err := customResponseFormat (r )
400
382
if err != nil {
401
383
webError (w , "error while processing the Accept header" , err , http .StatusBadRequest )
402
384
return
403
385
}
404
386
trace .SpanFromContext (r .Context ()).SetAttributes (attribute .String ("ResponseFormat" , responseFormat ))
387
+
388
+ resolvedPath , contentPath , ok := i .handlePathResolution (w , r , responseFormat , contentPath , logger )
389
+ if ! ok {
390
+ return
391
+ }
405
392
trace .SpanFromContext (r .Context ()).SetAttributes (attribute .String ("ResolvedPath" , resolvedPath .String ()))
406
393
407
394
// Detect when If-None-Match HTTP header allows returning HTTP 304 Not Modified
@@ -450,36 +437,6 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
450
437
}
451
438
}
452
439
453
- func (i * gatewayHandler ) servePretty404IfPresent (w http.ResponseWriter , r * http.Request , contentPath ipath.Path ) bool {
454
- resolved404Path , ctype , err := i .searchUpTreeFor404 (r , contentPath )
455
- if err != nil {
456
- return false
457
- }
458
-
459
- dr , err := i .api .Unixfs ().Get (r .Context (), resolved404Path )
460
- if err != nil {
461
- return false
462
- }
463
- defer dr .Close ()
464
-
465
- f , ok := dr .(files.File )
466
- if ! ok {
467
- return false
468
- }
469
-
470
- size , err := f .Size ()
471
- if err != nil {
472
- return false
473
- }
474
-
475
- log .Debugw ("using pretty 404 file" , "path" , contentPath )
476
- w .Header ().Set ("Content-Type" , ctype )
477
- w .Header ().Set ("Content-Length" , strconv .FormatInt (size , 10 ))
478
- w .WriteHeader (http .StatusNotFound )
479
- _ , err = io .CopyN (w , f , size )
480
- return err == nil
481
- }
482
-
483
440
func (i * gatewayHandler ) postHandler (w http.ResponseWriter , r * http.Request ) {
484
441
p , err := i .api .Unixfs ().Add (r .Context (), files .NewReaderFile (r .Body ))
485
442
if err != nil {
@@ -920,55 +877,56 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string]
920
877
return "" , nil , nil
921
878
}
922
879
923
- func (i * gatewayHandler ) searchUpTreeFor404 (r * http.Request , contentPath ipath.Path ) (ipath.Resolved , string , error ) {
924
- filename404 , ctype , err := preferred404Filename (r .Header .Values ("Accept" ))
925
- if err != nil {
926
- return nil , "" , err
880
+ // returns unquoted path with all special characters revealed as \u codes
881
+ func debugStr (path string ) string {
882
+ q := fmt .Sprintf ("%+q" , path )
883
+ if len (q ) >= 3 {
884
+ q = q [1 : len (q )- 1 ]
927
885
}
886
+ return q
887
+ }
928
888
929
- pathComponents := strings .Split (contentPath .String (), "/" )
930
-
931
- for idx := len (pathComponents ); idx >= 3 ; idx -- {
932
- pretty404 := gopath .Join (append (pathComponents [0 :idx ], filename404 )... )
933
- parsed404Path := ipath .New ("/" + pretty404 )
934
- if parsed404Path .IsValid () != nil {
935
- break
936
- }
937
- resolvedPath , err := i .api .ResolvePath (r .Context (), parsed404Path )
938
- if err != nil {
939
- continue
940
- }
941
- return resolvedPath , ctype , nil
942
- }
889
+ // Resolve the provided contentPath including any special handling related to
890
+ // the requested responseFormat. Returned ok flag indicates if gateway handler
891
+ // should continue processing the request.
892
+ func (i * gatewayHandler ) handlePathResolution (w http.ResponseWriter , r * http.Request , responseFormat string , contentPath ipath.Path , logger * zap.SugaredLogger ) (resolvedPath ipath.Resolved , newContentPath ipath.Path , ok bool ) {
893
+ // Attempt to resolve the provided path.
894
+ resolvedPath , err := i .api .ResolvePath (r .Context (), contentPath )
943
895
944
- return nil , "" , fmt .Errorf ("no pretty 404 in any parent folder" )
945
- }
896
+ switch err {
897
+ case nil :
898
+ return resolvedPath , contentPath , true
899
+ case coreiface .ErrOffline :
900
+ webError (w , "ipfs resolve -r " + debugStr (contentPath .String ()), err , http .StatusServiceUnavailable )
901
+ return nil , nil , false
902
+ default :
903
+ // The path can't be resolved.
904
+ if isUnixfsResponseFormat (responseFormat ) {
905
+ // If we have origin isolation (subdomain gw, DNSLink website),
906
+ // and response type is UnixFS (default for website hosting)
907
+ // check for presence of _redirects file and apply rules defined there.
908
+ // See: https://github.com/ipfs/specs/pull/290
909
+ if hasOriginIsolation (r ) {
910
+ resolvedPath , newContentPath , ok , hadMatchingRule := i .serveRedirectsIfPresent (w , r , resolvedPath , contentPath , logger )
911
+ if hadMatchingRule {
912
+ logger .Debugw ("applied a rule from _redirects file" )
913
+ return resolvedPath , newContentPath , ok
914
+ }
915
+ }
946
916
947
- func preferred404Filename (acceptHeaders []string ) (string , string , error ) {
948
- // If we ever want to offer a 404 file for a different content type
949
- // then this function will need to parse q weightings, but for now
950
- // the presence of anything matching HTML is enough.
951
- for _ , acceptHeader := range acceptHeaders {
952
- accepted := strings .Split (acceptHeader , "," )
953
- for _ , spec := range accepted {
954
- contentType := strings .SplitN (spec , ";" , 1 )[0 ]
955
- switch contentType {
956
- case "*/*" , "text/*" , "text/html" :
957
- return "ipfs-404.html" , "text/html" , nil
917
+ // if Accept is text/html, see if ipfs-404.html is present
918
+ // This logic isn't documented and will likely be removed at some point.
919
+ // Any 404 logic in _redirects above will have already run by this time, so it's really an extra fall back
920
+ if i .serveLegacy404IfPresent (w , r , contentPath ) {
921
+ logger .Debugw ("served legacy 404" )
922
+ return nil , nil , false
958
923
}
959
924
}
960
- }
961
925
962
- return "" , "" , fmt .Errorf ("there is no 404 file for the requested content types" )
963
- }
964
-
965
- // returns unquoted path with all special characters revealed as \u codes
966
- func debugStr (path string ) string {
967
- q := fmt .Sprintf ("%+q" , path )
968
- if len (q ) >= 3 {
969
- q = q [1 : len (q )- 1 ]
926
+ // Note: webError will replace http.StatusBadRequest with StatusNotFound if necessary
927
+ webError (w , "ipfs resolve -r " + debugStr (contentPath .String ()), err , http .StatusBadRequest )
928
+ return nil , nil , false
970
929
}
971
- return q
972
930
}
973
931
974
932
// Detect 'Cache-Control: only-if-cached' in request and return data if it is already in the local datastore.
0 commit comments