aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-02 21:25:46 +0200
committerAgando <roduccomm@gmail.com>2026-10-02 21:25:46 +0200
commit2ec6532511eb5cae90d4ec2813742000914853a7 (patch)
tree252ce16ec96be93d807faec11809cb119c249192
parentca7cd43de05620f372b55f49147eb6820d858fac (diff)
newOS4 v4.6.18: nginx dav trailing-slash semantics
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)
-rw-r--r--main.html6
-rw-r--r--modules/core/module-loader.js2
-rw-r--r--modules/ext/webdav.js24
-rw-r--r--scripts/test-webdav-paths.mjs9
4 files changed, 29 insertions, 12 deletions
diff --git a/main.html b/main.html
index 3ec6a94..a747582 100644
--- a/main.html
+++ b/main.html
@@ -1345,13 +1345,13 @@
</script>
<!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix -->
-<script src="modules/core/core-bundle.js?v=v4.6.16"></script>
+<script src="modules/core/core-bundle.js?v=v4.6.18"></script>
<!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules -->
-<script src="modules/core/module-loader.js?v=v4.6.16"></script>
+<script src="modules/core/module-loader.js?v=v4.6.18"></script>
<!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work -->
-<script src="modules/ext/critical-ext-bundle.js?v=v4.6.16"></script>
+<script src="modules/ext/critical-ext-bundle.js?v=v4.6.18"></script>
<script>
// ═══ ONE TRUE VERSION SOURCE ═══
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js
index 16723ad..ef3e2ee 100644
--- a/modules/core/module-loader.js
+++ b/modules/core/module-loader.js
@@ -4,7 +4,7 @@
// Core module bundle loaded synchronously in main.html.
// This loader only handles version cache-busting, splash, terminal init, and optional ext modules.
- const LATEST_VER = 'v4.6.16';
+ const LATEST_VER = 'v4.6.18';
const CACHE = '?v=' + LATEST_VER;
const stored = localStorage.getItem('webx11_cache_ver');
if (stored !== LATEST_VER) {
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() {
diff --git a/scripts/test-webdav-paths.mjs b/scripts/test-webdav-paths.mjs
index a8340c0..d75f9f6 100644
--- a/scripts/test-webdav-paths.mjs
+++ b/scripts/test-webdav-paths.mjs
@@ -57,7 +57,7 @@ function check(name, cond, got) {
// relative + traversal + dupes:
['./a//b/../c.txt', '/a/c.txt'],
['/a/b/../../d.txt', '/d.txt'],
- ['/skills/', '/skills'],
+ ['/skills/', '/skills/'], // trailing slash PRESERVED (nginx dav collection ops need it)
['backups', '/backups'],
// foreign space stays intact (only own space prefix is stripped):
['/spaces/otheruser/file.txt', '/spaces/otheruser/file.txt'],
@@ -90,8 +90,8 @@ function check(name, cond, got) {
};
m._listDir = async () => { throw new Error('ENOENT'); };
await m._ensureDir('https://webx11.duckdns.org/spaces/mindtech/thaimassage-kiefersfelden');
- check('_ensureDir MKCOLs normalized path (no https:/ junk)',
- mkcols.length === 1 && mkcols[0] === '/thaimassage-kiefersfelden', mkcols);
+ check('_ensureDir MKCOLs normalized path (no https:/ junk, trailing slash for nginx)',
+ mkcols.length === 1 && mkcols[0] === '/thaimassage-kiefersfelden/', mkcols);
// Existing dir fast-path: __list__ probe succeeds -> no MKCOL at all
const m2 = makeMod();
@@ -107,7 +107,8 @@ function check(name, cond, got) {
m3._listDir = async (p) => { if (p === '/backups') return []; throw new Error('ENOENT'); };
m3._webdavRequest = async (method, path) => { if (method === 'MKCOL') mkcols3.push(path); return { ok: true, status: 201 }; };
await m3._ensureDir('/backups/2026');
- check('_ensureDir MKCOLs only missing tail', mkcols3.length === 1 && mkcols3[0] === '/backups/2026', mkcols3);
+ check('_ensureDir MKCOLs only missing tail (trailing slash for nginx)',
+ mkcols3.length === 1 && mkcols3[0] === '/backups/2026/', mkcols3);
}
// --- PUT via _webdavRequest hits normalized URL ---