From 97c866749b442ee6b333a8991070a9f33b6ac405 Mon Sep 17 00:00:00 2001 From: Adin Schmahmann Date: Tue, 18 Jul 2023 14:17:44 -0400 Subject: [PATCH 1/5] fix: handle _redirects for If-None-Match headers --- gateway/handler.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/gateway/handler.go b/gateway/handler.go index 102593449..dc34d3e4b 100644 --- a/gateway/handler.go +++ b/gateway/handler.go @@ -700,9 +700,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() From 267b14f342df696fda52402d4f8c28a3a2c9f41b Mon Sep 17 00:00:00 2001 From: Adin Schmahmann Date: Tue, 18 Jul 2023 14:17:44 -0400 Subject: [PATCH 2/5] fix: handle _redirects for If-None-Match headers --- gateway/handler.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) 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() From 0ca10d98f60bc5e419dc7912e85063cd85baab85 Mon Sep 17 00:00:00 2001 From: Adin Schmahmann Date: Mon, 24 Jul 2023 22:38:03 -0400 Subject: [PATCH 3/5] fix(gateway): HEAD requests now respect _redirects --- gateway/handler_defaults.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) 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 { From 4f4f157c9d6467c882c1b4633493512b56258935 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 25 Jul 2023 13:29:26 +0200 Subject: [PATCH 4/5] feat: add _redirects regression test --- gateway/gateway_test.go | 55 +++++++++++++++++++++++++++++ gateway/testdata/redirects-spa.car | Bin 0 -> 360 bytes 2 files changed, 55 insertions(+) create mode 100644 gateway/testdata/redirects-spa.car 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/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 From d9b38ab236933175693d0373d855aa8aa6790883 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 25 Jul 2023 13:30:39 +0200 Subject: [PATCH 5/5] docs: add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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