Skip to content

Commit d849115

Browse files
committed
refactor: gateway_handler_unixfs.go
- Moved UnixFS response handling to gateway_handler_unixfs*.go files. - Removed support for X-Ipfs-Gateway-Prefix (Closes #7702)
1 parent 84f2b05 commit d849115

File tree

4 files changed

+267
-238
lines changed

4 files changed

+267
-238
lines changed

Diff for: core/corehttp/gateway_handler.go

+33-238
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package corehttp
33
import (
44
"context"
55
"fmt"
6+
"html"
67
"html/template"
78
"io"
89
"net/http"
@@ -15,10 +16,8 @@ import (
1516
"strings"
1617
"time"
1718

18-
humanize "github.com/dustin/go-humanize"
1919
cid "github.com/ipfs/go-cid"
2020
files "github.com/ipfs/go-ipfs-files"
21-
assets "github.com/ipfs/go-ipfs/assets"
2221
dag "github.com/ipfs/go-merkledag"
2322
mfs "github.com/ipfs/go-mfs"
2423
path "github.com/ipfs/go-path"
@@ -197,38 +196,17 @@ func (i *gatewayHandler) optionsHandler(w http.ResponseWriter, r *http.Request)
197196

198197
func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) {
199198
begin := time.Now()
200-
urlPath := r.URL.Path
201-
escapedURLPath := r.URL.EscapedPath()
202199

203200
logger := log.With("from", r.RequestURI)
204201
logger.Debug("http request received")
205202

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)
229208
return
230209
}
231-
originalUrlPath := prefix + requestURI.Path
232210

233211
// ?uri query param support for requests produced by web browsers
234212
// via navigator.registerProtocolHandler Web API
@@ -249,7 +227,7 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
249227
path = path + "?" + u.RawQuery
250228
}
251229

252-
redirectURL := gopath.Join("/", prefix, u.Scheme, u.Host, path)
230+
redirectURL := gopath.Join("/", u.Scheme, u.Host, path)
253231
logger.Debugw("uri param, redirect", "to", redirectURL, "status", http.StatusMovedPermanently)
254232
http.Redirect(w, r, redirectURL, http.StatusMovedPermanently)
255233
return
@@ -267,9 +245,9 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
267245
}
268246
}
269247

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) {
273251
// the error was due to redundant namespace, which we were able to fix
274252
// by returning error/redirect page, nothing left to do here
275253
logger.Debugw("redundant namespace; noop")
@@ -281,19 +259,19 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
281259
}
282260

283261
// 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)
285263
switch err {
286264
case nil:
287265
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)
289267
return
290268
default:
291-
if i.servePretty404IfPresent(w, r, parsedPath) {
269+
if i.servePretty404IfPresent(w, r, contentPath) {
292270
logger.Debugw("serve pretty 404 if present")
293271
return
294272
}
295273

296-
webError(w, "ipfs resolve -r "+escapedURLPath, err, http.StatusNotFound)
274+
webError(w, "ipfs resolve -r "+html.EscapeString(contentPath.String()), err, http.StatusNotFound)
297275
return
298276
}
299277

@@ -312,225 +290,42 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
312290
webError(w, "ipfs block get "+resolvedPath.Cid().String(), err, http.StatusInternalServerError)
313291
return
314292
}
315-
i.unixfsGetMetric.WithLabelValues(parsedPath.Namespace()).Observe(time.Since(begin).Seconds())
293+
i.unixfsGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds())
316294

317295
// HTTP Headers
318296
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())
320298

321-
if rootCids, err := i.buildIpfsRootsHeader(urlPath, r); err == nil {
299+
if rootCids, err := i.buildIpfsRootsHeader(contentPath.String(), r); err == nil {
322300
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
324302
webError(w, "error while resolving X-Ipfs-Roots", err, http.StatusInternalServerError)
325303
return
326304
}
327305

328306
// Support custom response formats passed via ?format or Accept HTTP header
329307
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
333312
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)
336315
return
337316
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)
340319
return
341320
default:
342321
err := fmt.Errorf("unsupported format %q", contentType)
343322
webError(w, "failed respond with requested content type", err, http.StatusBadRequest)
344323
return
345324
}
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-
}
530325
}
531326

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)
534329
if err != nil {
535330
return false
536331
}
@@ -551,7 +346,7 @@ func (i *gatewayHandler) servePretty404IfPresent(w http.ResponseWriter, r *http.
551346
return false
552347
}
553348

554-
log.Debugw("using pretty 404 file", "path", parsedPath)
349+
log.Debugw("using pretty 404 file", "path", contentPath)
555350
w.Header().Set("Content-Type", ctype)
556351
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
557352
w.WriteHeader(http.StatusNotFound)
@@ -762,7 +557,7 @@ func addCacheControlHeaders(w http.ResponseWriter, r *http.Request, contentPath
762557
modtime = time.Now()
763558

764559
// 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?
766561

767562
} else {
768563
// immutable! CACHE ALL THE THINGS, FOREVER! wolololol
@@ -771,7 +566,7 @@ func addCacheControlHeaders(w http.ResponseWriter, r *http.Request, contentPath
771566
// Set modtime to 'zero time' to disable Last-Modified header (superseded by Cache-Control)
772567
modtime = noModtime
773568

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?
775570
}
776571

777572
return modtime
@@ -909,13 +704,13 @@ func getExplicitContentType(r *http.Request) string {
909704
return ""
910705
}
911706

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) {
913708
filename404, ctype, err := preferred404Filename(r.Header.Values("Accept"))
914709
if err != nil {
915710
return nil, "", err
916711
}
917712

918-
pathComponents := strings.Split(parsedPath.String(), "/")
713+
pathComponents := strings.Split(contentPath.String(), "/")
919714

920715
for idx := len(pathComponents); idx >= 3; idx-- {
921716
pretty404 := gopath.Join(append(pathComponents[0:idx], filename404)...)

0 commit comments

Comments
 (0)