diff options
Diffstat (limited to 'modules/ext/cryptogzip.js')
| -rw-r--r-- | modules/ext/cryptogzip.js | 67 |
1 files changed, 54 insertions, 13 deletions
diff --git a/modules/ext/cryptogzip.js b/modules/ext/cryptogzip.js index 200abdd..cc275df 100644 --- a/modules/ext/cryptogzip.js +++ b/modules/ext/cryptogzip.js @@ -15,7 +15,8 @@ class CryptoGzipModule { 'decrypt': 'decrypt <path.enc> <passphrase> — Decrypt a .enc file', 'tar': 'tar <src-dir> <out.tar[.gz]> — Bundle a VFS directory into a tar archive (gzip if .gz)', 'untar': 'untar <path.tar[.gz]> [dest-dir] — Extract a tar archive into VFS (auto-detects gzip)', - 'wget': 'wget <url> [filename] — Download a URL and save to VFS' + 'wget': 'wget <url> [filename] — Download a URL (via /api/fetch) and save it to VFS', + 'curl': 'curl [-s] [-o FILE] <url> — Same as wget: proxy fetch + verified VFS write' }; } @@ -342,7 +343,10 @@ class CryptoGzipModule { } // ============================================================ - // WGET — fetch URL and save to VFS (binary-safe) + // WGET — fetch a URL through the /api/fetch proxy and save to VFS + // A direct browser fetch() only works same-origin: cross-origin URLs + // throw "Failed to fetch" (CORS), measured 2026-11-06 on the served + // v4.6.150 page. /api/fetch returns the raw source as UTF-8 text. // ============================================================ async _cmdWget(args) { @@ -350,20 +354,40 @@ class CryptoGzipModule { this.terminal.print('Usage: wget <url> [filename] — Download a URL and save to VFS', 'warning'); return; } - const url = args[0]; - const filename = args[1] || url.split('/').pop() || 'download.bin'; + const url = /^https?:\/\//i.test(args[0]) ? args[0] : 'https://' + args[0]; + const filename = args[1] || url.split('/').pop().split('?')[0] || 'download.bin'; this.terminal.print(`Downloading ${url}...`, 'info'); + let text; try { - const resp = await fetch(url); - if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${resp.statusText}`); - const buf = await resp.arrayBuffer(); - const bytes = new Uint8Array(buf); - this._vfsWriteBytes(filename, bytes); - const sizeKb = (bytes.length / 1024).toFixed(1); - this.terminal.print(`✅ wget: ${filename} (${sizeKb} KB, ${bytes.length} bytes)`, 'success'); + const resp = await fetch('/api/fetch?url=' + encodeURIComponent(url)); + if (resp.status === 403) { + this.terminal.print('wget error: URL blocked — internal network', 'error'); + return; + } + if (!resp.ok) { + let detail = 'HTTP ' + resp.status; + try { const j = await resp.json(); detail = j.error || detail; } catch (e) {} + this.terminal.print('wget error: ' + detail, 'error'); + return; + } + text = await resp.text(); } catch (e) { - this.terminal.print(`wget error: ${e.message}`, 'error'); + this.terminal.print('wget error: ' + e.message, 'error'); + return; + } + // writeFile() returns false rather than throwing when the parent directory is + // missing — the old code printed ✅ on that path (measured: writeFile('nosuchdir/x.txt') === false). + const dir = filename.includes('/') ? filename.slice(0, filename.lastIndexOf('/')) : ''; + if (dir) { try { this.terminal.fs.mkdir(dir); } catch (e) {} } + if (!this.terminal.fs.writeFile(filename, text)) { + this.terminal.print(`wget error: could not write ${filename} (missing directory, or path is a directory)`, 'error'); + return; } + if (this.terminal.fs.readFile(filename) !== text) { + this.terminal.print(`wget error: write verification failed for ${filename}`, 'error'); + return; + } + this.terminal.print(`✅ wget: ${filename} (${(text.length / 1024).toFixed(1)} KB, ${text.length} chars)`, 'success'); } // ============================================================ @@ -582,6 +606,22 @@ class CryptoGzipModule { this.commands['tar'] = async (args) => self._cmdTar(args); this.commands['untar'] = async (args) => self._cmdUntar(args); this.commands['wget'] = async (args) => self._cmdWget(args); + // curl — the flags an agent actually types. No shell semantics: -s/-L/-f are + // accepted and ignored, -o/--output selects the VFS target. Delegates to the + // single verified writer above so the two cannot drift. + this.commands['curl'] = async (args) => { + const positional = []; + let out = null; + for (let i = 0; i < args.length; i++) { + const a = String(args[i]); + if (a === '-o' || a === '-O' || a === '--output') { out = args[++i]; } + else if (a.startsWith('-')) { /* accepted, ignored */ } + else positional.push(a); + } + if (!positional[0]) { this.terminal.print('Usage: curl [-s] [-o FILE] <url>', 'warning'); return; } + const target = out || (positional[0].split('/').pop().split('?')[0] || 'download.bin'); + return self._cmdWget([positional[0], target]); + }; this.commandsMeta = { gzip: { argType: 'file', description: 'Compress a VFS file to .gz' }, gunzip: { argType: 'file', description: 'Decompress a .gz file (binary-safe)' }, @@ -589,7 +629,8 @@ class CryptoGzipModule { decrypt: { argType: 'file', description: 'Decrypt a .enc file' }, tar: { argType: 'file', description: 'Bundle a VFS directory into a tar archive' }, untar: { argType: 'file', description: 'Extract a tar archive into VFS' }, - wget: { argType: 'url', description: 'Download a URL and save to VFS' } + wget: { argType: 'url', description: 'Download a URL and save to VFS' }, + curl: { argType: 'url', description: 'curl [-s] [-o FILE] <url> — same as wget' } }; } } |