Refuse API writes that are not application/json (closes #271)
check / check (push) Failing after 3s

A page on another origin of the same site could send a text/plain or
form POST with the user's cookie, since a browser sends such a request
without a preflight. RequireJSON on the /api/v1 router answers 415 to a
POST, PUT or DELETE whose Content-Type is not application/json, or that
has none, before the handler runs.

The web client and neoirc-cli already send the header on every request.

Model: opus-5-5
This commit is contained in:
sneak committed 2026-11-11 00:34:16 +00:00
1 parent ba79d6de02
commit 0dd748ddf7
5 files changed
+253 -2

No files matched your search

+67
View File
@@ -152,6 +152,73 @@ func TestClientIP(t *testing.T) {
}
}
func TestRequireJSON(t *testing.T) {
t.Parallel()
mware := newMiddleware(t, trustedNetwork)
const (
jsonType = "application/json"
textType = "text/plain"
refused = http.StatusUnsupportedMediaType
)
tests := []struct {
method, contentType string
want int
}{
{http.MethodPost, jsonType, http.StatusOK},
{http.MethodPost, "application/json; charset=utf-8", http.StatusOK},
{http.MethodPost, "Application/JSON", http.StatusOK},
{http.MethodPost, textType, refused},
{http.MethodPost, "application/x-www-form-urlencoded", refused},
{http.MethodPost, "multipart/form-data; boundary=x", refused},
{http.MethodPost, "", refused},
{http.MethodPost, "application/json; charset", refused},
{http.MethodPut, textType, refused},
{http.MethodPut, jsonType, http.StatusOK},
{http.MethodDelete, "", refused},
{http.MethodDelete, jsonType, http.StatusOK},
{http.MethodGet, "", http.StatusOK},
{http.MethodGet, textType, http.StatusOK},
}
for _, test := range tests {
t.Run(test.method+" "+test.contentType, func(t *testing.T) {
t.Parallel()
handler := mware.RequireJSON()(http.HandlerFunc(func(
http.ResponseWriter, *http.Request,
) {
}))
request := httptest.NewRequestWithContext(
t.Context(), test.method, "/", nil,
)
if test.contentType != "" {
request.Header.Set("Content-Type", test.contentType)
}
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
if recorder.Code != test.want {
t.Fatalf("status %d, want %d", recorder.Code, test.want)
}
if test.want == http.StatusOK {
return
}
want := `{"error":"content type must be application/json"}` + "\n"
if recorder.Body.String() != want {
t.Errorf("body %q, want %q", recorder.Body.String(), want)
}
})
}
}
// newMiddleware returns a Middleware that trusts the network trusted.
func newMiddleware(t *testing.T, trusted string) *middleware.Middleware {
t.Helper()