Skip to content

Commit

Permalink
fix: allow user to set "Host" request header (#118)
Browse files Browse the repository at this point in the history
The golang net/http package's Request type will ignore
the "Host" header if set in the Request.Header field.
Instead, one must explicitly set this header in the Request.Host
field.

This commit includes changes to the RequestBuilder and
BaseService types to allow a user to set "Host" in the normal
ways that an SDK user can set request headers:
- as a custom header while constructing an IAM or CP4D authenticator
- as a default header set on a service instance
- as a custom header within an options model instance when
  invoking an operation
In each of these scenarios, if the user sets the "Host" header
then the resulting Request.Host field is set with this value.
If no "Host" header is specified by the user, then Request.Host
field would be set to the host:port specified in the request URL
(the default behavior).
  • Loading branch information
padamstx authored May 28, 2021
1 parent add530f commit efd7fe3
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 0 deletions.
9 changes: 9 additions & 0 deletions v5/core/base_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,15 @@ func (service *BaseService) Request(req *http.Request, result interface{}) (deta
for k, v := range service.DefaultHeaders {
req.Header.Add(k, strings.Join(v, ""))
}

// After adding the default headers, make one final check to see if the user
// specified the "Host" header within the default headers.
// This needs to be handled separately because it will be ignored by
// the Request.Write() method.
host := service.DefaultHeaders.Get("Host")
if host != "" {
req.Host = host
}
}

// Add the default User-Agent header if not already present.
Expand Down
29 changes: 29 additions & 0 deletions v5/core/base_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,35 @@ func TestRequestForProvidedUserAgent(t *testing.T) {
_, _ = service.Request(req, &foo)
}

func TestRequestHostHeaderDefault(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "application/json")
fmt.Fprint(w, `{"name": "wonder woman"}`)
assert.Equal(t, "server1.cloud.ibm.com", r.Host)
}))
defer server.Close()

authenticator := &NoAuthAuthenticator{}
options := &ServiceOptions{
URL: server.URL,
Authenticator: authenticator,
}
service, _ := NewBaseService(options)
headers := http.Header{}
headers.Add("Host", "server1.cloud.ibm.com")
service.SetDefaultHeaders(headers)

builder := NewRequestBuilder("GET")
_, err := builder.ResolveRequestURL(server.URL, "", nil)
assert.Nil(t, err)
builder.AddHeader("Content-Type", "Application/json").
AddQuery("Version", "2018-22-09")
req, _ := builder.Build()

var foo *Foo
_, _ = service.Request(req, &foo)
}

func TestIncorrectURL(t *testing.T) {
authenticator, _ := NewNoAuthAuthenticator()
options := &ServiceOptions{
Expand Down
2 changes: 2 additions & 0 deletions v5/core/cp4d_authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ func TestCp4dUserHeaders(t *testing.T) {
verifyAuthRequest(t, r, "mookie", "", "King of the North")
assert.Equal(t, "Value1", r.Header.Get("Header1"))
assert.Equal(t, "Value2", r.Header.Get("Header2"))
assert.Equal(t, "cp4d.cloud.ibm.com", r.Host)

w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{ "_messageCode_":"200", "message":"success", "token":"%s"}`, cp4dUsernameApikey1)
Expand All @@ -563,6 +564,7 @@ func TestCp4dUserHeaders(t *testing.T) {
headers := make(map[string]string)
headers["Header1"] = "Value1"
headers["Header2"] = "Value2"
headers["Host"] = "cp4d.cloud.ibm.com"

authenticator := &CloudPakForDataAuthenticator{
URL: server.URL,
Expand Down
2 changes: 2 additions & 0 deletions v5/core/iam_authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,12 +535,14 @@ func TestIamUserHeaders(t *testing.T) {
assert.False(t, ok)
assert.Equal(t, "Value1", r.Header.Get("Header1"))
assert.Equal(t, "Value2", r.Header.Get("Header2"))
assert.Equal(t, "iam.cloud.ibm.com", r.Host)
}))
defer server.Close()

headers := make(map[string]string)
headers["Header1"] = "Value1"
headers["Header2"] = "Value2"
headers["Host"] = "iam.cloud.ibm.com"

authenticator, err := NewIamAuthenticator("bogus-apikey", server.URL, "", "", false, headers)
assert.Nil(t, err)
Expand Down
7 changes: 7 additions & 0 deletions v5/core/request_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,13 @@ func (requestBuilder *RequestBuilder) Build() (req *http.Request, err error) {
// Headers
req.Header = requestBuilder.Header

// If "Host" was specified as a header, we need to explicitly copy it
// to the request's Host field since the "Host" header will be ignored by Request.Write().
host := req.Header.Get("Host")
if host != "" {
req.Host = host
}

// Query
query := req.URL.Query()
for k, l := range requestBuilder.Query {
Expand Down
34 changes: 34 additions & 0 deletions v5/core/request_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,3 +675,37 @@ func TestRequestWithContextTimeout(t *testing.T) {
assert.Contains(t, err.Error(), "context deadline exceeded")
assert.Nil(t, detailedResponse)
}

func TestHostHeader1(t *testing.T) {
// "baseline" test to ensure that if the Host header is not explicitly set,
// then the host from the request URL is used.

builder := NewRequestBuilder("GET")
_, err := builder.ResolveRequestURL("https://localhost:80/api/v1", "", nil)
assert.Nil(t, err)

req, err := builder.Build()
assert.Nil(t, err)
assert.NotNil(t, req)

assert.Equal(t, "localhost:80", req.Host)
t.Logf("Host: %s\n", req.Host)
}

func TestHostHeader2(t *testing.T) {
// Verify that if the "Host" header is set on the request builder,
// then the resulting Request object will have its Host field set as well.

builder := NewRequestBuilder("GET")
_, err := builder.ResolveRequestURL("https://localhost:80/api/v1", "", nil)
assert.Nil(t, err)

builder.AddHeader("Host", "overridehost:81")

req, err := builder.Build()
assert.Nil(t, err)
assert.NotNil(t, req)

assert.Equal(t, "overridehost:81", req.Host)
t.Logf("Host: %s\n", req.Host)
}

0 comments on commit efd7fe3

Please sign in to comment.