Skip to content

Commit

Permalink
Allowed content-types controlled via Accept header. closes hashicorp#15
Browse files Browse the repository at this point in the history
  • Loading branch information
mvanholsteijn committed Jan 11, 2019
1 parent 17e98be commit 645de40
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 8 deletions.
37 changes: 29 additions & 8 deletions http/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func dataSourceRead(d *schema.ResourceData, meta interface{}) error {
}

contentType := resp.Header.Get("Content-Type")
if contentType == "" || isContentTypeAllowed(contentType) == false {
if contentType == "" || isContentTypeAllowed(contentType, allowedContentTypes(headers)) == false {
return fmt.Errorf("Content-Type is not a text type. Got: %s", contentType)
}

Expand All @@ -87,22 +87,43 @@ func dataSourceRead(d *schema.ResourceData, meta interface{}) error {
return nil
}

// returns an array of regular expressions matching allowed content types.
// The default accepted content types are supplemented with the types
// specified in the Accept header. All user specified types must be convertible to string
func allowedContentTypes(headers map[string]interface{}) []*regexp.Regexp {

result := []*regexp.Regexp{
regexp.MustCompile("^text/.+"),
regexp.MustCompile("^application/json$"),
regexp.MustCompile("^application/samlmetadata\\+xml"),
}

for k, v := range headers {
if strings.EqualFold("accept", k) {
switch s := v.(type) {
case string:
for _, a := range strings.Split(s, ",") {
t := strings.TrimSpace(strings.Split(a, ";")[0])
if t != "" {
result = append(result, regexp.MustCompile("^"+t+"$"))
}
}
}
}
}
return result
}

// This is to prevent potential issues w/ binary files
// and generally unprintable characters
// See https://github.com/hashicorp/terraform/pull/3858#issuecomment-156856738
func isContentTypeAllowed(contentType string) bool {
func isContentTypeAllowed(contentType string, allowedContentTypes []*regexp.Regexp) bool {

parsedType, params, err := mime.ParseMediaType(contentType)
if err != nil {
return false
}

allowedContentTypes := []*regexp.Regexp{
regexp.MustCompile("^text/.+"),
regexp.MustCompile("^application/json$"),
regexp.MustCompile("^application/samlmetadata\\+xml"),
}

for _, r := range allowedContentTypes {
if r.MatchString(parsedType) {
charset := strings.ToLower(params["charset"])
Expand Down
79 changes: 79 additions & 0 deletions http/data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,81 @@ func TestDataSource_compileError(t *testing.T) {
})
}

const testDataSourceConfig_xml = `
data "http" "http_test" {
url = "%s/xml"
request_headers = {
"Accept" = "application/xml"
}
}
output "body" {
value = "${data.http.http_test.body}"
}
`

func TestDataSource_xml(t *testing.T) {
testHttpMock := setUpMockHttpServer()

defer testHttpMock.server.Close()

resource.UnitTest(t, resource.TestCase{
Providers: testProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testDataSourceConfig_xml, testHttpMock.server.URL),
Check: func(s *terraform.State) error {
_, ok := s.RootModule().Resources["data.http.http_test"]
if !ok {
return fmt.Errorf("missing data resource")
}

outputs := s.RootModule().Outputs

if outputs["body"].Value != "<?xml version=\"1.0\"><body/>" {
return fmt.Errorf(
`'body' output is %s; want <?xml version=\"1.0\"><body/>`,
outputs["body"].Value,
)
}

return nil
},
},
},
})
}

func TestAcceptHeaderParsing(t *testing.T) {
headers := map[string]interface{}{"user-agent": "golang"}
regex := allowedContentTypes(headers)
if len(regex) != 3 {
t.Errorf("expected three default regular expression, got %d", len(regex))
}
headers = map[string]interface{}{"accept": "application/xml"}
regex = allowedContentTypes(headers)
if len(regex) != 4 {
t.Errorf("expected two regular expression, got %d", len(regex))
}
if regex[3].String() != "^application/xml$" {
t.Errorf("expected application/xml , got %s", regex[3].String())
}

headers = map[string]interface{}{"accept": "application/xml, application/xxml;q=0.5,"}
regex = allowedContentTypes(headers)
if len(regex) != 5 {
t.Errorf("expected two regular expression, got %d", len(regex))
}

if regex[4].String() != "^application/xxml$" {
t.Errorf("expected application/xxml , got %s", regex[4].String())
}

if regex[3].String() != "^application/xml$" {
t.Errorf("expected application/xml , got %s", regex[3].String())
}
}

func setUpMockHttpServer() *TestHttpMock {
Server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -228,6 +303,10 @@ func setUpMockHttpServer() *TestHttpMock {
w.Header().Set("Content-Type", "application/json; charset=UTF-16")
w.WriteHeader(http.StatusOK)
w.Write([]byte("\"1.0.0\""))
} else if r.URL.Path == "/xml" {
w.Header().Set("Content-Type", "application/xml; charset=UTF-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte("<?xml version=\"1.0\"><body/>"))
} else if r.URL.Path == "/meta_404.txt" {
w.WriteHeader(http.StatusNotFound)
} else {
Expand Down

0 comments on commit 645de40

Please sign in to comment.