@@ -3,6 +3,7 @@ package corehttp
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "html"
6
7
"html/template"
7
8
"io"
8
9
"net/http"
@@ -15,10 +16,8 @@ import (
15
16
"strings"
16
17
"time"
17
18
18
- humanize "github.com/dustin/go-humanize"
19
19
cid "github.com/ipfs/go-cid"
20
20
files "github.com/ipfs/go-ipfs-files"
21
- assets "github.com/ipfs/go-ipfs/assets"
22
21
dag "github.com/ipfs/go-merkledag"
23
22
mfs "github.com/ipfs/go-mfs"
24
23
path "github.com/ipfs/go-path"
@@ -197,38 +196,17 @@ func (i *gatewayHandler) optionsHandler(w http.ResponseWriter, r *http.Request)
197
196
198
197
func (i * gatewayHandler ) getOrHeadHandler (w http.ResponseWriter , r * http.Request ) {
199
198
begin := time .Now ()
200
- urlPath := r .URL .Path
201
- escapedURLPath := r .URL .EscapedPath ()
202
199
203
200
logger := log .With ("from" , r .RequestURI )
204
201
logger .Debug ("http request received" )
205
202
206
- // If the gateway is behind a reverse proxy and mounted at a sub-path,
207
- // the prefix header can be set to signal this sub-path.
208
- // It will be prepended to links in directory listings and the index.html redirect.
209
- // TODO: this feature is deprecated and will be removed (https://github.com/ipfs/go-ipfs/issues/7702)
210
- prefix := ""
211
- if prfx := r .Header .Get ("X-Ipfs-Gateway-Prefix" ); len (prfx ) > 0 {
212
- for _ , p := range i .config .PathPrefixes {
213
- if prfx == p || strings .HasPrefix (prfx , p + "/" ) {
214
- prefix = prfx
215
- break
216
- }
217
- }
218
- logger .Debugw ("sub-path (deprecrated)" , "prefix" , prefix )
219
- }
220
-
221
- // HostnameOption might have constructed an IPNS/IPFS path using the Host header.
222
- // In this case, we need the original path for constructing redirects
223
- // and links that match the requested URL.
224
- // For example, http://example.net would become /ipns/example.net, and
225
- // the redirects and links would end up as http://example.net/ipns/example.net
226
- requestURI , err := url .ParseRequestURI (r .RequestURI )
227
- if err != nil {
228
- webError (w , "failed to parse request path" , err , http .StatusInternalServerError )
203
+ // X-Ipfs-Gateway-Prefix was removed (https://github.com/ipfs/go-ipfs/issues/7702)
204
+ // TODO: remove this after go-ipfs 0.13 ships
205
+ if prfx := r .Header .Get ("X-Ipfs-Gateway-Prefix" ); prfx != "" {
206
+ err := fmt .Errorf ("X-Ipfs-Gateway-Prefix support was removed: https://github.com/ipfs/go-ipfs/issues/7702" )
207
+ webError (w , "unsupported HTTP header" , err , http .StatusBadRequest )
229
208
return
230
209
}
231
- originalUrlPath := prefix + requestURI .Path
232
210
233
211
// ?uri query param support for requests produced by web browsers
234
212
// via navigator.registerProtocolHandler Web API
@@ -249,7 +227,7 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
249
227
path = path + "?" + u .RawQuery
250
228
}
251
229
252
- redirectURL := gopath .Join ("/" , prefix , u .Scheme , u .Host , path )
230
+ redirectURL := gopath .Join ("/" , u .Scheme , u .Host , path )
253
231
logger .Debugw ("uri param, redirect" , "to" , redirectURL , "status" , http .StatusMovedPermanently )
254
232
http .Redirect (w , r , redirectURL , http .StatusMovedPermanently )
255
233
return
@@ -267,9 +245,9 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
267
245
}
268
246
}
269
247
270
- parsedPath := ipath .New (urlPath )
271
- if pathErr := parsedPath .IsValid (); pathErr != nil {
272
- if prefix == "" && fixupSuperfluousNamespace (w , urlPath , r .URL .RawQuery ) {
248
+ contentPath := ipath .New (r . URL . Path )
249
+ if pathErr := contentPath .IsValid (); pathErr != nil {
250
+ if fixupSuperfluousNamespace (w , r . URL . Path , r .URL .RawQuery ) {
273
251
// the error was due to redundant namespace, which we were able to fix
274
252
// by returning error/redirect page, nothing left to do here
275
253
logger .Debugw ("redundant namespace; noop" )
@@ -281,19 +259,19 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
281
259
}
282
260
283
261
// Resolve path to the final DAG node for the ETag
284
- resolvedPath , err := i .api .ResolvePath (r .Context (), parsedPath )
262
+ resolvedPath , err := i .api .ResolvePath (r .Context (), contentPath )
285
263
switch err {
286
264
case nil :
287
265
case coreiface .ErrOffline :
288
- webError (w , "ipfs resolve -r " + escapedURLPath , err , http .StatusServiceUnavailable )
266
+ webError (w , "ipfs resolve -r " + html . EscapeString ( contentPath . String ()) , err , http .StatusServiceUnavailable )
289
267
return
290
268
default :
291
- if i .servePretty404IfPresent (w , r , parsedPath ) {
269
+ if i .servePretty404IfPresent (w , r , contentPath ) {
292
270
logger .Debugw ("serve pretty 404 if present" )
293
271
return
294
272
}
295
273
296
- webError (w , "ipfs resolve -r " + escapedURLPath , err , http .StatusNotFound )
274
+ webError (w , "ipfs resolve -r " + html . EscapeString ( contentPath . String ()) , err , http .StatusNotFound )
297
275
return
298
276
}
299
277
@@ -312,225 +290,42 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
312
290
webError (w , "ipfs block get " + resolvedPath .Cid ().String (), err , http .StatusInternalServerError )
313
291
return
314
292
}
315
- i .unixfsGetMetric .WithLabelValues (parsedPath .Namespace ()).Observe (time .Since (begin ).Seconds ())
293
+ i .unixfsGetMetric .WithLabelValues (contentPath .Namespace ()).Observe (time .Since (begin ).Seconds ())
316
294
317
295
// HTTP Headers
318
296
i .addUserHeaders (w ) // ok, _now_ write user's headers.
319
- w .Header ().Set ("X-Ipfs-Path" , urlPath )
297
+ w .Header ().Set ("X-Ipfs-Path" , contentPath . String () )
320
298
321
- if rootCids , err := i .buildIpfsRootsHeader (urlPath , r ); err == nil {
299
+ if rootCids , err := i .buildIpfsRootsHeader (contentPath . String () , r ); err == nil {
322
300
w .Header ().Set ("X-Ipfs-Roots" , rootCids )
323
- } else { // this should never happen, as we resolved the urlPath already
301
+ } else { // this should never happen, as we resolved the contentPath already
324
302
webError (w , "error while resolving X-Ipfs-Roots" , err , http .StatusInternalServerError )
325
303
return
326
304
}
327
305
328
306
// Support custom response formats passed via ?format or Accept HTTP header
329
307
switch contentType := getExplicitContentType (r ); contentType {
330
- case "" :
331
- // nothing we should special-case, skip
332
- break
308
+ case "" : // The default, implicit response format is UnixFS
309
+ logger .Debugw ("serving unixfs" , "path" , contentPath )
310
+ i .serveUnixFs (w , r , resolvedPath , contentPath , logger )
311
+ return
333
312
case "application/vnd.ipld.raw" :
334
- logger .Debugw ("serving raw block" , "path" , parsedPath )
335
- i .serveRawBlock (w , r , resolvedPath .Cid (), parsedPath )
313
+ logger .Debugw ("serving raw block" , "path" , contentPath )
314
+ i .serveRawBlock (w , r , resolvedPath .Cid (), contentPath )
336
315
return
337
316
case "application/vnd.ipld.car" , "application/vnd.ipld.car; version=1" :
338
- logger .Debugw ("serving car stream" , "path" , parsedPath )
339
- i .serveCar (w , r , resolvedPath .Cid (), parsedPath )
317
+ logger .Debugw ("serving car stream" , "path" , contentPath )
318
+ i .serveCar (w , r , resolvedPath .Cid (), contentPath )
340
319
return
341
320
default :
342
321
err := fmt .Errorf ("unsupported format %q" , contentType )
343
322
webError (w , "failed respond with requested content type" , err , http .StatusBadRequest )
344
323
return
345
324
}
346
-
347
- // Handling Unixfs
348
- dr , err := i .api .Unixfs ().Get (r .Context (), resolvedPath )
349
- if err != nil {
350
- webError (w , "ipfs cat " + escapedURLPath , err , http .StatusNotFound )
351
- return
352
- }
353
- defer dr .Close ()
354
-
355
- // Handling Unixfs file
356
- if f , ok := dr .(files.File ); ok {
357
- logger .Debugw ("serving file" , "path" , parsedPath )
358
- i .serveFile (w , r , parsedPath , resolvedPath .Cid (), f )
359
- return
360
- }
361
-
362
- // Handling Unixfs directory
363
- dir , ok := dr .(files.Directory )
364
- if ! ok {
365
- internalWebError (w , fmt .Errorf ("unsupported file type" ))
366
- return
367
- }
368
-
369
- // Check if directory has index.html, if so, serveFile
370
- idxPath := ipath .Join (resolvedPath , "index.html" )
371
- idx , err := i .api .Unixfs ().Get (r .Context (), idxPath )
372
- switch err .(type ) {
373
- case nil :
374
- dirwithoutslash := urlPath [len (urlPath )- 1 ] != '/'
375
- goget := r .URL .Query ().Get ("go-get" ) == "1"
376
- if dirwithoutslash && ! goget {
377
- // See comment above where originalUrlPath is declared.
378
- suffix := "/"
379
- if r .URL .RawQuery != "" {
380
- // preserve query parameters
381
- suffix = suffix + "?" + r .URL .RawQuery
382
- }
383
-
384
- redirectURL := originalUrlPath + suffix
385
- logger .Debugw ("serving index.html file" , "to" , redirectURL , "status" , http .StatusFound , "path" , idxPath )
386
- http .Redirect (w , r , redirectURL , http .StatusFound )
387
- return
388
- }
389
-
390
- f , ok := idx .(files.File )
391
- if ! ok {
392
- internalWebError (w , files .ErrNotReader )
393
- return
394
- }
395
-
396
- logger .Debugw ("serving index.html file" , "path" , idxPath )
397
- // write to request
398
- i .serveFile (w , r , idxPath , resolvedPath .Cid (), f )
399
- return
400
- case resolver.ErrNoLink :
401
- logger .Debugw ("no index.html; noop" , "path" , idxPath )
402
- default :
403
- internalWebError (w , err )
404
- return
405
- }
406
-
407
- // See statusResponseWriter.WriteHeader
408
- // and https://github.com/ipfs/go-ipfs/issues/7164
409
- // Note: this needs to occur before listingTemplate.Execute otherwise we get
410
- // superfluous response.WriteHeader call from prometheus/client_golang
411
- if w .Header ().Get ("Location" ) != "" {
412
- logger .Debugw ("location moved permanently" , "status" , http .StatusMovedPermanently )
413
- w .WriteHeader (http .StatusMovedPermanently )
414
- return
415
- }
416
-
417
- // A HTML directory index will be presented, be sure to set the correct
418
- // type instead of relying on autodetection (which may fail).
419
- w .Header ().Set ("Content-Type" , "text/html" )
420
-
421
- // Generated dir index requires custom Etag (it may change between go-ipfs versions)
422
- if assets .BindataVersionHash != "" {
423
- dirEtag := `"DirIndex-` + assets .BindataVersionHash + `_CID-` + resolvedPath .Cid ().String () + `"`
424
- w .Header ().Set ("Etag" , dirEtag )
425
- if r .Header .Get ("If-None-Match" ) == dirEtag {
426
- w .WriteHeader (http .StatusNotModified )
427
- return
428
- }
429
- }
430
-
431
- if r .Method == http .MethodHead {
432
- logger .Debug ("return as request's HTTP method is HEAD" )
433
- return
434
- }
435
-
436
- // storage for directory listing
437
- var dirListing []directoryItem
438
- dirit := dir .Entries ()
439
- for dirit .Next () {
440
- size := "?"
441
- if s , err := dirit .Node ().Size (); err == nil {
442
- // Size may not be defined/supported. Continue anyways.
443
- size = humanize .Bytes (uint64 (s ))
444
- }
445
-
446
- resolved , err := i .api .ResolvePath (r .Context (), ipath .Join (resolvedPath , dirit .Name ()))
447
- if err != nil {
448
- internalWebError (w , err )
449
- return
450
- }
451
- hash := resolved .Cid ().String ()
452
-
453
- // See comment above where originalUrlPath is declared.
454
- di := directoryItem {
455
- Size : size ,
456
- Name : dirit .Name (),
457
- Path : gopath .Join (originalUrlPath , dirit .Name ()),
458
- Hash : hash ,
459
- ShortHash : shortHash (hash ),
460
- }
461
- dirListing = append (dirListing , di )
462
- }
463
- if dirit .Err () != nil {
464
- internalWebError (w , dirit .Err ())
465
- return
466
- }
467
-
468
- // construct the correct back link
469
- // https://github.com/ipfs/go-ipfs/issues/1365
470
- var backLink string = originalUrlPath
471
-
472
- // don't go further up than /ipfs/$hash/
473
- pathSplit := path .SplitList (urlPath )
474
- switch {
475
- // keep backlink
476
- case len (pathSplit ) == 3 : // url: /ipfs/$hash
477
-
478
- // keep backlink
479
- case len (pathSplit ) == 4 && pathSplit [3 ] == "" : // url: /ipfs/$hash/
480
-
481
- // add the correct link depending on whether the path ends with a slash
482
- default :
483
- if strings .HasSuffix (backLink , "/" ) {
484
- backLink += "./.."
485
- } else {
486
- backLink += "/.."
487
- }
488
- }
489
-
490
- size := "?"
491
- if s , err := dir .Size (); err == nil {
492
- // Size may not be defined/supported. Continue anyways.
493
- size = humanize .Bytes (uint64 (s ))
494
- }
495
-
496
- hash := resolvedPath .Cid ().String ()
497
-
498
- // Gateway root URL to be used when linking to other rootIDs.
499
- // This will be blank unless subdomain or DNSLink resolution is being used
500
- // for this request.
501
- var gwURL string
502
-
503
- // Get gateway hostname and build gateway URL.
504
- if h , ok := r .Context ().Value ("gw-hostname" ).(string ); ok {
505
- gwURL = "//" + h
506
- } else {
507
- gwURL = ""
508
- }
509
-
510
- dnslink := hasDNSLinkOrigin (gwURL , urlPath )
511
-
512
- // See comment above where originalUrlPath is declared.
513
- tplData := listingTemplateData {
514
- GatewayURL : gwURL ,
515
- DNSLink : dnslink ,
516
- Listing : dirListing ,
517
- Size : size ,
518
- Path : urlPath ,
519
- Breadcrumbs : breadcrumbs (urlPath , dnslink ),
520
- BackLink : backLink ,
521
- Hash : hash ,
522
- }
523
-
524
- logger .Debugw ("request processed" , "tplDataDNSLink" , dnslink , "tplDataSize" , size , "tplDataBackLink" , backLink , "tplDataHash" , hash , "duration" , time .Since (begin ))
525
-
526
- if err := listingTemplate .Execute (w , tplData ); err != nil {
527
- internalWebError (w , err )
528
- return
529
- }
530
325
}
531
326
532
- func (i * gatewayHandler ) servePretty404IfPresent (w http.ResponseWriter , r * http.Request , parsedPath ipath.Path ) bool {
533
- resolved404Path , ctype , err := i .searchUpTreeFor404 (r , parsedPath )
327
+ func (i * gatewayHandler ) servePretty404IfPresent (w http.ResponseWriter , r * http.Request , contentPath ipath.Path ) bool {
328
+ resolved404Path , ctype , err := i .searchUpTreeFor404 (r , contentPath )
534
329
if err != nil {
535
330
return false
536
331
}
@@ -551,7 +346,7 @@ func (i *gatewayHandler) servePretty404IfPresent(w http.ResponseWriter, r *http.
551
346
return false
552
347
}
553
348
554
- log .Debugw ("using pretty 404 file" , "path" , parsedPath )
349
+ log .Debugw ("using pretty 404 file" , "path" , contentPath )
555
350
w .Header ().Set ("Content-Type" , ctype )
556
351
w .Header ().Set ("Content-Length" , strconv .FormatInt (size , 10 ))
557
352
w .WriteHeader (http .StatusNotFound )
@@ -762,7 +557,7 @@ func addCacheControlHeaders(w http.ResponseWriter, r *http.Request, contentPath
762
557
modtime = time .Now ()
763
558
764
559
// TODO: set Cache-Control based on TTL of IPNS/DNSLink: https://github.com/ipfs/go-ipfs/issues/1818#issuecomment-1015849462
765
- // TODO: set Last-Modified if modification metadata is present in unixfs 1.5: https://github.com/ipfs/go-ipfs/issues/6920
560
+ // TODO: set Last-Modified based on /ipns/ publishing timestamp?
766
561
767
562
} else {
768
563
// immutable! CACHE ALL THE THINGS, FOREVER! wolololol
@@ -771,7 +566,7 @@ func addCacheControlHeaders(w http.ResponseWriter, r *http.Request, contentPath
771
566
// Set modtime to 'zero time' to disable Last-Modified header (superseded by Cache-Control)
772
567
modtime = noModtime
773
568
774
- // TODO: set Last-Modified if modification metadata is present in unixfs 1.5: https://github.com/ipfs/go-ipfs/issues/6920
569
+ // TODO: set Last-Modified - TBD - /ipfs/ modification metadata is present in unixfs 1.5 https://github.com/ipfs/go-ipfs/issues/6920?
775
570
}
776
571
777
572
return modtime
@@ -909,13 +704,13 @@ func getExplicitContentType(r *http.Request) string {
909
704
return ""
910
705
}
911
706
912
- func (i * gatewayHandler ) searchUpTreeFor404 (r * http.Request , parsedPath ipath.Path ) (ipath.Resolved , string , error ) {
707
+ func (i * gatewayHandler ) searchUpTreeFor404 (r * http.Request , contentPath ipath.Path ) (ipath.Resolved , string , error ) {
913
708
filename404 , ctype , err := preferred404Filename (r .Header .Values ("Accept" ))
914
709
if err != nil {
915
710
return nil , "" , err
916
711
}
917
712
918
- pathComponents := strings .Split (parsedPath .String (), "/" )
713
+ pathComponents := strings .Split (contentPath .String (), "/" )
919
714
920
715
for idx := len (pathComponents ); idx >= 3 ; idx -- {
921
716
pretty404 := gopath .Join (append (pathComponents [0 :idx ], filename404 )... )
0 commit comments