diff --git a/internal/server/api_paths_test.go b/internal/server/api_paths_test.go index 748e7a371..e22304226 100644 --- a/internal/server/api_paths_test.go +++ b/internal/server/api_paths_test.go @@ -1,6 +1,8 @@ package server_test import ( + "net/http" + "strings" "testing" "github.com/stretchr/testify/require" @@ -36,3 +38,33 @@ func TestPathsAPI(t *testing.T) { require.Equal(t, env.LocalPathSourceInfo(dir0), resp.SourceInfo) } + +func TestPathsAPI_RequestBodyTooLarge(t *testing.T) { + ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant) + srvInfo := servertesting.StartServer(t, env, false) + + cli, err := apiclient.NewKopiaAPIClient(apiclient.Options{ + BaseURL: srvInfo.BaseURL, + TrustedServerCertificateFingerprint: srvInfo.TrustedServerCertificateFingerprint, + Username: servertesting.TestUIUsername, + Password: servertesting.TestUIPassword, + }) + + require.NoError(t, err) + require.NoError(t, cli.FetchCSRFTokenForTesting(ctx)) + + // the request body limit is 100_000 bytes, so a path much longer than that + // must be rejected before it reaches the handler. + req := &serverapi.ResolvePathRequest{ + Path: strings.Repeat("a", 105_000), + } + resp := &serverapi.ResolvePathResponse{} + + err = cli.Post(ctx, "paths/resolve", req, resp) + require.Error(t, err) + + var hsr apiclient.HTTPStatusError + + require.ErrorAs(t, err, &hsr) + require.Equal(t, http.StatusRequestEntityTooLarge, hsr.HTTPStatusCode) +} diff --git a/internal/server/server.go b/internal/server/server.go index 1407448b0..6c41021a3 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -358,13 +358,24 @@ func (s *Server) handleUIPossiblyNotConnected(f apiRequestFunc) http.HandlerFunc } func (s *Server) handleRequestPossiblyNotConnected(isAuthorized isAuthorizedFunc, checkCSRFToken csrfTokenOption, f apiRequestFunc) http.HandlerFunc { + const maxRequestBodySizeBytes = 100_000 + return s.requireAuth(checkCSRFToken, func(ctx context.Context, rc requestContext) { // we must pre-read request body before acquiring the lock as it sometimes leads to deadlock // in HTTP/2 server. // See https://github.com/golang/go/issues/40816 - body, berr := io.ReadAll(rc.req.Body) + body, berr := io.ReadAll(http.MaxBytesReader(rc.w, rc.req.Body, maxRequestBodySizeBytes)) if berr != nil { - http.Error(rc.w, "error reading request body", http.StatusInternalServerError) + errCode := http.StatusInternalServerError + + var mbe *http.MaxBytesError + + if errors.As(berr, &mbe) { + errCode = http.StatusRequestEntityTooLarge + } + + http.Error(rc.w, "error reading request body", errCode) + return }