Skip to content

Commit b6e8ad2

Browse files
committed
WebDAV: Limit PUT upload size to the configured originals limit
Cap the WebDAV PUT request body at OriginalsLimitBytes when an originals limit is configured, so a single upload cannot stream an unbounded body to disk past the per-file ceiling; no-op when unlimited. Adds a load-bearing regression test.
1 parent d3a94bb commit b6e8ad2

2 files changed

Lines changed: 41 additions & 0 deletions

File tree

internal/server/webdav.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/gin-gonic/gin"
1212
"golang.org/x/net/webdav"
1313

14+
"github.com/photoprism/photoprism/internal/api"
1415
"github.com/photoprism/photoprism/internal/config"
1516
"github.com/photoprism/photoprism/internal/mutex"
1617
"github.com/photoprism/photoprism/internal/workers/auto"
@@ -118,6 +119,15 @@ func WebDAV(dir string, router *gin.RouterGroup, conf *config.Config) {
118119
}
119120
}
120121

122+
// Bound an uploaded file to the configured originals size limit (when set) so a single
123+
// PUT cannot stream an unbounded body to disk; the free-storage check above only catches
124+
// the next request. No-op when no originals limit is configured.
125+
if c.Request.Method == header.MethodPut {
126+
if limit := conf.OriginalsLimitBytes(); limit > 0 {
127+
api.LimitRequestBodyBytes(c, limit)
128+
}
129+
}
130+
121131
// Invoke handler callback.
122132
WebDAVHandler(c, router, srv)
123133
}

internal/server/webdav_write_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,37 @@ func TestWebDAVWrite_MKCOL_PUT(t *testing.T) {
6464
assert.Equal(t, "hello", string(b))
6565
}
6666

67+
func TestWebDAVWrite_PUT_OriginalsLimit(t *testing.T) {
68+
conf := newWebDAVTestConfig(t)
69+
conf.Options().OriginalsLimit = 1 // cap uploaded files at 1 MB
70+
if err := conf.CreateDirectories(); err != nil {
71+
t.Fatalf("failed to create test directories: %v", err)
72+
}
73+
r := setupWebDAVRouter(conf)
74+
75+
t.Run("UnderLimitAccepted", func(t *testing.T) {
76+
w := httptest.NewRecorder()
77+
req := httptest.NewRequest(header.MethodPut, conf.BaseUri(WebDAVOriginals)+"/small.bin", bytes.NewReader(make([]byte, 512*1024)))
78+
authBearer(req)
79+
r.ServeHTTP(w, req)
80+
assert.InDelta(t, 201, w.Code, 1)
81+
})
82+
t.Run("OverLimitRejected", func(t *testing.T) {
83+
w := httptest.NewRecorder()
84+
req := httptest.NewRequest(header.MethodPut, conf.BaseUri(WebDAVOriginals)+"/big.bin", bytes.NewReader(make([]byte, 2*1024*1024)))
85+
authBearer(req)
86+
r.ServeHTTP(w, req)
87+
// The oversized PUT must not be accepted, and the bytes written to disk must not
88+
// exceed the configured originals limit (the body cap stops io.Copy mid-stream).
89+
assert.NotEqual(t, http.StatusCreated, w.Code)
90+
path := filepath.Join(conf.OriginalsPath(), "big.bin")
91+
// #nosec G304 -- test reads file created under controlled temp directory.
92+
if info, err := os.Stat(path); err == nil {
93+
assert.LessOrEqual(t, info.Size(), conf.OriginalsLimitBytes())
94+
}
95+
})
96+
}
97+
6798
func TestWebDAV_NoTrailingSlashRedirectOnBasePath(t *testing.T) {
6899
testCases := []struct {
69100
name string

0 commit comments

Comments
 (0)