feat: download .wpress backups from cloud storage URLs - #52
Conversation
Build Artifacts
Built from 29337f4. Artifacts expire after 90 days. |
Code ReviewThanks for the PR - the feature is well-organized and the signal/slot wiring is clean. However, all 4 substantive CI checks are failing (Linux/macOS/Windows builds + Code Style), and there are correctness issues that need to be addressed before this can merge. I also checked the branch out and reproduced the failures locally. Blockers1. Lines 40-43 (the Every other string in the file uses straight quotes correctly. Fix is mechanical: replace the curly-quote delimiters with 2. clang-format violations (Code Style check) The project's formatter was not run.
Fix per find src tests \( -name '*.cpp' -o -name '*.h' \) ! -name 'moc_*' | xargs clang-format -i3. PR description contradicts the implementation The description says "Note on Mega: ... not supported in this PR." but the branch HEAD ( Correctness4. Mega AES key is likely used without un-mangling. Mega file links embed a 256-bit key; the real 128-bit AES key is the XOR of the two 16-byte halves ( 5. Mega multi-part ( 6. FTP is claimed but not supported in Qt 6. Bugs7. Temp files leak.
Centralize cleanup: remove the temp file in 8. 9. No content validation of the downloaded file. Google Drive's Test coverageNo tests added. Code quality
SecurityThe user-initiated-only network model is reasonable, and the
RecommendationRequest changes. Fix the build (item 1), formatting (item 2), and description (item 3) blockers, plus the temp-file leaks (item 7) and tests (item 9). For Mega: given the unverified key handling (item 4) and multi-part gap (item 5), I'd suggest splitting Mega into a separate follow-up PR with proper test vectors, and landing the simpler, well-understood provider normalization (Drive/Dropbox/OneDrive/Box/pCloud/S3) first - that part looks solid. |
6784c3a to
8c3119c
Compare
|
Could you split |
f9a3e18 to
b38af16
Compare
Add CloudDownloader (src/clouddownloader.{h,cpp}) which downloads a
.wpress file from a remote HTTPS URL to a temp file. Normalizes share
links from Google Drive, Dropbox, OneDrive, Box, and pCloud into direct
download URLs. Mega (mega.nz) is handled via a two-step flow: parse the
AES-128-CTR key from the URL fragment (never sent to any server), call
the Mega public API for a CDN URL, then decrypt the stream on-the-fly
with OpenSSL EVP_aes_128_ctr().
Drop overlay (dropoverlay.{h,cpp}): accept remote URLs in addition to
local .wpress files. Two-path drag handling — text/uri-list primary,
text/plain fallback — so cloud web-UI drags work and Mega #KEY fragments
are preserved (text/uri-list strips fragments per RFC 2483).
URL dialog (urlopendialog.{h,cpp}): File → Open from URL (Ctrl+Shift+O)
lets the user paste any cloud share link or direct URL.
MainWindow wiring: urlDropped signal → openBackupFromUrl slot, download
progress bar with indeterminate mode (setRange(0,0)) when Content-Length
is unknown, re-entrancy guard (m_downloading flag), temp file cleanup on
clear/re-download.
Tests: 24 new test cases in tst_clouddownloader.cpp covering isRemoteUrl,
normalizeUrl (per provider), and parseMegaUrl (key derivation, legacy
format, edge cases). All 121 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The .ui file already defines a File menu (menu_File). The previous code
created a second File menu via menuBar()->addMenu("&File"), which on
macOS gets silently merged/hidden — making "Open from URL…" invisible.
Fix: insert the action into ui->menu_File (after "Open backup", before
"Clear file") instead of creating a duplicate menu.
Also update the drop zone hint text from "Drop .wpress file here" to
"Drop .wpress file or cloud link here" to hint at URL drop support.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add an "Open URL..." button in the bottom button row, between "Open backup..." and "Clear", so the cloud-storage feature is immediately discoverable without needing the File menu or keyboard shortcut. The button is wired to openFromUrl() via the .ui file and is disabled during downloads (same as the other buttons). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…OpenSSL master (#57) replaced OpenSSL with vendored tiny-AES-c for the .wpress AES-256-CBC path, so CloudDownloader's Mega AES-128-CTR decryption was the last remaining OpenSSL user. Port it to tiny-AES-c too, dropping the OpenSSL dependency entirely. tiny-AES-c fixes its key size at compile time and the app already compiles it as AES-256 (for .wpress), so AES-128 needs its own translation unit that does not inherit AES256=1. Add a second aes.c compilation as the tiny-aes-mega target with AES-128 and prefixed symbols (mega_AES_*) so it does not clash with the AES-256 instance at link time. CTR streaming lives in the new src/megaaes.{c,h}, built on tiny-AES-c's ECB primitive with a big-endian counter matching the previous EVP_aes_128_ctr(). Unlike tiny-AES-c's own AES_CTR_xcrypt_buffer, this keeps the keystream aligned across arbitrary-sized network chunks. CloudDownloader now writes through a single writeChunk() helper (which also decrypts the final flushed bytes, previously written undecrypted), and the now-unreachable decrypt-failure path is removed. Tests: NIST SP 800-38A CTR-AES128 vector plus a chunk-alignment test proving cross-chunk keystream continuity. 120 tests pass.
4341813 to
adec5a1
Compare
Greptile SummaryThis PR adds remote
Confidence Score: 4/5Safe to merge after the stale contributor-guide provider notes are corrected. The changed download and UI paths are covered by focused tests. Previously reported blocking issues are addressed in the current code. The remaining accepted issue is non-blocking documentation drift. Files Needing Attention: CLAUDE.md
What T-Rex did
|
| Filename | Overview |
|---|---|
| .github/workflows/ci.yml | Keeps the macOS SecureTransport TLS plugin while continuing to strip unused Qt plugins. |
| .github/workflows/release.yml | Mirrors the CI packaging fix so release bundles retain HTTPS TLS support. |
| CLAUDE.md | Documents the new cloud download flow, but still lists stale HTTP, OneDrive, and Box support details. |
| src/clouddownloader.cpp | Implements HTTPS-only provider normalization, multi-step cloud download flows, temp-file streaming, and Mega AES-CTR decryption. |
| src/dropoverlay.cpp | Extends drag-and-drop handling to accept remote HTTPS URLs and preserve Mega URL fragments from text/plain. |
| src/mainwindow.cpp | Wires URL downloads into the main UI with progress, cancellation, temp-file ownership, and busy-state guards. |
| src/megaaes.c | Implements chunk-safe AES-128-CTR streaming on top of the vendored tiny-AES ECB primitive. |
| tests/tst_clouddownloader.cpp | Adds coverage for URL acceptance, provider normalization, Mega key derivation, AES-CTR behavior, and abort lifecycle. |
Sequence Diagram
sequenceDiagram
actor User
participant UI as DropOverlay / UrlOpenDialog
participant MW as MainWindow
participant CD as CloudDownloader
participant API as Provider API
participant CDN as HTTPS CDN
participant TMP as QTemporaryFile
User->>UI: Drop URL or paste Open from URL
UI->>MW: urlDropped(url) / accepted url()
MW->>CD: download(url)
alt Direct / Google Drive / Dropbox
CD->>CD: normalizeUrl(url)
else pCloud / WeTransfer / Mega
CD->>API: Resolve share link
API-->>CD: HTTPS direct download URL
opt Mega
CD->>CD: Parse fragment key and init AES-128-CTR
end
end
CD->>CDN: GET resolved HTTPS URL
CDN-->>CD: download chunks + progress
opt Mega
CD->>CD: Decrypt chunk in AES-128-CTR
end
CD->>TMP: Stream bytes to temp file
CD-->>MW: finished(tempFilePath, suggestedName)
MW->>MW: replace loaded backup and remove old temp file
Reviews (12): Last reviewed commit: "fix: make the Open-from-URL provider tex..." | Re-trigger Greptile
- pCloud: resolve the real CDN URL via the getpublinkdownload API (two-step flow) instead of the dead /publink/code endpoint - Google Drive: use drive.usercontent.google.com/download; the old drive.google.com/uc endpoint now returns 403 - Remove OneDrive: SharePoint-migrated shares need a JS guest-session handshake plain HTTP can't perform (badge-token + shares API both 401/404) - Remove Box: the /shared/static/ direct-download path returns 404 - Drop-overlay text: "Drop .wpress file here" (cloud links aren't drag-droppable) - Update the Open-from-URL provider list/tips and tests to match Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
macdeployqt cleanup was stripping the whole PlugIns/tls directory, including libqsecuretransportbackend.dylib. Without it CloudDownloader fails every HTTPS request with "TLS initialization failed". Keep the securetransport backend (and the tls dir) in ci.yml and release.yml. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve WeTransfer share links to a direct CDN download via the public
transfers API (no auth, CSRF token, or session cookie required):
- we.tl short links: resolve the redirect to wetransfer.com/downloads/{id}/{hash}
- POST {security_hash, intent} to /api/v4/transfers/{id}/download for a direct_link
- stream the direct_link like any other HTTPS download (served plaintext,
so no tiny-AES-c decryption — that path stays Mega-only)
Handles both 2-part links and 3-part email links (with recipient_id),
takes the real filename from the CDN URL, and reports friendly errors
for expired or removed transfers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The WeTransfer changes tripped the cpp-linter clang-format check: - clouddownloader.cpp: the apiUrl construction fits on one line (<=120) - urlopendialog.cpp: adding "WeTransfer" pushed the provider string past the 120-column limit, so the QLabel(tr(...)) block reflows Formatted with clang-format 18.1.8 to match the CI version exactly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…iders These were pass-through providers with no dedicated code path — they only appeared in the Open-from-URL provider list and a comment. Removing them de-advertises the three; it does not block their URLs, which still download via the "Any direct HTTPS link" catch-all. - urlopendialog: drop all three from the supported list, and remove the "For Amazon S3, use a pre-signed URL" tip - clouddownloader: trim the pass-through comment accordingly - tests: generalize testNormalize_s3_unchanged -> testNormalize_directUrl_unchanged so pre-signed pass-through stays covered without naming a dropped provider Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ed download Addresses two review findings on PR #52: - isRemoteUrl() now accepts https:// only. Plain http:// let a network attacker tamper with backup bytes in transit, contradicting the HTTPS-only security posture. Flip testIsRemoteUrl_http accordingly. - setDownloadingState(false) unconditionally enabled the extract button; after a failed download from the empty state that left "Extract" clickable with no backup loaded. Gate it on !backupFilename.isEmpty(), matching the clearButton line beside it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
writeChunk() discarded QTemporaryFile::write()'s return value, so a full or read-only temp volume produced a truncated .wpress that reached finished() as success — the user then saw a confusing extraction/corruption error instead of the real cause. Track a short write and fail explicitly in onFinished(), cleaning up the partial file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses review findings on PR #52: - abort() no longer segfaults. QNetworkReply::abort() emits finished() synchronously and our slots null the member in their m_aborted branch, so the old abort()/deleteLater() dereferenced a dangling member. A killReply() helper disconnects and clears the member before aborting. Add an in-flight abort() regression test (QTcpServer that never completes the handshake). - Enforce HTTPS on API-resolved CDN URLs too: a pCloud/Mega/WeTransfer direct_link of http:// would defeat the HTTPS-only guarantee. - pcloud host match uses an exact/dot boundary (was endsWith("pcloud.link"), which matched evilpcloud.link). - Prefer the Content-Disposition filename so Google Drive downloads keep the real name instead of "view.wpress" (basename only, no path smuggling). - 30s transfer timeout on every request so a stalled connection can't wedge. - Drop dead m_isPCloud / m_isWeTransfer members. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses review findings on PR #52: - Downloads and extractions can no longer run concurrently. A new isBusy() (download OR extraction) gates every entry point, and openUrlButton is disabled during extraction, so the two flows can't fight over the progress bar or re-enable each other's buttons. - Clear File now aborts an in-flight download instead of leaving it running to repopulate the file the user just cleared; actionClearFile is disabled while downloading. - A failed download restores the previously-open file's label instead of blanking it, so Extract no longer stays enabled pointing at a hidden file. - dropEvent() prefers the text/plain URL when it preserves a fragment the text/uri-list one dropped (RFC 2483), so Mega #KEY drags resolve. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes review item 5. While a download runs, the Clear button is swapped for a Cancel button that calls CloudDownloader::abort() (now crash-safe via killReply) and restores the prior UI state — giving the user an immediate way out of a live transfer rather than waiting for the 30s transfer timeout or closing the window. The button is hidden on every non-downloading state path (finish, fail, cancel, clear). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mega's API returns its CDN download URL as plain http:// by default, which startCdnDownload()'s HTTPS-only guard (added for the provider-URL review finding) now rightly refuses — so Mega downloads regressed. Add "ssl":2 to the API request so Mega hands back an https:// URL. The payload is already AES-128-CTR encrypted; this keeps the transport encrypted too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cceeds Opening a second URL deleted the current backup's temp file up front, so a cancelled/failed second download left Extract enabled pointing at a deleted file (review finding on openBackupFromUrl). - openBackupFromUrl no longer deletes the previous temp file at download start - onDownloadFinished removes it only once the new download succeeds - CloudDownloader::onFinished now relinquishes its QTemporaryFile on success (releases the object, keeps the file) so a later download() can't delete the file the caller now owns Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@developeritsme Thanks for the thorough review 🙏 — I've addressed all of it and re-requested your review. Each fix is its own commit for easy walking ( Blocking
Should fix
Two follow-ups also fixed:
The two Notes (routing through the planned |
After a successful cloud download the drop zone shows the friendly name (e.g. mysite.wpress) while backupFilename holds the temp path. A later cancelled or failed download restored QFileInfo(backupFilename).fileName(), degrading the label to the temp name (traktor-a1b2c3.wpress). Cache the display name in m_loadedDisplayName and restore that instead. Addresses a minor item from the PR #52 round-2 review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@developeritsme Thanks again for the round-2 pass 🙏 — small delta since then, re-requesting your review. Changed since
The other two "Remaining items" I've split into follow-ups rather than growing this PR:
That leaves only the two manual-QA boxes from your review as the merge gate:
Appreciate the thorough reviews — this is in good shape now. |
The description/"Supported providers" label used color: palette(mid), a 3D-shadow grey that sits almost on top of the window background in dark mode, so the text was nearly invisible. Use the PlaceholderText palette role instead — muted but legible in both light and dark themes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@developeritsme One more small fix since my last note — re-requesting your review. New since
Everything else is unchanged from my previous summary:
Merge gate is still just the two manual-QA boxes from your review — Mega end-to-end and local |
Summary
Adds the ability to open a
.wpressbackup directly from a cloud share link or any direct HTTPS URL — no manual download step.src/clouddownloader.{h,cpp}): streams a.wpressfile from a remote HTTPS URL into aQTemporaryFileviaQNetworkAccessManager. Simple providers are rewritten into direct-download URLs; the rest use a multi-step API flow. This is the only file in the app that touchesQNetworkAccessManager.src/dropoverlay.{h,cpp}): accepts remote URLs alongside local.wpressfiles. Two-path drag handling —text/uri-listprimary,text/plainfallback — so cloud web-UI drags work and Mega#KEYfragments survive (text/uri-liststrips fragments per RFC 2483).src/urlopendialog.{h,cpp}): File → Open from URL… (⌘⇧O / Ctrl+Shift+O). Validates the URL before enabling OK.urlDropped→openBackupFromUrl, progress bar with indeterminate mode whenContent-Lengthis unknown, re-entrancy guard, temp-file cleanup on clear/re-download.src/megaaes.{c,h},tiny-aes-megatarget): a second vendored tiny-AES-c instance. tiny-AES-c bakes its key size in at compile time, so AES-128 (Mega) cannot share the app's AES-256 instance; symbols are prefixed (mega_AES_*) via compile definitions to avoid link clashes. CTR is driven from the ECB primitive inmegaaes.c.macdeployqtcleanup was stripping the entirePlugIns/tlsdirectory, so every HTTPS request failed with "TLS initialization failed".libqsecuretransportbackend.dylibis now kept in bothci.ymlandrelease.yml.Supported providers
/file/d/{ID}/viewand/open?id={ID}→drive.usercontent.google.com/download?id=…&export=download&confirm=tdl=0→dl=1GET api.pcloud.com/getpublinkdownload?code=…→ CDN host + pathwe.tlshort link →POST /api/v4/transfers/{id}/download→direct_linkNot supported (investigated and deliberately excluded):
migratedtospo=true). Every server-side endpoint (anonymous shares API, badge-token flow, legacyauthkey) returns 401/404; the file only unlocks after a JavaScript guest-session handshake that plain HTTP can't perform. Verified against live links./shared/static/{code}direct-download endpoint now returns 404.Security notes
QTemporaryFilein the system temp dir, removed on clear/re-download/abort. The path is never written toQSettings.QNetworkAccessManager.Tests
25 new tests in
tst_clouddownloader.cpp(a 7th test class, wired intotst_main.cpp):isRemoteUrl, per-providernormalizeUrl,parseMegaUrlkey derivation + legacy format + edge cases, and AES-128-CTR correctness (NIST vector, plus chunked-vs-single-shot equivalence to prove the keystream carries across chunk boundaries).Test plan
ctest --output-on-failure— all tests pass across 7 classesclang-format --dry-run --Werror(v18, matching CI) — no violationswe.tl/…) → downloadsdl=1.wpress.wpressfile — still works as before🤖 Generated with Claude Code