Skip to content

Commit 05260ea

Browse files
authored
Check HTTP response content type before trying to decode it as JSON (#1196)
1 parent 14f2349 commit 05260ea

File tree

4 files changed

+66
-2
lines changed

4 files changed

+66
-2
lines changed

cmd/cmd.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7+
"regexp"
78
"strings"
89

910
"io"
@@ -23,6 +24,8 @@ var (
2324
Out io.Writer
2425
// Err is used to write errors.
2526
Err io.Writer
27+
// jsonContentTypeRe is used to match Content-Type which contains JSON.
28+
jsonContentTypeRe = regexp.MustCompile(`^application/([[:alpha:]]+\+)?json$`)
2629
)
2730

2831
const msgWelcomePleaseConfigure = `
@@ -77,6 +80,13 @@ func validateUserConfig(cfg *viper.Viper) error {
7780
// decodedAPIError decodes and returns the error message from the API response.
7881
// If the message is blank, it returns a fallback message with the status code.
7982
func decodedAPIError(resp *http.Response) error {
83+
if contentType := resp.Header.Get("Content-Type"); !jsonContentTypeRe.MatchString(contentType) {
84+
return fmt.Errorf(
85+
"expected response with Content-Type \"application/json\" but got status %q with Content-Type %q",
86+
resp.Status,
87+
contentType,
88+
)
89+
}
8090
var apiError struct {
8191
Error struct {
8292
Type string `json:"type"`

cmd/cmd_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package cmd
22

33
import (
44
"io"
5+
"io/ioutil"
6+
"net/http"
57
"os"
8+
"strings"
69
"testing"
710

811
"github.com/spf13/cobra"
@@ -119,3 +122,54 @@ func (co capturedOutput) reset() {
119122
Out = co.oldOut
120123
Err = co.oldErr
121124
}
125+
126+
func errorResponse(contentType string, body string) *http.Response {
127+
response := &http.Response{
128+
Status: "418 I'm a teapot",
129+
StatusCode: 418,
130+
Header: make(http.Header),
131+
Body: ioutil.NopCloser(strings.NewReader(body)),
132+
ContentLength: int64(len(body)),
133+
}
134+
response.Header.Set("Content-Type", contentType)
135+
return response
136+
}
137+
138+
func TestDecodeErrorResponse(t *testing.T) {
139+
testCases := []struct {
140+
response *http.Response
141+
wantMessage string
142+
}{
143+
{
144+
response: errorResponse("text/html", "Time for tea"),
145+
wantMessage: `expected response with Content-Type "application/json" but got status "418 I'm a teapot" with Content-Type "text/html"`,
146+
},
147+
{
148+
response: errorResponse("application/json", `{"error": {"type": "json", "valid": no}}`),
149+
wantMessage: "failed to parse API error response: invalid character 'o' in literal null (expecting 'u')",
150+
},
151+
{
152+
response: errorResponse("application/json", `{"error": {"type": "track_ambiguous", "message": "message", "possible_track_ids": ["a", "b"]}}`),
153+
wantMessage: "message: a, b",
154+
},
155+
{
156+
response: errorResponse("application/json", `{"error": {"message": "message"}}`),
157+
wantMessage: "message",
158+
},
159+
{
160+
response: errorResponse("application/problem+json", `{"error": {"message": "new json format"}}`),
161+
wantMessage: "new json format",
162+
},
163+
{
164+
response: errorResponse("application/json", `{"error": {}}`),
165+
wantMessage: "unexpected API response: 418",
166+
},
167+
}
168+
tc := testCases[0]
169+
got := decodedAPIError(tc.response)
170+
assert.Equal(t, tc.wantMessage, got.Error())
171+
for _, tc = range testCases {
172+
got := decodedAPIError(tc.response)
173+
assert.Equal(t, tc.wantMessage, got.Error())
174+
}
175+
}

cmd/download_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ func TestDownloadError(t *testing.T) {
403403

404404
err = runDownload(cfg, flags, []string{})
405405

406-
assert.Equal(t, "test error", err.Error())
406+
assert.Equal(t, `expected response with Content-Type "application/json" but got status "400 Bad Request" with Content-Type "text/plain; charset=utf-8"`, err.Error())
407407

408408
}
409409

cmd/submit_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ func TestSubmitServerErr(t *testing.T) {
657657

658658
err = runSubmit(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), files)
659659

660-
assert.Regexp(t, "test error", err.Error())
660+
assert.Regexp(t, `expected response with Content-Type "application/json" but got status "400 Bad Request" with Content-Type "text/plain; charset=utf-8"`, err.Error())
661661
}
662662

663663
func TestHandleErrorResponse(t *testing.T) {

0 commit comments

Comments
 (0)