diff --git a/lib/doodstream-upload.js b/lib/doodstream-upload.js index aac1baa..caafab7 100644 --- a/lib/doodstream-upload.js +++ b/lib/doodstream-upload.js @@ -210,7 +210,8 @@ class DoodstreamUploader { let preamble = ''; preamble += `--${boundary}\r\nContent-Disposition: form-data; name="sess_id"\r\n\r\n${this.sessId}\r\n`; preamble += `--${boundary}\r\nContent-Disposition: form-data; name="utype"\r\n\r\nreg\r\n`; - preamble += `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${fileName}"\r\nContent-Type: application/octet-stream\r\n\r\n`; + const safeFileName = fileName.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + preamble += `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${safeFileName}"\r\nContent-Type: application/octet-stream\r\n\r\n`; const epilogue = `\r\n--${boundary}--\r\n`; const preambleBuf = Buffer.from(preamble, 'utf-8'); diff --git a/lib/hosters.js b/lib/hosters.js index 4ddd685..80e2b49 100644 --- a/lib/hosters.js +++ b/lib/hosters.js @@ -232,7 +232,8 @@ function buildMultipart(filePath, formFields) { preamble += `${value}\r\n`; } preamble += `--${boundary}\r\n`; - preamble += `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n`; + const safeFileName = fileName.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + preamble += `Content-Disposition: form-data; name="file"; filename="${safeFileName}"\r\n`; preamble += `Content-Type: application/octet-stream\r\n\r\n`; const epilogue = `\r\n--${boundary}--\r\n`; diff --git a/lib/vidmoly-upload.js b/lib/vidmoly-upload.js index 54952df..2156fc2 100644 --- a/lib/vidmoly-upload.js +++ b/lib/vidmoly-upload.js @@ -200,7 +200,8 @@ class VidmolyUploader { preamble += `${value}\r\n`; } preamble += `--${boundary}\r\n`; - preamble += `Content-Disposition: form-data; name="${fileFieldName || 'file'}"; filename="${fileName}"\r\n`; + const safeFileName = fileName.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + preamble += `Content-Disposition: form-data; name="${fileFieldName || 'file'}"; filename="${safeFileName}"\r\n`; preamble += `Content-Type: application/octet-stream\r\n\r\n`; const epilogue = `\r\n--${boundary}--\r\n`; @@ -216,6 +217,7 @@ class VidmolyUploader { yield preambleBuf; const fileStream = fs.createReadStream(filePath, { highWaterMark: CHUNK_SIZE }); for await (const chunk of fileStream) { + if (signal && signal.aborted) throw new Error('Aborted'); if (throttle) await throttle.consume(chunk.length, signal); bytesRead += chunk.length; yield chunk; diff --git a/lib/voe-upload.js b/lib/voe-upload.js index ec2479a..a76c2ca 100644 --- a/lib/voe-upload.js +++ b/lib/voe-upload.js @@ -228,7 +228,8 @@ class VoeUploader { preamble += `${sessionId}\r\n`; } preamble += `--${boundary}\r\n`; - preamble += `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n`; + const safeFileName = fileName.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + preamble += `Content-Disposition: form-data; name="file"; filename="${safeFileName}"\r\n`; preamble += `Content-Type: application/octet-stream\r\n\r\n`; const epilogue = `\r\n--${boundary}--\r\n`; @@ -244,6 +245,7 @@ class VoeUploader { yield preambleBuf; const fileStream = fs.createReadStream(filePath, { highWaterMark: CHUNK_SIZE }); for await (const chunk of fileStream) { + if (signal && signal.aborted) throw new Error('Aborted'); if (throttle) await throttle.consume(chunk.length, signal); bytesRead += chunk.length; yield chunk; diff --git a/main.js b/main.js index 0611a01..55fe9b3 100644 --- a/main.js +++ b/main.js @@ -802,6 +802,10 @@ ipcMain.handle('import-backup', async (_event, password) => { if (canceled || !filePaths.length) return { ok: false, canceled: true }; const buffer = fs.readFileSync(filePaths[0]); const imported = backupCrypto.decrypt(buffer, password); + // Validate imported data has required structure + if (!imported || typeof imported !== 'object' || !imported.hosters || !imported.hosterSettings || !imported.globalSettings) { + return { ok: false, error: 'Backup-Datei hat ungültige Struktur (hosters, hosterSettings oder globalSettings fehlt).' }; + } // Safety net: timestamped backup so multiple imports don't overwrite each other const ts = new Date().toISOString().replace(/[:.]/g, '-'); const preImportPath = configStore.filePath.replace('.json', `.pre-import-${ts}.json`); diff --git a/renderer/app.js b/renderer/app.js index 99a6e87..bdc1c5f 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -1310,7 +1310,9 @@ function getSelectedJobLinks() { // --- Upload --- async function startUpload() { - if (healthCheckRunning || uploading) return; + if (uploading) return; + // Wait for any running health check to finish (e.g. startup auto-check) + while (healthCheckRunning) await new Promise(r => setTimeout(r, 100)); uploading = true; // set immediately to prevent double-click race updateQueueActionButtons(); @@ -1345,35 +1347,43 @@ async function startUpload() { } } - queueJobs.forEach(j => { - if (j.status === 'preview') j.status = 'queued'; - }); - updateQueueActionButtons(); - renderQueueTable(); - updateStatusBar(); + try { + queueJobs.forEach(j => { + if (j.status === 'preview') j.status = 'queued'; + }); + updateQueueActionButtons(); + renderQueueTable(); + updateStatusBar(); - const uploadPayload = { - hosters, - jobs: jobsToStart.map((job) => ({ - id: job.id, - file: job.file, - fileName: job.fileName, - hoster: job.hoster - })) - }; - const result = await window.api.startUpload(uploadPayload); - persistQueueStateSoon(); + const uploadPayload = { + hosters, + jobs: jobsToStart.map((job) => ({ + id: job.id, + file: job.file, + fileName: job.fileName, + hoster: job.hoster + })) + }; + const result = await window.api.startUpload(uploadPayload); + persistQueueStateSoon(); - if (result && result.error) { - alert(result.error); + if (result && result.error) { + alert(result.error); + uploading = false; + updateQueueActionButtons(); + updateStatusBar(); + } + } catch (err) { uploading = false; updateQueueActionButtons(); updateStatusBar(); + alert(`Upload-Start fehlgeschlagen: ${err.message}`); } } async function startSelectedUpload() { - if (healthCheckRunning || uploading) return; + if (uploading) return; + while (healthCheckRunning) await new Promise(r => setTimeout(r, 100)); uploading = true; // set immediately to prevent double-click race updateQueueActionButtons(); @@ -1407,30 +1417,37 @@ async function startSelectedUpload() { } } - jobsToStart.forEach(j => { - if (j.status === 'preview') j.status = 'queued'; - }); - updateQueueActionButtons(); - renderQueueTable(); - updateStatusBar(); + try { + jobsToStart.forEach(j => { + if (j.status === 'preview') j.status = 'queued'; + }); + updateQueueActionButtons(); + renderQueueTable(); + updateStatusBar(); - const uploadPayload = { - hosters, - jobs: jobsToStart.map((job) => ({ + const uploadPayload = { + hosters, + jobs: jobsToStart.map((job) => ({ id: job.id, file: job.file, fileName: job.fileName, hoster: job.hoster })) }; - const result = await window.api.startUpload(uploadPayload); - persistQueueStateSoon(); + const result = await window.api.startUpload(uploadPayload); + persistQueueStateSoon(); - if (result && result.error) { - alert(result.error); + if (result && result.error) { + alert(result.error); + uploading = false; + updateQueueActionButtons(); + updateStatusBar(); + } + } catch (err) { uploading = false; updateQueueActionButtons(); updateStatusBar(); + alert(`Upload-Start fehlgeschlagen: ${err.message}`); } }