Skip to content

Commit

Permalink
fix: handle _redirects for If-None-Match headers (#412)
Browse files Browse the repository at this point in the history
* 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

(cherry picked from commit 1f5df74)
  • Loading branch information
aschmahmann authored and Jorropo committed Aug 8, 2023
1 parent 61f2939 commit 582e297
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ The following emojis are used to highlight certain changes:

### Fixed

- Handle `_redirects` file when `If-None-Match` header is present ([#412](https://github.com/ipfs/boxo/pull/412))

### Security

## [0.10.2] - 2023-06-29
Expand Down
55 changes: 55 additions & 0 deletions gateway/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
16 changes: 13 additions & 3 deletions gateway/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
19 changes: 17 additions & 2 deletions gateway/handler_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Binary file added gateway/testdata/redirects-spa.car
Binary file not shown.

0 comments on commit 582e297

Please sign in to comment.