rcserver: improve content-type check

Some libraries use `application/json; charset=utf-8` as their `Content-Type`, which is valid.
However we were not decoding the JSON body in that case, resulting in issues communicating with the rcserver.
This commit is contained in:
Jonathan Giannuzzi 2025-03-04 13:55:43 +00:00 committed by Nick Craig-Wood
parent 401cf81034
commit 65f7eb0fba
2 changed files with 84 additions and 2 deletions

View File

@ -209,8 +209,21 @@ func (s *Server) handlePost(w http.ResponseWriter, r *http.Request, path string)
ctx := r.Context() ctx := r.Context()
contentType := r.Header.Get("Content-Type") contentType := r.Header.Get("Content-Type")
var (
contentTypeMediaType string
contentTypeParams map[string]string
)
if contentType != "" {
var err error
contentTypeMediaType, contentTypeParams, err = mime.ParseMediaType(contentType)
if err != nil {
writeError(path, nil, w, fmt.Errorf("failed to parse Content-Type: %w", err), http.StatusBadRequest)
return
}
}
values := r.URL.Query() values := r.URL.Query()
if contentType == "application/x-www-form-urlencoded" { if contentTypeMediaType == "application/x-www-form-urlencoded" {
// Parse the POST and URL parameters into r.Form, for others r.Form will be empty value // Parse the POST and URL parameters into r.Form, for others r.Form will be empty value
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
@ -229,7 +242,13 @@ func (s *Server) handlePost(w http.ResponseWriter, r *http.Request, path string)
} }
// Parse a JSON blob from the input // Parse a JSON blob from the input
if contentType == "application/json" { if contentTypeMediaType == "application/json" {
// Check the charset is utf-8 or unset
if charset, ok := contentTypeParams["charset"]; ok && !strings.EqualFold(charset, "utf-8") {
writeError(path, in, w, fmt.Errorf("unsupported charset %q for JSON input", charset), http.StatusBadRequest)
return
}
err := json.NewDecoder(r.Body).Decode(&in) err := json.NewDecoder(r.Body).Decode(&in)
if err != nil { if err != nil {
writeError(path, in, w, fmt.Errorf("failed to read input JSON: %w", err), http.StatusBadRequest) writeError(path, in, w, fmt.Errorf("failed to read input JSON: %w", err), http.StatusBadRequest)

View File

@ -429,6 +429,18 @@ func TestRC(t *testing.T) {
"param1": "string", "param1": "string",
"param2": true "param2": true
} }
`,
}, {
Name: "json-mixed-case-content-type",
URL: "rc/noop",
Method: "POST",
Body: `{ "param1":"string", "param2":true }`,
ContentType: "ApplicAtion/JsOn",
Status: http.StatusOK,
Expected: `{
"param1": "string",
"param2": true
}
`, `,
}, { }, {
Name: "json-and-url-params", Name: "json-and-url-params",
@ -459,6 +471,44 @@ func TestRC(t *testing.T) {
"path": "rc/noop", "path": "rc/noop",
"status": 400 "status": 400
} }
`,
}, {
Name: "json-charset",
URL: "rc/noop",
Method: "POST",
Body: `{ "param1":"string", "param2":true }`,
ContentType: "application/json; charset=utf-8",
Status: http.StatusOK,
Expected: `{
"param1": "string",
"param2": true
}
`,
}, {
Name: "json-mixed-case-charset",
URL: "rc/noop",
Method: "POST",
Body: `{ "param1":"string", "param2":true }`,
ContentType: "aPPlication/jSoN; charset=UtF-8",
Status: http.StatusOK,
Expected: `{
"param1": "string",
"param2": true
}
`,
}, {
Name: "json-bad-charset",
URL: "rc/noop",
Method: "POST",
Body: `{ "param1":"string", "param2":true }`,
ContentType: "application/json; charset=latin1",
Status: http.StatusBadRequest,
Expected: `{
"error": "unsupported charset \"latin1\" for JSON input",
"input": {},
"path": "rc/noop",
"status": 400
}
`, `,
}, { }, {
Name: "form", Name: "form",
@ -498,6 +548,19 @@ func TestRC(t *testing.T) {
"path": "rc/noop", "path": "rc/noop",
"status": 400 "status": 400
} }
`,
}, {
Name: "malformed-content-type",
URL: "rc/noop",
Method: "POST",
ContentType: "malformed/",
Status: http.StatusBadRequest,
Expected: `{
"error": "failed to parse Content-Type: mime: expected token after slash",
"input": null,
"path": "rc/noop",
"status": 400
}
`, `,
}} }}
opt := newTestOpt() opt := newTestOpt()