From 1f5df74ba66a6f42bce56c6b7b9b8bf322c1a4ba Mon Sep 17 00:00:00 2001 From: Adin Schmahmann Date: Tue, 25 Jul 2023 09:54:47 -0400 Subject: [PATCH] fix: handle _redirects for If-None-Match headers (#412) * fix: handle _redirects for If-None-Match headers * fix: handle _redirects for If-None-Match headers * fix(gateway): HEAD requests now respect _redirects * feat: add _redirects regression test * docs: add changelog entry --------- Co-authored-by: Henrique Dias --- CHANGELOG.md | 1 + gateway/gateway_test.go | 55 +++++++++++++++++++++++++++++ gateway/handler.go | 16 +++++++-- gateway/handler_defaults.go | 19 ++++++++-- gateway/testdata/redirects-spa.car | Bin 0 -> 360 bytes 5 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 gateway/testdata/redirects-spa.car diff --git a/CHANGELOG.md b/CHANGELOG.md index d2d4b2536..8388c06f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ The following emojis are used to highlight certain changes: ### Fixed - Removed mentions of unused ARC algorithm ([#336](https://github.com/ipfs/boxo/issues/366#issuecomment-1597253540)) +- Handle `_redirects` file when `If-None-Match` header is present ([#412](https://github.com/ipfs/boxo/pull/412)) ### Security diff --git a/gateway/gateway_test.go b/gateway/gateway_test.go index cc36da68f..7fae55f3e 100644 --- a/gateway/gateway_test.go +++ b/gateway/gateway_test.go @@ -523,6 +523,61 @@ func TestRedirects(t *testing.T) { res = mustDoWithoutRedirect(t, req) require.Equal(t, http.StatusNotFound, res.StatusCode) }) + + t.Run("_redirects file with If-None-Match header", func(t *testing.T) { + t.Parallel() + + backend, root := newMockBackend(t, "redirects-spa.car") + backend.namesys["/ipns/example.com"] = path.FromCid(root) + + ts := newTestServerWithConfig(t, backend, Config{ + Headers: map[string][]string{}, + NoDNSLink: false, + PublicGateways: map[string]*PublicGateway{ + "example.com": { + UseSubdomains: true, + DeserializedResponses: true, + }, + }, + DeserializedResponses: true, + }) + + missingPageURL := ts.URL + "/missing-page" + + do := func(method string) { + // Make initial request to non-existing page that should return the contents + // of index.html as per the _redirects file. + req := mustNewRequest(t, method, missingPageURL, nil) + req.Header.Add("Accept", "text/html") + req.Host = "example.com" + + res := mustDoWithoutRedirect(t, req) + defer res.Body.Close() + + // Check statuses and body. + require.Equal(t, http.StatusOK, res.StatusCode) + body, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, "hello world\n", string(body)) + + // Check Etag. + etag := res.Header.Get("Etag") + require.NotEmpty(t, etag) + + // Repeat request with Etag as If-None-Match value. Expect 304 Not Modified. + req = mustNewRequest(t, method, missingPageURL, nil) + req.Header.Add("Accept", "text/html") + req.Host = "example.com" + req.Header.Add("If-None-Match", etag) + + res = mustDoWithoutRedirect(t, req) + defer res.Body.Close() + require.Equal(t, http.StatusNotModified, res.StatusCode) + } + + do(http.MethodGet) + do(http.MethodHead) + }) } func TestDeserializedResponses(t *testing.T) { diff --git a/gateway/handler.go b/gateway/handler.go index 8c1358553..ecf505617 100644 --- a/gateway/handler.go +++ b/gateway/handler.go @@ -703,9 +703,19 @@ func (i *handler) handleIfNoneMatch(w http.ResponseWriter, r *http.Request, rq * if ifNoneMatch := r.Header.Get("If-None-Match"); ifNoneMatch != "" { pathMetadata, err := i.backend.ResolvePath(r.Context(), rq.immutablePath) if err != nil { - err = fmt.Errorf("failed to resolve %s: %w", debugStr(rq.contentPath.String()), err) - i.webError(w, r, err, http.StatusInternalServerError) - return true + var forwardedPath ImmutablePath + var continueProcessing bool + if isWebRequest(rq.responseFormat) { + forwardedPath, continueProcessing = i.handleWebRequestErrors(w, r, rq.mostlyResolvedPath(), rq.immutablePath, rq.contentPath, err, rq.logger) + if continueProcessing { + pathMetadata, err = i.backend.ResolvePath(r.Context(), forwardedPath) + } + } + if !continueProcessing || err != nil { + err = fmt.Errorf("failed to resolve %s: %w", debugStr(rq.contentPath.String()), err) + i.webError(w, r, err, http.StatusInternalServerError) + return true + } } pathCid := pathMetadata.LastSegment.Cid() diff --git a/gateway/handler_defaults.go b/gateway/handler_defaults.go index 8e96d8b15..de31c1fc1 100644 --- a/gateway/handler_defaults.go +++ b/gateway/handler_defaults.go @@ -33,8 +33,23 @@ func (i *handler) serveDefaults(ctx context.Context, w http.ResponseWriter, r *h case http.MethodHead: var data files.Node pathMetadata, data, err = i.backend.Head(ctx, rq.mostlyResolvedPath()) - if !i.handleRequestErrors(w, r, rq.contentPath, err) { - return false + if err != nil { + if isWebRequest(rq.responseFormat) { + forwardedPath, continueProcessing := i.handleWebRequestErrors(w, r, rq.mostlyResolvedPath(), rq.immutablePath, rq.contentPath, err, rq.logger) + if !continueProcessing { + return false + } + pathMetadata, data, err = i.backend.Head(ctx, forwardedPath) + if err != nil { + err = fmt.Errorf("failed to resolve %s: %w", debugStr(rq.contentPath.String()), err) + i.webError(w, r, err, http.StatusInternalServerError) + return false + } + } else { + if !i.handleRequestErrors(w, r, rq.contentPath, err) { + return false + } + } } defer data.Close() if _, ok := data.(files.Directory); ok { diff --git a/gateway/testdata/redirects-spa.car b/gateway/testdata/redirects-spa.car new file mode 100644 index 0000000000000000000000000000000000000000..b92b253991c300332a456410a770b9355972bca9 GIT binary patch literal 360 zcmcColv%#k2 zU*Dvcr4|)u=I1d^VI)SmkO`LxW2lgV)iJIn3I2n>3cqYs`NaH8<#v`+tM856a*vLM z&cZ^S)$V=6J0DJ%FU7AOP@CfnsQAo?oN!8=h*HX|&SfgNMV8CSnvW1Z2GE#GL@)gSSi*i!90HeZ;ssI20 literal 0 HcmV?d00001