fix: webdav not working when filenames have "&"
Massimo Melina committed
Mar 15, 2026 at 21:39 UTC
2c732444a93f78eb5b45cee083db3c54f23159e7
3 files changed
+14
-3
src/webdav.ts
+7
-3
@@ -218,8 +218,8 @@ export async function handledWebdav(ctx: Koa.Context) {
218
return `<?xml version="1.0" encoding="utf-8"?><prop xmlns="DAV:"><lockdiscovery><activelock>
219
<locktype><write/></locktype>
220
<lockscope><exclusive/></lockscope>
221
- <locktoken><href>${token}</href></locktoken>
222
- <lockroot><href>${path}</href></lockroot>
221
+ <locktoken><href>${escapeXml(token)}</href></locktoken>
222
+ <lockroot><href>${escapeXml(path)}</href></lockroot>
223
<depth>0</depth>
224
<timeout>Second-${seconds}</timeout>
225
</activelock></lockdiscovery></prop>`
@@ -259,7 +259,7 @@ export async function handledWebdav(ctx: Koa.Context) {
259
const isDir = await nodeIsFolder(node)
260
const st = await nodeStats(node)
261
res.write(`<response>
262
- <href>${outPath + (append ? pathEncode(name, true) + (isDir ? '/' : '') : '')}</href>
262
+ <href>${escapeXml(outPath + (append ? pathEncode(name, true) + (isDir ? '/' : '') : ''))}</href>
263
<propstat>
264
<status>HTTP/1.1 200 OK</status>
265
<prop>
@@ -311,6 +311,10 @@ export async function handledWebdav(ctx: Koa.Context) {
311
312
}
313
314
+function escapeXml(value: string) {
315
+ return value.replace(/[<>&'\"]/g, c => ({ '<': '<', '>': '>', '&': '&', "'": ''', '"': '"', }[c] || c))
316
+}
317
+
318
function compileWebdavAgentRegex(v: boolean|string) {
319
return !v ? null : v === true ? /.*/ : new RegExp(v.trim(), 'i')
320
}
tests/config.yaml
+5
@@ -37,6 +37,11 @@ vfs:
37
source: ../page/gpl.png
38
can_read:
39
- rejetto
40
+ - name: hidden
41
+ can_see:
42
+ this: false
43
+ children:
44
+ - name: a<&>;#'z
45
- name: for-admins
46
can_read:
47
- admins
tests/test.ts
+2
@@ -12,6 +12,7 @@ import { httpStream, parseHttpUrl, stream2string, XRequestOptions } from '../src
12
import { ThrottledStream, ThrottleGroup } from '../src/ThrottledStream'
13
import { mkdir, rm, rename, writeFile, access } from 'fs/promises'
14
import { Readable } from 'stream'
15
+import { XMLValidator } from 'fast-xml-parser'
16
/*
17
import { PORT, srv } from '../src'
18
@@ -343,6 +344,7 @@ describe('webdav', () => {
344
await rmAny(destPath)
345
}
346
})
347
+ test('webdav.escaping', req('/f1/hidden', data => XMLValidator.validate(data) === true, { method: 'PROPFIND', auth, jar: {}, headers: { depth: '1' } }))
348
349
function webdavUpload(uri: string, tester: Tester, body: string, userAgent=WEBDAV_UA) {
350
return () => req(uri, tester, {