From 5b974b82f251121cf79af0129dfea945518c032e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 09:04:34 +0000 Subject: [PATCH] Fix JWT middleware to skip body dump for multipart/form-data requests Co-authored-by: kevwan <1918356+kevwan@users.noreply.github.com> --- rest/handler/authhandler.go | 10 ++++- rest/handler/authhandler_test.go | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/rest/handler/authhandler.go b/rest/handler/authhandler.go index ab781bbfa..8eb11cadb 100644 --- a/rest/handler/authhandler.go +++ b/rest/handler/authhandler.go @@ -5,6 +5,7 @@ import ( "errors" "net/http" "net/http/httputil" + "strings" "github.com/golang-jwt/jwt/v4" "github.com/zeromicro/go-zero/core/logc" @@ -99,10 +100,17 @@ func WithUnauthorizedCallback(callback UnauthorizedCallback) AuthorizeOption { func detailAuthLog(r *http.Request, reason string) { // discard dump error, only for debug purpose - details, _ := httputil.DumpRequest(r, true) + // Skip dumping request body for multipart/form-data to avoid reading large files + dumpBody := !isMultipartFormData(r) + details, _ := httputil.DumpRequest(r, dumpBody) logc.Errorf(r.Context(), "authorize failed: %s\n=> %+v", reason, string(details)) } +func isMultipartFormData(r *http.Request) bool { + contentType := r.Header.Get("Content-Type") + return strings.Contains(contentType, "multipart/form-data") +} + func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback UnauthorizedCallback) { writer := response.NewHeaderOnceResponseWriter(w) diff --git a/rest/handler/authhandler_test.go b/rest/handler/authhandler_test.go index 27347a5c6..b3b20911c 100644 --- a/rest/handler/authhandler_test.go +++ b/rest/handler/authhandler_test.go @@ -90,6 +90,72 @@ func TestAuthHandler_NilError(t *testing.T) { }) } +func TestAuthHandlerWithMultipartFormData(t *testing.T) { + const key = "B63F477D-BBA3-4E52-96D3-C0034C27694A" + + // Create a multipart form-data request + // We don't need actual body content since we're testing that + // the body is NOT read when Content-Type is multipart/form-data + req := httptest.NewRequest(http.MethodPost, "http://localhost/upload", + http.NoBody) + req.Header.Set("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundary") + // Missing authorization header to trigger the unauthorized path + + handler := Authorize(key)( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + + resp := httptest.NewRecorder() + handler.ServeHTTP(resp, req) + + // Should return unauthorized + assert.Equal(t, http.StatusUnauthorized, resp.Code) +} + +func TestIsMultipartFormData(t *testing.T) { + tests := []struct { + name string + contentType string + expected bool + }{ + { + name: "multipart/form-data", + contentType: "multipart/form-data", + expected: true, + }, + { + name: "multipart/form-data with boundary", + contentType: "multipart/form-data; boundary=----WebKitFormBoundary", + expected: true, + }, + { + name: "application/json", + contentType: "application/json", + expected: false, + }, + { + name: "application/x-www-form-urlencoded", + contentType: "application/x-www-form-urlencoded", + expected: false, + }, + { + name: "empty content type", + contentType: "", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "http://localhost", http.NoBody) + req.Header.Set("Content-Type", tt.contentType) + result := isMultipartFormData(req) + assert.Equal(t, tt.expected, result) + }) + } +} + func buildToken(secretKey string, payloads map[string]any, seconds int64) (string, error) { now := time.Now().Unix() claims := make(jwt.MapClaims)