From 339ec17da598fc41d25fbd867feacc50cd326ad3 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 19 Sep 2023 14:19:51 +0200 Subject: [PATCH] feat(gateway): add DisableHTMLErrors option --- CHANGELOG.md | 3 +++ gateway/errors.go | 2 +- gateway/errors_test.go | 34 ++++++++++++++++++++++++++++++++++ gateway/gateway.go | 5 +++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f35bfc66..2af0d395a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ The following emojis are used to highlight certain changes: ### Added * ✨ The `routing/http` implements Delegated Peer Routing introduced in [IPIP-417](https://github.com/ipfs/specs/pull/417). +* An option `DisableHTMLErrors` has been added to `gateway.Config`. When this option + is `true`, pretty HTML error pages for web browsers are disabled. Instead, a + `text/plain` page with the raw error message as the body is returned. ### Changed diff --git a/gateway/errors.go b/gateway/errors.go index 17a22ca62..a39bd2a01 100644 --- a/gateway/errors.go +++ b/gateway/errors.go @@ -159,7 +159,7 @@ func webError(w http.ResponseWriter, r *http.Request, c *Config, err error, defa code = gwErr.StatusCode } - acceptsHTML := strings.Contains(r.Header.Get("Accept"), "text/html") + acceptsHTML := !c.DisableHTMLErrors && strings.Contains(r.Header.Get("Accept"), "text/html") if acceptsHTML { w.Header().Set("Content-Type", "text/html") w.WriteHeader(code) diff --git a/gateway/errors_test.go b/gateway/errors_test.go index 4f251822e..cad7ae061 100644 --- a/gateway/errors_test.go +++ b/gateway/errors_test.go @@ -87,4 +87,38 @@ func TestWebError(t *testing.T) { webError(w, r, config, err, http.StatusInternalServerError) require.Equal(t, http.StatusTeapot, w.Result().StatusCode) }) + + t.Run("Error is sent as HTML when 'Accept' header contains 'text/html'", func(t *testing.T) { + t.Parallel() + + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/blah", nil) + r.Header.Set("Accept", "something/else, text/html") + webError(w, r, config, NewErrorStatusCodeFromStatus(http.StatusTeapot), http.StatusInternalServerError) + require.Equal(t, http.StatusTeapot, w.Result().StatusCode) + require.Contains(t, w.Result().Header.Get("Content-Type"), "text/html") + }) + + t.Run("Error is sent as plain text when 'Accept' header does not contain 'text/html'", func(t *testing.T) { + t.Parallel() + + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/blah", nil) + r.Header.Set("Accept", "application/json") + webError(w, r, config, NewErrorStatusCodeFromStatus(http.StatusTeapot), http.StatusInternalServerError) + require.Equal(t, http.StatusTeapot, w.Result().StatusCode) + require.Contains(t, w.Result().Header.Get("Content-Type"), "text/plain") + }) + + t.Run("Error is sent as plain text when 'Accept' header contains 'text/html' and config.DisableHTMLErrors is true", func(t *testing.T) { + t.Parallel() + + config := &Config{Headers: map[string][]string{}, DisableHTMLErrors: true} + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/blah", nil) + r.Header.Set("Accept", "something/else, text/html") + webError(w, r, config, NewErrorStatusCodeFromStatus(http.StatusTeapot), http.StatusInternalServerError) + require.Equal(t, http.StatusTeapot, w.Result().StatusCode) + require.Contains(t, w.Result().Header.Get("Content-Type"), "text/plain") + }) } diff --git a/gateway/gateway.go b/gateway/gateway.go index 780691a45..19b801bba 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -40,6 +40,11 @@ type Config struct { // overridden per FQDN in PublicGateways. To be used with WithHostname. NoDNSLink bool + // DisableHTMLErrors disables pretty HTML pages when an error occurs. Instead, a `text/plain` + // page will be sent with the raw error message. This can be useful if this gateway + // is being proxied by other service, which wants to use the error message. + DisableHTMLErrors bool + // PublicGateways configures the behavior of known public gateways. Each key is // a fully qualified domain name (FQDN). To be used with WithHostname. PublicGateways map[string]*PublicGateway