diff --git a/agent/http.go b/agent/http.go index 68abc426f0..1feab52d48 100644 --- a/agent/http.go +++ b/agent/http.go @@ -45,6 +45,11 @@ import ( "github.com/hashicorp/consul/proto/private/pbcommon" ) +const ( + contentTypeHeader = "Content-Type" + plainContentType = "text/plain; charset=utf-8" +) + var HTTPSummaries = []prometheus.SummaryDefinition{ { Name: []string{"api", "http"}, @@ -222,7 +227,7 @@ func (s *HTTPHandlers) handler() http.Handler { // If enableDebug register wrapped pprof handlers if !s.agent.enableDebug.Load() && s.checkACLDisabled() { resp.WriteHeader(http.StatusNotFound) - resp.Header().Set(api.ContentTypeHeader, api.PlainContentType) + resp.Header().Set(contentTypeHeader, plainContentType) return } @@ -231,7 +236,7 @@ func (s *HTTPHandlers) handler() http.Handler { authz, err := s.agent.delegate.ResolveTokenAndDefaultMeta(token, nil, nil) if err != nil { - resp.Header().Set(api.ContentTypeHeader, api.PlainContentType) + resp.Header().Set(contentTypeHeader, plainContentType) resp.WriteHeader(http.StatusForbidden) return } @@ -241,7 +246,7 @@ func (s *HTTPHandlers) handler() http.Handler { // TODO(partitions): should this be possible in a partition? // TODO(acl-error-enhancements): We should return error details somehow here. if authz.OperatorRead(nil) != acl.Allow { - resp.Header().Set(api.ContentTypeHeader, api.PlainContentType) + resp.Header().Set(contentTypeHeader, plainContentType) resp.WriteHeader(http.StatusForbidden) return } @@ -349,22 +354,22 @@ func ensureContentTypeHeader(next http.Handler, logger hclog.Logger) http.Handle return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { next.ServeHTTP(resp, req) - contentType := api.DetermineContentType(req) + contentType := api.SetContentType(req) // validate request content-type header - reqContentType := req.Header.Get(api.ContentTypeHeader) + reqContentType := req.Header.Get(contentTypeHeader) if reqContentType == "" || reqContentType != contentType { logger.Debug("warning: request content-type is not supported", "request-path", req.URL) // set request type to expected content-type - req.Header.Set(api.ContentTypeHeader, contentType) + req.Header.Set(contentTypeHeader, contentType) } // validate response content-type header if resp != nil { - respContentType := resp.Header().Get(api.ContentTypeHeader) + respContentType := resp.Header().Get(contentTypeHeader) if respContentType == "" || respContentType != contentType { logger.Debug("warning: response content-type header not explicitly set.", "request-path", req.URL) - resp.Header().Set(api.ContentTypeHeader, contentType) + resp.Header().Set(contentTypeHeader, contentType) } } }) @@ -414,7 +419,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc "error", err, ) //set response type to plain to prevent XSS - resp.Header().Set(api.ContentTypeHeader, api.PlainContentType) + resp.Header().Set(contentTypeHeader, plainContentType) resp.WriteHeader(http.StatusInternalServerError) return } @@ -442,7 +447,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc "error", errMsg, ) //set response type to plain to prevent XSS - resp.Header().Set(api.ContentTypeHeader, api.PlainContentType) + resp.Header().Set(contentTypeHeader, plainContentType) resp.WriteHeader(http.StatusForbidden) fmt.Fprint(resp, errMsg) return @@ -610,7 +615,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc obj, err = handler(resp, req) } } - contentType := api.JSONContentType + contentType := "application/json" httpCode := http.StatusOK if err != nil { if errPayload, ok := err.(CodeWithPayloadError); ok { @@ -623,7 +628,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc } } else { //set response type to plain to prevent XSS - resp.Header().Set(api.ContentTypeHeader, api.PlainContentType) + resp.Header().Set(contentTypeHeader, plainContentType) handleErr(err) return } @@ -632,11 +637,11 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc return } var buf []byte - if contentType == api.JSONContentType { + if contentType == "application/json" { buf, err = s.marshalJSON(req, obj) if err != nil { //set response type to plain to prevent XSS - resp.Header().Set(api.ContentTypeHeader, api.PlainContentType) + resp.Header().Set(contentTypeHeader, plainContentType) handleErr(err) return } @@ -647,7 +652,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc } } } - resp.Header().Set(api.ContentTypeHeader, contentType) + resp.Header().Set(contentTypeHeader, contentType) resp.WriteHeader(httpCode) resp.Write(buf) } diff --git a/agent/http_test.go b/agent/http_test.go index b5edf33667..377f913004 100644 --- a/agent/http_test.go +++ b/agent/http_test.go @@ -638,11 +638,11 @@ func TestHTTPAPIResponseHeaders(t *testing.T) { `) defer a.Shutdown() - requireHasHeadersSet(t, a, http.MethodGet, "/v1/agent/self", nil, api.JSONContentType) + requireHasHeadersSet(t, a, http.MethodGet, "/v1/agent/self", nil, "application/json") // Check the Index page that just renders a simple message with UI disabled // also gets the right headers. - requireHasHeadersSet(t, a, http.MethodGet, "/", nil, api.PlainContentType) + requireHasHeadersSet(t, a, http.MethodGet, "/", nil, "text/plain; charset=utf-8") } func TestHTTPAPISnapshotEndpointResponseHeaders(t *testing.T) { @@ -684,8 +684,6 @@ func requireHasHeadersSet(t *testing.T, a *TestAgent, method, path string, body hdrs := resp.Header() reqHdrs := req.Header - fmt.Println("Body: ", resp.Body.String()) - require.Equal(t, "*", hdrs.Get("Access-Control-Allow-Origin"), "Access-Control-Allow-Origin header value incorrect") @@ -719,7 +717,7 @@ func TestUIResponseHeaders(t *testing.T) { defer a.Shutdown() //response header for the UI appears to be being handled by the UI itself. - requireHasHeadersSet(t, a, http.MethodGet, "/ui", nil, api.PlainContentType) + requireHasHeadersSet(t, a, http.MethodGet, "/ui", nil, "text/plain; charset=utf-8") } func TestErrorContentTypeHeaderSet(t *testing.T) { @@ -739,7 +737,7 @@ func TestErrorContentTypeHeaderSet(t *testing.T) { `) defer a.Shutdown() - requireHasHeadersSet(t, a, http.MethodGet, "/fake-path-doesn't-exist", nil, api.JSONContentType) + requireHasHeadersSet(t, a, http.MethodGet, "/fake-path-doesn't-exist", nil, "application/json") } func TestAcceptEncodingGzip(t *testing.T) { diff --git a/api/api.go b/api/api.go index 12009f3aed..91043b2b73 100644 --- a/api/api.go +++ b/api/api.go @@ -1088,21 +1088,23 @@ func (c *Client) doRequest(r *request) (time.Duration, *http.Response, error) { return 0, nil, err } - contentType := DetermineContentType(req) + contentType := SetContentType(req) - // validate request content-type header - reqContentType := req.Header.Get(ContentTypeHeader) + reqContentType := req.Header.Get(contentTypeHeader) if reqContentType == "" || reqContentType != contentType { - req.Header.Set(ContentTypeHeader, contentType) + // Content-Type must always be set + req.Header.Set(contentTypeHeader, contentType) } start := time.Now() resp, err := c.config.HttpClient.Do(req) + // validate response content-type header if resp != nil { - respContentType := resp.Header.Get(ContentTypeHeader) + respContentType := resp.Header.Get(contentTypeHeader) if respContentType == "" || respContentType != contentType { - resp.Header.Set(ContentTypeHeader, contentType) + // Content-Type must always be set + resp.Header.Set(contentTypeHeader, contentType) } } diff --git a/api/content_type.go b/api/content_type.go index 6bdb5c1a57..e749850807 100644 --- a/api/content_type.go +++ b/api/content_type.go @@ -9,9 +9,9 @@ import ( ) const ( - ContentTypeHeader = "Content-Type" - PlainContentType = "text/plain; charset=utf-8" - JSONContentType = "application/json" // Default content type + contentTypeHeader = "Content-Type" + plainContentType = "text/plain; charset=utf-8" + jsonContentType = "application/json" // Default content type ) // ContentTypeRule defines a rule for content type determination @@ -55,23 +55,23 @@ var contentTypeRules = []contentTypeRule{ { path: "/ui", method: http.MethodGet, - contentType: PlainContentType, + contentType: plainContentType, }, } -// DetermineContentType returns the appropriate content type based on the request -// If the request is nil, returns the default content type -func DetermineContentType(req *http.Request) string { +// SetContentType sets the request and response Content-Type header +func SetContentType(req *http.Request) string { + // If the request is nil, returns the default content type if req == nil { - return PlainContentType + return plainContentType } if isIndexPage(req) { - return PlainContentType + return plainContentType } if strings.HasPrefix(req.URL.Path, "/v1/internal") { - return req.Header.Get(ContentTypeHeader) + return req.Header.Get(contentTypeHeader) } // Check against defined endpoint and required content type rules @@ -82,7 +82,7 @@ func DetermineContentType(req *http.Request) string { } // Default to JSON for all other endpoints - return JSONContentType + return jsonContentType } // matchesRule checks if a request matches a content type rule