From 6a0cb8cc84ad2cf9bf17d008823d07e5958c6e0a Mon Sep 17 00:00:00 2001 From: Agando Date: Fri, 2 Oct 2026 21:25:46 +0200 Subject: newOS4 v4.6.18: nginx dav trailing-slash semantics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Discovered by dav-health-check.sh E2E (10/10 pass now): - nginx dav module REQUIRES trailing slash on MKCOL (no-slash → 409) and on directory DELETE (no-slash → 409/404); existing dir + slash MKCOL → 405 (the real 'already exists' signal) - _normalizeRemotePath now PRESERVES trailing slashes (collection ops) - _ensureDir MKCOLs with trailing slash - _deleteFile retries with slash on 409/404 (collection delete) - health-check expectations corrected (405 = existing, slash required) --- modules/ext/webdav.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'modules/ext') diff --git a/modules/ext/webdav.js b/modules/ext/webdav.js index 5bcc4ee..e989ac9 100644 --- a/modules/ext/webdav.js +++ b/modules/ext/webdav.js @@ -194,14 +194,19 @@ class WebDAVModule { } p = p.split('?')[0].split('#')[0]; if (!p.startsWith('/')) p = '/' + p; - // Collapse duplicate slashes and resolve '..' / '.' segments + // Collapse duplicate slashes and resolve '..' / '.' segments. + // PRESERVE a trailing slash — nginx's dav module needs it for + // collection operations (MKCOL/DELETE on directories). + const trailing = /\/+$/.test(p); const out = []; for (const seg of p.split('/')) { if (seg === '' || seg === '.') continue; if (seg === '..') { out.pop(); continue; } out.push(seg); } - return '/' + out.join('/'); + let res = '/' + out.join('/'); + if (trailing && res !== '/') res += '/'; + return res; } _absoluteUrl(remotePath) { @@ -414,7 +419,10 @@ class WebDAVModule { for (let i = deepest; i < segments.length; i++) { current += '/' + segments[i]; try { - const attempt = await this._webdavRequest('MKCOL', current); + // nginx dav requires a TRAILING SLASH on MKCOL for the + // collection itself (no-slash → 409 'conflict'). Verified + // live 2026-10-02: no-slash 409, with-slash 201. + const attempt = await this._webdavRequest('MKCOL', current + '/'); if (attempt.ok || attempt.status === 405 || attempt.status === 301 || attempt.status === 302) created = true; } catch (e) { /* ignore — component may exist */ } } @@ -422,8 +430,16 @@ class WebDAVModule { } async _deleteFile(remotePath) { + const p = this._normalizeRemotePath(remotePath); const resp = await this._webdavRequest('DELETE', remotePath); - return resp.ok || resp.status === 204 || resp.status === 404; + if (resp.ok || resp.status === 204 || resp.status === 404) return true; + // nginx dav needs a trailing slash to delete a COLLECTION; a no-slash + // DELETE on a directory returns 409/404. Retry with slash. + if ((resp.status === 409 || resp.status === 404) && !p.endsWith('/')) { + const retry = await this._webdavRequest('DELETE', p + '/'); + return retry.ok || retry.status === 204 || retry.status === 404; + } + return false; } async _cleanupRetention() { -- cgit v1.2.3