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
This commit is contained in:
Rémi Lapeyre 2021-05-25 17:03:48 +02:00 committed by Paul Banks
parent f054099e84
commit 4677321753
7 changed files with 58 additions and 1 deletions

3
.changelog/10204.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
api: The `Content-Type` header is now always set when a body is present in a request.
```

View File

@ -900,6 +900,7 @@ func (a *ACL) PolicyList(q *QueryOptions) ([]*ACLPolicyListEntry, *QueryMeta, er
func (a *ACL) RulesTranslate(rules io.Reader) (string, error) { func (a *ACL) RulesTranslate(rules io.Reader) (string, error) {
r := a.c.newRequest("POST", "/v1/acl/rules/translate") r := a.c.newRequest("POST", "/v1/acl/rules/translate")
r.body = rules r.body = rules
r.header.Set("Content-Type", "text/plain")
rtt, resp, err := requireOK(a.c.doRequest(r)) rtt, resp, err := requireOK(a.c.doRequest(r))
if err != nil { if err != nil {
return "", err return "", err

View File

@ -871,6 +871,12 @@ func (r *request) toHTTP() (*http.Request, error) {
req.Host = r.url.Host req.Host = r.url.Host
req.Header = r.header 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 // Setup auth
if r.config.HttpAuth != nil { if r.config.HttpAuth != nil {
req.SetBasicAuth(r.config.HttpAuth.Username, r.config.HttpAuth.Password) req.SetBasicAuth(r.config.HttpAuth.Username, r.config.HttpAuth.Password)

View File

@ -7,6 +7,7 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -810,7 +811,17 @@ func TestAPI_SetWriteOptions(t *testing.T) {
func TestAPI_Headers(t *testing.T) { func TestAPI_Headers(t *testing.T) {
t.Parallel() 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() defer s.Stop()
if len(c.Headers()) != 0 { if len(c.Headers()) != 0 {
@ -836,6 +847,39 @@ func TestAPI_Headers(t *testing.T) {
if r.header.Get("Auth") != "Token" { if r.header.Get("Auth") != "Token" {
t.Fatalf("Auth header not set: %v", r.header) 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) { func TestAPI_RequestToHTTP(t *testing.T) {

View File

@ -45,6 +45,7 @@ func (e *Event) Fire(params *UserEvent, q *WriteOptions) (string, *WriteMeta, er
if params.Payload != nil { if params.Payload != nil {
r.body = bytes.NewReader(params.Payload) r.body = bytes.NewReader(params.Payload)
} }
r.header.Set("Content-Type", "application/octet-stream")
rtt, resp, err := requireOK(e.c.doRequest(r)) rtt, resp, err := requireOK(e.c.doRequest(r))
if err != nil { if err != nil {

View File

@ -205,6 +205,7 @@ func (k *KV) put(key string, params map[string]string, body []byte, q *WriteOpti
r.params.Set(param, val) r.params.Set(param, val)
} }
r.body = bytes.NewReader(body) r.body = bytes.NewReader(body)
r.header.Set("Content-Type", "application/octet-stream")
rtt, resp, err := requireOK(k.c.doRequest(r)) rtt, resp, err := requireOK(k.c.doRequest(r))
if err != nil { if err != nil {
return false, nil, err return false, nil, err

View File

@ -38,6 +38,7 @@ func (s *Snapshot) Save(q *QueryOptions) (io.ReadCloser, *QueryMeta, error) {
func (s *Snapshot) Restore(q *WriteOptions, in io.Reader) error { func (s *Snapshot) Restore(q *WriteOptions, in io.Reader) error {
r := s.c.newRequest("PUT", "/v1/snapshot") r := s.c.newRequest("PUT", "/v1/snapshot")
r.body = in r.body = in
r.header.Set("Content-Type", "application/octet-stream")
r.setWriteOptions(q) r.setWriteOptions(q)
_, _, err := requireOK(s.c.doRequest(r)) _, _, err := requireOK(s.c.doRequest(r))
if err != nil { if err != nil {