From 5ae4e4e80e90efa9673c11c939aa70aea0d1c722 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 7 Apr 2022 09:05:24 -0500 Subject: [PATCH 01/54] WIP _redirects support --- core/corehttp/gateway_handler.go | 44 ++- .../gateway_handler_unixfs__redirects.go | 314 ++++++++++++++++++ core/corehttp/hostname.go | 3 +- go.mod | 6 +- go.sum | 7 + test/sharness/t0109-gateway-web-_redirects.sh | 127 +++++++ 6 files changed, 481 insertions(+), 20 deletions(-) create mode 100644 core/corehttp/gateway_handler_unixfs__redirects.go create mode 100755 test/sharness/t0109-gateway-web-_redirects.sh diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index ca386700100..f3445ee3ce9 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -378,23 +378,6 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request return } - // Resolve path to the final DAG node for the ETag - resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) - switch err { - case nil: - case coreiface.ErrOffline: - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable) - return - default: - // if Accept is text/html, see if ipfs-404.html is present - if i.servePretty404IfPresent(w, r, contentPath) { - logger.Debugw("serve pretty 404 if present") - return - } - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusBadRequest) - return - } - // Detect when explicit Accept header or ?format parameter are present responseFormat, formatParams, err := customResponseFormat(r) if err != nil { @@ -402,6 +385,17 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request return } trace.SpanFromContext(r.Context()).SetAttributes(attribute.String("ResponseFormat", responseFormat)) + + var ok bool + var resolvedPath ipath.Resolved + if isUnixfsResponseFormat(responseFormat) { + resolvedPath, contentPath, ok = i.handleUnixfsPathResolution(w, r, contentPath, logger) + } else { + resolvedPath, contentPath, ok = i.handleNonUnixfsPathResolution(w, r, contentPath, logger) + } + if !ok { + return + } trace.SpanFromContext(r.Context()).SetAttributes(attribute.String("ResolvedPath", resolvedPath.String())) // Detect when If-None-Match HTTP header allows returning HTTP 304 Not Modified @@ -1116,3 +1110,19 @@ func (i *gatewayHandler) setCommonHeaders(w http.ResponseWriter, r *http.Request return nil } + +func (i *gatewayHandler) handleNonUnixfsPathResolution(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { + // Resolve the path for the provided contentPath + resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) + + switch err { + case nil: + return resolvedPath, contentPath, true + case coreiface.ErrOffline: + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable) + return nil, nil, false + default: + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusNotFound) + return nil, nil, false + } +} diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go new file mode 100644 index 00000000000..75ae714e242 --- /dev/null +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -0,0 +1,314 @@ +package corehttp + +import ( + "errors" + "fmt" + "io" + "net/http" + gopath "path" + "strconv" + "strings" + + "github.com/fission-suite/go-redirects" + files "github.com/ipfs/go-ipfs-files" + "github.com/ipfs/go-path/resolver" + coreiface "github.com/ipfs/interface-go-ipfs-core" + ipath "github.com/ipfs/interface-go-ipfs-core/path" + "github.com/ucarion/urlpath" + "go.uber.org/zap" +) + +// Resolve the provided path. +// +// Resolving a UnixFS path involves determining if the provided `path.Path` exists and returning the `path.Resolved` corresponding to that path. +// For UnixFS, path resolution is more involved if a `_redirects`` file exists stored under the root CID of the path. +// +// Example 1: +// If a path exists, usually we should return the `path.Resolved` corresponding to that path. +// However, the `_redirects` file may contain a forced redirect rule corresponding to the path (i.e. a rule with a `!` after the status code). +// Forced redirect rules must be evaluated, even if the path exists, thus overriding the path. +// +// Example 2: +// If a path does not exist, usually we should return a nil resolution path and an error indicating that the path doesn't exist. +// However, the `_redirects` file may contain a redirect rule that redirects that path to a different path. +// We need to evaluate the rule and perform the redirect if present. +// +// Example 3: +// Another possibility is that the path corresponds to a rewrite rule (i.e. a rule with a status of 200). +// In this case, we don't perform a redirect, but do need to return a `path.Resolved` and `path.Path` corresponding to the rewrite destination path. +// +// Note that for security reasons, redirect rules are only processed when the request has origin isolation. +// +func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { + // Attempt to resolve the path for the provided contentPath + resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) + + // If path resolved and we have origin isolation, we need to attempt to read redirects file and find a force redirect for the corresponding path. If found, redirect, but only if force. + // If path resolved and we do not have origin isolation, no need to attempt to read redirects file. Just return resolvedPath, contentPath, true. + // If path didn't resolve, if ErrOffline, write error and return nil, nil, false. + // If path didn't resolve for any other error, if we have origin isolation, attempt to read redirects file and apply any redirect rules, regardless of force. + // Fallback to pretty 404 page, and then normal 404 + + switch err { + case nil: + // TODO: I believe for the force option, we might need to short circuit this, and thus we would need to read the redirects file first + return resolvedPath, contentPath, true + case coreiface.ErrOffline: + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable) + return nil, nil, false + default: + // If we can't resolve the path + // Only look for _redirects file if we have Unixfs and Origin isolation + if hasOriginIsolation(r) { + // Check for _redirects file and redirect as needed + // /ipfs/CID/a/b/c/ + // /ipfs/CID/_redirects + // /ipns/domain/ipfs/CID + // /ipns/domain + logger.Debugf("r.URL.Path=%v", r.URL.Path) + redirectsFile, err := i.getRedirectsFile(r) + + if err != nil { + switch err.(type) { + case resolver.ErrNoLink: + // _redirects files doesn't exist, so don't error + // case coreiface.ErrResolveFailed.(type): + // How to get type? + // // Couldn't resolve ipns name when trying to compute root + // Tests indicate we should return 404, not 500 + // internalWebError(w, err) + // return nil, nil, false + default: + // Let users know about issues with _redirects file handling + internalWebError(w, err) + return nil, nil, false + } + } else { + // _redirects file exists, so parse it and redirect + redirected, newPath, err := i.handleRedirectsFile(w, r, redirectsFile, logger) + if err != nil { + err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) + internalWebError(w, err) + return nil, nil, false + } + + if redirected { + return nil, nil, false + } + + // 200 is treated as a rewrite, so update the path and continue + if newPath != "" { + // Reassign contentPath and resolvedPath since the URL was rewritten + contentPath = ipath.New(newPath) + resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath) + if err != nil { + internalWebError(w, err) + return nil, nil, false + } + logger.Debugf("_redirects: 200 rewrite. newPath=%v", newPath) + + return resolvedPath, contentPath, true + } + } + } + + // if Accept is text/html, see if ipfs-404.html is present + // This logic isn't documented and will likely be removed at some point. + // Any 404 logic in _redirects above will have already run by this time, so it's really an extra fall back + if i.servePretty404IfPresent(w, r, contentPath) { + logger.Debugw("serve pretty 404 if present") + return nil, nil, false + } + + // Fallback + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusNotFound) + return nil, nil, false + } +} + +func (i *gatewayHandler) handleRedirectsFile(w http.ResponseWriter, r *http.Request, redirectsFilePath ipath.Resolved, logger *zap.SugaredLogger) (bool, string, error) { + // Convert the path into a file node + node, err := i.api.Unixfs().Get(r.Context(), redirectsFilePath) + if err != nil { + return false, "", fmt.Errorf("could not get _redirects node: %v", err) + } + defer node.Close() + + // Convert the node into a file + f, ok := node.(files.File) + if !ok { + return false, "", fmt.Errorf("could not convert _redirects node to file") + } + + // Parse redirect rules from file + redirectRules, err := redirects.Parse(f) + if err != nil { + return false, "", fmt.Errorf("could not parse redirect rules: %v", err) + } + logger.Debugf("redirectRules=%v", redirectRules) + + // Attempt to match a rule to the URL path, and perform the corresponding redirect or rewrite + pathParts := strings.Split(r.URL.Path, "/") + if len(pathParts) > 3 { + // All paths should start with /ipfs/cid/, so get the path after that + urlPath := "/" + strings.Join(pathParts[3:], "/") + rootPath := strings.Join(pathParts[:3], "/") + // Trim off the trailing / + urlPath = strings.TrimSuffix(urlPath, "/") + + logger.Debugf("_redirects: urlPath=", urlPath) + for _, rule := range redirectRules { + // get rule.From, trim trailing slash, ... + fromPath := urlpath.New(strings.TrimSuffix(rule.From, "/")) + logger.Debugf("_redirects: fromPath=%v", strings.TrimSuffix(rule.From, "/")) + match, ok := fromPath.Match(urlPath) + if !ok { + continue + } + + // We have a match! Perform substitutions. + toPath := rule.To + toPath = replacePlaceholders(toPath, match) + toPath = replaceSplat(toPath, match) + + logger.Debugf("_redirects: toPath=%v", toPath) + + // Rewrite + if rule.Status == 200 { + // Prepend the rootPath + toPath = rootPath + rule.To + return false, toPath, nil + } + + // Or 404 + if rule.Status == 404 { + toPath = rootPath + rule.To + content404Path := ipath.New(toPath) + err = i.serve404(w, r, content404Path) + return true, toPath, err + } + + // Or redirect + http.Redirect(w, r, toPath, rule.Status) + return true, toPath, nil + } + } + + // No redirects matched + return false, "", nil +} + +func replacePlaceholders(to string, match urlpath.Match) string { + if len(match.Params) > 0 { + for key, value := range match.Params { + to = strings.ReplaceAll(to, ":"+key, value) + } + } + + return to +} + +func replaceSplat(to string, match urlpath.Match) string { + return strings.ReplaceAll(to, ":splat", match.Trailing) +} + +// Returns a resolved path to the _redirects file located in the root CID path of the requested path +func (i *gatewayHandler) getRedirectsFile(r *http.Request) (ipath.Resolved, error) { + // r.URL.Path is the full ipfs path to the requested resource, + // regardless of whether path or subdomain resolution is used. + rootPath, err := getRootPath(r.URL.Path) + if err != nil { + return nil, err + } + + path := ipath.Join(rootPath, "_redirects") + resolvedPath, err := i.api.ResolvePath(r.Context(), path) + if err != nil { + return nil, err + } + return resolvedPath, nil +} + +// Returns the root CID Path for the given path +// /ipfs/CID/* +// CID is the root CID +// /ipns/domain/* +// Need to resolve domain ipns path to get CID +// /ipns/domain/ipfs/CID +// Is this legit? If so, we should use CID? +func getRootPath(path string) (ipath.Path, error) { + if isIpfsPath(path) { + parts := strings.Split(path, "/") + return ipath.New(gopath.Join(ipfsPathPrefix, parts[2])), nil + } + + if isIpnsPath(path) { + parts := strings.Split(path, "/") + return ipath.New(gopath.Join(ipnsPathPrefix, parts[2])), nil + } + + return ipath.New(""), errors.New("failed to get root CID path") +} + +func (i *gatewayHandler) serve404(w http.ResponseWriter, r *http.Request, content404Path ipath.Path) error { + resolved404Path, err := i.api.ResolvePath(r.Context(), content404Path) + if err != nil { + return err + } + + node, err := i.api.Unixfs().Get(r.Context(), resolved404Path) + if err != nil { + return err + } + defer node.Close() + + f, ok := node.(files.File) + if !ok { + return fmt.Errorf("could not convert node for 404 page to file") + } + + size, err := f.Size() + if err != nil { + return fmt.Errorf("could not get size of 404 page") + } + + log.Debugw("using _redirects 404 file", "path", content404Path) + w.Header().Set("Content-Type", "text/html") + w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) + w.WriteHeader(http.StatusNotFound) + _, err = io.CopyN(w, f, size) + return err +} + +// TODO(JJ): Confirm approach +func hasOriginIsolation(r *http.Request) bool { + _, gw := r.Context().Value("gw-hostname").(string) + _, dnslink := r.Context().Value("dnslink-hostname").(string) + + if gw || dnslink { + return true + } + + return false +} + +func isIpfsPath(path string) bool { + if strings.HasPrefix(path, ipfsPathPrefix) && strings.Count(gopath.Clean(path), "/") >= 2 { + return true + } + + return false +} + +func isIpnsPath(path string) bool { + if strings.HasPrefix(path, ipnsPathPrefix) && strings.Count(gopath.Clean(path), "/") >= 2 { + return true + } + + return false +} + +func isUnixfsResponseFormat(responseFormat string) bool { + // The implicit response format is UnixFS + return responseFormat == "" +} diff --git a/core/corehttp/hostname.go b/core/corehttp/hostname.go index 3d022fc5a7f..c9365d4cf6d 100644 --- a/core/corehttp/hostname.go +++ b/core/corehttp/hostname.go @@ -221,7 +221,8 @@ func HostnameOption() ServeOption { if !cfg.Gateway.NoDNSLink && isDNSLinkName(r.Context(), coreAPI, host) { // rewrite path and handle as DNSLink r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path - childMux.ServeHTTP(w, withHostnameContext(r, host)) + ctx := context.WithValue(r.Context(), "dnslink-hostname", host) + childMux.ServeHTTP(w, withHostnameContext(r.WithContext(ctx), host)) return } diff --git a/go.mod b/go.mod index 38f407cc862..21a96e80dac 100644 --- a/go.mod +++ b/go.mod @@ -12,8 +12,9 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/elgris/jsondiff v0.0.0-20160530203242-765b5c24c302 github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 - github.com/fsnotify/fsnotify v1.5.4 - github.com/gabriel-vasile/mimetype v1.4.1 + github.com/fission-suite/go-redirects v0.0.0-20220412202828-a86b2398567d + github.com/fsnotify/fsnotify v1.5.1 + github.com/gabriel-vasile/mimetype v1.4.0 github.com/hashicorp/go-multierror v1.1.1 github.com/ipfs/go-bitswap v0.10.2 github.com/ipfs/go-block-format v0.0.3 @@ -93,6 +94,7 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/stretchr/testify v1.8.0 github.com/syndtr/goleveldb v1.0.0 + github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb github.com/wI2L/jsondiff v0.2.0 github.com/whyrusleeping/go-sysinfo v0.0.0-20190219211824-4a357d4b90b1 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 diff --git a/go.sum b/go.sum index 734e5ea1edb..c59ab8e3f54 100644 --- a/go.sum +++ b/go.sum @@ -230,6 +230,7 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fission-suite/go-redirects v0.0.0-20220412202828-a86b2398567d/go.mod h1:ku06PAzEkYterp0eal/H+Qauo+Zsb+2GAYG0PMew4sg= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= @@ -1511,9 +1512,13 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= +github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= +github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb h1:Ywfo8sUltxogBpFuMOFRrrSifO788kAFxmvVw31PtQQ= +github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb/go.mod h1:ikPs9bRWicNw3S7XpJ8sK/smGwU9WcSVU3dy9qahYBM= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -2137,6 +2142,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh new file mode 100755 index 00000000000..bb6dcb6f90a --- /dev/null +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash + +test_description="Test HTTP Gateway _redirects support" + +. lib/test-lib.sh + +test_init_ipfs +test_launch_ipfs_daemon + +## ============================================================================ +## Test _redirects file support +## ============================================================================ + +# Directory tree crafted to test _redirects file support +test_expect_success "Add the _redirects file test directory" ' + mkdir -p testredirect/ && + echo "my index" > testredirect/index.html && + echo "my one" > testredirect/one.html && + echo "my two" > testredirect/two.html && + echo "my 404" > testredirect/404.html && + mkdir testredirect/redirected-splat && + echo "redirected splat one" > testredirect/redirected-splat/one.html && + echo "/redirect-one /one.html" > testredirect/_redirects && + echo "/301-redirect-one /one.html 301" >> testredirect/_redirects && + echo "/302-redirect-two /two.html 302" >> testredirect/_redirects && + echo "/200-index /index.html 200" >> testredirect/_redirects && + echo "/posts/:year/:month/:day/:title /articles/:year/:month/:day/:title 301" >> testredirect/_redirects && + echo "/splat/:splat /redirected-splat/:splat 301" >> testredirect/_redirects && + echo "/en/* /404.html 404" >> testredirect/_redirects && + echo "/* /index.html 200" >> testredirect/_redirects && + REDIRECTS_DIR_CID=$(ipfs add -Qr --cid-version 1 testredirect) +' + +REDIRECTS_DIR_HOSTNAME="${REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" + +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/redirect-one" > response && + test_should_contain "one.html" response && + test_should_contain "301 Moved Permanently" response +' + +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/301-redirect-one redirects with 301, per _redirects file" ' + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/301-redirect-one" > response && + test_should_contain "one.html" response && + test_should_contain "301 Moved Permanently" response +' + +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/302-redirect-two redirects with 302, per _redirects file" ' + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/302-redirect-two" > response && + test_should_contain "two.html" response && + test_should_contain "302 Found" response +' + +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/200-index returns 200, per _redirects file" ' + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/200-index" > response && + test_should_contain "my index" response && + test_should_contain "200 OK" response +' + +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/posts/:year/:month/:day/:title redirects with 301 and placeholders, per _redirects file" ' + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/posts/2022/01/01/hello-world" > response && + test_should_contain "/articles/2022/01/01/hello-world" response && + test_should_contain "301 Moved Permanently" response +' + +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/splat/one.html redirects with 301 and splat placeholder, per _redirects file" ' + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/splat/one.html" > response && + test_should_contain "/redirected-splat/one.html" response && + test_should_contain "301 Moved Permanently" response +' + +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/en/has-no-redirects-entry returns custom 404, per _redirects file" ' + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/en/has-no-redirects-entry" > response && + test_should_contain "404 Not Found" response +' + +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/catch-all returns 200, per _redirects file" ' + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/catch-all" > response && + test_should_contain "200 OK" response +' + +test_expect_success "request for http://127.0.0.1:$GWAY_PORT/ipfs/$REDIRECTS_DIR_CID/301-redirect-one returns 404, no _redirects since no origin isolation" ' + curl -sD - "http://127.0.0.1:$GWAY_PORT/ipfs/$REDIRECTS_DIR_CID/301-redirect-one" > response && + test_should_contain "404 Not Found" response +' + +test_kill_ipfs_daemon + +# disable wildcard DNSLink gateway +# and enable it on specific NSLink hostname +ipfs config --json Gateway.NoDNSLink true && \ +ipfs config --json Gateway.PublicGateways '{ + "dnslink-enabled-on-fqdn.example.org": { + "NoDNSLink": false, + "UseSubdomains": false, + "Paths": ["/ipfs"] + }, + "only-dnslink-enabled-on-fqdn.example.org": { + "NoDNSLink": false, + "UseSubdomains": false, + "Paths": [] + }, + "dnslink-disabled-on-fqdn.example.com": { + "NoDNSLink": true, + "UseSubdomains": false, + "Paths": [] + } +}' || exit 1 + +# DNSLink test requires a daemon in online mode with precached /ipns/ mapping +DNSLINK_FQDN="dnslink-enabled-on-fqdn.example.org" +ONLY_DNSLINK_FQDN="only-dnslink-enabled-on-fqdn.example.org" +NO_DNSLINK_FQDN="dnslink-disabled-on-fqdn.example.com" +export IPFS_NS_MAP="$DNSLINK_FQDN:/ipfs/$REDIRECTS_DIR_CID,$ONLY_DNSLINK_FQDN:/ipfs/$REDIRECTS_DIR_CID" + +# restart daemon to apply config changes +test_launch_ipfs_daemon + +# make sure test setup is valid (fail if CoreAPI is unable to resolve) +test_expect_success "spoofed DNSLink record resolves in cli" " + ipfs resolve /ipns/$DNSLINK_FQDN > result && + test_should_contain \"$REDIRECTS_DIR_CID\" result && + ipfs cat /ipns/$DNSLINK_FQDN/_redirects > result && + test_should_contain \"index.html\" result +" + +test_done From 937e998edc8700d8ce7c0e68f8313b0be3e73178 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 20 Apr 2022 09:59:50 -0500 Subject: [PATCH 02/54] Handle forced redirects --- core/corehttp/gateway_handler.go | 2 + .../gateway_handler_unixfs__redirects.go | 186 +++++++++++------- 2 files changed, 121 insertions(+), 67 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index f3445ee3ce9..95492e10a3c 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -389,8 +389,10 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request var ok bool var resolvedPath ipath.Resolved if isUnixfsResponseFormat(responseFormat) { + // TODO(JJ): Update function signature re: err, bool returning, per clean up PR feedback resolvedPath, contentPath, ok = i.handleUnixfsPathResolution(w, r, contentPath, logger) } else { + // TODO(JJ): Update function signature re: err, bool returning, per clean up PR feedback resolvedPath, contentPath, ok = i.handleNonUnixfsPathResolution(w, r, contentPath, logger) } if !ok { diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 75ae714e242..d777dcfc31a 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -21,7 +21,7 @@ import ( // Resolve the provided path. // // Resolving a UnixFS path involves determining if the provided `path.Path` exists and returning the `path.Resolved` corresponding to that path. -// For UnixFS, path resolution is more involved if a `_redirects`` file exists stored under the root CID of the path. +// For UnixFS, path resolution is more involved if a `_redirects`` file exists, stored underneath the root CID of the path. // // Example 1: // If a path exists, usually we should return the `path.Resolved` corresponding to that path. @@ -29,7 +29,7 @@ import ( // Forced redirect rules must be evaluated, even if the path exists, thus overriding the path. // // Example 2: -// If a path does not exist, usually we should return a nil resolution path and an error indicating that the path doesn't exist. +// If a path does not exist, usually we should return a `nil` resolution path and an error indicating that the path doesn't exist. // However, the `_redirects` file may contain a redirect rule that redirects that path to a different path. // We need to evaluate the rule and perform the redirect if present. // @@ -39,76 +39,109 @@ import ( // // Note that for security reasons, redirect rules are only processed when the request has origin isolation. // +// if redirectRules, err = i.getRedirectRules(r, redirectsFile); redirectRules != nil { +// redirected, newPath, err := i.handleRedirectsFile(w, r, redirectsFile, true, logger) func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { - // Attempt to resolve the path for the provided contentPath - resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) + // Before we do anything, determine if we must evaluate redirect rules + redirectsFile, err := i.getRedirectsFile(r) + if err != nil { + switch err.(type) { + case resolver.ErrNoLink: + // _redirects files doesn't exist, so don't error + // case coreiface.ErrResolveFailed.(type): + // TODO(JJ): How to get type? + // // Couldn't resolve ipns name when trying to compute root + // Tests indicate we should return 404, not 500 + // internalWebError(w, err) + // return nil, nil, false + default: + // Let users know about issues with _redirects file handling + internalWebError(w, err) + return nil, nil, false + } + } + + mustEvaluateRedirectRules := hasOriginIsolation(r) && redirectsFile != nil + + // Get redirect rules if we know we'll need them + var redirectRules []redirects.Rule + if mustEvaluateRedirectRules { + redirectRules, err = i.getRedirectRules(r, redirectsFile) + if err != nil { + internalWebError(w, err) + return nil, nil, false + } + } + + // If we must evaluate redirect rules and there is a forced redirect for our path, there's no point in attempting to resolve the path, + // since we will have to redirect regardless of whether or not the path exists. Just handle the forced redirect instead. + redirected, newPath, err := i.handleForcedRedirect(w, r, redirectRules, logger) + if err != nil { + err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) + internalWebError(w, err) + return nil, nil, false + } + + if redirected { + return nil, nil, false + } + + // 200 is treated as a rewrite, so update the path and continue + if newPath != "" { + // Reassign contentPath and resolvedPath since the URL was rewritten + contentPath = ipath.New(newPath) + resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) + if err != nil { + internalWebError(w, err) + return nil, nil, false + } + logger.Debugf("_redirects: 200 rewrite. newPath=%v", newPath) - // If path resolved and we have origin isolation, we need to attempt to read redirects file and find a force redirect for the corresponding path. If found, redirect, but only if force. - // If path resolved and we do not have origin isolation, no need to attempt to read redirects file. Just return resolvedPath, contentPath, true. - // If path didn't resolve, if ErrOffline, write error and return nil, nil, false. - // If path didn't resolve for any other error, if we have origin isolation, attempt to read redirects file and apply any redirect rules, regardless of force. - // Fallback to pretty 404 page, and then normal 404 + return resolvedPath, contentPath, true + } + + // No forced redirect. Attempt to resolve the provided path. + resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) switch err { case nil: - // TODO: I believe for the force option, we might need to short circuit this, and thus we would need to read the redirects file first return resolvedPath, contentPath, true case coreiface.ErrOffline: webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable) return nil, nil, false default: - // If we can't resolve the path - // Only look for _redirects file if we have Unixfs and Origin isolation - if hasOriginIsolation(r) { + // If we can't resolve the path, look for matching _redirects rules and process them + if mustEvaluateRedirectRules { // Check for _redirects file and redirect as needed // /ipfs/CID/a/b/c/ // /ipfs/CID/_redirects // /ipns/domain/ipfs/CID // /ipns/domain logger.Debugf("r.URL.Path=%v", r.URL.Path) - redirectsFile, err := i.getRedirectsFile(r) + redirected, newPath, err := i.handleNonForcedRedirect(w, r, redirectRules, logger) if err != nil { - switch err.(type) { - case resolver.ErrNoLink: - // _redirects files doesn't exist, so don't error - // case coreiface.ErrResolveFailed.(type): - // How to get type? - // // Couldn't resolve ipns name when trying to compute root - // Tests indicate we should return 404, not 500 - // internalWebError(w, err) - // return nil, nil, false - default: - // Let users know about issues with _redirects file handling - internalWebError(w, err) - return nil, nil, false - } - } else { - // _redirects file exists, so parse it and redirect - redirected, newPath, err := i.handleRedirectsFile(w, r, redirectsFile, logger) + err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) + internalWebError(w, err) + return nil, nil, false + } + + if redirected { + return nil, nil, false + } + + // 200 is treated as a rewrite, so update the path and continue + if newPath != "" { + // Reassign contentPath and resolvedPath since the URL was rewritten + contentPath = ipath.New(newPath) + resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath) if err != nil { - err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) internalWebError(w, err) return nil, nil, false } + logger.Debugf("_redirects: 200 rewrite. newPath=%v", newPath) - if redirected { - return nil, nil, false - } - - // 200 is treated as a rewrite, so update the path and continue - if newPath != "" { - // Reassign contentPath and resolvedPath since the URL was rewritten - contentPath = ipath.New(newPath) - resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath) - if err != nil { - internalWebError(w, err) - return nil, nil, false - } - logger.Debugf("_redirects: 200 rewrite. newPath=%v", newPath) - - return resolvedPath, contentPath, true - } + return resolvedPath, contentPath, true } } @@ -126,25 +159,15 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht } } -func (i *gatewayHandler) handleRedirectsFile(w http.ResponseWriter, r *http.Request, redirectsFilePath ipath.Resolved, logger *zap.SugaredLogger) (bool, string, error) { - // Convert the path into a file node - node, err := i.api.Unixfs().Get(r.Context(), redirectsFilePath) - if err != nil { - return false, "", fmt.Errorf("could not get _redirects node: %v", err) - } - defer node.Close() +func (i *gatewayHandler) handleForcedRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule, logger *zap.SugaredLogger) (bool, string, error) { + return i.handleRedirectsFile(w, r, redirectRules, true, logger) +} - // Convert the node into a file - f, ok := node.(files.File) - if !ok { - return false, "", fmt.Errorf("could not convert _redirects node to file") - } +func (i *gatewayHandler) handleNonForcedRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule, logger *zap.SugaredLogger) (bool, string, error) { + return i.handleRedirectsFile(w, r, redirectRules, false, logger) +} - // Parse redirect rules from file - redirectRules, err := redirects.Parse(f) - if err != nil { - return false, "", fmt.Errorf("could not parse redirect rules: %v", err) - } +func (i *gatewayHandler) handleRedirectsFile(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule, onlyForce bool, logger *zap.SugaredLogger) (bool, string, error) { logger.Debugf("redirectRules=%v", redirectRules) // Attempt to match a rule to the URL path, and perform the corresponding redirect or rewrite @@ -158,6 +181,11 @@ func (i *gatewayHandler) handleRedirectsFile(w http.ResponseWriter, r *http.Requ logger.Debugf("_redirects: urlPath=", urlPath) for _, rule := range redirectRules { + if onlyForce && !rule.Force { + logger.Debugf("_redirects: skipping non-forced rule: %v", rule) + continue + } + // get rule.From, trim trailing slash, ... fromPath := urlpath.New(strings.TrimSuffix(rule.From, "/")) logger.Debugf("_redirects: fromPath=%v", strings.TrimSuffix(rule.From, "/")) @@ -184,7 +212,7 @@ func (i *gatewayHandler) handleRedirectsFile(w http.ResponseWriter, r *http.Requ if rule.Status == 404 { toPath = rootPath + rule.To content404Path := ipath.New(toPath) - err = i.serve404(w, r, content404Path) + err := i.serve404(w, r, content404Path) return true, toPath, err } @@ -212,6 +240,29 @@ func replaceSplat(to string, match urlpath.Match) string { return strings.ReplaceAll(to, ":splat", match.Trailing) } +func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipath.Resolved) ([]redirects.Rule, error) { + // Convert the path into a file node + node, err := i.api.Unixfs().Get(r.Context(), redirectsFilePath) + if err != nil { + return nil, fmt.Errorf("could not get _redirects node: %v", err) + } + defer node.Close() + + // Convert the node into a file + f, ok := node.(files.File) + if !ok { + return nil, fmt.Errorf("could not convert _redirects node to file") + } + + // Parse redirect rules from file + redirectRules, err := redirects.Parse(f) + if err != nil { + return nil, fmt.Errorf("could not parse redirect rules: %v", err) + } + + return redirectRules, nil +} + // Returns a resolved path to the _redirects file located in the root CID path of the requested path func (i *gatewayHandler) getRedirectsFile(r *http.Request) (ipath.Resolved, error) { // r.URL.Path is the full ipfs path to the requested resource, @@ -224,6 +275,7 @@ func (i *gatewayHandler) getRedirectsFile(r *http.Request) (ipath.Resolved, erro path := ipath.Join(rootPath, "_redirects") resolvedPath, err := i.api.ResolvePath(r.Context(), path) if err != nil { + // TODO(JJ): should this error be wrapped for more context? return nil, err } return resolvedPath, nil From 7145efcdfb3c4509d2766637935e4ee52db2ddad Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 20 Apr 2022 11:46:52 -0500 Subject: [PATCH 03/54] Return 404 where we did previously --- .../gateway_handler_unixfs__redirects.go | 58 +++++++------------ 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index d777dcfc31a..f68bbc61f76 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -20,43 +20,37 @@ import ( // Resolve the provided path. // -// Resolving a UnixFS path involves determining if the provided `path.Path` exists and returning the `path.Resolved` corresponding to that path. -// For UnixFS, path resolution is more involved if a `_redirects`` file exists, stored underneath the root CID of the path. +// Resolving a UnixFS path involves determining if the provided `path.Path` exists and returning the `path.Resolved` +// corresponding to that path. For UnixFS, path resolution is more involved if a `_redirects`` file exists, stored +// underneath the root CID of the path. // // Example 1: // If a path exists, usually we should return the `path.Resolved` corresponding to that path. -// However, the `_redirects` file may contain a forced redirect rule corresponding to the path (i.e. a rule with a `!` after the status code). -// Forced redirect rules must be evaluated, even if the path exists, thus overriding the path. +// However, the `_redirects` file may contain a forced redirect rule corresponding to the path +// (i.e. a rule with a `!` after the status code). Forced redirect rules must be evaluated, +// even if the path exists, thus overriding the path. // // Example 2: -// If a path does not exist, usually we should return a `nil` resolution path and an error indicating that the path doesn't exist. -// However, the `_redirects` file may contain a redirect rule that redirects that path to a different path. +// If a path does not exist, usually we should return a `nil` resolution path and an error indicating that the path +// doesn't exist. However, the `_redirects` file may contain a redirect rule that redirects that path to a different path. // We need to evaluate the rule and perform the redirect if present. // // Example 3: // Another possibility is that the path corresponds to a rewrite rule (i.e. a rule with a status of 200). -// In this case, we don't perform a redirect, but do need to return a `path.Resolved` and `path.Path` corresponding to the rewrite destination path. +// In this case, we don't perform a redirect, but do need to return a `path.Resolved` and `path.Path` corresponding to +// the rewrite destination path. // // Note that for security reasons, redirect rules are only processed when the request has origin isolation. -// -// if redirectRules, err = i.getRedirectRules(r, redirectsFile); redirectRules != nil { -// redirected, newPath, err := i.handleRedirectsFile(w, r, redirectsFile, true, logger) func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { // Before we do anything, determine if we must evaluate redirect rules redirectsFile, err := i.getRedirectsFile(r) if err != nil { switch err.(type) { case resolver.ErrNoLink: - // _redirects files doesn't exist, so don't error - // case coreiface.ErrResolveFailed.(type): - // TODO(JJ): How to get type? - // // Couldn't resolve ipns name when trying to compute root - // Tests indicate we should return 404, not 500 - // internalWebError(w, err) - // return nil, nil, false + // The _redirects files doesn't exist. Don't error since its existence is optional. default: - // Let users know about issues with _redirects file handling - internalWebError(w, err) + // Any other path resolution issues should be treated as 404 + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusNotFound) return nil, nil, false } } @@ -112,14 +106,9 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht default: // If we can't resolve the path, look for matching _redirects rules and process them if mustEvaluateRedirectRules { - // Check for _redirects file and redirect as needed - // /ipfs/CID/a/b/c/ - // /ipfs/CID/_redirects - // /ipns/domain/ipfs/CID - // /ipns/domain logger.Debugf("r.URL.Path=%v", r.URL.Path) - redirected, newPath, err := i.handleNonForcedRedirect(w, r, redirectRules, logger) + redirected, newPath, err := i.handleRedirect(w, r, redirectRules, logger) if err != nil { err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) internalWebError(w, err) @@ -160,14 +149,17 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht } func (i *gatewayHandler) handleForcedRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule, logger *zap.SugaredLogger) (bool, string, error) { - return i.handleRedirectsFile(w, r, redirectRules, true, logger) -} + forcedRedirectRules := []redirects.Rule{} + for _, rule := range redirectRules { + if rule.Force { + forcedRedirectRules = append(forcedRedirectRules, rule) + } + } -func (i *gatewayHandler) handleNonForcedRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule, logger *zap.SugaredLogger) (bool, string, error) { - return i.handleRedirectsFile(w, r, redirectRules, false, logger) + return i.handleRedirect(w, r, forcedRedirectRules, logger) } -func (i *gatewayHandler) handleRedirectsFile(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule, onlyForce bool, logger *zap.SugaredLogger) (bool, string, error) { +func (i *gatewayHandler) handleRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule, logger *zap.SugaredLogger) (bool, string, error) { logger.Debugf("redirectRules=%v", redirectRules) // Attempt to match a rule to the URL path, and perform the corresponding redirect or rewrite @@ -181,11 +173,6 @@ func (i *gatewayHandler) handleRedirectsFile(w http.ResponseWriter, r *http.Requ logger.Debugf("_redirects: urlPath=", urlPath) for _, rule := range redirectRules { - if onlyForce && !rule.Force { - logger.Debugf("_redirects: skipping non-forced rule: %v", rule) - continue - } - // get rule.From, trim trailing slash, ... fromPath := urlpath.New(strings.TrimSuffix(rule.From, "/")) logger.Debugf("_redirects: fromPath=%v", strings.TrimSuffix(rule.From, "/")) @@ -275,7 +262,6 @@ func (i *gatewayHandler) getRedirectsFile(r *http.Request) (ipath.Resolved, erro path := ipath.Join(rootPath, "_redirects") resolvedPath, err := i.api.ResolvePath(r.Context(), path) if err != nil { - // TODO(JJ): should this error be wrapped for more context? return nil, err } return resolvedPath, nil From 7f130474354ffec3a758c9dfd3312eb35cea0133 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 20 Apr 2022 11:52:07 -0500 Subject: [PATCH 04/54] Remove go.mod replace --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 21a96e80dac..9e394de0a60 100644 --- a/go.mod +++ b/go.mod @@ -227,6 +227,7 @@ require ( github.com/tidwall/gjson v1.14.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37 // indirect github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2 // indirect github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect diff --git a/go.sum b/go.sum index c59ab8e3f54..31c7e98a938 100644 --- a/go.sum +++ b/go.sum @@ -1514,6 +1514,8 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= +github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37 h1:K11tjwz8zTTSZkz4TUjfLN+y8uJWP38BbyPqZ2yB/Yk= +github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37/go.mod h1:E0E2H2gQA+uoi27VCSU+a/BULPtadQA78q3cpTjZbZw= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= From 748a6ea2e97752a057366210ed5e3a4962879254 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 20 Apr 2022 12:09:37 -0500 Subject: [PATCH 05/54] Remove log statements based on CodeQL results --- core/corehttp/gateway_handler.go | 4 ++-- .../gateway_handler_unixfs__redirects.go | 20 +++++-------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 95492e10a3c..defa228a864 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -393,7 +393,7 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request resolvedPath, contentPath, ok = i.handleUnixfsPathResolution(w, r, contentPath, logger) } else { // TODO(JJ): Update function signature re: err, bool returning, per clean up PR feedback - resolvedPath, contentPath, ok = i.handleNonUnixfsPathResolution(w, r, contentPath, logger) + resolvedPath, contentPath, ok = i.handleNonUnixfsPathResolution(w, r, contentPath) } if !ok { return @@ -1113,7 +1113,7 @@ func (i *gatewayHandler) setCommonHeaders(w http.ResponseWriter, r *http.Request return nil } -func (i *gatewayHandler) handleNonUnixfsPathResolution(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { +func (i *gatewayHandler) handleNonUnixfsPathResolution(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) (ipath.Resolved, ipath.Path, bool) { // Resolve the path for the provided contentPath resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index f68bbc61f76..a06de4f4bd4 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -69,7 +69,7 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht // If we must evaluate redirect rules and there is a forced redirect for our path, there's no point in attempting to resolve the path, // since we will have to redirect regardless of whether or not the path exists. Just handle the forced redirect instead. - redirected, newPath, err := i.handleForcedRedirect(w, r, redirectRules, logger) + redirected, newPath, err := i.handleForcedRedirect(w, r, redirectRules) if err != nil { err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) internalWebError(w, err) @@ -89,7 +89,6 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht internalWebError(w, err) return nil, nil, false } - logger.Debugf("_redirects: 200 rewrite. newPath=%v", newPath) return resolvedPath, contentPath, true } @@ -106,9 +105,7 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht default: // If we can't resolve the path, look for matching _redirects rules and process them if mustEvaluateRedirectRules { - logger.Debugf("r.URL.Path=%v", r.URL.Path) - - redirected, newPath, err := i.handleRedirect(w, r, redirectRules, logger) + redirected, newPath, err := i.handleRedirect(w, r, redirectRules) if err != nil { err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) internalWebError(w, err) @@ -128,7 +125,6 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht internalWebError(w, err) return nil, nil, false } - logger.Debugf("_redirects: 200 rewrite. newPath=%v", newPath) return resolvedPath, contentPath, true } @@ -148,7 +144,7 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht } } -func (i *gatewayHandler) handleForcedRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule, logger *zap.SugaredLogger) (bool, string, error) { +func (i *gatewayHandler) handleForcedRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule) (bool, string, error) { forcedRedirectRules := []redirects.Rule{} for _, rule := range redirectRules { if rule.Force { @@ -156,12 +152,10 @@ func (i *gatewayHandler) handleForcedRedirect(w http.ResponseWriter, r *http.Req } } - return i.handleRedirect(w, r, forcedRedirectRules, logger) + return i.handleRedirect(w, r, forcedRedirectRules) } -func (i *gatewayHandler) handleRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule, logger *zap.SugaredLogger) (bool, string, error) { - logger.Debugf("redirectRules=%v", redirectRules) - +func (i *gatewayHandler) handleRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule) (bool, string, error) { // Attempt to match a rule to the URL path, and perform the corresponding redirect or rewrite pathParts := strings.Split(r.URL.Path, "/") if len(pathParts) > 3 { @@ -171,11 +165,9 @@ func (i *gatewayHandler) handleRedirect(w http.ResponseWriter, r *http.Request, // Trim off the trailing / urlPath = strings.TrimSuffix(urlPath, "/") - logger.Debugf("_redirects: urlPath=", urlPath) for _, rule := range redirectRules { // get rule.From, trim trailing slash, ... fromPath := urlpath.New(strings.TrimSuffix(rule.From, "/")) - logger.Debugf("_redirects: fromPath=%v", strings.TrimSuffix(rule.From, "/")) match, ok := fromPath.Match(urlPath) if !ok { continue @@ -186,8 +178,6 @@ func (i *gatewayHandler) handleRedirect(w http.ResponseWriter, r *http.Request, toPath = replacePlaceholders(toPath, match) toPath = replaceSplat(toPath, match) - logger.Debugf("_redirects: toPath=%v", toPath) - // Rewrite if rule.Status == 200 { // Prepend the rootPath From ab87d10667c70af74fd2f7bf642c54b63a15a47c Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 20 Apr 2022 13:00:29 -0500 Subject: [PATCH 06/54] Add missing test_kill_ipfs_daemon to sharness --- test/sharness/t0109-gateway-web-_redirects.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index bb6dcb6f90a..7324ffa1a63 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -124,4 +124,6 @@ test_expect_success "spoofed DNSLink record resolves in cli" " test_should_contain \"index.html\" result " +test_kill_ipfs_daemon + test_done From acd5d1ae32bf938986f0985e3b418cd0d94b8748 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 21 Apr 2022 12:53:57 -0500 Subject: [PATCH 07/54] Deps changes --- core/corehttp/gateway_handler_unixfs__redirects.go | 2 +- go.mod | 2 +- go.sum | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index a06de4f4bd4..a042dbe0d49 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -9,11 +9,11 @@ import ( "strconv" "strings" - "github.com/fission-suite/go-redirects" files "github.com/ipfs/go-ipfs-files" "github.com/ipfs/go-path/resolver" coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" + "github.com/tj/go-redirects" "github.com/ucarion/urlpath" "go.uber.org/zap" ) diff --git a/go.mod b/go.mod index 9e394de0a60..ee9bc6133f9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/elgris/jsondiff v0.0.0-20160530203242-765b5c24c302 github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 - github.com/fission-suite/go-redirects v0.0.0-20220412202828-a86b2398567d github.com/fsnotify/fsnotify v1.5.1 github.com/gabriel-vasile/mimetype v1.4.0 github.com/hashicorp/go-multierror v1.1.1 @@ -171,6 +170,7 @@ require ( github.com/ipfs/go-peertaskqueue v0.7.1 // indirect github.com/ipld/edelweiss v0.2.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect + github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018 // indirect github.com/klauspost/compress v1.15.10 // indirect github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/koron/go-ssdp v0.0.3 // indirect diff --git a/go.sum b/go.sum index 31c7e98a938..9822261c76a 100644 --- a/go.sum +++ b/go.sum @@ -713,6 +713,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018 h1:/Jt0uTnq8V6hN9x2UJH8ehnH81Xf+PLSgfU7Pp+ZBzY= +github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018/go.mod h1:H4KjwG5DGXkAM+qu9w44kUYvS3FHKhjgTgX+bIdtcn0= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= From 8958efec805427d6357eb0afdf64de3721b4e5a7 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 27 Apr 2022 08:55:37 -0500 Subject: [PATCH 08/54] Comment cleanup --- core/corehttp/gateway_handler.go | 2 -- core/corehttp/gateway_handler_unixfs__redirects.go | 7 ------- 2 files changed, 9 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index defa228a864..a628eb0714b 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -389,10 +389,8 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request var ok bool var resolvedPath ipath.Resolved if isUnixfsResponseFormat(responseFormat) { - // TODO(JJ): Update function signature re: err, bool returning, per clean up PR feedback resolvedPath, contentPath, ok = i.handleUnixfsPathResolution(w, r, contentPath, logger) } else { - // TODO(JJ): Update function signature re: err, bool returning, per clean up PR feedback resolvedPath, contentPath, ok = i.handleNonUnixfsPathResolution(w, r, contentPath) } if !ok { diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index a042dbe0d49..80977ce9d5d 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -258,12 +258,6 @@ func (i *gatewayHandler) getRedirectsFile(r *http.Request) (ipath.Resolved, erro } // Returns the root CID Path for the given path -// /ipfs/CID/* -// CID is the root CID -// /ipns/domain/* -// Need to resolve domain ipns path to get CID -// /ipns/domain/ipfs/CID -// Is this legit? If so, we should use CID? func getRootPath(path string) (ipath.Path, error) { if isIpfsPath(path) { parts := strings.Split(path, "/") @@ -308,7 +302,6 @@ func (i *gatewayHandler) serve404(w http.ResponseWriter, r *http.Request, conten return err } -// TODO(JJ): Confirm approach func hasOriginIsolation(r *http.Request) bool { _, gw := r.Context().Value("gw-hostname").(string) _, dnslink := r.Context().Value("dnslink-hostname").(string) From f79b4b4527198a52381bae18c64477eb53a158b3 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 27 Apr 2022 13:22:00 -0500 Subject: [PATCH 09/54] Any path resolution errors mean the file doesn't exist --- .../gateway_handler_unixfs__redirects.go | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 80977ce9d5d..ca94b8d66e7 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -10,7 +10,6 @@ import ( "strings" files "github.com/ipfs/go-ipfs-files" - "github.com/ipfs/go-path/resolver" coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" "github.com/tj/go-redirects" @@ -43,22 +42,13 @@ import ( // Note that for security reasons, redirect rules are only processed when the request has origin isolation. func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { // Before we do anything, determine if we must evaluate redirect rules - redirectsFile, err := i.getRedirectsFile(r) - if err != nil { - switch err.(type) { - case resolver.ErrNoLink: - // The _redirects files doesn't exist. Don't error since its existence is optional. - default: - // Any other path resolution issues should be treated as 404 - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusNotFound) - return nil, nil, false - } - } + redirectsFile := i.getRedirectsFile(r, logger) mustEvaluateRedirectRules := hasOriginIsolation(r) && redirectsFile != nil // Get redirect rules if we know we'll need them var redirectRules []redirects.Rule + var err error if mustEvaluateRedirectRules { redirectRules, err = i.getRedirectRules(r, redirectsFile) if err != nil { @@ -241,20 +231,26 @@ func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipa } // Returns a resolved path to the _redirects file located in the root CID path of the requested path -func (i *gatewayHandler) getRedirectsFile(r *http.Request) (ipath.Resolved, error) { +func (i *gatewayHandler) getRedirectsFile(r *http.Request, logger *zap.SugaredLogger) ipath.Resolved { // r.URL.Path is the full ipfs path to the requested resource, // regardless of whether path or subdomain resolution is used. rootPath, err := getRootPath(r.URL.Path) if err != nil { - return nil, err + logger.Debugf("getRootPath failed", err) + return nil } + logger.Debugf("rootPath type=%t", rootPath) path := ipath.Join(rootPath, "_redirects") resolvedPath, err := i.api.ResolvePath(r.Context(), path) + if err != nil { - return nil, err + // Any path resolution failures are ignored and we just assume there's no _redirects file. + // Note that ignoring these errors also ensures that the use of the empty CID (bafkqaaa) in tests doesn't fail. + logger.Debugf("ResolvePath failed. rootPath=%v, err=%v", rootPath, err) + return nil } - return resolvedPath, nil + return resolvedPath } // Returns the root CID Path for the given path From 99720d28198264ff6ff478541b397b6a42ec11da Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 28 Apr 2022 13:27:37 -0500 Subject: [PATCH 10/54] WIP comments --- core/corehttp/gateway_handler.go | 2 ++ core/corehttp/gateway_handler_unixfs__redirects.go | 1 + 2 files changed, 3 insertions(+) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index a628eb0714b..2a7ecdf7ddb 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -412,7 +412,9 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request } } + // TODO(JJ): With the UnixFS redirects handling above, this is no longer the first block if err := i.handleGettingFirstBlock(r, begin, contentPath, resolvedPath); err != nil { + // TODO(JJ): This is where the error comes from when requesting a non-existing CID hangs and eventually returns with an error webRequestError(w, err) return } diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index ca94b8d66e7..bae347ec704 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -242,6 +242,7 @@ func (i *gatewayHandler) getRedirectsFile(r *http.Request, logger *zap.SugaredLo logger.Debugf("rootPath type=%t", rootPath) path := ipath.Join(rootPath, "_redirects") + // TODO(JJ): When requesting a non-existent CID with NoFetch=true, this path resolution can take about a minute (on my slow internet). No similar hang for master. Why? resolvedPath, err := i.api.ResolvePath(r.Context(), path) if err != nil { From fc617bf9ffa9ecd031b08dc0b664582e931bc5db Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 28 Apr 2022 16:40:24 -0500 Subject: [PATCH 11/54] Check for root path CID before joining with _redirects --- .../corehttp/gateway_handler_unixfs__redirects.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index bae347ec704..22577a015e4 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -240,15 +240,18 @@ func (i *gatewayHandler) getRedirectsFile(r *http.Request, logger *zap.SugaredLo return nil } - logger.Debugf("rootPath type=%t", rootPath) + // Check for root path. + _, err = i.api.Block().Get(r.Context(), rootPath) + if err != nil { + return nil + } + + // Check for _redirects file. + // Any path resolution failures are ignored and we just assume there's no _redirects file. + // Note that ignoring these errors also ensures that the use of the empty CID (bafkqaaa) in tests doesn't fail. path := ipath.Join(rootPath, "_redirects") - // TODO(JJ): When requesting a non-existent CID with NoFetch=true, this path resolution can take about a minute (on my slow internet). No similar hang for master. Why? resolvedPath, err := i.api.ResolvePath(r.Context(), path) - if err != nil { - // Any path resolution failures are ignored and we just assume there's no _redirects file. - // Note that ignoring these errors also ensures that the use of the empty CID (bafkqaaa) in tests doesn't fail. - logger.Debugf("ResolvePath failed. rootPath=%v, err=%v", rootPath, err) return nil } return resolvedPath From 1f867138fe49778f91628dcbe971c8d446f5fb85 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 29 Apr 2022 14:35:56 -0500 Subject: [PATCH 12/54] DNSLink test --- test/sharness/t0109-gateway-web-_redirects.sh | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 7324ffa1a63..204e2289298 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -87,7 +87,7 @@ test_expect_success "request for http://127.0.0.1:$GWAY_PORT/ipfs/$REDIRECTS_DIR test_kill_ipfs_daemon # disable wildcard DNSLink gateway -# and enable it on specific NSLink hostname +# and enable it on specific DNSLink hostname ipfs config --json Gateway.NoDNSLink true && \ ipfs config --json Gateway.PublicGateways '{ "dnslink-enabled-on-fqdn.example.org": { @@ -95,11 +95,6 @@ ipfs config --json Gateway.PublicGateways '{ "UseSubdomains": false, "Paths": ["/ipfs"] }, - "only-dnslink-enabled-on-fqdn.example.org": { - "NoDNSLink": false, - "UseSubdomains": false, - "Paths": [] - }, "dnslink-disabled-on-fqdn.example.com": { "NoDNSLink": true, "UseSubdomains": false, @@ -109,9 +104,8 @@ ipfs config --json Gateway.PublicGateways '{ # DNSLink test requires a daemon in online mode with precached /ipns/ mapping DNSLINK_FQDN="dnslink-enabled-on-fqdn.example.org" -ONLY_DNSLINK_FQDN="only-dnslink-enabled-on-fqdn.example.org" NO_DNSLINK_FQDN="dnslink-disabled-on-fqdn.example.com" -export IPFS_NS_MAP="$DNSLINK_FQDN:/ipfs/$REDIRECTS_DIR_CID,$ONLY_DNSLINK_FQDN:/ipfs/$REDIRECTS_DIR_CID" +export IPFS_NS_MAP="$DNSLINK_FQDN:/ipfs/$REDIRECTS_DIR_CID" # restart daemon to apply config changes test_launch_ipfs_daemon @@ -124,6 +118,18 @@ test_expect_success "spoofed DNSLink record resolves in cli" " test_should_contain \"index.html\" result " +test_expect_success "request for $DNSLINK_FQDN/redirect-one redirects with default of 301, per _redirects file" ' + curl -sD - --resolve $DNSLINK_FQDN:$GWAY_PORT:127.0.0.1 "http://$DNSLINK_FQDN:$GWAY_PORT/redirect-one" > response && + test_should_contain "one.html" response && + test_should_contain "301 Moved Permanently" response +' + +test_expect_success "request for $NO_DNSLINK_FQDN/redirect-one does not redirect, since DNSLink is disabled" ' + curl -sD - --resolve $NO_DNSLINK_FQDN:$GWAY_PORT:127.0.0.1 "http://$NO_DNSLINK_FQDN:$GWAY_PORT/redirect-one" > response && + test_should_not_contain "one.html" response && + test_should_not_contain "301 Moved Permanently" response +' + test_kill_ipfs_daemon test_done From 5d59ce9e62ac78a7c181252e4ae1c6482e56c20f Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 29 Apr 2022 14:42:40 -0500 Subject: [PATCH 13/54] Comments --- core/corehttp/gateway_handler.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 2a7ecdf7ddb..98087a10210 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -412,9 +412,8 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request } } - // TODO(JJ): With the UnixFS redirects handling above, this is no longer the first block + // TODO(JJ): Should this be somewhere else for UnixFS, given the redirects handling above? if err := i.handleGettingFirstBlock(r, begin, contentPath, resolvedPath); err != nil { - // TODO(JJ): This is where the error comes from when requesting a non-existing CID hangs and eventually returns with an error webRequestError(w, err) return } From 3cfb84ebb75c8431eb39d53039be7173c828ae0b Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 25 May 2022 07:46:26 -0500 Subject: [PATCH 14/54] Fix placeholder and splat usage for 200 and 404 --- core/corehttp/gateway_handler_unixfs__redirects.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 22577a015e4..3a13b264ba3 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -171,13 +171,13 @@ func (i *gatewayHandler) handleRedirect(w http.ResponseWriter, r *http.Request, // Rewrite if rule.Status == 200 { // Prepend the rootPath - toPath = rootPath + rule.To + toPath = rootPath + toPath return false, toPath, nil } // Or 404 if rule.Status == 404 { - toPath = rootPath + rule.To + toPath = rootPath + toPath content404Path := ipath.New(toPath) err := i.serve404(w, r, content404Path) return true, toPath, err From 56db72e925946a0386fa8450d6725e481fd8aff2 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 25 May 2022 07:52:03 -0500 Subject: [PATCH 15/54] go mod tidy --- go.mod | 8 +++++--- go.sum | 2 -- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index ee9bc6133f9..4542712d5f2 100644 --- a/go.mod +++ b/go.mod @@ -121,6 +121,8 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 ) +require github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37 + require ( github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/Kubuxu/go-os-helper v0.0.1 // indirect @@ -171,8 +173,8 @@ require ( github.com/ipld/edelweiss v0.2.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018 // indirect - github.com/klauspost/compress v1.15.10 // indirect - github.com/klauspost/cpuid/v2 v2.1.1 // indirect + github.com/klauspost/compress v1.15.1 // indirect + github.com/klauspost/cpuid/v2 v2.1.0 // indirect github.com/koron/go-ssdp v0.0.3 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect @@ -227,7 +229,7 @@ require ( github.com/tidwall/gjson v1.14.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect - github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37 // indirect + github.com/tj/assert v0.0.3 // indirect github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2 // indirect github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect diff --git a/go.sum b/go.sum index 9822261c76a..31c7e98a938 100644 --- a/go.sum +++ b/go.sum @@ -713,8 +713,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018 h1:/Jt0uTnq8V6hN9x2UJH8ehnH81Xf+PLSgfU7Pp+ZBzY= -github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018/go.mod h1:H4KjwG5DGXkAM+qu9w44kUYvS3FHKhjgTgX+bIdtcn0= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= From 0e2a3a8329362fb9cd2c1ddbac2a330342003a58 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 15 Jun 2022 11:57:56 -0500 Subject: [PATCH 16/54] Move test case to car file --- .../redirects.car | Bin 0 -> 1061 bytes test/sharness/t0109-gateway-web-_redirects.sh | 22 ++++-------------- 2 files changed, 4 insertions(+), 18 deletions(-) create mode 100644 test/sharness/t0109-gateway-web-_redirects-data/redirects.car diff --git a/test/sharness/t0109-gateway-web-_redirects-data/redirects.car b/test/sharness/t0109-gateway-web-_redirects-data/redirects.car new file mode 100644 index 0000000000000000000000000000000000000000..02c520a6294cbc8ba22fa515b6e21672a27c0582 GIT binary patch literal 1061 zcmcColvzY z9xaXLpL$=2!^FTuFQX(kM}l3*6rv|$Li~^E9hW7yADkHx5Z#&kdB>scf6J#vf6SUK z%e3Ry5+SbmqSTbkqSWM)Vu>CmArpwUaFvZq67`sV+>5pi-2X^r{-M9$9`x+B;7``z ztLfB=H5TH^%u7kFfLO)}b6|$V^nIJ=Ki|^-n2TBYc^SvQXQ_pD)jHeeu**lBe`H=F z#F3wu3Ui!8&*93V8TCbMcMc6C8iq>3Mq#N)fPs4e%>nmv`t6==DySv z-Qt3r#1aW#n0?_k1{>|9;uc<3U;MwmOXd88@;SwQ_ZK`)5xDSe*=`$7A&!#re5id~ zOdO2b#DrpQr2;S*xi&C?6cY+QeVF5Pfj(2vhk1`n-`K!V7rUGSPzoe(gpdciQb8Z) zRG_>Om%fpKfi5_R74#7ytpF6|(l5v_E-BWxs!UBR(znXZ&nwB$w@OK@)VC_hEXe^n zBC)6>GdU*}j}ov8z+tIx1*R0>{(uECOaiPlHBVnlK_8a5z`+9&Ky#QbD8L9M9AM~z zwQ+&tf>441ss;urR}4`J1)57T)`?ur;?~rg#>m!pLvpF=Z^r%nOWvqUwD@=W*F^p9 lL`sCBphRH_ayB7Nu&7K;QGf&($XQ@75K4nUF92hf3jnCaqKg0k literal 0 HcmV?d00001 diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 204e2289298..be30cbb407d 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -11,26 +11,12 @@ test_launch_ipfs_daemon ## Test _redirects file support ## ============================================================================ -# Directory tree crafted to test _redirects file support +# Import test case +# Run `ipfs cat /ipfs/$REDIRECTS_DIR_CID/_redirects` to see sample _redirects file test_expect_success "Add the _redirects file test directory" ' - mkdir -p testredirect/ && - echo "my index" > testredirect/index.html && - echo "my one" > testredirect/one.html && - echo "my two" > testredirect/two.html && - echo "my 404" > testredirect/404.html && - mkdir testredirect/redirected-splat && - echo "redirected splat one" > testredirect/redirected-splat/one.html && - echo "/redirect-one /one.html" > testredirect/_redirects && - echo "/301-redirect-one /one.html 301" >> testredirect/_redirects && - echo "/302-redirect-two /two.html 302" >> testredirect/_redirects && - echo "/200-index /index.html 200" >> testredirect/_redirects && - echo "/posts/:year/:month/:day/:title /articles/:year/:month/:day/:title 301" >> testredirect/_redirects && - echo "/splat/:splat /redirected-splat/:splat 301" >> testredirect/_redirects && - echo "/en/* /404.html 404" >> testredirect/_redirects && - echo "/* /index.html 200" >> testredirect/_redirects && - REDIRECTS_DIR_CID=$(ipfs add -Qr --cid-version 1 testredirect) + ipfs dag import ../t0109-gateway-web-_redirects-data/redirects.car ' - +REDIRECTS_DIR_CID=bafybeigs3wowz6pug7ckfgtwrsrltjjx5disx5pztnucgt4ygryv5w6qy4 REDIRECTS_DIR_HOSTNAME="${REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' From 2e4357bb93c92c829afee2354eb3c7082eae7f19 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 15 Jun 2022 16:25:18 -0500 Subject: [PATCH 17/54] Update CAR file and test after updating CAR file for spec --- .../redirects.car | Bin 1061 -> 1630 bytes test/sharness/t0109-gateway-web-_redirects.sh | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sharness/t0109-gateway-web-_redirects-data/redirects.car b/test/sharness/t0109-gateway-web-_redirects-data/redirects.car index 02c520a6294cbc8ba22fa515b6e21672a27c0582..5d8ebfabc11ef4c271d10226e46d8d386d57a2e2 100644 GIT binary patch literal 1630 zcmcCmlvoDB>07lK{}o&hu<=Kys6S>UX|1TZ`UU8UY@k^`B_o96ZbDo&i*+0 zk`PyXQEEzNQEGBYvBW$kAw!U+E&F55)(dN;A3maUGG%q*=Z(4QTH;yk#}_ap-}xS2 zFCfH`SX7dkoReBCF^fsa2&BdQ(&X*m4;37V$<7wPJom?(Yk?)MS0)FoT97*_$M8wL zr4UzUUP@{O#BG8Q-*DW~@G72T=cB?@>v1W^TxIsG%+L9yQj<9sD&Fhgz``QLk)M|e z^Nj__mYh=lt^(HeaTC8j2~}|UV(=zgFq=bZR-SIOxo>3DmOVlOFwdl>=oS~`B$h}7 zL+pwW-CDlU(prD-g<6B0{0}ZRAE$BDwniQiF@AN`Fm%UGA&!#re5hSqOdO1cgaez4 zmxD=&J-1Q;80K6O><5?#hc?%2CZL|lO!_cq>H>YJpbzsVm%g!qp)Phg1)vm2-UuNN zbg_ay%-KMBBQAX-0|Q-fKr84Y!dn3-%B5eBUtChGZ&jI^Sfp>2o1a&bp>LIvSgCJS zl39`ibObC)aVr740353NR$xj2?hjaS!z92;^YTk{)ACF6QuMVH^kE4T99kd&H1|nN zW(p-D<>`Ww!M)FiQn&Rs(4bIXF zzyDllF|Xq5mec?@1D`uOf_}SLxn2awyj?ANVLs0%Q6cV()SR4r-SYgRoD>NkP#8oB zDG<~J&c<$6JVbULT`wYIwQBo;*rP98US0huFVx!2B)7}8tlNhVB^!%?j5ZTeAf$;) z2$<-3z>ZP?JBmw!$Cz-D!Nm{M#tF<{;FQ88!3oThgo+G!o&;toV4jR1qR0Rjtn0E^ z59_xs;ZJ>Lyj<;V+rIC=Sx>h8-^s4#I`b!^Oy3uzqDU6xYkMIDLRz?_fzB6&W#-fr X1xS_wJ4_Vl6+$Hv+$+Ew4)h8DI(c%? literal 1061 zcmcColvzY z9xaXLpL$=2!^FTuFQX(kM}l3*6rv|$Li~^E9hW7yADkHx5Z#&kdB>scf6J#vf6SUK z%e3Ry5+SbmqSTbkqSWM)Vu>CmArpwUaFvZq67`sV+>5pi-2X^r{-M9$9`x+B;7``z ztLfB=H5TH^%u7kFfLO)}b6|$V^nIJ=Ki|^-n2TBYc^SvQXQ_pD)jHeeu**lBe`H=F z#F3wu3Ui!8&*93V8TCbMcMc6C8iq>3Mq#N)fPs4e%>nmv`t6==DySv z-Qt3r#1aW#n0?_k1{>|9;uc<3U;MwmOXd88@;SwQ_ZK`)5xDSe*=`$7A&!#re5id~ zOdO2b#DrpQr2;S*xi&C?6cY+QeVF5Pfj(2vhk1`n-`K!V7rUGSPzoe(gpdciQb8Z) zRG_>Om%fpKfi5_R74#7ytpF6|(l5v_E-BWxs!UBR(znXZ&nwB$w@OK@)VC_hEXe^n zBC)6>GdU*}j}ov8z+tIx1*R0>{(uECOaiPlHBVnlK_8a5z`+9&Ky#QbD8L9M9AM~z zwQ+&tf>441ss;urR}4`J1)57T)`?ur;?~rg#>m!pLvpF=Z^r%nOWvqUwD@=W*F^p9 lL`sCBphRH_ayB7Nu&7K;QGf&($XQ@75K4nUF92hf3jnCaqKg0k diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index be30cbb407d..1dc8c775289 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -16,7 +16,7 @@ test_launch_ipfs_daemon test_expect_success "Add the _redirects file test directory" ' ipfs dag import ../t0109-gateway-web-_redirects-data/redirects.car ' -REDIRECTS_DIR_CID=bafybeigs3wowz6pug7ckfgtwrsrltjjx5disx5pztnucgt4ygryv5w6qy4 +REDIRECTS_DIR_CID=QmaiAcL7pFedPJXxNJNDVDTUR78We7yBhdLzg151ZMzLCv REDIRECTS_DIR_HOSTNAME="${REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' @@ -56,7 +56,7 @@ test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/splat/one.html redirect ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/en/has-no-redirects-entry returns custom 404, per _redirects file" ' - curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/en/has-no-redirects-entry" > response && + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/not-found/has-no-redirects-entry" > response && test_should_contain "404 Not Found" response ' From 6fcca569cc63254974b92d5bde823f05dc844377 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 16 Jun 2022 09:21:16 -0500 Subject: [PATCH 18/54] go mod tidy --- go.sum | 1 - 1 file changed, 1 deletion(-) diff --git a/go.sum b/go.sum index 31c7e98a938..1b708825692 100644 --- a/go.sum +++ b/go.sum @@ -230,7 +230,6 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fission-suite/go-redirects v0.0.0-20220412202828-a86b2398567d/go.mod h1:ku06PAzEkYterp0eal/H+Qauo+Zsb+2GAYG0PMew4sg= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= From 08ddc0730fa12f0327782cd339469eb16964544e Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 16 Jun 2022 13:34:03 -0500 Subject: [PATCH 19/54] Use type for context.WithValue, per docs --- core/corehttp/gateway_handler_unixfs__redirects.go | 2 +- core/corehttp/gateway_handler_unixfs_dir.go | 2 +- core/corehttp/hostname.go | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 3a13b264ba3..81eaf15a0ca 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -303,7 +303,7 @@ func (i *gatewayHandler) serve404(w http.ResponseWriter, r *http.Request, conten } func hasOriginIsolation(r *http.Request) bool { - _, gw := r.Context().Value("gw-hostname").(string) + _, gw := r.Context().Value(requestContextKey("gw-hostname")).(string) _, dnslink := r.Context().Value("dnslink-hostname").(string) if gw || dnslink { diff --git a/core/corehttp/gateway_handler_unixfs_dir.go b/core/corehttp/gateway_handler_unixfs_dir.go index a6ab7cb55fd..511066f4358 100644 --- a/core/corehttp/gateway_handler_unixfs_dir.go +++ b/core/corehttp/gateway_handler_unixfs_dir.go @@ -185,7 +185,7 @@ func (i *gatewayHandler) serveDirectory(ctx context.Context, w http.ResponseWrit var gwURL string // Get gateway hostname and build gateway URL. - if h, ok := r.Context().Value("gw-hostname").(string); ok { + if h, ok := r.Context().Value(requestContextKey("gw-hostname")).(string); ok { gwURL = "//" + h } else { gwURL = "" diff --git a/core/corehttp/hostname.go b/core/corehttp/hostname.go index c9365d4cf6d..31ce85d4190 100644 --- a/core/corehttp/hostname.go +++ b/core/corehttp/hostname.go @@ -243,6 +243,8 @@ type wildcardHost struct { spec *config.GatewaySpec } +type requestContextKey string + // Extends request context to include hostname of a canonical gateway root // (subdomain root or dnslink fqdn) func withHostnameContext(r *http.Request, hostname string) *http.Request { @@ -251,7 +253,7 @@ func withHostnameContext(r *http.Request, hostname string) *http.Request { // Host header, subdomain gateways have more comples rules (knownSubdomainDetails) // More: https://github.com/ipfs/dir-index-html/issues/42 // nolint: staticcheck // non-backward compatible change - ctx := context.WithValue(r.Context(), "gw-hostname", hostname) + ctx := context.WithValue(r.Context(), requestContextKey("gw-hostname"), hostname) return r.WithContext(ctx) } From babb6adf4676b9a6b070131e855720b975729efa Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 16 Jun 2022 13:50:58 -0500 Subject: [PATCH 20/54] More types --- core/coreapi/name.go | 4 +++- core/corehttp/hostname.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/coreapi/name.go b/core/coreapi/name.go index 3cd9850b409..69dc1137bf0 100644 --- a/core/coreapi/name.go +++ b/core/coreapi/name.go @@ -37,6 +37,8 @@ func (e *ipnsEntry) Value() path.Path { return e.value } +type requestContextKey string + // Publish announces new IPNS name and returns the new IPNS entry. func (api *NameAPI) Publish(ctx context.Context, p path.Path, opts ...caopts.NamePublishOption) (coreiface.IpnsEntry, error) { ctx, span := tracing.Span(ctx, "CoreAPI.NameAPI", "Publish", trace.WithAttributes(attribute.String("path", p.String()))) @@ -76,7 +78,7 @@ func (api *NameAPI) Publish(ctx context.Context, p path.Path, opts ...caopts.Nam if options.TTL != nil { // nolint: staticcheck // non-backward compatible change - ctx = context.WithValue(ctx, "ipns-publish-ttl", *options.TTL) + ctx = context.WithValue(ctx, requestContextKey("ipns-publish-ttl"), *options.TTL) } eol := time.Now().Add(options.ValidTime) diff --git a/core/corehttp/hostname.go b/core/corehttp/hostname.go index 31ce85d4190..5445740e634 100644 --- a/core/corehttp/hostname.go +++ b/core/corehttp/hostname.go @@ -221,7 +221,7 @@ func HostnameOption() ServeOption { if !cfg.Gateway.NoDNSLink && isDNSLinkName(r.Context(), coreAPI, host) { // rewrite path and handle as DNSLink r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path - ctx := context.WithValue(r.Context(), "dnslink-hostname", host) + ctx := context.WithValue(r.Context(), requestContextKey("dnslink-hostname"), host) childMux.ServeHTTP(w, withHostnameContext(r.WithContext(ctx), host)) return } From 6830b1b5621af36bd0417030020b7785db9de149 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 16 Jun 2022 15:09:28 -0500 Subject: [PATCH 21/54] Remove forced redirect support, to avoid the performance hit --- .../gateway_handler_unixfs__redirects.go | 108 +++++------------- 1 file changed, 31 insertions(+), 77 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 81eaf15a0ca..f3a14042cbd 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -24,14 +24,11 @@ import ( // underneath the root CID of the path. // // Example 1: -// If a path exists, usually we should return the `path.Resolved` corresponding to that path. -// However, the `_redirects` file may contain a forced redirect rule corresponding to the path -// (i.e. a rule with a `!` after the status code). Forced redirect rules must be evaluated, -// even if the path exists, thus overriding the path. +// If a path exists, we always return the `path.Resolved` corresponding to that path, regardless of the existence of a `_redirects` file. // // Example 2: // If a path does not exist, usually we should return a `nil` resolution path and an error indicating that the path -// doesn't exist. However, the `_redirects` file may contain a redirect rule that redirects that path to a different path. +// doesn't exist. However, a `_redirects` file may exist and contain a redirect rule that redirects that path to a different path. // We need to evaluate the rule and perform the redirect if present. // // Example 3: @@ -41,49 +38,7 @@ import ( // // Note that for security reasons, redirect rules are only processed when the request has origin isolation. func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { - // Before we do anything, determine if we must evaluate redirect rules - redirectsFile := i.getRedirectsFile(r, logger) - - mustEvaluateRedirectRules := hasOriginIsolation(r) && redirectsFile != nil - - // Get redirect rules if we know we'll need them - var redirectRules []redirects.Rule - var err error - if mustEvaluateRedirectRules { - redirectRules, err = i.getRedirectRules(r, redirectsFile) - if err != nil { - internalWebError(w, err) - return nil, nil, false - } - } - - // If we must evaluate redirect rules and there is a forced redirect for our path, there's no point in attempting to resolve the path, - // since we will have to redirect regardless of whether or not the path exists. Just handle the forced redirect instead. - redirected, newPath, err := i.handleForcedRedirect(w, r, redirectRules) - if err != nil { - err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) - internalWebError(w, err) - return nil, nil, false - } - - if redirected { - return nil, nil, false - } - - // 200 is treated as a rewrite, so update the path and continue - if newPath != "" { - // Reassign contentPath and resolvedPath since the URL was rewritten - contentPath = ipath.New(newPath) - resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) - if err != nil { - internalWebError(w, err) - return nil, nil, false - } - - return resolvedPath, contentPath, true - } - - // No forced redirect. Attempt to resolve the provided path. + // Attempt to resolve the provided path. resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) switch err { @@ -93,30 +48,40 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable) return nil, nil, false default: - // If we can't resolve the path, look for matching _redirects rules and process them - if mustEvaluateRedirectRules { - redirected, newPath, err := i.handleRedirect(w, r, redirectRules) - if err != nil { - err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) - internalWebError(w, err) - return nil, nil, false - } - - if redirected { - return nil, nil, false - } + // The path can't be resolved. + // If we have origin isolation, attempt to handle any redirect rules. + if hasOriginIsolation(r) { + redirectsFile := i.getRedirectsFile(r, logger) + if redirectsFile != nil { + redirectRules, err := i.getRedirectRules(r, redirectsFile) + if err != nil { + internalWebError(w, err) + return nil, nil, false + } - // 200 is treated as a rewrite, so update the path and continue - if newPath != "" { - // Reassign contentPath and resolvedPath since the URL was rewritten - contentPath = ipath.New(newPath) - resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath) + redirected, newPath, err := i.handleRedirect(w, r, redirectRules) if err != nil { + err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) internalWebError(w, err) return nil, nil, false } - return resolvedPath, contentPath, true + if redirected { + return nil, nil, false + } + + // 200 is treated as a rewrite, so update the path and continue + if newPath != "" { + // Reassign contentPath and resolvedPath since the URL was rewritten + contentPath = ipath.New(newPath) + resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath) + if err != nil { + internalWebError(w, err) + return nil, nil, false + } + + return resolvedPath, contentPath, true + } } } @@ -134,17 +99,6 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht } } -func (i *gatewayHandler) handleForcedRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule) (bool, string, error) { - forcedRedirectRules := []redirects.Rule{} - for _, rule := range redirectRules { - if rule.Force { - forcedRedirectRules = append(forcedRedirectRules, rule) - } - } - - return i.handleRedirect(w, r, forcedRedirectRules) -} - func (i *gatewayHandler) handleRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule) (bool, string, error) { // Attempt to match a rule to the URL path, and perform the corresponding redirect or rewrite pathParts := strings.Split(r.URL.Path, "/") From 4d9bddf1669572be3b35b55ff77b26c957ef97b9 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 7 Jul 2022 13:03:57 -0500 Subject: [PATCH 22/54] Use justincjohnson/go-ipfs-redirects, which I'll switch to the ipfs org once officially moved. --- core/corehttp/gateway_handler_unixfs__redirects.go | 2 +- go.mod | 3 +-- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index f3a14042cbd..2d57e9ae589 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -12,7 +12,7 @@ import ( files "github.com/ipfs/go-ipfs-files" coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" - "github.com/tj/go-redirects" + redirects "github.com/justincjohnson/go-ipfs-redirects" "github.com/ucarion/urlpath" "go.uber.org/zap" ) diff --git a/go.mod b/go.mod index 4542712d5f2..23bab8e0d41 100644 --- a/go.mod +++ b/go.mod @@ -121,7 +121,7 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 ) -require github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37 +require github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018 require ( github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect @@ -229,7 +229,6 @@ require ( github.com/tidwall/gjson v1.14.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect - github.com/tj/assert v0.0.3 // indirect github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2 // indirect github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect diff --git a/go.sum b/go.sum index 1b708825692..856ac942b8d 100644 --- a/go.sum +++ b/go.sum @@ -712,6 +712,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018 h1:/Jt0uTnq8V6hN9x2UJH8ehnH81Xf+PLSgfU7Pp+ZBzY= +github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018/go.mod h1:H4KjwG5DGXkAM+qu9w44kUYvS3FHKhjgTgX+bIdtcn0= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= @@ -1513,8 +1515,6 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= -github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37 h1:K11tjwz8zTTSZkz4TUjfLN+y8uJWP38BbyPqZ2yB/Yk= -github.com/tj/go-redirects v0.0.0-20200911105812-fd1ba1020b37/go.mod h1:E0E2H2gQA+uoi27VCSU+a/BULPtadQA78q3cpTjZbZw= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= From d2e6106e8803058999b33f95ed83234e0bbdeaa4 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 11 Aug 2022 09:00:22 -0500 Subject: [PATCH 23/54] Switch to github.com/ipfs-shipyard/go-ipfs-redirects --- core/corehttp/gateway_handler_unixfs__redirects.go | 2 +- go.mod | 3 +-- go.sum | 12 ++++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 2d57e9ae589..0de73282349 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -9,10 +9,10 @@ import ( "strconv" "strings" + redirects "github.com/ipfs-shipyard/go-ipfs-redirects" files "github.com/ipfs/go-ipfs-files" coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" - redirects "github.com/justincjohnson/go-ipfs-redirects" "github.com/ucarion/urlpath" "go.uber.org/zap" ) diff --git a/go.mod b/go.mod index 23bab8e0d41..026a51eff25 100644 --- a/go.mod +++ b/go.mod @@ -121,8 +121,6 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 ) -require github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018 - require ( github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/Kubuxu/go-os-helper v0.0.1 // indirect @@ -164,6 +162,7 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/huin/goupnp v1.0.3 // indirect + github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220811135158-8b4073eab362 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-bitfield v1.0.0 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect diff --git a/go.sum b/go.sum index 856ac942b8d..c5ea1a417cb 100644 --- a/go.sum +++ b/go.sum @@ -245,10 +245,10 @@ github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUork github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q= -github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro= +github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= @@ -439,6 +439,8 @@ github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220811135158-8b4073eab362 h1:GJroVyFugVM90cMqNr2goNnyUviKIkeU7Xll8P5rYgc= +github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220811135158-8b4073eab362/go.mod h1:ppTfKlouEoKF1W2TKD1kpI2ojMPDVT6LXajpL+ZzrS8= github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= @@ -712,8 +714,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018 h1:/Jt0uTnq8V6hN9x2UJH8ehnH81Xf+PLSgfU7Pp+ZBzY= -github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018/go.mod h1:H4KjwG5DGXkAM+qu9w44kUYvS3FHKhjgTgX+bIdtcn0= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= From 3a614ef942d7e3424e1a1b1bf9fd8885d709b90a Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 11 Aug 2022 09:28:21 -0500 Subject: [PATCH 24/54] Address feedback, correct car fixture --- .../gateway_handler_unixfs__redirects.go | 2 +- .../redirects.car | Bin 1630 -> 1625 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 0de73282349..caa61fcf693 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -178,7 +178,7 @@ func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipa // Parse redirect rules from file redirectRules, err := redirects.Parse(f) if err != nil { - return nil, fmt.Errorf("could not parse redirect rules: %v", err) + return nil, fmt.Errorf("could not parse _redirects: %v", err) } return redirectRules, nil diff --git a/test/sharness/t0109-gateway-web-_redirects-data/redirects.car b/test/sharness/t0109-gateway-web-_redirects-data/redirects.car index 5d8ebfabc11ef4c271d10226e46d8d386d57a2e2..ef9edcacdef03ef1962e773552ffbfbef9c50f66 100644 GIT binary patch delta 213 zcmcb|bCW05Vo_>Qett=D;|;9{WdNS)S}|d{5-}<%!CzBjJ42w^2NNbhYOY@ ze0AqaUm?U5UzD1XS(KVwQY{?cGd53WT(AO=IF<66%|*#dL^KYw{;1U&g-8 I4$N}Q0IvjHU;qFB delta 227 zcmcb~bB`z1Vo_>Qett=D;|;9{Wd6oL+zRYv1|#()%8l=>3?cf#>tzlndO)PQHE;p From f66d64bd2796f3b5529a0ea3745f10af54db3c9c Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 11 Aug 2022 11:40:02 -0500 Subject: [PATCH 25/54] More feedback --- core/corehttp/gateway_handler.go | 1 - .../gateway_handler_unixfs__redirects.go | 36 +++++++++++++------ test/sharness/t0109-gateway-web-_redirects.sh | 20 +++++++---- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 98087a10210..a628eb0714b 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -412,7 +412,6 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request } } - // TODO(JJ): Should this be somewhere else for UnixFS, given the redirects handling above? if err := i.handleGettingFirstBlock(r, begin, contentPath, resolvedPath); err != nil { webRequestError(w, err) return diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index caa61fcf693..ec7b92fae75 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -51,7 +51,7 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht // The path can't be resolved. // If we have origin isolation, attempt to handle any redirect rules. if hasOriginIsolation(r) { - redirectsFile := i.getRedirectsFile(r, logger) + redirectsFile := i.getRedirectsFile(r, contentPath, logger) if redirectsFile != nil { redirectRules, err := i.getRedirectRules(r, redirectsFile) if err != nil { @@ -59,7 +59,7 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht return nil, nil, false } - redirected, newPath, err := i.handleRedirect(w, r, redirectRules) + redirected, newPath, err := i.handleRedirectsFileRules(w, r, contentPath, redirectRules) if err != nil { err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) internalWebError(w, err) @@ -99,9 +99,9 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht } } -func (i *gatewayHandler) handleRedirect(w http.ResponseWriter, r *http.Request, redirectRules []redirects.Rule) (bool, string, error) { +func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, redirectRules []redirects.Rule) (bool, string, error) { // Attempt to match a rule to the URL path, and perform the corresponding redirect or rewrite - pathParts := strings.Split(r.URL.Path, "/") + pathParts := strings.Split(contentPath.String(), "/") if len(pathParts) > 3 { // All paths should start with /ipfs/cid/, so get the path after that urlPath := "/" + strings.Join(pathParts[3:], "/") @@ -129,7 +129,7 @@ func (i *gatewayHandler) handleRedirect(w http.ResponseWriter, r *http.Request, return false, toPath, nil } - // Or 404 + // Or 400s if rule.Status == 404 { toPath = rootPath + toPath content404Path := ipath.New(toPath) @@ -137,9 +137,24 @@ func (i *gatewayHandler) handleRedirect(w http.ResponseWriter, r *http.Request, return true, toPath, err } + if rule.Status == 410 { + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), coreiface.ErrResolveFailed, http.StatusGone) + return true, toPath, nil + } + + if rule.Status == 451 { + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), coreiface.ErrResolveFailed, http.StatusUnavailableForLegalReasons) + return true, toPath, nil + } + // Or redirect - http.Redirect(w, r, toPath, rule.Status) - return true, toPath, nil + if rule.Status >= 301 && rule.Status <= 308 { + http.Redirect(w, r, toPath, rule.Status) + return true, toPath, nil + } + + // Unsupported status code + return false, toPath, fmt.Errorf("unsupported redirect status code: %d", rule.Status) } } @@ -172,7 +187,8 @@ func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipa // Convert the node into a file f, ok := node.(files.File) if !ok { - return nil, fmt.Errorf("could not convert _redirects node to file") + return nil, fmt.Errorf("could not parse _redirects: %v", err) + } // Parse redirect rules from file @@ -185,10 +201,10 @@ func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipa } // Returns a resolved path to the _redirects file located in the root CID path of the requested path -func (i *gatewayHandler) getRedirectsFile(r *http.Request, logger *zap.SugaredLogger) ipath.Resolved { +func (i *gatewayHandler) getRedirectsFile(r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) ipath.Resolved { // r.URL.Path is the full ipfs path to the requested resource, // regardless of whether path or subdomain resolution is used. - rootPath, err := getRootPath(r.URL.Path) + rootPath, err := getRootPath(contentPath.String()) if err != nil { logger.Debugf("getRootPath failed", err) return nil diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 1dc8c775289..2fe50971d3f 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -16,19 +16,21 @@ test_launch_ipfs_daemon test_expect_success "Add the _redirects file test directory" ' ipfs dag import ../t0109-gateway-web-_redirects-data/redirects.car ' -REDIRECTS_DIR_CID=QmaiAcL7pFedPJXxNJNDVDTUR78We7yBhdLzg151ZMzLCv +REDIRECTS_DIR_CID=QmcZzEbsNsQM6PmnvPbtDJdRAen5skkCxDRS8K7HafpAsX REDIRECTS_DIR_HOSTNAME="${REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/redirect-one" > response && test_should_contain "one.html" response && - test_should_contain "301 Moved Permanently" response + test_should_contain "301 Moved Permanently" response && + test_should_contain "Location" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/301-redirect-one redirects with 301, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/301-redirect-one" > response && test_should_contain "one.html" response && - test_should_contain "301 Moved Permanently" response + test_should_contain "301 Moved Permanently" response && + test_should_contain "Location" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/302-redirect-two redirects with 302, per _redirects file" ' @@ -46,13 +48,15 @@ test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/200-index returns 200, test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/posts/:year/:month/:day/:title redirects with 301 and placeholders, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/posts/2022/01/01/hello-world" > response && test_should_contain "/articles/2022/01/01/hello-world" response && - test_should_contain "301 Moved Permanently" response + test_should_contain "301 Moved Permanently" response && + test_should_contain "Location" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/splat/one.html redirects with 301 and splat placeholder, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/splat/one.html" > response && test_should_contain "/redirected-splat/one.html" response && - test_should_contain "301 Moved Permanently" response + test_should_contain "301 Moved Permanently" response && + test_should_contain "Location" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/en/has-no-redirects-entry returns custom 404, per _redirects file" ' @@ -107,13 +111,15 @@ test_expect_success "spoofed DNSLink record resolves in cli" " test_expect_success "request for $DNSLINK_FQDN/redirect-one redirects with default of 301, per _redirects file" ' curl -sD - --resolve $DNSLINK_FQDN:$GWAY_PORT:127.0.0.1 "http://$DNSLINK_FQDN:$GWAY_PORT/redirect-one" > response && test_should_contain "one.html" response && - test_should_contain "301 Moved Permanently" response + test_should_contain "301 Moved Permanently" response && + test_should_contain "Location" response ' test_expect_success "request for $NO_DNSLINK_FQDN/redirect-one does not redirect, since DNSLink is disabled" ' curl -sD - --resolve $NO_DNSLINK_FQDN:$GWAY_PORT:127.0.0.1 "http://$NO_DNSLINK_FQDN:$GWAY_PORT/redirect-one" > response && test_should_not_contain "one.html" response && - test_should_not_contain "301 Moved Permanently" response + test_should_not_contain "301 Moved Permanently" response && + test_should_not_contain "Location" response ' test_kill_ipfs_daemon From bb9437401f71d1e0a2a3629eb95345dce09d9ba7 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 11 Aug 2022 12:52:11 -0500 Subject: [PATCH 26/54] More feedback --- .../gateway_handler_unixfs__redirects.go | 8 +++--- test/sharness/t0109-gateway-web-_redirects.sh | 25 +++++++++++-------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index ec7b92fae75..00836d955cd 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -211,10 +211,10 @@ func (i *gatewayHandler) getRedirectsFile(r *http.Request, contentPath ipath.Pat } // Check for root path. - _, err = i.api.Block().Get(r.Context(), rootPath) - if err != nil { - return nil - } + // _, err = i.api.Block().Get(r.Context(), rootPath) + // if err != nil { + // return nil + // } // Check for _redirects file. // Any path resolution failures are ignored and we just assume there's no _redirects file. diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 2fe50971d3f..9b59aac520b 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -23,20 +23,20 @@ test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/redirect-one redirects curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/redirect-one" > response && test_should_contain "one.html" response && test_should_contain "301 Moved Permanently" response && - test_should_contain "Location" response + test_should_contain "Location:" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/301-redirect-one redirects with 301, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/301-redirect-one" > response && test_should_contain "one.html" response && - test_should_contain "301 Moved Permanently" response && - test_should_contain "Location" response + test_should_contain "301 Moved Permanently" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/302-redirect-two redirects with 302, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/302-redirect-two" > response && test_should_contain "two.html" response && - test_should_contain "302 Found" response + test_should_contain "302 Found" response && + test_should_contain "Location:" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/200-index returns 200, per _redirects file" ' @@ -49,29 +49,32 @@ test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/posts/:year/:month/:day curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/posts/2022/01/01/hello-world" > response && test_should_contain "/articles/2022/01/01/hello-world" response && test_should_contain "301 Moved Permanently" response && - test_should_contain "Location" response + test_should_contain "Location:" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/splat/one.html redirects with 301 and splat placeholder, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/splat/one.html" > response && test_should_contain "/redirected-splat/one.html" response && test_should_contain "301 Moved Permanently" response && - test_should_contain "Location" response + test_should_contain "Location:" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/en/has-no-redirects-entry returns custom 404, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/not-found/has-no-redirects-entry" > response && - test_should_contain "404 Not Found" response + test_should_contain "404 Not Found" response && + test_should_contain "my 404" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/catch-all returns 200, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/catch-all" > response && - test_should_contain "200 OK" response + test_should_contain "200 OK" response && + test_should_contain "my index" response ' test_expect_success "request for http://127.0.0.1:$GWAY_PORT/ipfs/$REDIRECTS_DIR_CID/301-redirect-one returns 404, no _redirects since no origin isolation" ' curl -sD - "http://127.0.0.1:$GWAY_PORT/ipfs/$REDIRECTS_DIR_CID/301-redirect-one" > response && - test_should_contain "404 Not Found" response + test_should_contain "404 Not Found" response && + test_should_not_contain "my 404" response ' test_kill_ipfs_daemon @@ -112,14 +115,14 @@ test_expect_success "request for $DNSLINK_FQDN/redirect-one redirects with defau curl -sD - --resolve $DNSLINK_FQDN:$GWAY_PORT:127.0.0.1 "http://$DNSLINK_FQDN:$GWAY_PORT/redirect-one" > response && test_should_contain "one.html" response && test_should_contain "301 Moved Permanently" response && - test_should_contain "Location" response + test_should_contain "Location:" response ' test_expect_success "request for $NO_DNSLINK_FQDN/redirect-one does not redirect, since DNSLink is disabled" ' curl -sD - --resolve $NO_DNSLINK_FQDN:$GWAY_PORT:127.0.0.1 "http://$NO_DNSLINK_FQDN:$GWAY_PORT/redirect-one" > response && test_should_not_contain "one.html" response && test_should_not_contain "301 Moved Permanently" response && - test_should_not_contain "Location" response + test_should_not_contain "Location:" response ' test_kill_ipfs_daemon From 2acbf7072717797b6dc04355b72253fb7fa0dd54 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 11 Aug 2022 13:01:36 -0500 Subject: [PATCH 27/54] go mod tidy --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 026a51eff25..ed71a1a3443 100644 --- a/go.mod +++ b/go.mod @@ -117,6 +117,7 @@ require ( require ( github.com/benbjohnson/clock v1.3.0 + github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220811135158-8b4073eab362 github.com/ipfs/go-delegated-routing v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 ) @@ -162,7 +163,6 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/huin/goupnp v1.0.3 // indirect - github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220811135158-8b4073eab362 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-bitfield v1.0.0 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect From f4cf5a62aca6af42aa83e311329ab8ed72af54fc Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 11 Aug 2022 13:24:34 -0500 Subject: [PATCH 28/54] Fix test --- core/corehttp/gateway_handler_unixfs__redirects.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 00836d955cd..5514257107a 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -94,7 +94,7 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht } // Fallback - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusNotFound) + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusBadRequest) return nil, nil, false } } From 46faca8ea17b32352102802cad5af5ab3a139b3d Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 11 Aug 2022 18:32:17 -0500 Subject: [PATCH 29/54] Error early if invalid status --- .../gateway_handler_unixfs__redirects.go | 19 +++++-- .../redirects.car | Bin 1625 -> 3072 bytes test/sharness/t0109-gateway-web-_redirects.sh | 47 +++++++++++++++++- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 5514257107a..c735f0f86db 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -110,6 +110,11 @@ func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http urlPath = strings.TrimSuffix(urlPath, "/") for _, rule := range redirectRules { + // Error right away if the rule is invalid + if !isValidCode(rule.Status) { + return false, "", fmt.Errorf("unsupported redirect status code: %d", rule.Status) + } + // get rule.From, trim trailing slash, ... fromPath := urlpath.New(strings.TrimSuffix(rule.From, "/")) match, ok := fromPath.Match(urlPath) @@ -152,9 +157,6 @@ func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http http.Redirect(w, r, toPath, rule.Status) return true, toPath, nil } - - // Unsupported status code - return false, toPath, fmt.Errorf("unsupported redirect status code: %d", rule.Status) } } @@ -172,6 +174,16 @@ func replacePlaceholders(to string, match urlpath.Match) string { return to } +func isValidCode(code int) bool { + validCodes := []int{200, 301, 302, 303, 307, 308, 404, 410, 451} + for _, validCode := range validCodes { + if validCode == code { + return true + } + } + return false +} + func replaceSplat(to string, match urlpath.Match) string { return strings.ReplaceAll(to, ":splat", match.Trailing) } @@ -211,6 +223,7 @@ func (i *gatewayHandler) getRedirectsFile(r *http.Request, contentPath ipath.Pat } // Check for root path. + // TODO(JJ): I don't think I need this anymore. // _, err = i.api.Block().Get(r.Context(), rootPath) // if err != nil { // return nil diff --git a/test/sharness/t0109-gateway-web-_redirects-data/redirects.car b/test/sharness/t0109-gateway-web-_redirects-data/redirects.car index ef9edcacdef03ef1962e773552ffbfbef9c50f66..a3390b41f6b17d2160edb8d1adfa94cf1da148c0 100644 GIT binary patch delta 1400 zcmah|eMnPL6!$H2^O9y)ktY>CCx(5jx4mttlw~E$A}eT-7-h5fbc?q=n{zF!UQyYX zR<5Yfsih>8nHUDqr=g*QVwgdeMyX^-W*{1&QujU!6Qccb4*U-H-19rXb57^1BcvEx z&e;XQk$v>dqLfGKPDO)!d0xo)q0gC4&Yo=% zY(%BZ`(wxeC8S#KmN+-pw~u#61Hm8ZV zu%rQ(hE!>MZ^g^q;y_r@iS*i<)%mh5)_0MIl$~{bm$omyML#k^g^kiV~Wdd_ux?bmazmf(4E2r`5m8@tr(u=3;_Oh^YQ zZ&{H+HlsUjf#-80avYIly$6i*`&ybeej zhh!SE@AVvzrN)KWYjzw6f)**~NGK@92Aoj>G z*5iWMBLn0GBGr4pldG{Hyk_L5Y-i7eK@2D|d+tZf-_4nF2ZHOutK#b-mlPNvu-I76 zHMtu>SQ`dXQ(6B`0)a>w1)4Gz0gwtr$Lke|(th7C_xZjVc>Bej+VE)ZvMA4NYnD%d z-P1`n?mZ@~cW$3H+CWT44JmI)U>2g|XXbcb0Hj|%n(gF?PP@?6(@#`l)85yub6bAy z=qeggdvYVI&z5Ww&8w>89vWKs99MMpG&b>t-!oXuC=F8H62WrBNKgMhRfkHjaTJZ5 XBy#|u(H^NJ-6?uXe!N`)fYkp28Rrn^ delta 38 tcmZpWxXHt3u_(1DKfk27@rG7}GQ;E;CMC}FvecsD%=|pY%~4DWYyb+q48H&X diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 9b59aac520b..0df9747108c 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -16,7 +16,9 @@ test_launch_ipfs_daemon test_expect_success "Add the _redirects file test directory" ' ipfs dag import ../t0109-gateway-web-_redirects-data/redirects.car ' -REDIRECTS_DIR_CID=QmcZzEbsNsQM6PmnvPbtDJdRAen5skkCxDRS8K7HafpAsX +CAR_ROOT_CID=QmVg9ckSKEZ9oc248zg8aUV5rNqrRWjnk6zbFAJhBPLQRd + +REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/examples | cut -d "/" -f3) REDIRECTS_DIR_HOSTNAME="${REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' @@ -77,6 +79,48 @@ test_expect_success "request for http://127.0.0.1:$GWAY_PORT/ipfs/$REDIRECTS_DIR test_should_not_contain "my 404" response ' +# With \r\n newline carriage return +NEWLINE_REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/newlines | cut -d "/" -f3) +NEWLINE_REDIRECTS_DIR_HOSTNAME="${NEWLINE_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" + +test_expect_success "newline: request for $NEWLINE_REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' + curl -sD - --resolve $NEWLINE_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$NEWLINE_REDIRECTS_DIR_HOSTNAME/redirect-one" > response && + test_should_contain "one.html" response && + test_should_contain "301 Moved Permanently" response && + test_should_contain "Location:" response +' + +# Good codes +GOOD_REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/good-codes | cut -d "/" -f3) +GOOD_REDIRECTS_DIR_HOSTNAME="${GOOD_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" + +test_expect_success "good codes: request for $GOOD_REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' + curl -sD - --resolve $GOOD_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$GOOD_REDIRECTS_DIR_HOSTNAME/a301" > response && + test_should_contain "b301" response && + test_should_contain "301 Moved Permanently" response && + test_should_contain "Location:" response +' + +# Bad codes +BAD_REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/bad-codes | cut -d "/" -f3) +BAD_REDIRECTS_DIR_HOSTNAME="${BAD_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" + + +# if accessing a path that doesn't exist, read _redirects and fail parsing, and return error +test_expect_success "bad codes: request for $BAD_REDIRECTS_DIR_HOSTNAME/not-found returns error about bad code" ' + curl -sD - --resolve $BAD_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$BAD_REDIRECTS_DIR_HOSTNAME/not-found" > response && + test_should_contain "500" response && + test_should_contain "unsupported redirect status" response +' + +# if accessing a path that does exist, don't read _redirects and therefore don't fail parsing +test_expect_success "bad codes: request for $BAD_REDIRECTS_DIR_HOSTNAME/found.html doesn't return error about bad code" ' + curl -sD - --resolve $BAD_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$BAD_REDIRECTS_DIR_HOSTNAME/found.html" > response && + test_should_contain "200" response && + test_should_contain "my found" response && + test_should_not_contain "unsupported redirect status" response +' + test_kill_ipfs_daemon # disable wildcard DNSLink gateway @@ -96,6 +140,7 @@ ipfs config --json Gateway.PublicGateways '{ }' || exit 1 # DNSLink test requires a daemon in online mode with precached /ipns/ mapping +# REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/examples | cut -d "/" -f3) DNSLINK_FQDN="dnslink-enabled-on-fqdn.example.org" NO_DNSLINK_FQDN="dnslink-disabled-on-fqdn.example.com" export IPFS_NS_MAP="$DNSLINK_FQDN:/ipfs/$REDIRECTS_DIR_CID" From f9cd9641db72cc55680579470eaffcb366a7d67b Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 12 Aug 2022 08:18:10 -0500 Subject: [PATCH 30/54] Confirm CRLF line terminator --- test/sharness/t0109-gateway-web-_redirects.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 0df9747108c..7b92062c906 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -79,10 +79,15 @@ test_expect_success "request for http://127.0.0.1:$GWAY_PORT/ipfs/$REDIRECTS_DIR test_should_not_contain "my 404" response ' -# With \r\n newline carriage return +# With CRLF line terminator NEWLINE_REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/newlines | cut -d "/" -f3) NEWLINE_REDIRECTS_DIR_HOSTNAME="${NEWLINE_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" +test_expect_success "newline: _redirects has CRLF line terminators" ' + ipfs cat /ipfs/$NEWLINE_REDIRECTS_DIR_CID/_redirects | file - > response && + test_should_contain "with CRLF line terminators" response +' + test_expect_success "newline: request for $NEWLINE_REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' curl -sD - --resolve $NEWLINE_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$NEWLINE_REDIRECTS_DIR_HOSTNAME/redirect-one" > response && test_should_contain "one.html" response && From 5153b9a57fc273d8d2155d8e74e409a98fb6a219 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 12 Aug 2022 09:34:01 -0500 Subject: [PATCH 31/54] Add tests for invalid _redirects file --- core/corehttp/gateway_handler_unixfs__redirects.go | 3 +-- test/sharness/t0109-gateway-web-_redirects.sh | 12 +++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index c735f0f86db..7fae77351d1 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -200,7 +200,6 @@ func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipa f, ok := node.(files.File) if !ok { return nil, fmt.Errorf("could not parse _redirects: %v", err) - } // Parse redirect rules from file @@ -214,7 +213,7 @@ func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipa // Returns a resolved path to the _redirects file located in the root CID path of the requested path func (i *gatewayHandler) getRedirectsFile(r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) ipath.Resolved { - // r.URL.Path is the full ipfs path to the requested resource, + // contentPath is the full ipfs path to the requested resource, // regardless of whether path or subdomain resolution is used. rootPath, err := getRootPath(contentPath.String()) if err != nil { diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 7b92062c906..797ced434b7 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -110,7 +110,6 @@ test_expect_success "good codes: request for $GOOD_REDIRECTS_DIR_HOSTNAME/redire BAD_REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/bad-codes | cut -d "/" -f3) BAD_REDIRECTS_DIR_HOSTNAME="${BAD_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" - # if accessing a path that doesn't exist, read _redirects and fail parsing, and return error test_expect_success "bad codes: request for $BAD_REDIRECTS_DIR_HOSTNAME/not-found returns error about bad code" ' curl -sD - --resolve $BAD_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$BAD_REDIRECTS_DIR_HOSTNAME/not-found" > response && @@ -126,6 +125,17 @@ test_expect_success "bad codes: request for $BAD_REDIRECTS_DIR_HOSTNAME/found.ht test_should_not_contain "unsupported redirect status" response ' +# Bad codes +INVALID_REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/invalid | cut -d "/" -f3) +INVALID_REDIRECTS_DIR_HOSTNAME="${INVALID_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" + +# if accessing a path that doesn't exist, read _redirects and fail parsing, and return error +test_expect_success "invalid file: request for $INVALID_REDIRECTS_DIR_HOSTNAME/not-found returns error about invalid redirects file" ' + curl -sD - --resolve $INVALID_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$INVALID_REDIRECTS_DIR_HOSTNAME/not-found" > response && + test_should_contain "500" response && + test_should_contain "could not parse _redirects:" response +' + test_kill_ipfs_daemon # disable wildcard DNSLink gateway From 0570b1d7ce37ce90e23ee4d9291af352ff54c300 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 12 Aug 2022 09:42:31 -0500 Subject: [PATCH 32/54] Add test with attempted forced redirect --- .../redirects.car | Bin 3072 -> 3265 bytes test/sharness/t0109-gateway-web-_redirects.sh | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/test/sharness/t0109-gateway-web-_redirects-data/redirects.car b/test/sharness/t0109-gateway-web-_redirects-data/redirects.car index a3390b41f6b17d2160edb8d1adfa94cf1da148c0..610f283564b790b01634d7250e837478a894b336 100644 GIT binary patch delta 313 zcmZpWI4Buwu_(1DKfk27@rG7}GJ}x9@9nQA{de3W-e|dZ@5)P!{vj_~nQmNVR{tQn zxWA@v)5G+#)S}|d{5-~MOoSCrjLk6C<5Ch*2z_q7-1d`lz;5?F936AxKE9r&9bEDD z=+fMj)a1yW-rhoNY57ITsVNfPLdIN5lWRDXH@@y?ua6=^rxD2f~$@kRS(>5VwA! Uf_{>Mv4No?mjw6bDvma00Ddik`2YX_ delta 119 zcmX>o*&q>Xu_(1DKfk27@rG7}GJ}vpE<^jGyOsH$|F%f3bTWM`ymhrtfZ)S2j>GKM sZ6beaU!<3%78Pga=P@p0BCL2~YzDJ2m(pZgPUX$gjQ#AJtvTD60lsQ5D*ylh diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 797ced434b7..893d3fb5de8 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -16,7 +16,7 @@ test_launch_ipfs_daemon test_expect_success "Add the _redirects file test directory" ' ipfs dag import ../t0109-gateway-web-_redirects-data/redirects.car ' -CAR_ROOT_CID=QmVg9ckSKEZ9oc248zg8aUV5rNqrRWjnk6zbFAJhBPLQRd +CAR_ROOT_CID=QmfHFheaikRRB6ap7AdL4FHBkyHPhPBDX7fS25rMzYhLuW REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/examples | cut -d "/" -f3) REDIRECTS_DIR_HOSTNAME="${REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" @@ -125,7 +125,7 @@ test_expect_success "bad codes: request for $BAD_REDIRECTS_DIR_HOSTNAME/found.ht test_should_not_contain "unsupported redirect status" response ' -# Bad codes +# Invalid file, containing "hello" INVALID_REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/invalid | cut -d "/" -f3) INVALID_REDIRECTS_DIR_HOSTNAME="${INVALID_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" @@ -136,6 +136,18 @@ test_expect_success "invalid file: request for $INVALID_REDIRECTS_DIR_HOSTNAME/n test_should_contain "could not parse _redirects:" response ' +# Invalid file, containing forced redirect +INVALID_REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/forced | cut -d "/" -f3) +INVALID_REDIRECTS_DIR_HOSTNAME="${INVALID_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" + +# if accessing a path that doesn't exist, read _redirects and fail parsing, and return error +test_expect_success "invalid file: request for $INVALID_REDIRECTS_DIR_HOSTNAME/not-found returns error about invalid redirects file" ' + curl -sD - --resolve $INVALID_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$INVALID_REDIRECTS_DIR_HOSTNAME/not-found" > response && + test_should_contain "500" response && + test_should_contain "could not parse _redirects:" response && + test_should_contain "forced redirects (or \"shadowing\") are not supported by IPFS gateways" response +' + test_kill_ipfs_daemon # disable wildcard DNSLink gateway From f4b9ec22c972943152adc7d4e9f386e406dc398b Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 12 Aug 2022 12:58:47 -0500 Subject: [PATCH 33/54] Simplify getRootPath by using ipath.Path --- .../gateway_handler_unixfs__redirects.go | 45 ++----------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 7fae77351d1..693d464ed10 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -1,7 +1,6 @@ package corehttp import ( - "errors" "fmt" "io" "net/http" @@ -215,18 +214,7 @@ func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipa func (i *gatewayHandler) getRedirectsFile(r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) ipath.Resolved { // contentPath is the full ipfs path to the requested resource, // regardless of whether path or subdomain resolution is used. - rootPath, err := getRootPath(contentPath.String()) - if err != nil { - logger.Debugf("getRootPath failed", err) - return nil - } - - // Check for root path. - // TODO(JJ): I don't think I need this anymore. - // _, err = i.api.Block().Get(r.Context(), rootPath) - // if err != nil { - // return nil - // } + rootPath := getRootPath(contentPath) // Check for _redirects file. // Any path resolution failures are ignored and we just assume there's no _redirects file. @@ -240,18 +228,9 @@ func (i *gatewayHandler) getRedirectsFile(r *http.Request, contentPath ipath.Pat } // Returns the root CID Path for the given path -func getRootPath(path string) (ipath.Path, error) { - if isIpfsPath(path) { - parts := strings.Split(path, "/") - return ipath.New(gopath.Join(ipfsPathPrefix, parts[2])), nil - } - - if isIpnsPath(path) { - parts := strings.Split(path, "/") - return ipath.New(gopath.Join(ipnsPathPrefix, parts[2])), nil - } - - return ipath.New(""), errors.New("failed to get root CID path") +func getRootPath(path ipath.Path) ipath.Path { + parts := strings.Split(path.String(), "/") + return ipath.New(gopath.Join("/", path.Namespace(), parts[2])) } func (i *gatewayHandler) serve404(w http.ResponseWriter, r *http.Request, content404Path ipath.Path) error { @@ -295,22 +274,6 @@ func hasOriginIsolation(r *http.Request) bool { return false } -func isIpfsPath(path string) bool { - if strings.HasPrefix(path, ipfsPathPrefix) && strings.Count(gopath.Clean(path), "/") >= 2 { - return true - } - - return false -} - -func isIpnsPath(path string) bool { - if strings.HasPrefix(path, ipnsPathPrefix) && strings.Count(gopath.Clean(path), "/") >= 2 { - return true - } - - return false -} - func isUnixfsResponseFormat(responseFormat string) bool { // The implicit response format is UnixFS return responseFormat == "" From 3cd0c6df46d77552797306afe6fcae5792223fb9 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 12 Aug 2022 13:32:35 -0500 Subject: [PATCH 34/54] Consolidate unixfs and non-unixfs handling into a single method. --- core/corehttp/gateway_handler.go | 24 +----- .../gateway_handler_unixfs__redirects.go | 81 ++++++++++--------- 2 files changed, 44 insertions(+), 61 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index a628eb0714b..b8ec8e284ad 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -386,13 +386,7 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request } trace.SpanFromContext(r.Context()).SetAttributes(attribute.String("ResponseFormat", responseFormat)) - var ok bool - var resolvedPath ipath.Resolved - if isUnixfsResponseFormat(responseFormat) { - resolvedPath, contentPath, ok = i.handleUnixfsPathResolution(w, r, contentPath, logger) - } else { - resolvedPath, contentPath, ok = i.handleNonUnixfsPathResolution(w, r, contentPath) - } + resolvedPath, contentPath, ok := i.handlePathResolution(w, r, responseFormat, contentPath, logger) if !ok { return } @@ -1110,19 +1104,3 @@ func (i *gatewayHandler) setCommonHeaders(w http.ResponseWriter, r *http.Request return nil } - -func (i *gatewayHandler) handleNonUnixfsPathResolution(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) (ipath.Resolved, ipath.Path, bool) { - // Resolve the path for the provided contentPath - resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) - - switch err { - case nil: - return resolvedPath, contentPath, true - case coreiface.ErrOffline: - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable) - return nil, nil, false - default: - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusNotFound) - return nil, nil, false - } -} diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 693d464ed10..859b6718b73 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -36,7 +36,7 @@ import ( // the rewrite destination path. // // Note that for security reasons, redirect rules are only processed when the request has origin isolation. -func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { +func (i *gatewayHandler) handlePathResolution(w http.ResponseWriter, r *http.Request, responseFormat string, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { // Attempt to resolve the provided path. resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) @@ -47,54 +47,59 @@ func (i *gatewayHandler) handleUnixfsPathResolution(w http.ResponseWriter, r *ht webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable) return nil, nil, false default: - // The path can't be resolved. - // If we have origin isolation, attempt to handle any redirect rules. - if hasOriginIsolation(r) { - redirectsFile := i.getRedirectsFile(r, contentPath, logger) - if redirectsFile != nil { - redirectRules, err := i.getRedirectRules(r, redirectsFile) - if err != nil { - internalWebError(w, err) - return nil, nil, false - } - - redirected, newPath, err := i.handleRedirectsFileRules(w, r, contentPath, redirectRules) - if err != nil { - err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) - internalWebError(w, err) - return nil, nil, false - } - - if redirected { - return nil, nil, false - } + if isUnixfsResponseFormat(responseFormat) { + // The path can't be resolved. + // If we have origin isolation, attempt to handle any redirect rules. + if hasOriginIsolation(r) { + redirectsFile := i.getRedirectsFile(r, contentPath, logger) + if redirectsFile != nil { + redirectRules, err := i.getRedirectRules(r, redirectsFile) + if err != nil { + internalWebError(w, err) + return nil, nil, false + } - // 200 is treated as a rewrite, so update the path and continue - if newPath != "" { - // Reassign contentPath and resolvedPath since the URL was rewritten - contentPath = ipath.New(newPath) - resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath) + redirected, newPath, err := i.handleRedirectsFileRules(w, r, contentPath, redirectRules) if err != nil { + err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) internalWebError(w, err) return nil, nil, false } - return resolvedPath, contentPath, true + if redirected { + return nil, nil, false + } + + // 200 is treated as a rewrite, so update the path and continue + if newPath != "" { + // Reassign contentPath and resolvedPath since the URL was rewritten + contentPath = ipath.New(newPath) + resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath) + if err != nil { + internalWebError(w, err) + return nil, nil, false + } + + return resolvedPath, contentPath, true + } } } - } - // if Accept is text/html, see if ipfs-404.html is present - // This logic isn't documented and will likely be removed at some point. - // Any 404 logic in _redirects above will have already run by this time, so it's really an extra fall back - if i.servePretty404IfPresent(w, r, contentPath) { - logger.Debugw("serve pretty 404 if present") + // if Accept is text/html, see if ipfs-404.html is present + // This logic isn't documented and will likely be removed at some point. + // Any 404 logic in _redirects above will have already run by this time, so it's really an extra fall back + if i.servePretty404IfPresent(w, r, contentPath) { + logger.Debugw("serve pretty 404 if present") + return nil, nil, false + } + + // Fallback + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusBadRequest) + return nil, nil, false + } else { + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusNotFound) return nil, nil, false } - - // Fallback - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusBadRequest) - return nil, nil, false } } From 4c8ccbdd74b84dbc5bc6476b699a454f8f82d821 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 12 Aug 2022 14:54:16 -0500 Subject: [PATCH 35/54] Remove direct dependency on ucarion/urlpath --- .../gateway_handler_unixfs__redirects.go | 37 ++++--------------- go.mod | 4 +- go.sum | 6 ++- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 859b6718b73..47c16e807b4 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -12,7 +12,6 @@ import ( files "github.com/ipfs/go-ipfs-files" coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" - "github.com/ucarion/urlpath" "go.uber.org/zap" ) @@ -119,28 +118,22 @@ func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http return false, "", fmt.Errorf("unsupported redirect status code: %d", rule.Status) } - // get rule.From, trim trailing slash, ... - fromPath := urlpath.New(strings.TrimSuffix(rule.From, "/")) - match, ok := fromPath.Match(urlPath) - if !ok { + if !rule.MatchAndExpandPlaceholders(urlPath) { continue } - // We have a match! Perform substitutions. - toPath := rule.To - toPath = replacePlaceholders(toPath, match) - toPath = replaceSplat(toPath, match) + // We have a match! // Rewrite if rule.Status == 200 { // Prepend the rootPath - toPath = rootPath + toPath + toPath := rootPath + rule.To return false, toPath, nil } // Or 400s if rule.Status == 404 { - toPath = rootPath + toPath + toPath := rootPath + rule.To content404Path := ipath.New(toPath) err := i.serve404(w, r, content404Path) return true, toPath, err @@ -148,18 +141,18 @@ func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http if rule.Status == 410 { webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), coreiface.ErrResolveFailed, http.StatusGone) - return true, toPath, nil + return true, rule.To, nil } if rule.Status == 451 { webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), coreiface.ErrResolveFailed, http.StatusUnavailableForLegalReasons) - return true, toPath, nil + return true, rule.To, nil } // Or redirect if rule.Status >= 301 && rule.Status <= 308 { - http.Redirect(w, r, toPath, rule.Status) - return true, toPath, nil + http.Redirect(w, r, rule.To, rule.Status) + return true, rule.To, nil } } } @@ -168,16 +161,6 @@ func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http return false, "", nil } -func replacePlaceholders(to string, match urlpath.Match) string { - if len(match.Params) > 0 { - for key, value := range match.Params { - to = strings.ReplaceAll(to, ":"+key, value) - } - } - - return to -} - func isValidCode(code int) bool { validCodes := []int{200, 301, 302, 303, 307, 308, 404, 410, 451} for _, validCode := range validCodes { @@ -188,10 +171,6 @@ func isValidCode(code int) bool { return false } -func replaceSplat(to string, match urlpath.Match) string { - return strings.ReplaceAll(to, ":splat", match.Trailing) -} - func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipath.Resolved) ([]redirects.Rule, error) { // Convert the path into a file node node, err := i.api.Unixfs().Get(r.Context(), redirectsFilePath) diff --git a/go.mod b/go.mod index ed71a1a3443..3186758cfd1 100644 --- a/go.mod +++ b/go.mod @@ -93,7 +93,6 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/stretchr/testify v1.8.0 github.com/syndtr/goleveldb v1.0.0 - github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb github.com/wI2L/jsondiff v0.2.0 github.com/whyrusleeping/go-sysinfo v0.0.0-20190219211824-4a357d4b90b1 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 @@ -117,7 +116,7 @@ require ( require ( github.com/benbjohnson/clock v1.3.0 - github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220811135158-8b4073eab362 + github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812193924-389cc1df778a github.com/ipfs/go-delegated-routing v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 ) @@ -228,6 +227,7 @@ require ( github.com/tidwall/gjson v1.14.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb // indirect github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20210219115102-f37d292932f2 // indirect github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect diff --git a/go.sum b/go.sum index c5ea1a417cb..6893737c306 100644 --- a/go.sum +++ b/go.sum @@ -439,8 +439,10 @@ github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220811135158-8b4073eab362 h1:GJroVyFugVM90cMqNr2goNnyUviKIkeU7Xll8P5rYgc= -github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220811135158-8b4073eab362/go.mod h1:ppTfKlouEoKF1W2TKD1kpI2ojMPDVT6LXajpL+ZzrS8= +github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812184534-37f47105965f h1:tVyzxq044vA7D1jyLwKltaw3XeZVB2UbYUXIFtPl+08= +github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812184534-37f47105965f/go.mod h1:Xa2lXkc6eBMOzurl5DF6dU7NEOSLf3zf2dyHRpuKa1c= +github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812193924-389cc1df778a h1:lC0WiXE74JnkNcbqjSdQKGWWZVRcBeR6weWjX0q5Hl4= +github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812193924-389cc1df778a/go.mod h1:Xa2lXkc6eBMOzurl5DF6dU7NEOSLf3zf2dyHRpuKa1c= github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= From 428278542b9b0f912ac4101e4b172497026a1e3e Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 12 Aug 2022 15:02:51 -0500 Subject: [PATCH 36/54] go mod tidy --- go.mod | 2 +- go.sum | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 3186758cfd1..275e02aeabd 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/elgris/jsondiff v0.0.0-20160530203242-765b5c24c302 github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 - github.com/fsnotify/fsnotify v1.5.1 + github.com/fsnotify/fsnotify v1.5.4 github.com/gabriel-vasile/mimetype v1.4.0 github.com/hashicorp/go-multierror v1.1.1 github.com/ipfs/go-bitswap v0.10.2 diff --git a/go.sum b/go.sum index 6893737c306..c10871f2b08 100644 --- a/go.sum +++ b/go.sum @@ -245,8 +245,8 @@ github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUork github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -439,8 +439,6 @@ github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812184534-37f47105965f h1:tVyzxq044vA7D1jyLwKltaw3XeZVB2UbYUXIFtPl+08= -github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812184534-37f47105965f/go.mod h1:Xa2lXkc6eBMOzurl5DF6dU7NEOSLf3zf2dyHRpuKa1c= github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812193924-389cc1df778a h1:lC0WiXE74JnkNcbqjSdQKGWWZVRcBeR6weWjX0q5Hl4= github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812193924-389cc1df778a/go.mod h1:Xa2lXkc6eBMOzurl5DF6dU7NEOSLf3zf2dyHRpuKa1c= github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= @@ -1793,6 +1791,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -2146,7 +2145,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From b299f813553f9a238e9d8e3b2007c6eca55e3ab2 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 19 Aug 2022 07:00:02 -0500 Subject: [PATCH 37/54] Cleanup after rebase --- core/corehttp/gateway_handler.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index b8ec8e284ad..f85c49b1a96 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -24,7 +24,6 @@ import ( mfs "github.com/ipfs/go-mfs" path "github.com/ipfs/go-path" "github.com/ipfs/go-path/resolver" - coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" routing "github.com/libp2p/go-libp2p/core/routing" prometheus "github.com/prometheus/client_golang/prometheus" From cf2ae8c0d31be07e8e409776704a5deae9d58be1 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 19 Aug 2022 07:10:00 -0500 Subject: [PATCH 38/54] Revert unnecessary downgrade of a dep --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 275e02aeabd..a887b76d4d7 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/elgris/jsondiff v0.0.0-20160530203242-765b5c24c302 github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 github.com/fsnotify/fsnotify v1.5.4 - github.com/gabriel-vasile/mimetype v1.4.0 + github.com/gabriel-vasile/mimetype v1.4.1 github.com/hashicorp/go-multierror v1.1.1 github.com/ipfs/go-bitswap v0.10.2 github.com/ipfs/go-block-format v0.0.3 diff --git a/go.sum b/go.sum index c10871f2b08..97aa1c5bbdb 100644 --- a/go.sum +++ b/go.sum @@ -247,8 +247,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro= -github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= +github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q= +github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= @@ -1791,7 +1791,6 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= From 2495b8d9ce4cf41fb8ca65628d74a42fc2290c28 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 15 Sep 2022 09:41:29 -0500 Subject: [PATCH 39/54] go mod tidy --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 97aa1c5bbdb..4742c1fe960 100644 --- a/go.sum +++ b/go.sum @@ -1514,7 +1514,6 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= -github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= @@ -2143,7 +2142,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 825fc1ed6a881fa26c79c0720a6a3dd81e7f7c91 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 15 Sep 2022 09:48:27 -0500 Subject: [PATCH 40/54] function rename, per lidel --- core/corehttp/gateway_handler.go | 5 ++++- core/corehttp/gateway_handler_unixfs__redirects.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index f85c49b1a96..e3be79e31bb 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -437,7 +437,10 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request } } -func (i *gatewayHandler) servePretty404IfPresent(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) bool { +// Deprecated: legacy ipfs-404.html files are superseded by _redirects file +// This is provided only for backward-compatibility, until websites migrate +// to 404s managed via _redirects file (https://github.com/ipfs/specs/pull/290) +func (i *gatewayHandler) serveLegacy404IfPresent(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) bool { resolved404Path, ctype, err := i.searchUpTreeFor404(r, contentPath) if err != nil { return false diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 47c16e807b4..77d407d917c 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -87,7 +87,7 @@ func (i *gatewayHandler) handlePathResolution(w http.ResponseWriter, r *http.Req // if Accept is text/html, see if ipfs-404.html is present // This logic isn't documented and will likely be removed at some point. // Any 404 logic in _redirects above will have already run by this time, so it's really an extra fall back - if i.servePretty404IfPresent(w, r, contentPath) { + if i.serveLegacy404IfPresent(w, r, contentPath) { logger.Debugw("serve pretty 404 if present") return nil, nil, false } From 24d248c78f31e884753fa536fd3c34c0993a2335 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 15 Sep 2022 09:52:04 -0500 Subject: [PATCH 41/54] Switch from github.com/ipfs-shipyard/go-ipfs-redirects to github.com/ipfs/go-ipfs-redirects-file --- core/corehttp/gateway_handler_unixfs__redirects.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 77d407d917c..fef0ac5ea64 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -8,8 +8,8 @@ import ( "strconv" "strings" - redirects "github.com/ipfs-shipyard/go-ipfs-redirects" files "github.com/ipfs/go-ipfs-files" + redirects "github.com/ipfs/go-ipfs-redirects-file" coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" "go.uber.org/zap" diff --git a/go.mod b/go.mod index a887b76d4d7..cc9a4fcceac 100644 --- a/go.mod +++ b/go.mod @@ -116,8 +116,8 @@ require ( require ( github.com/benbjohnson/clock v1.3.0 - github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812193924-389cc1df778a github.com/ipfs/go-delegated-routing v0.6.0 + github.com/ipfs/go-ipfs-redirects-file v0.0.0-20220915124754-4946b031ee37 github.com/ipfs/go-log/v2 v2.5.1 ) diff --git a/go.sum b/go.sum index 4742c1fe960..4f189fdf571 100644 --- a/go.sum +++ b/go.sum @@ -439,8 +439,6 @@ github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812193924-389cc1df778a h1:lC0WiXE74JnkNcbqjSdQKGWWZVRcBeR6weWjX0q5Hl4= -github.com/ipfs-shipyard/go-ipfs-redirects v0.0.0-20220812193924-389cc1df778a/go.mod h1:Xa2lXkc6eBMOzurl5DF6dU7NEOSLf3zf2dyHRpuKa1c= github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= @@ -567,6 +565,8 @@ github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-provider v0.7.1 h1:eKToBUAb6ZY8iiA6AYVxzW4G1ep67XUaaEBUIYpxhfw= github.com/ipfs/go-ipfs-provider v0.7.1/go.mod h1:QwdDYRYnC5sYGLlOwVDY/0ZB6T3zcMtu+5+GdGeUuw8= +github.com/ipfs/go-ipfs-redirects-file v0.0.0-20220915124754-4946b031ee37 h1:dLsZj3RA40WYZHJr4L+S31/BJkOMDlwXyvEYgY/Nv8c= +github.com/ipfs/go-ipfs-redirects-file v0.0.0-20220915124754-4946b031ee37/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= github.com/ipfs/go-ipfs-routing v0.0.1/go.mod h1:k76lf20iKFxQTjcJokbPM9iBXVXVZhcOwc360N4nuKs= github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= github.com/ipfs/go-ipfs-routing v0.2.1 h1:E+whHWhJkdN9YeoHZNj5itzc+OR292AJ2uE9FFiW0BY= From 9778197ce4d2cfb4b593e95d6170e617d15cb877 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 15 Sep 2022 12:01:58 -0500 Subject: [PATCH 42/54] Move status code validation logic to github.com/ipfs/go-ipfs-redirects-file --- core/corehttp/gateway_handler_unixfs__redirects.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index fef0ac5ea64..c60171d59eb 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -114,10 +114,6 @@ func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http for _, rule := range redirectRules { // Error right away if the rule is invalid - if !isValidCode(rule.Status) { - return false, "", fmt.Errorf("unsupported redirect status code: %d", rule.Status) - } - if !rule.MatchAndExpandPlaceholders(urlPath) { continue } @@ -161,16 +157,6 @@ func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http return false, "", nil } -func isValidCode(code int) bool { - validCodes := []int{200, 301, 302, 303, 307, 308, 404, 410, 451} - for _, validCode := range validCodes { - if validCode == code { - return true - } - } - return false -} - func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipath.Resolved) ([]redirects.Rule, error) { // Convert the path into a file node node, err := i.api.Unixfs().Get(r.Context(), redirectsFilePath) From 6334c5acfeb81394b9f180b86e5bb341d4d5e02b Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 15 Sep 2022 12:34:05 -0500 Subject: [PATCH 43/54] fix comment, in part to avoid inconsistent go fmt issue with 1.19 --- core/corehttp/gateway_handler_unixfs__redirects.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index c60171d59eb..587477c22f4 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -18,7 +18,7 @@ import ( // Resolve the provided path. // // Resolving a UnixFS path involves determining if the provided `path.Path` exists and returning the `path.Resolved` -// corresponding to that path. For UnixFS, path resolution is more involved if a `_redirects`` file exists, stored +// corresponding to that path. For UnixFS, path resolution is more involved if a `_redirects` file exists, stored // underneath the root CID of the path. // // Example 1: From f25ed84abd3487cdf4cea7bfe41b9c98c2304b43 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Thu, 15 Sep 2022 15:06:33 -0500 Subject: [PATCH 44/54] Update comment --- core/corehttp/gateway_handler_unixfs__redirects.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 587477c22f4..4c03d5e8e92 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -35,6 +35,7 @@ import ( // the rewrite destination path. // // Note that for security reasons, redirect rules are only processed when the request has origin isolation. +// See https://github.com/ipfs/specs/pull/290 for more information. func (i *gatewayHandler) handlePathResolution(w http.ResponseWriter, r *http.Request, responseFormat string, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { // Attempt to resolve the provided path. resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) From 5b6d7e23490e7002b587d778ebc90d683d83b301 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 16 Sep 2022 08:37:09 -0500 Subject: [PATCH 45/54] Fix test to match new output with status code parsing happening in go-ipfs-redirects-file --- test/sharness/t0109-gateway-web-_redirects.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 893d3fb5de8..1d33f9b719f 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -114,7 +114,7 @@ BAD_REDIRECTS_DIR_HOSTNAME="${BAD_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" test_expect_success "bad codes: request for $BAD_REDIRECTS_DIR_HOSTNAME/not-found returns error about bad code" ' curl -sD - --resolve $BAD_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$BAD_REDIRECTS_DIR_HOSTNAME/not-found" > response && test_should_contain "500" response && - test_should_contain "unsupported redirect status" response + test_should_contain "status code 999 is not supported" response ' # if accessing a path that does exist, don't read _redirects and therefore don't fail parsing From 38ac69d7f2b43afbef83c56e5894af6e38aa4b16 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 16 Sep 2022 14:03:03 -0500 Subject: [PATCH 46/54] Add test for max file size --- .../redirects.car | Bin 3265 -> 69010 bytes test/sharness/t0109-gateway-web-_redirects.sh | 12 +++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/test/sharness/t0109-gateway-web-_redirects-data/redirects.car b/test/sharness/t0109-gateway-web-_redirects-data/redirects.car index 610f283564b790b01634d7250e837478a894b336..600e2b30c288a17379e22b450bf5dd56a1e7c176 100644 GIT binary patch literal 69010 zcmeI!dsGzH9S3lCMOelMcm*>?EQ2Ub#C0FMQPf001E{pZ5%IaQBM*0X$g;d@ET*Zi zsHo^6iaaC+ty)D4CQQFxjS=b ze!u(peQxN-yj2ivS?^yPmE_xta3db>8j-kiSDSzSyH{HB!Ye8#zumOC;o6+{lZ?$a z-AqGj4sXp$7c7c3z2zS!dS*D`E*vb&nSSE()6&HDTY*nKX8eBeodWeCrfY&f|1W+*DHul>~qC3{$fkr#PT!V?DXD^JM*p|sCTbgeX4ax%?94fHFN+` z@p%Swripf`Rgp1bm&Eq5yCNR@#@9`*SFI|W_vjzR{xkCKpRO_+`IK3QV`GW)BEg!% z8`)SAAU>bt<6Lswwl}%LvZGJ*&IIqywd&^0>pXT|y>GnR^pVRT!lVj7uHn2wN@E#{@Qox@Rr)P%`^S?XzCM)%EITG(k(QRZiPKC?+-3LkNNaoyynxG zopH^b^%+s+4OgfALAB&>ek(3E9l1hWZGxaS8LX*1+q}$)QK}S$idPtBX)fX~e zUC(?zQ6I2lh5yKlX9hP-X}x)&`buu&wjras2{Yfy8`G_PiY<#J_6eMPyy(oky^`M9 z&ZicYcRkHfS3X>Kg3tTyqz8q0=LdPY5N45(Z845XvzbloKrx;`!pVJzaaSn`qcL!r zgOc<3>BVn)5m7gAD6hL|38gTG5I#_w|$flt0}vsCQ3HZt2+Fws~Y# zgnPiTRo$82Ju(Id#l{d-pf=E+f}4FF=2RLs7I_ujKJ%+yXH9><<5T$dKWE)7?`p{W zWI@+tCiQnchH9(QKHs&+=LS~~8kO46IQpz{uc5QX{PR(-Wwz7dmEIzDcMa_N-IrjQZaO42)p?AJHBR{Gpnf56F!s00h|$RD| zc^kXh61m7H+Ix!!E>;aMv8ZQT&(Gyz(j`oG`f=q>TS{>S??Vou`-o8H$m8ogGlmIj*B{5akm z{g=^X3R$)+O?W7w%eMt^i@ z%+|b=z}IgtNcxH>wZZIXj(5sFExq$EaT$t&H~$=V{@{Uy3myy`-x|gw?eSZ5&d}Jh zde4g*`c7RCyIgwbY{JPtEtUJqXU{7`%k#Qw`S@sUT-}Cv2xu@?&c3ge#O_r}w{_IFp z&YHLZJ$A5%`08N7$#r4~UGM#*pg1WghV4g7q+GRlSt6yCLQ5pwj&jxF<#wdIiEhWF zSEv?J722QCr$Mv)6F2@&z%IZ0D~>(u?sIm94dD2wmiqXqd=;o%W&?%HX7f4gsG`-`f!t3C@Fa6gfj8P@mohhmg zE6(}Nk{RQ!MVy=+ncJh-4-%6JBAi?&W*|+buiZFW0el#??`kTw?8!q~u-(%qx)gsH zIqXP9`N?nEQ=5AyHCBw@pHW6O&X3V1Xi1NVwkgPHaX0?8!WOh`a6+(YV&sX&lcndD z?Edx21fPwjix-cORW+@riPJ(F2s=eg;3y46?jy_=1x;atLSvwXkWwvPrIKi%uc2-6 zN`KUGT0^C_-d;^i&@w9?VGcoj{cZJqc%@k)PU zJ4pPVc%?sv-5~Bx*=(h$_4BUodZCP)r}z7|e!J2X6}KrTF;=a9aNxZj15{!^jMp!7lJCeA zFLR;^tX<}$S!5N=^e`gOjX*aNS{Mne0_+FW0#rqa01crbG=zrG5E?>5Xb26VAvA=B z&=49zLud#Mp&>MchR_fiLPKZ>4WS`4goe-%8bU*82o0ejG=zrG5E?>5Xb26VAvA=B z&=49zLud#Mp&>MchR_fiLPKZ>4WS`4goe-%8bU*82o0ejG=zrG5E?>5Xb26VAvA=B z&=49zLud#Mp&>MchR_fiLPKZ>4WS`4goe-%8bU*82o0ejG=zrG5E?>5Xb26VAvA=B z&=49zLud#Mp&>MchR_fiLPKZ>4WS`4goe-%8bU*82o0ejG=zrG5E?>5Xb26VAvA=B z&=49zLud#Mp&>MchR_fiLPKZ>4WS`4goe-%8bU*82o0ejG=zrG5E?>5Xb26VAvA=B z&=49zLud#Mp&>MchR_fiLPKZ>4WS`4goe-%8bU*82o0ejG=zrG5E?>5Xb26VAvA=B z&=49zLud#Mp&>MchR_fiLPKZ>4WS`4goe-%8bU*82o0ejG=zrG5E?>5Xb26VAvA=B b&=49zLud#Mp&>MchR_fiLPLgKyUgidb|53K delta 118 zcmbOH_V5y}ig3ct6%p7h^wk9ecy-n}a?HTs9VXl1%_m0A6R s=;HpGzD*C)%TkMqGxPHpuQ3r;yfM~*k&TOqgRzfsVxscq*NoiU0Q<5vm;e9( diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 1d33f9b719f..7a7cc3a74f0 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -16,7 +16,7 @@ test_launch_ipfs_daemon test_expect_success "Add the _redirects file test directory" ' ipfs dag import ../t0109-gateway-web-_redirects-data/redirects.car ' -CAR_ROOT_CID=QmfHFheaikRRB6ap7AdL4FHBkyHPhPBDX7fS25rMzYhLuW +CAR_ROOT_CID=QmQprDnhzptPQZwC5dnSvGgmeF9yKCvnNqWA94yVAZCv3a REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/examples | cut -d "/" -f3) REDIRECTS_DIR_HOSTNAME="${REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" @@ -148,6 +148,16 @@ test_expect_success "invalid file: request for $INVALID_REDIRECTS_DIR_HOSTNAME/n test_should_contain "forced redirects (or \"shadowing\") are not supported by IPFS gateways" response ' +# if accessing a path that doesn't exist and _redirects file is too large, return error +TOO_LARGE_REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/too-large | cut -d "/" -f3) +TOO_LARGE_REDIRECTS_DIR_HOSTNAME="${TOO_LARGE_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" +test_expect_success "invalid file: request for $TOO_LARGE_REDIRECTS_DIR_HOSTNAME/not-found returns error about too large redirects file" ' + curl -sD - --resolve $TOO_LARGE_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$TOO_LARGE_REDIRECTS_DIR_HOSTNAME/not-found" > response && + test_should_contain "500" response && + test_should_contain "could not parse _redirects:" response && + test_should_contain "redirects file size cannot exceed" response +' + test_kill_ipfs_daemon # disable wildcard DNSLink gateway From 2a7b0b6121a504660491caa52ca58c2adf36cae5 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Mon, 19 Sep 2022 09:46:49 -0500 Subject: [PATCH 47/54] Refactor as requested in review --- .../gateway_handler_unixfs__redirects.go | 110 ++++++++++-------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 4c03d5e8e92..79007f17188 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -16,26 +16,6 @@ import ( ) // Resolve the provided path. -// -// Resolving a UnixFS path involves determining if the provided `path.Path` exists and returning the `path.Resolved` -// corresponding to that path. For UnixFS, path resolution is more involved if a `_redirects` file exists, stored -// underneath the root CID of the path. -// -// Example 1: -// If a path exists, we always return the `path.Resolved` corresponding to that path, regardless of the existence of a `_redirects` file. -// -// Example 2: -// If a path does not exist, usually we should return a `nil` resolution path and an error indicating that the path -// doesn't exist. However, a `_redirects` file may exist and contain a redirect rule that redirects that path to a different path. -// We need to evaluate the rule and perform the redirect if present. -// -// Example 3: -// Another possibility is that the path corresponds to a rewrite rule (i.e. a rule with a status of 200). -// In this case, we don't perform a redirect, but do need to return a `path.Resolved` and `path.Path` corresponding to -// the rewrite destination path. -// -// Note that for security reasons, redirect rules are only processed when the request has origin isolation. -// See https://github.com/ipfs/specs/pull/290 for more information. func (i *gatewayHandler) handlePathResolution(w http.ResponseWriter, r *http.Request, responseFormat string, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { // Attempt to resolve the provided path. resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) @@ -51,37 +31,9 @@ func (i *gatewayHandler) handlePathResolution(w http.ResponseWriter, r *http.Req // The path can't be resolved. // If we have origin isolation, attempt to handle any redirect rules. if hasOriginIsolation(r) { - redirectsFile := i.getRedirectsFile(r, contentPath, logger) - if redirectsFile != nil { - redirectRules, err := i.getRedirectRules(r, redirectsFile) - if err != nil { - internalWebError(w, err) - return nil, nil, false - } - - redirected, newPath, err := i.handleRedirectsFileRules(w, r, contentPath, redirectRules) - if err != nil { - err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) - internalWebError(w, err) - return nil, nil, false - } - - if redirected { - return nil, nil, false - } - - // 200 is treated as a rewrite, so update the path and continue - if newPath != "" { - // Reassign contentPath and resolvedPath since the URL was rewritten - contentPath = ipath.New(newPath) - resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath) - if err != nil { - internalWebError(w, err) - return nil, nil, false - } - - return resolvedPath, contentPath, true - } + resolvedPath, contentPath, ok, hadMatchingRule := i.serveRedirectsIfPresent(w, r, resolvedPath, contentPath, logger) + if hadMatchingRule { + return resolvedPath, contentPath, ok } } @@ -103,6 +55,62 @@ func (i *gatewayHandler) handlePathResolution(w http.ResponseWriter, r *http.Req } } +// Resolving a UnixFS path involves determining if the provided `path.Path` exists and returning the `path.Resolved` +// corresponding to that path. For UnixFS, path resolution is more involved if a `_redirects` file exists, stored +// underneath the root CID of the path. +// +// Example 1: +// If a path exists, we always return the `path.Resolved` corresponding to that path, regardless of the existence of a `_redirects` file. +// +// Example 2: +// If a path does not exist, usually we should return a `nil` resolution path and an error indicating that the path +// doesn't exist. However, a `_redirects` file may exist and contain a redirect rule that redirects that path to a different path. +// We need to evaluate the rule and perform the redirect if present. +// +// Example 3: +// Another possibility is that the path corresponds to a rewrite rule (i.e. a rule with a status of 200). +// In this case, we don't perform a redirect, but do need to return a `path.Resolved` and `path.Path` corresponding to +// the rewrite destination path. +// +// Note that for security reasons, redirect rules are only processed when the request has origin isolation. +// See https://github.com/ipfs/specs/pull/290 for more information. +func (i *gatewayHandler) serveRedirectsIfPresent(w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, logger *zap.SugaredLogger) (newResolvedPath ipath.Resolved, newContentPath ipath.Path, ok bool, hadMatchingRule bool) { + redirectsFile := i.getRedirectsFile(r, contentPath, logger) + if redirectsFile != nil { + redirectRules, err := i.getRedirectRules(r, redirectsFile) + if err != nil { + internalWebError(w, err) + return nil, nil, false, true + } + + redirected, newPath, err := i.handleRedirectsFileRules(w, r, contentPath, redirectRules) + if err != nil { + err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err) + internalWebError(w, err) + return nil, nil, false, true + } + + if redirected { + return nil, nil, false, true + } + + // 200 is treated as a rewrite, so update the path and continue + if newPath != "" { + // Reassign contentPath and resolvedPath since the URL was rewritten + contentPath = ipath.New(newPath) + resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath) + if err != nil { + internalWebError(w, err) + return nil, nil, false, true + } + + return resolvedPath, contentPath, true, true + } + } + // No matching rule + return resolvedPath, contentPath, false, false +} + func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, redirectRules []redirects.Rule) (bool, string, error) { // Attempt to match a rule to the URL path, and perform the corresponding redirect or rewrite pathParts := strings.Split(contentPath.String(), "/") From 462cd1a1d40bead7912289f08aa508e09950b01b Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 21 Sep 2022 13:00:21 -0500 Subject: [PATCH 48/54] Missing changes from lidel --- .../gateway_handler_unixfs__redirects.go | 1 + test/sharness/t0109-gateway-web-_redirects.sh | 42 +++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 79007f17188..a10b3d8b2f1 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -237,6 +237,7 @@ func (i *gatewayHandler) serve404(w http.ResponseWriter, r *http.Request, conten log.Debugw("using _redirects 404 file", "path", content404Path) w.Header().Set("Content-Type", "text/html") w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) + addCacheControlHeaders(w, r, content404Path, resolved404Path.Cid()) w.WriteHeader(http.StatusNotFound) _, err = io.CopyN(w, f, size) return err diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 7a7cc3a74f0..2b97587bd3c 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -23,22 +23,20 @@ REDIRECTS_DIR_HOSTNAME="${REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/redirect-one" > response && - test_should_contain "one.html" response && test_should_contain "301 Moved Permanently" response && - test_should_contain "Location:" response + test_should_contain "Location: /one.html" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/301-redirect-one redirects with 301, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/301-redirect-one" > response && - test_should_contain "one.html" response && - test_should_contain "301 Moved Permanently" response + test_should_contain "301 Moved Permanently" response && + test_should_contain "Location: /one.html" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/302-redirect-two redirects with 302, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/302-redirect-two" > response && - test_should_contain "two.html" response && test_should_contain "302 Found" response && - test_should_contain "Location:" response + test_should_contain "Location: /two.html" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/200-index returns 200, per _redirects file" ' @@ -49,21 +47,22 @@ test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/200-index returns 200, test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/posts/:year/:month/:day/:title redirects with 301 and placeholders, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/posts/2022/01/01/hello-world" > response && - test_should_contain "/articles/2022/01/01/hello-world" response && test_should_contain "301 Moved Permanently" response && - test_should_contain "Location:" response + test_should_contain "Location: /articles/2022/01/01/hello-world" response ' test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/splat/one.html redirects with 301 and splat placeholder, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/splat/one.html" > response && - test_should_contain "/redirected-splat/one.html" response && test_should_contain "301 Moved Permanently" response && - test_should_contain "Location:" response + test_should_contain "Location: /redirected-splat/one.html" response ' +# ensure custom 404 works and has the same cache headers as regular /ipfs/ path test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/en/has-no-redirects-entry returns custom 404, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/not-found/has-no-redirects-entry" > response && test_should_contain "404 Not Found" response && + test_should_contain "Cache-Control: public, max-age=29030400, immutable" response && + test_should_contain "Etag: \"Qmd9GD7Bauh6N2ZLfNnYS3b7QVAijbud83b8GE8LPMNBBP\"" response && test_should_contain "my 404" response ' @@ -73,7 +72,8 @@ test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/catch-all returns 200, test_should_contain "my index" response ' -test_expect_success "request for http://127.0.0.1:$GWAY_PORT/ipfs/$REDIRECTS_DIR_CID/301-redirect-one returns 404, no _redirects since no origin isolation" ' +# This test ensures _redirects is supported only on Web Gateways that use Host header (DNSLink, Subdomain) +test_expect_success "request for http://127.0.0.1:$GWAY_PORT/ipfs/$REDIRECTS_DIR_CID/301-redirect-one returns generic 404 (no custom 404 from _redirects since no origin isolation)" ' curl -sD - "http://127.0.0.1:$GWAY_PORT/ipfs/$REDIRECTS_DIR_CID/301-redirect-one" > response && test_should_contain "404 Not Found" response && test_should_not_contain "my 404" response @@ -90,9 +90,8 @@ test_expect_success "newline: _redirects has CRLF line terminators" ' test_expect_success "newline: request for $NEWLINE_REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' curl -sD - --resolve $NEWLINE_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$NEWLINE_REDIRECTS_DIR_HOSTNAME/redirect-one" > response && - test_should_contain "one.html" response && test_should_contain "301 Moved Permanently" response && - test_should_contain "Location:" response + test_should_contain "Location: /one.html" response ' # Good codes @@ -101,9 +100,8 @@ GOOD_REDIRECTS_DIR_HOSTNAME="${GOOD_REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT test_expect_success "good codes: request for $GOOD_REDIRECTS_DIR_HOSTNAME/redirect-one redirects with default of 301, per _redirects file" ' curl -sD - --resolve $GOOD_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$GOOD_REDIRECTS_DIR_HOSTNAME/a301" > response && - test_should_contain "b301" response && test_should_contain "301 Moved Permanently" response && - test_should_contain "Location:" response + test_should_contain "Location: /b301" response ' # Bad codes @@ -195,9 +193,19 @@ test_expect_success "spoofed DNSLink record resolves in cli" " test_expect_success "request for $DNSLINK_FQDN/redirect-one redirects with default of 301, per _redirects file" ' curl -sD - --resolve $DNSLINK_FQDN:$GWAY_PORT:127.0.0.1 "http://$DNSLINK_FQDN:$GWAY_PORT/redirect-one" > response && - test_should_contain "one.html" response && test_should_contain "301 Moved Permanently" response && - test_should_contain "Location:" response + test_should_contain "Location: /one.html" response +' + +# ensure custom 404 works and has the same cache headers as regular /ipns/ paths +test_expect_success "request for $DNSLINK_FQDN/en/has-no-redirects-entry returns custom 404, per _redirects file" ' + curl -sD - --resolve $DNSLINK_FQDN:$GWAY_PORT:127.0.0.1 "http://$DNSLINK_FQDN:$GWAY_PORT/not-found/has-no-redirects-entry" > response && + test_should_contain "404 Not Found" response && + test_should_contain "Etag: \"Qmd9GD7Bauh6N2ZLfNnYS3b7QVAijbud83b8GE8LPMNBBP\"" response && + test_should_not_contain "Cache-Control: public, max-age=29030400, immutable" response && + test_should_not_contain "immutable" response && + test_should_contain "Date: " response && + test_should_contain "my 404" response ' test_expect_success "request for $NO_DNSLINK_FQDN/redirect-one does not redirect, since DNSLink is disabled" ' From 8bfa333b5be2e0a5c3a6995567fc4540db2b2510 Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Wed, 21 Sep 2022 13:31:02 -0500 Subject: [PATCH 49/54] fix go mod tidy error in ci --- docs/examples/kubo-as-a-library/go.sum | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index 30ccf800f64..107392e3216 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -571,6 +571,7 @@ github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-provider v0.7.1 h1:eKToBUAb6ZY8iiA6AYVxzW4G1ep67XUaaEBUIYpxhfw= github.com/ipfs/go-ipfs-provider v0.7.1/go.mod h1:QwdDYRYnC5sYGLlOwVDY/0ZB6T3zcMtu+5+GdGeUuw8= +github.com/ipfs/go-ipfs-redirects-file v0.0.0-20220915124754-4946b031ee37/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= github.com/ipfs/go-ipfs-routing v0.0.1/go.mod h1:k76lf20iKFxQTjcJokbPM9iBXVXVZhcOwc360N4nuKs= github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= github.com/ipfs/go-ipfs-routing v0.2.1 h1:E+whHWhJkdN9YeoHZNj5itzc+OR292AJ2uE9FFiW0BY= @@ -1535,9 +1536,11 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= +github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb/go.mod h1:ikPs9bRWicNw3S7XpJ8sK/smGwU9WcSVU3dy9qahYBM= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -2179,6 +2182,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From eb7138db3e0db75fae6aa28f4413f9fcc9bde1cf Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Thu, 22 Sep 2022 16:25:26 +0200 Subject: [PATCH 50/54] chore: go-ipfs-redirects-file v0.1.0 https://github.com/ipfs/go-ipfs-redirects-file/releases/tag/v0.1.0 --- docs/examples/kubo-as-a-library/go.sum | 2 +- go.mod | 7 +++---- go.sum | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index 107392e3216..50eee5be407 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -571,7 +571,7 @@ github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-provider v0.7.1 h1:eKToBUAb6ZY8iiA6AYVxzW4G1ep67XUaaEBUIYpxhfw= github.com/ipfs/go-ipfs-provider v0.7.1/go.mod h1:QwdDYRYnC5sYGLlOwVDY/0ZB6T3zcMtu+5+GdGeUuw8= -github.com/ipfs/go-ipfs-redirects-file v0.0.0-20220915124754-4946b031ee37/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= +github.com/ipfs/go-ipfs-redirects-file v0.1.0/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= github.com/ipfs/go-ipfs-routing v0.0.1/go.mod h1:k76lf20iKFxQTjcJokbPM9iBXVXVZhcOwc360N4nuKs= github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= github.com/ipfs/go-ipfs-routing v0.2.1 h1:E+whHWhJkdN9YeoHZNj5itzc+OR292AJ2uE9FFiW0BY= diff --git a/go.mod b/go.mod index cc9a4fcceac..b156e373932 100644 --- a/go.mod +++ b/go.mod @@ -117,7 +117,7 @@ require ( require ( github.com/benbjohnson/clock v1.3.0 github.com/ipfs/go-delegated-routing v0.6.0 - github.com/ipfs/go-ipfs-redirects-file v0.0.0-20220915124754-4946b031ee37 + github.com/ipfs/go-ipfs-redirects-file v0.1.0 github.com/ipfs/go-log/v2 v2.5.1 ) @@ -170,9 +170,8 @@ require ( github.com/ipfs/go-peertaskqueue v0.7.1 // indirect github.com/ipld/edelweiss v0.2.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect - github.com/justincjohnson/go-ipfs-redirects v0.0.0-20220421174837-04fcc92d1018 // indirect - github.com/klauspost/compress v1.15.1 // indirect - github.com/klauspost/cpuid/v2 v2.1.0 // indirect + github.com/klauspost/compress v1.15.10 // indirect + github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/koron/go-ssdp v0.0.3 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect diff --git a/go.sum b/go.sum index 4f189fdf571..250b46b036c 100644 --- a/go.sum +++ b/go.sum @@ -565,8 +565,8 @@ github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-provider v0.7.1 h1:eKToBUAb6ZY8iiA6AYVxzW4G1ep67XUaaEBUIYpxhfw= github.com/ipfs/go-ipfs-provider v0.7.1/go.mod h1:QwdDYRYnC5sYGLlOwVDY/0ZB6T3zcMtu+5+GdGeUuw8= -github.com/ipfs/go-ipfs-redirects-file v0.0.0-20220915124754-4946b031ee37 h1:dLsZj3RA40WYZHJr4L+S31/BJkOMDlwXyvEYgY/Nv8c= -github.com/ipfs/go-ipfs-redirects-file v0.0.0-20220915124754-4946b031ee37/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= +github.com/ipfs/go-ipfs-redirects-file v0.1.0 h1:fD7iK1TAb294Kx7ps3eq1ofzy5EJyx7t0dl4Ds0FtVU= +github.com/ipfs/go-ipfs-redirects-file v0.1.0/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= github.com/ipfs/go-ipfs-routing v0.0.1/go.mod h1:k76lf20iKFxQTjcJokbPM9iBXVXVZhcOwc360N4nuKs= github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= github.com/ipfs/go-ipfs-routing v0.2.1 h1:E+whHWhJkdN9YeoHZNj5itzc+OR292AJ2uE9FFiW0BY= From 0ae85450fc6a543866c091983a8db57d030eb0aa Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Fri, 23 Sep 2022 00:50:35 +0200 Subject: [PATCH 51/54] refactor: move handlePathResolution - this function is core to all response types, does not belong to _redirects specific file - small improvements that improve readability and remove surface for bugs --- core/corehttp/gateway_handler.go | 44 +++++++++++ .../gateway_handler_unixfs__redirects.go | 73 +++++-------------- 2 files changed, 62 insertions(+), 55 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index e3be79e31bb..92e3d7c23f2 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -24,6 +24,7 @@ import ( mfs "github.com/ipfs/go-mfs" path "github.com/ipfs/go-path" "github.com/ipfs/go-path/resolver" + coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" routing "github.com/libp2p/go-libp2p/core/routing" prometheus "github.com/prometheus/client_golang/prometheus" @@ -961,6 +962,49 @@ func debugStr(path string) string { return q } +// Resolve the provided contentPath including any special handling related to +// the requested responseFormat. Returned ok flag indicates if gateway handler +// should continue processing the request. +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) { + // Attempt to resolve the provided path. + resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) + + switch err { + case nil: + return resolvedPath, contentPath, true + case coreiface.ErrOffline: + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable) + return nil, nil, false + default: + // The path can't be resolved. + if isUnixfsResponseFormat(responseFormat) { + // If we have origin isolation (subdomain gw, DNSLink website), + // and response type is UnixFS (default for website hosting) + // check for presence of _redirects file and apply rules defined there. + // See: https://github.com/ipfs/specs/pull/290 + if hasOriginIsolation(r) { + resolvedPath, newContentPath, ok, hadMatchingRule := i.serveRedirectsIfPresent(w, r, resolvedPath, contentPath, logger) + if hadMatchingRule { + logger.Debugw("applied a rule from _redirects file") + return resolvedPath, newContentPath, ok + } + } + + // if Accept is text/html, see if ipfs-404.html is present + // This logic isn't documented and will likely be removed at some point. + // Any 404 logic in _redirects above will have already run by this time, so it's really an extra fall back + if i.serveLegacy404IfPresent(w, r, contentPath) { + logger.Debugw("served legacy 404") + return nil, nil, false + } + } + + // Note: webError will replace http.StatusBadRequest with StatusNotFound if necessary + webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusBadRequest) + return nil, nil, false + } +} + // Detect 'Cache-Control: only-if-cached' in request and return data if it is already in the local datastore. // https://github.com/ipfs/specs/blob/main/http-gateways/PATH_GATEWAY.md#cache-control-request-header func (i *gatewayHandler) handleOnlyIfCached(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (requestHandled bool) { diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index a10b3d8b2f1..78357eb0ec1 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -15,66 +15,29 @@ import ( "go.uber.org/zap" ) -// Resolve the provided path. -func (i *gatewayHandler) handlePathResolution(w http.ResponseWriter, r *http.Request, responseFormat string, contentPath ipath.Path, logger *zap.SugaredLogger) (ipath.Resolved, ipath.Path, bool) { - // Attempt to resolve the provided path. - resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath) - - switch err { - case nil: - return resolvedPath, contentPath, true - case coreiface.ErrOffline: - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable) - return nil, nil, false - default: - if isUnixfsResponseFormat(responseFormat) { - // The path can't be resolved. - // If we have origin isolation, attempt to handle any redirect rules. - if hasOriginIsolation(r) { - resolvedPath, contentPath, ok, hadMatchingRule := i.serveRedirectsIfPresent(w, r, resolvedPath, contentPath, logger) - if hadMatchingRule { - return resolvedPath, contentPath, ok - } - } - - // if Accept is text/html, see if ipfs-404.html is present - // This logic isn't documented and will likely be removed at some point. - // Any 404 logic in _redirects above will have already run by this time, so it's really an extra fall back - if i.serveLegacy404IfPresent(w, r, contentPath) { - logger.Debugw("serve pretty 404 if present") - return nil, nil, false - } - - // Fallback - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusBadRequest) - return nil, nil, false - } else { - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusNotFound) - return nil, nil, false - } - } -} - // Resolving a UnixFS path involves determining if the provided `path.Path` exists and returning the `path.Resolved` -// corresponding to that path. For UnixFS, path resolution is more involved if a `_redirects` file exists, stored -// underneath the root CID of the path. +// corresponding to that path. For UnixFS, path resolution is more involved. +// +// When a path under requested CID does not exist, Gateway will check if a `_redirects` file exists +// underneath the root CID of the path, and apply rules defined there. +// See sepcification introduced in: https://github.com/ipfs/specs/pull/290 // -// Example 1: +// Scenario 1: // If a path exists, we always return the `path.Resolved` corresponding to that path, regardless of the existence of a `_redirects` file. // -// Example 2: +// Scenario 2: // If a path does not exist, usually we should return a `nil` resolution path and an error indicating that the path // doesn't exist. However, a `_redirects` file may exist and contain a redirect rule that redirects that path to a different path. // We need to evaluate the rule and perform the redirect if present. // -// Example 3: +// Scenario 3: // Another possibility is that the path corresponds to a rewrite rule (i.e. a rule with a status of 200). // In this case, we don't perform a redirect, but do need to return a `path.Resolved` and `path.Path` corresponding to // the rewrite destination path. // // Note that for security reasons, redirect rules are only processed when the request has origin isolation. // See https://github.com/ipfs/specs/pull/290 for more information. -func (i *gatewayHandler) serveRedirectsIfPresent(w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, logger *zap.SugaredLogger) (newResolvedPath ipath.Resolved, newContentPath ipath.Path, ok bool, hadMatchingRule bool) { +func (i *gatewayHandler) serveRedirectsIfPresent(w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, logger *zap.SugaredLogger) (newResolvedPath ipath.Resolved, newContentPath ipath.Path, continueProcessing bool, hadMatchingRule bool) { redirectsFile := i.getRedirectsFile(r, contentPath, logger) if redirectsFile != nil { redirectRules, err := i.getRedirectRules(r, redirectsFile) @@ -107,11 +70,11 @@ func (i *gatewayHandler) serveRedirectsIfPresent(w http.ResponseWriter, r *http. return resolvedPath, contentPath, true, true } } - // No matching rule - return resolvedPath, contentPath, false, false + // No matching rule, paths remain the same, continue regular processing + return resolvedPath, contentPath, true, false } -func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, redirectRules []redirects.Rule) (bool, string, error) { +func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, redirectRules []redirects.Rule) (redirected bool, newContentPath string, err error) { // Attempt to match a rule to the URL path, and perform the corresponding redirect or rewrite pathParts := strings.Split(contentPath.String(), "/") if len(pathParts) > 3 { @@ -146,18 +109,18 @@ func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http if rule.Status == 410 { webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), coreiface.ErrResolveFailed, http.StatusGone) - return true, rule.To, nil + return true, "", nil } if rule.Status == 451 { webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), coreiface.ErrResolveFailed, http.StatusUnavailableForLegalReasons) - return true, rule.To, nil + return true, "", nil } // Or redirect if rule.Status >= 301 && rule.Status <= 308 { http.Redirect(w, r, rule.To, rule.Status) - return true, rule.To, nil + return true, "", nil } } } @@ -170,20 +133,20 @@ func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipa // Convert the path into a file node node, err := i.api.Unixfs().Get(r.Context(), redirectsFilePath) if err != nil { - return nil, fmt.Errorf("could not get _redirects node: %v", err) + return nil, fmt.Errorf("could not get _redirects: %w", err) } defer node.Close() // Convert the node into a file f, ok := node.(files.File) if !ok { - return nil, fmt.Errorf("could not parse _redirects: %v", err) + return nil, fmt.Errorf("could not parse _redirects: %w", err) } // Parse redirect rules from file redirectRules, err := redirects.Parse(f) if err != nil { - return nil, fmt.Errorf("could not parse _redirects: %v", err) + return nil, fmt.Errorf("could not parse _redirects: %w", err) } return redirectRules, nil From a9a065876cd22cc02ad82c2d953faa85cff229bb Mon Sep 17 00:00:00 2001 From: Justin Johnson Date: Fri, 23 Sep 2022 10:17:18 -0500 Subject: [PATCH 52/54] Serve custom 410 and 451 pages as well --- .../gateway_handler_unixfs__redirects.go | 35 ++++++------------ .../redirects.car | Bin 69010 -> 69257 bytes test/sharness/t0109-gateway-web-_redirects.sh | 27 ++++++++++++-- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 78357eb0ec1..59d0c8396cd 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -10,7 +10,6 @@ import ( files "github.com/ipfs/go-ipfs-files" redirects "github.com/ipfs/go-ipfs-redirects-file" - coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" "go.uber.org/zap" ) @@ -99,24 +98,14 @@ func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http return false, toPath, nil } - // Or 400s - if rule.Status == 404 { + // Or 4xx + if rule.Status == 404 || rule.Status == 410 || rule.Status == 451 { toPath := rootPath + rule.To - content404Path := ipath.New(toPath) - err := i.serve404(w, r, content404Path) + content4xxPath := ipath.New(toPath) + err := i.serve4xx(w, r, content4xxPath, rule.Status) return true, toPath, err } - if rule.Status == 410 { - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), coreiface.ErrResolveFailed, http.StatusGone) - return true, "", nil - } - - if rule.Status == 451 { - webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), coreiface.ErrResolveFailed, http.StatusUnavailableForLegalReasons) - return true, "", nil - } - // Or redirect if rule.Status >= 301 && rule.Status <= 308 { http.Redirect(w, r, rule.To, rule.Status) @@ -175,13 +164,13 @@ func getRootPath(path ipath.Path) ipath.Path { return ipath.New(gopath.Join("/", path.Namespace(), parts[2])) } -func (i *gatewayHandler) serve404(w http.ResponseWriter, r *http.Request, content404Path ipath.Path) error { - resolved404Path, err := i.api.ResolvePath(r.Context(), content404Path) +func (i *gatewayHandler) serve4xx(w http.ResponseWriter, r *http.Request, content4xxPath ipath.Path, status int) error { + resolved4xxPath, err := i.api.ResolvePath(r.Context(), content4xxPath) if err != nil { return err } - node, err := i.api.Unixfs().Get(r.Context(), resolved404Path) + node, err := i.api.Unixfs().Get(r.Context(), resolved4xxPath) if err != nil { return err } @@ -189,19 +178,19 @@ func (i *gatewayHandler) serve404(w http.ResponseWriter, r *http.Request, conten f, ok := node.(files.File) if !ok { - return fmt.Errorf("could not convert node for 404 page to file") + return fmt.Errorf("could not convert node for %d page to file", status) } size, err := f.Size() if err != nil { - return fmt.Errorf("could not get size of 404 page") + return fmt.Errorf("could not get size of %d page", status) } - log.Debugw("using _redirects 404 file", "path", content404Path) + log.Debugf("using _redirects %d file, path=%v", status, content4xxPath) w.Header().Set("Content-Type", "text/html") w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) - addCacheControlHeaders(w, r, content404Path, resolved404Path.Cid()) - w.WriteHeader(http.StatusNotFound) + addCacheControlHeaders(w, r, content4xxPath, resolved4xxPath.Cid()) + w.WriteHeader(status) _, err = io.CopyN(w, f, size) return err } diff --git a/test/sharness/t0109-gateway-web-_redirects-data/redirects.car b/test/sharness/t0109-gateway-web-_redirects-data/redirects.car index 600e2b30c288a17379e22b450bf5dd56a1e7c176..ba4da8ba535d3fd0fb32546fda7f855e3d1232dd 100644 GIT binary patch delta 586 zcmbO@x)jw!%3cV#l8QQN4TgwU7dHPHZn}ALQ(s)!SCFS z$7e^Xah((5NUccBEyzhNmUzl3q{pQ+`2mOW#@AOERlWgjAf$WpVY1;=CW ztT$&h%%0;RS@U4Y_Qn6WrNu&f z<{UV+ztmSm#VL&pbrZZu+93V sd5L9-nK@tysMi&MVZa3v$jnPgtpJCHf{_8xm3x~HvTi@f%6O3-05k delta 337 zcmeC2%Q9&;ORUAB)S~?SlH$f2S`o?&LJBG$maBxeFMpz4S$O$oh1L9p?oP+29KI3L z7?E=9VndFYPh z#tEr1oECgS9H|wFxdl0?#S&XMHXixSs4)p>GC^GiTuPI7uqaLDWac$C=28+;h*_98 zagxlWrzffyPUgIPrr787FY?8_uZIhkBz$$}N?#$w6sUc?a9}9c+vj*#TKBkF@{* diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 2b97587bd3c..22e7618d062 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -16,7 +16,7 @@ test_launch_ipfs_daemon test_expect_success "Add the _redirects file test directory" ' ipfs dag import ../t0109-gateway-web-_redirects-data/redirects.car ' -CAR_ROOT_CID=QmQprDnhzptPQZwC5dnSvGgmeF9yKCvnNqWA94yVAZCv3a +CAR_ROOT_CID=QmQyqMY5vUBSbSxyitJqthgwZunCQjDVtNd8ggVCxzuPQ4 REDIRECTS_DIR_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/examples | cut -d "/" -f3) REDIRECTS_DIR_HOSTNAME="${REDIRECTS_DIR_CID}.ipfs.localhost:$GWAY_PORT" @@ -57,15 +57,34 @@ test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/splat/one.html redirect test_should_contain "Location: /redirected-splat/one.html" response ' -# ensure custom 404 works and has the same cache headers as regular /ipfs/ path -test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/en/has-no-redirects-entry returns custom 404, per _redirects file" ' +# ensure custom 4xx works and has the same cache headers as regular /ipfs/ path +CUSTOM_4XX_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/examples/404.html | cut -d "/" -f3) +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/not-found/has-no-redirects-entry returns custom 404, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/not-found/has-no-redirects-entry" > response && test_should_contain "404 Not Found" response && test_should_contain "Cache-Control: public, max-age=29030400, immutable" response && - test_should_contain "Etag: \"Qmd9GD7Bauh6N2ZLfNnYS3b7QVAijbud83b8GE8LPMNBBP\"" response && + test_should_contain "Etag: \"$CUSTOM_4XX_CID\"" response && test_should_contain "my 404" response ' +CUSTOM_4XX_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/examples/410.html | cut -d "/" -f3) +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/gone/has-no-redirects-entry returns custom 410, per _redirects file" ' + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/gone/has-no-redirects-entry" > response && + test_should_contain "410 Gone" response && + test_should_contain "Cache-Control: public, max-age=29030400, immutable" response && + test_should_contain "Etag: \"$CUSTOM_4XX_CID\"" response && + test_should_contain "my 410" response +' + +CUSTOM_4XX_CID=$(ipfs resolve -r /ipfs/$CAR_ROOT_CID/examples/451.html | cut -d "/" -f3) +test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/unavail/has-no-redirects-entry returns custom 451, per _redirects file" ' + curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/unavail/has-no-redirects-entry" > response && + test_should_contain "451 Unavailable For Legal Reasons" response && + test_should_contain "Cache-Control: public, max-age=29030400, immutable" response && + test_should_contain "Etag: \"$CUSTOM_4XX_CID\"" response && + test_should_contain "my 451" response +' + test_expect_success "request for $REDIRECTS_DIR_HOSTNAME/catch-all returns 200, per _redirects file" ' curl -sD - --resolve $REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$REDIRECTS_DIR_HOSTNAME/catch-all" > response && test_should_contain "200 OK" response && From e18d6c7f949e2a5b930374313797d7179f9985b3 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Fri, 23 Sep 2022 18:09:42 +0200 Subject: [PATCH 53/54] core: go-ipfs-redirects-file v0.1.1 https://github.com/ipfs/go-ipfs-redirects-file/releases/tag/v0.1.1 --- core/corehttp/gateway_handler_unixfs__redirects.go | 2 +- docs/examples/kubo-as-a-library/go.sum | 2 +- go.mod | 2 +- go.sum | 4 ++-- test/sharness/t0109-gateway-web-_redirects.sh | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index 59d0c8396cd..f7680e8cb58 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -186,7 +186,7 @@ func (i *gatewayHandler) serve4xx(w http.ResponseWriter, r *http.Request, conten return fmt.Errorf("could not get size of %d page", status) } - log.Debugf("using _redirects %d file, path=%v", status, content4xxPath) + log.Debugf("using _redirects: custom %d file at %q", status, content4xxPath) w.Header().Set("Content-Type", "text/html") w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) addCacheControlHeaders(w, r, content4xxPath, resolved4xxPath.Cid()) diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index 50eee5be407..1c06099cfbd 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -571,7 +571,7 @@ github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-provider v0.7.1 h1:eKToBUAb6ZY8iiA6AYVxzW4G1ep67XUaaEBUIYpxhfw= github.com/ipfs/go-ipfs-provider v0.7.1/go.mod h1:QwdDYRYnC5sYGLlOwVDY/0ZB6T3zcMtu+5+GdGeUuw8= -github.com/ipfs/go-ipfs-redirects-file v0.1.0/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= +github.com/ipfs/go-ipfs-redirects-file v0.1.1/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= github.com/ipfs/go-ipfs-routing v0.0.1/go.mod h1:k76lf20iKFxQTjcJokbPM9iBXVXVZhcOwc360N4nuKs= github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= github.com/ipfs/go-ipfs-routing v0.2.1 h1:E+whHWhJkdN9YeoHZNj5itzc+OR292AJ2uE9FFiW0BY= diff --git a/go.mod b/go.mod index b156e373932..50994a61343 100644 --- a/go.mod +++ b/go.mod @@ -117,7 +117,7 @@ require ( require ( github.com/benbjohnson/clock v1.3.0 github.com/ipfs/go-delegated-routing v0.6.0 - github.com/ipfs/go-ipfs-redirects-file v0.1.0 + github.com/ipfs/go-ipfs-redirects-file v0.1.1 github.com/ipfs/go-log/v2 v2.5.1 ) diff --git a/go.sum b/go.sum index 250b46b036c..69613172178 100644 --- a/go.sum +++ b/go.sum @@ -565,8 +565,8 @@ github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-provider v0.7.1 h1:eKToBUAb6ZY8iiA6AYVxzW4G1ep67XUaaEBUIYpxhfw= github.com/ipfs/go-ipfs-provider v0.7.1/go.mod h1:QwdDYRYnC5sYGLlOwVDY/0ZB6T3zcMtu+5+GdGeUuw8= -github.com/ipfs/go-ipfs-redirects-file v0.1.0 h1:fD7iK1TAb294Kx7ps3eq1ofzy5EJyx7t0dl4Ds0FtVU= -github.com/ipfs/go-ipfs-redirects-file v0.1.0/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= +github.com/ipfs/go-ipfs-redirects-file v0.1.1 h1:Io++k0Vf/wK+tfnhEh63Yte1oQK5VGT2hIEYpD0Rzx8= +github.com/ipfs/go-ipfs-redirects-file v0.1.1/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= github.com/ipfs/go-ipfs-routing v0.0.1/go.mod h1:k76lf20iKFxQTjcJokbPM9iBXVXVZhcOwc360N4nuKs= github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= github.com/ipfs/go-ipfs-routing v0.2.1 h1:E+whHWhJkdN9YeoHZNj5itzc+OR292AJ2uE9FFiW0BY= diff --git a/test/sharness/t0109-gateway-web-_redirects.sh b/test/sharness/t0109-gateway-web-_redirects.sh index 22e7618d062..9ebfb911e35 100755 --- a/test/sharness/t0109-gateway-web-_redirects.sh +++ b/test/sharness/t0109-gateway-web-_redirects.sh @@ -162,7 +162,7 @@ test_expect_success "invalid file: request for $INVALID_REDIRECTS_DIR_HOSTNAME/n curl -sD - --resolve $INVALID_REDIRECTS_DIR_HOSTNAME:127.0.0.1 "http://$INVALID_REDIRECTS_DIR_HOSTNAME/not-found" > response && test_should_contain "500" response && test_should_contain "could not parse _redirects:" response && - test_should_contain "forced redirects (or \"shadowing\") are not supported by IPFS gateways" response + test_should_contain "forced redirects (or \"shadowing\") are not supported" response ' # if accessing a path that doesn't exist and _redirects file is too large, return error From 907dc6d096aa3a2dd20baefb884de28ab2b386a3 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Fri, 23 Sep 2022 18:24:46 +0200 Subject: [PATCH 54/54] refactor: move serveLegacy404IfPresent Reduce noise by moving legacy redirect logic to *redirects.go --- core/corehttp/gateway_handler.go | 76 ------------------- .../gateway_handler_unixfs__redirects.go | 75 ++++++++++++++++++ 2 files changed, 75 insertions(+), 76 deletions(-) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 92e3d7c23f2..585ca89cb17 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -13,7 +13,6 @@ import ( gopath "path" "regexp" "runtime/debug" - "strconv" "strings" "time" @@ -438,39 +437,6 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request } } -// Deprecated: legacy ipfs-404.html files are superseded by _redirects file -// This is provided only for backward-compatibility, until websites migrate -// to 404s managed via _redirects file (https://github.com/ipfs/specs/pull/290) -func (i *gatewayHandler) serveLegacy404IfPresent(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) bool { - resolved404Path, ctype, err := i.searchUpTreeFor404(r, contentPath) - if err != nil { - return false - } - - dr, err := i.api.Unixfs().Get(r.Context(), resolved404Path) - if err != nil { - return false - } - defer dr.Close() - - f, ok := dr.(files.File) - if !ok { - return false - } - - size, err := f.Size() - if err != nil { - return false - } - - log.Debugw("using pretty 404 file", "path", contentPath) - w.Header().Set("Content-Type", ctype) - w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) - w.WriteHeader(http.StatusNotFound) - _, err = io.CopyN(w, f, size) - return err == nil -} - func (i *gatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) { p, err := i.api.Unixfs().Add(r.Context(), files.NewReaderFile(r.Body)) if err != nil { @@ -911,48 +877,6 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string] return "", nil, nil } -func (i *gatewayHandler) searchUpTreeFor404(r *http.Request, contentPath ipath.Path) (ipath.Resolved, string, error) { - filename404, ctype, err := preferred404Filename(r.Header.Values("Accept")) - if err != nil { - return nil, "", err - } - - pathComponents := strings.Split(contentPath.String(), "/") - - for idx := len(pathComponents); idx >= 3; idx-- { - pretty404 := gopath.Join(append(pathComponents[0:idx], filename404)...) - parsed404Path := ipath.New("/" + pretty404) - if parsed404Path.IsValid() != nil { - break - } - resolvedPath, err := i.api.ResolvePath(r.Context(), parsed404Path) - if err != nil { - continue - } - return resolvedPath, ctype, nil - } - - return nil, "", fmt.Errorf("no pretty 404 in any parent folder") -} - -func preferred404Filename(acceptHeaders []string) (string, string, error) { - // If we ever want to offer a 404 file for a different content type - // then this function will need to parse q weightings, but for now - // the presence of anything matching HTML is enough. - for _, acceptHeader := range acceptHeaders { - accepted := strings.Split(acceptHeader, ",") - for _, spec := range accepted { - contentType := strings.SplitN(spec, ";", 1)[0] - switch contentType { - case "*/*", "text/*", "text/html": - return "ipfs-404.html", "text/html", nil - } - } - } - - return "", "", fmt.Errorf("there is no 404 file for the requested content types") -} - // returns unquoted path with all special characters revealed as \u codes func debugStr(path string) string { q := fmt.Sprintf("%+q", path) diff --git a/core/corehttp/gateway_handler_unixfs__redirects.go b/core/corehttp/gateway_handler_unixfs__redirects.go index f7680e8cb58..81cf057314a 100644 --- a/core/corehttp/gateway_handler_unixfs__redirects.go +++ b/core/corehttp/gateway_handler_unixfs__redirects.go @@ -210,3 +210,78 @@ func isUnixfsResponseFormat(responseFormat string) bool { // The implicit response format is UnixFS return responseFormat == "" } + +// Deprecated: legacy ipfs-404.html files are superseded by _redirects file +// This is provided only for backward-compatibility, until websites migrate +// to 404s managed via _redirects file (https://github.com/ipfs/specs/pull/290) +func (i *gatewayHandler) serveLegacy404IfPresent(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) bool { + resolved404Path, ctype, err := i.searchUpTreeFor404(r, contentPath) + if err != nil { + return false + } + + dr, err := i.api.Unixfs().Get(r.Context(), resolved404Path) + if err != nil { + return false + } + defer dr.Close() + + f, ok := dr.(files.File) + if !ok { + return false + } + + size, err := f.Size() + if err != nil { + return false + } + + log.Debugw("using pretty 404 file", "path", contentPath) + w.Header().Set("Content-Type", ctype) + w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) + w.WriteHeader(http.StatusNotFound) + _, err = io.CopyN(w, f, size) + return err == nil +} + +func (i *gatewayHandler) searchUpTreeFor404(r *http.Request, contentPath ipath.Path) (ipath.Resolved, string, error) { + filename404, ctype, err := preferred404Filename(r.Header.Values("Accept")) + if err != nil { + return nil, "", err + } + + pathComponents := strings.Split(contentPath.String(), "/") + + for idx := len(pathComponents); idx >= 3; idx-- { + pretty404 := gopath.Join(append(pathComponents[0:idx], filename404)...) + parsed404Path := ipath.New("/" + pretty404) + if parsed404Path.IsValid() != nil { + break + } + resolvedPath, err := i.api.ResolvePath(r.Context(), parsed404Path) + if err != nil { + continue + } + return resolvedPath, ctype, nil + } + + return nil, "", fmt.Errorf("no pretty 404 in any parent folder") +} + +func preferred404Filename(acceptHeaders []string) (string, string, error) { + // If we ever want to offer a 404 file for a different content type + // then this function will need to parse q weightings, but for now + // the presence of anything matching HTML is enough. + for _, acceptHeader := range acceptHeaders { + accepted := strings.Split(acceptHeader, ",") + for _, spec := range accepted { + contentType := strings.SplitN(spec, ";", 1)[0] + switch contentType { + case "*/*", "text/*", "text/html": + return "ipfs-404.html", "text/html", nil + } + } + } + + return "", "", fmt.Errorf("there is no 404 file for the requested content types") +}