diff --git a/NOTICE b/NOTICE index 338274043..dd1881a7f 100644 --- a/NOTICE +++ b/NOTICE @@ -1150,8 +1150,8 @@ SOFTWARE. -------------------------------------------------------------------------------- Package: github.com/gin-contrib/gzip -Version: v1.2.3 -License: MIT (https://github.com/gin-contrib/gzip/blob/v1.2.3/LICENSE) +Version: v1.2.5 +License: MIT (https://github.com/gin-contrib/gzip/blob/v1.2.5/LICENSE) MIT License diff --git a/go.mod b/go.mod index 767ea7707..f65a3d66b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/dsoprea/go-tiff-image-structure/v2 v2.0.0-20221003165014-8ecc4f52edca github.com/dustin/go-humanize v1.0.1 github.com/esimov/pigo v1.4.6 - github.com/gin-contrib/gzip v1.2.3 + github.com/gin-contrib/gzip v1.2.5 github.com/gin-gonic/gin v1.11.0 github.com/golang/geo v0.0.0-20251209161508-25c597310d4b github.com/google/open-location-code/go v0.0.0-20250620134813-83986da0156b @@ -173,7 +173,6 @@ require ( go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect - go.uber.org/mock v0.6.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/tools v0.39.0 // indirect diff --git a/go.sum b/go.sum index b7892cdab..0f2168174 100644 --- a/go.sum +++ b/go.sum @@ -131,8 +131,8 @@ github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= -github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U= -github.com/gin-contrib/gzip v1.2.3/go.mod h1:ad72i4Bzmaypk8M762gNXa2wkxxjbz0icRNnuLJ9a/c= +github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI= +github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= diff --git a/internal/api/swagger.json b/internal/api/swagger.json index 1457aacc0..ec45aaf26 100644 --- a/internal/api/swagger.json +++ b/internal/api/swagger.json @@ -4489,11 +4489,11 @@ "timestamppb.Timestamp": { "properties": { "nanos": { - "description": "Non-negative fractions of a second at nanosecond resolution. Negative\nsecond values with fractions must still have non-negative nanos values\nthat count forward in time. Must be from 0 to 999,999,999\ninclusive.", + "description": "Non-negative fractions of a second at nanosecond resolution. This field is\nthe nanosecond portion of the duration, not an alternative to seconds.\nNegative second values with fractions must still have non-negative nanos\nvalues that count forward in time. Must be between 0 and 999,999,999\ninclusive.", "type": "integer" }, "seconds": { - "description": "Represents seconds of UTC time since Unix epoch\n1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n9999-12-31T23:59:59Z inclusive.", + "description": "Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must\nbe between -315576000000 and 315576000000 inclusive (which corresponds to\n0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z).", "type": "integer" } }, diff --git a/internal/server/gzip_test.go b/internal/server/gzip_test.go new file mode 100644 index 000000000..aee27adeb --- /dev/null +++ b/internal/server/gzip_test.go @@ -0,0 +1,95 @@ +package server + +import ( + "bytes" + stdgzip "compress/gzip" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-contrib/gzip" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/photoprism/photoprism/internal/config" +) + +func TestGzipMiddleware(t *testing.T) { + gin.SetMode(gin.TestMode) + + // Enable gzip for this test router. + conf := config.TestConfig() + conf.Options().HttpCompression = "gzip" + + r := gin.New() + r.Use(gzip.Gzip( + gzip.DefaultCompression, + gzip.WithExcludedExtensions([]string{ + ".png", ".gif", ".jpeg", ".jpg", ".webp", ".mp3", ".mp4", ".zip", ".gz", + }), + gzip.WithExcludedPaths([]string{ + conf.BaseUri("/health"), + conf.BaseUri(config.ApiUri + "/t"), + conf.BaseUri(config.ApiUri + "/folders/t"), + conf.BaseUri(config.ApiUri + "/dl"), + conf.BaseUri(config.ApiUri + "/zip"), + conf.BaseUri(config.ApiUri + "/albums"), + conf.BaseUri(config.ApiUri + "/labels"), + conf.BaseUri(config.ApiUri + "/videos"), + }), + )) + + r.GET("/ok", func(c *gin.Context) { + c.String(http.StatusOK, "hello world") + }) + + excludedPath := conf.BaseUri(config.ApiUri + "/dl/test") + r.GET(excludedPath, func(c *gin.Context) { + c.String(http.StatusOK, "download") + }) + + t.Run("CompressesSuccessfulResponse", func(t *testing.T) { + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/ok", nil) + req.Header.Set("Accept-Encoding", "gzip") + + r.ServeHTTP(w, req) + + require.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "gzip", w.Header().Get("Content-Encoding")) + + zr, err := stdgzip.NewReader(bytes.NewReader(w.Body.Bytes())) + require.NoError(t, err) + defer zr.Close() + + b, err := io.ReadAll(zr) + require.NoError(t, err) + assert.Equal(t, "hello world", string(b)) + }) + + t.Run("DoesNotCompressExcludedPaths", func(t *testing.T) { + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", excludedPath, nil) + req.Header.Set("Accept-Encoding", "gzip") + + r.ServeHTTP(w, req) + + require.Equal(t, http.StatusOK, w.Code) + assert.Empty(t, w.Header().Get("Content-Encoding")) + assert.Equal(t, "download", w.Body.String()) + }) + + t.Run("DoesNotCompressNotFound", func(t *testing.T) { + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/missing", nil) + req.Header.Set("Accept-Encoding", "gzip") + + r.ServeHTTP(w, req) + + require.Equal(t, http.StatusNotFound, w.Code) + assert.Empty(t, w.Header().Get("Content-Encoding")) + assert.Contains(t, w.Body.String(), "404") + }) +}