From 18a875a7645acafa166df758808a9aabc181fac8 Mon Sep 17 00:00:00 2001 From: Administrator Date: Mon, 25 May 2026 00:32:25 +0200 Subject: [PATCH] fix(doodstream): use current page format (form action + matching sess_id) The 3.3.25 diagnostics captured the live upload page: doodstream moved the upload server from a `srv_url` JS variable into the multipart form's action, e.g. action="https://xxx.cloudatacdn.com/upload/01?SESSID", with a per-page session token in the query that matches the page's hidden sess_id input. The old parser found neither and fell through to the stale hardcoded node, which returns an empty filecode. - Parse the upload server from the form action (matched via the /upload/ path), un-escaping & in the query string. - Refresh this.sessId from the SAME page (only on action match) so the multipart sess_id field matches the node URL's token; login-time and node tokens otherwise diverge. Keep the existing sessId if the input is absent. - Keep the legacy ?op=upload_server JSON and srv_url paths as fallbacks; the fail-fast throw from 3.3.25 stays as the last resort. - Tests: form-action parse, sess_id refresh, & un-escape (9 total). Whether this fully resolves the uploads is for the next server logs to confirm; both the node and sess_id fixes are individually correct. Co-Authored-By: Claude Opus 4.7 --- lib/doodstream-upload.js | 25 +++++++++++++++++++++++++ tests/doodstream-upload.test.js | 21 +++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/lib/doodstream-upload.js b/lib/doodstream-upload.js index 47d786a..d9a6a2b 100644 --- a/lib/doodstream-upload.js +++ b/lib/doodstream-upload.js @@ -210,6 +210,31 @@ class DoodstreamUploader { // Fallback: try fetching from upload page HTML const pageRes = await this._fetch(BASE_URL + '/?op=upload'); const html = await pageRes.text(); + + // Current doodstream format: the upload server is the action of the + // multipart upload form, e.g. + //
+ // + // The node is assigned per page-load and the action carries a session token + // in its query string that matches the page's hidden sess_id. We refresh + // this.sessId from THIS page so the multipart sess_id field matches the node + // URL — login-time and node tokens otherwise diverge and the upload comes + // back with an empty filecode. + const actionMatch = html.match(/action=["'](https?:\/\/[^"']+\/upload\/[^"']*)["']/i); + if (actionMatch) { + const url = actionMatch[1].replace(/&/g, '&'); // un-escape HTML entities in query + const freshSess = html.match(/name=["']sess_id["'][^>]*value=["']([a-zA-Z0-9]+)["']/); + if (freshSess) { + this.sessId = freshSess[1]; + } else { + _debugLog('upload_server: form action found but no sess_id on page; keeping existing sessId'); + } + _debugLog(`upload_server: using form action node=${url} sess=${this.sessId}`); + return url; + } + + // Legacy fallback: srv_url JS variable (older doodstream theme). const srvMatch = html.match(/srv_url['":\s]+['"]?(https?:\/\/[^'">\s]+)['"]?/i); if (srvMatch) return srvMatch[1]; diff --git a/tests/doodstream-upload.test.js b/tests/doodstream-upload.test.js index ca1d895..8f315aa 100644 --- a/tests/doodstream-upload.test.js +++ b/tests/doodstream-upload.test.js @@ -87,6 +87,27 @@ test('getUploadServer: falls back to srv_url in upload-page HTML', async () => { assert.equal(await up._getUploadServer(), 'https://node7.cloudatacdn.com/upload/01'); }); +test('getUploadServer: parses current form-action node and refreshes sess_id from the same page', async () => { + const up = new DoodstreamUploader(); + up.sessId = 'stale-from-login'; + up._fetch = async (url) => { + if (/op=upload_server/.test(url)) return fakeRes('not json'); + return fakeRes('
'); + }; + const url = await up._getUploadServer(); + assert.equal(url, 'https://n9.cloudatacdn.com/upload/01?FRESH123'); + assert.equal(up.sessId, 'FRESH123'); // critical: form-field token must match the node URL token +}); + +test('getUploadServer: un-escapes & in the form-action query string', async () => { + const up = new DoodstreamUploader(); + up._fetch = async (url) => { + if (/op=upload_server/.test(url)) return fakeRes('not json'); + return fakeRes('
'); + }; + assert.equal(await up._getUploadServer(), 'https://n9.cloudatacdn.com/upload/01?a=1&b=2'); +}); + test('getUploadServer: throws (no silent dead fallback) when discovery fails', async () => { const up = new DoodstreamUploader(); up._fetch = async () => fakeRes('login required', { status: 200 });