From 46773217535a4ea0cd965e40921ffd3e59eb1807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Tue, 25 May 2021 17:03:48 +0200 Subject: [PATCH] Always set the Content-Type header when a body is present (#10204) * Always set the Content-Type header when a body is present Closes https://github.com/hashicorp/consul/issues/10011 * Add Changelog entry * Add more Content-Type exceptions * Fix tests --- .changelog/10204.txt | 3 +++ api/acl.go | 1 + api/api.go | 6 ++++++ api/api_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++- api/event.go | 1 + api/kv.go | 1 + api/snapshot.go | 1 + 7 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 .changelog/10204.txt diff --git a/.changelog/10204.txt b/.changelog/10204.txt new file mode 100644 index 0000000000..0c6f69190b --- /dev/null +++ b/.changelog/10204.txt @@ -0,0 +1,3 @@ +```release-note:improvement +api: The `Content-Type` header is now always set when a body is present in a request. +``` diff --git a/api/acl.go b/api/acl.go index d8e0e04d9e..2d42f73d8d 100644 --- a/api/acl.go +++ b/api/acl.go @@ -900,6 +900,7 @@ func (a *ACL) PolicyList(q *QueryOptions) ([]*ACLPolicyListEntry, *QueryMeta, er func (a *ACL) RulesTranslate(rules io.Reader) (string, error) { r := a.c.newRequest("POST", "/v1/acl/rules/translate") r.body = rules + r.header.Set("Content-Type", "text/plain") rtt, resp, err := requireOK(a.c.doRequest(r)) if err != nil { return "", err diff --git a/api/api.go b/api/api.go index dbecaa5afa..5cdd486805 100644 --- a/api/api.go +++ b/api/api.go @@ -871,6 +871,12 @@ func (r *request) toHTTP() (*http.Request, error) { req.Host = r.url.Host req.Header = r.header + // Content-Type must always be set when a body is present + // See https://github.com/hashicorp/consul/issues/10011 + if req.Body != nil && req.Header.Get("Content-Type") == "" { + req.Header.Set("Content-Type", "application/json") + } + // Setup auth if r.config.HttpAuth != nil { req.SetBasicAuth(r.config.HttpAuth.Username, r.config.HttpAuth.Password) diff --git a/api/api_test.go b/api/api_test.go index 7b593b22e1..56f764d830 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net" "net/http" + "net/url" "os" "path/filepath" "reflect" @@ -810,7 +811,17 @@ func TestAPI_SetWriteOptions(t *testing.T) { func TestAPI_Headers(t *testing.T) { t.Parallel() - c, s := makeClient(t) + + var request *http.Request + c, s := makeClientWithConfig(t, func(c *Config) { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.Proxy = func(r *http.Request) (*url.URL, error) { + // Keep track of the last request sent + request = r + return nil, nil + } + c.Transport = transport + }, nil) defer s.Stop() if len(c.Headers()) != 0 { @@ -836,6 +847,39 @@ func TestAPI_Headers(t *testing.T) { if r.header.Get("Auth") != "Token" { t.Fatalf("Auth header not set: %v", r.header) } + + kv := c.KV() + _, err := kv.Put(&KVPair{Key: "test-headers", Value: []byte("foo")}, nil) + require.NoError(t, err) + require.Equal(t, "application/octet-stream", request.Header.Get("Content-Type")) + + _, _, err = kv.Get("test-headers", nil) + require.NoError(t, err) + require.Equal(t, "", request.Header.Get("Content-Type")) + + _, err = kv.Delete("test-headers", nil) + require.NoError(t, err) + require.Equal(t, "", request.Header.Get("Content-Type")) + + err = c.Snapshot().Restore(nil, strings.NewReader("foo")) + require.Error(t, err) + require.Equal(t, "application/octet-stream", request.Header.Get("Content-Type")) + + _, err = c.ACL().RulesTranslate(strings.NewReader(` + agent "" { + policy = "read" + } + `)) + // ACL support is disabled + require.Error(t, err) + require.Equal(t, "text/plain", request.Header.Get("Content-Type")) + + _, _, err = c.Event().Fire(&UserEvent{ + Name: "test", + Payload: []byte("foo"), + }, nil) + require.NoError(t, err) + require.Equal(t, "application/octet-stream", request.Header.Get("Content-Type")) } func TestAPI_RequestToHTTP(t *testing.T) { diff --git a/api/event.go b/api/event.go index 85b5b069b0..1592b7efca 100644 --- a/api/event.go +++ b/api/event.go @@ -45,6 +45,7 @@ func (e *Event) Fire(params *UserEvent, q *WriteOptions) (string, *WriteMeta, er if params.Payload != nil { r.body = bytes.NewReader(params.Payload) } + r.header.Set("Content-Type", "application/octet-stream") rtt, resp, err := requireOK(e.c.doRequest(r)) if err != nil { diff --git a/api/kv.go b/api/kv.go index 351d287d66..650de0841b 100644 --- a/api/kv.go +++ b/api/kv.go @@ -205,6 +205,7 @@ func (k *KV) put(key string, params map[string]string, body []byte, q *WriteOpti r.params.Set(param, val) } r.body = bytes.NewReader(body) + r.header.Set("Content-Type", "application/octet-stream") rtt, resp, err := requireOK(k.c.doRequest(r)) if err != nil { return false, nil, err diff --git a/api/snapshot.go b/api/snapshot.go index e902377dd5..0c8294f37c 100644 --- a/api/snapshot.go +++ b/api/snapshot.go @@ -38,6 +38,7 @@ func (s *Snapshot) Save(q *QueryOptions) (io.ReadCloser, *QueryMeta, error) { func (s *Snapshot) Restore(q *WriteOptions, in io.Reader) error { r := s.c.newRequest("PUT", "/v1/snapshot") r.body = in + r.header.Set("Content-Type", "application/octet-stream") r.setWriteOptions(q) _, _, err := requireOK(s.c.doRequest(r)) if err != nil {