From dfc4e9b781b5b811a6502cb41732a07b72a95957 Mon Sep 17 00:00:00 2001 From: Schalli Date: Thu, 1 Oct 2026 15:28:59 +0200 Subject: [PATCH] fix(desktop): Downloads aus der Seite (blob/data) in der App speichern Der Client reichte jeden Download an den System-Browser weiter. Dateien, die die Seite selbst erzeugt (blob:/data:, z. B. alle Downloads im Zertifikat-Manager), kann der Browser nicht abrufen - Windows zeigte nur "Holen Sie sich eine App, um diesen blob-Link zu oeffnen" (VM 8233). Solche Downloads speichert die App jetzt selbst (Ordner Downloads) und meldet Dateiname und Ordner; http/https-Downloads gehen weiter an den Browser. Dazu CHANGELOG und Quick-Doku zu 261001-l4q. Co-Authored-By: Claude Opus 5.5 (1M context) --- .planning/STATE.md | 1 + .../261001-l4q-PLAN.md | 16 ++++ .../261001-l4q-SUMMARY.md | 11 +++ CHANGELOG.md | 9 ++ apps/desktop/src-tauri/src/lib.rs | 91 ++++++++++++++++++- 5 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 .planning/quick/261001-l4q-zertifikatsmodul-paket-hochladen-uebersi/261001-l4q-PLAN.md create mode 100644 .planning/quick/261001-l4q-zertifikatsmodul-paket-hochladen-uebersi/261001-l4q-SUMMARY.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 98963f8..276ef22 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -486,6 +486,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests. | 261001-cxo | Desktop-Client: Links mit target=_blank (Favoriten) oeffnen jetzt im System-Browser (DesktopExternalLinks -> window.open) | 2026-10-01 | 61a971c | [261001-cxo](./quick/261001-cxo-desktop-client-links-mit-target-blank-oe/) | | 261001-g68 | Erinnerung: Cursor sprang beim Schreiben der Beschreibung in den Titel (Fokus-Effekt hing an inline onClose, Kachel zeichnet alle 10 s neu) – Fokus nur beim Oeffnen | 2026-10-01 | siehe git log | [261001-g68](./quick/261001-g68-erinnerung-cursor-springt-aus-beschreibu/) | | 261001-hbi | Favoriten: Logo fuer per JavaScript gesetzte Symbole (hosteurope.de) – Rueckfall auf DuckDuckGo-Symboldienst beim Ausliefern, nur oeffentliche Seiten | 2026-10-01 | siehe git log | [261001-hbi](./quick/261001-hbi-favoriten-logo-fuer-per-javascript-geset/) | +| 261001-l4q | Zertifikat-Manager: Reiter Übersicht (Paket/ZIP hochladen, Teile erkennen/zuordnen, jedes Teil in jedem Format) + Desktop speichert blob-Downloads selbst | 2026-10-01 | siehe git log | [261001-l4q](./quick/261001-l4q-zertifikatsmodul-paket-hochladen-uebersi/) | ## Deferred Items diff --git a/.planning/quick/261001-l4q-zertifikatsmodul-paket-hochladen-uebersi/261001-l4q-PLAN.md b/.planning/quick/261001-l4q-zertifikatsmodul-paket-hochladen-uebersi/261001-l4q-PLAN.md new file mode 100644 index 0000000..b9962a7 --- /dev/null +++ b/.planning/quick/261001-l4q-zertifikatsmodul-paket-hochladen-uebersi/261001-l4q-PLAN.md @@ -0,0 +1,16 @@ +--- +quick_id: 261001-l4q +description: "Zertifikatsmodul: Paket hochladen, Uebersicht, Download in jedem Format" +date: 2026-10-01 +--- + +# Zertifikatsmodul: Paket hochladen, Uebersicht, Download in jedem Format + +**Auftrag (User):** Testdatei = ZIP vom Aussteller (pem mit Server+Zwischen, key, csr, pfx mit unbekanntem Passwort, .dnstxtrecord). Nach dem Hochladen soll angezeigt werden, welches Zertifikat was ist, darunter jedes Zertifikat in jedem Format herunterladbar. + +**Befund vorher:** Modul nimmt nur EINE Datei; .key/.csr/.zip nicht waehlbar; keine Uebersicht; Labels teils englisch; Texte duzen. + +## Tasks +1. API `cert-bundle.ts`: `analyzeBundle` (Dateien + ZIP mit Grenzen vor dem Entpacken; PEM/DER/PFX/P7B; Duplikate per SHA-256/Modulus; Schluessel/CSR-Zuordnung; Kette) und `exportBundleItem` (crt, cer, fullchain, p7b, pfx inkl. Schluessel+Kette; key PKCS#8/PKCS#1/DER; csr PEM/DER). Endpunkte POST analyze / export. +2. Web: Reiter „Übersicht“ (Standard), Mehrfach-Ablage, Karten je Teil mit Erklaerung, Status, Zuordnung, Download-Knoepfen, PFX-Passwort; geschuetzte PFX entsperren. Texte de/en, Sie-Form. +3. Desktop: blob:/data:-Downloads in der App speichern (Downloads-Ordner) + Meldung; vorher landete der Klick als „blob-Link“ im System-Browser (VM gemessen). diff --git a/.planning/quick/261001-l4q-zertifikatsmodul-paket-hochladen-uebersi/261001-l4q-SUMMARY.md b/.planning/quick/261001-l4q-zertifikatsmodul-paket-hochladen-uebersi/261001-l4q-SUMMARY.md new file mode 100644 index 0000000..edafc61 --- /dev/null +++ b/.planning/quick/261001-l4q-zertifikatsmodul-paket-hochladen-uebersi/261001-l4q-SUMMARY.md @@ -0,0 +1,11 @@ +--- +quick_id: 261001-l4q +status: complete +date: 2026-10-01 +--- + +# Summary +- Echte Aussteller-ZIP (nur lokal, nicht im Repo): 4 Teile erkannt (Server, Zwischen, Schluessel→Server, CSR→Server), PFX als geschuetzt gemeldet, .dnstxtrecord als nicht verwendet; alle 15 Exporte mit OpenSSL gueltig (PFX mit 3 Bloecken, Schluessel-Modulus passt). +- Tests: API cert-bundle.spec 11 (selbst erzeugte PKI), Web OverviewTab.test 5 + Seitentest angepasst; gesamt Web 1189, API 1686, Rust 65 gruen; tsc/clippy/rustfmt sauber. +- Browser lokal: Uebersicht + Downloads (fullchain, pfx, rsa.key, csr.der) geprueft. +- Desktop-Client (VM, lokal): Upload/Anzeige ok; Download zeigte „blob-Link“-Fehler → Client-Fix (on_download speichert blob:/data: selbst, Meldung danach). Nachweis auf VM folgt mit neuem Client-Build. diff --git a/CHANGELOG.md b/CHANGELOG.md index e244b93..61a36fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T ## Unveröffentlicht +### Neu + +- Zertifikat-Manager: Neuer Reiter „Übersicht“. Ziehen Sie alle Dateien, die Sie vom Zertifikatsaussteller bekommen haben, auf einmal hinein – gern auch direkt die ZIP-Datei. Tessera zeigt, was jede Datei ist (Serverzertifikat, Zwischenzertifikat, Stammzertifikat, privater Schlüssel, Zertifikatsanfrage), wofür sie gebraucht wird, wie lange sie gültig ist und was zusammengehört. Unter jedem Teil können Sie es in jedem passenden Format herunterladen: Zertifikate als PEM (.crt), DER (.cer), mit Kette, PKCS#7 (.p7b) oder als PFX mit Schlüssel und Kette; den Schlüssel als PEM, RSA-PEM oder DER; die Anfrage als PEM oder DER. Passwortgeschützte PFX-Dateien lassen sich mit dem Passwort entsperren. + +### Behoben + +- Desktop-App: Downloads, die Tessera erst im Fenster erstellt (zum Beispiel im Zertifikat-Manager), werden jetzt im Ordner „Downloads“ gespeichert; eine Meldung nennt den Dateinamen. Bisher öffnete Windows nur den Hinweis „Holen Sie sich eine App, um diesen ‚blob‘-Link zu öffnen“. Dafür ist die neue Version der Desktop-App nötig. +- Zertifikat-Manager: Die Texte sprechen Sie jetzt durchgehend mit „Sie“ an. + ## 1.9.1 – 2026-10-01 ### Behoben diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 1b69cab..9222293 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -939,6 +939,41 @@ fn external_target(url: &tauri::Url) -> Option { } } +/// Downloads, die die App selbst speichert statt sie an den System-Browser +/// zu geben: Inhalte, die die Seite im Speicher erzeugt hat (`blob:`, +/// `data:`) -- der Browser kann sie nicht abrufen. +fn saves_in_app(url: &tauri::Url) -> bool { + matches!(url.scheme(), "blob" | "data") +} + +/// Titel und Text der Meldung nach einem in der App gespeicherten Download. +fn download_notice(path: Option<&std::path::Path>, success: bool) -> (String, String) { + let name = path + .and_then(|p| p.file_name()) + .map(|n| n.to_string_lossy().into_owned()); + let folder = path + .and_then(|p| p.parent()) + .and_then(|p| p.file_name()) + .map(|n| n.to_string_lossy().into_owned()); + match (success, name) { + (true, Some(name)) => ( + "Download gespeichert".to_string(), + match folder { + Some(folder) => format!("„{name}“ liegt im Ordner {folder}."), + None => format!("„{name}“ wurde gespeichert."), + }, + ), + (true, None) => ( + "Download gespeichert".to_string(), + "Die Datei wurde gespeichert.".to_string(), + ), + (false, _) => ( + "Download fehlgeschlagen".to_string(), + "Die Datei konnte nicht gespeichert werden.".to_string(), + ), + } +} + /// Hauptfenster nach vorne holen. Ein minimiertes Fenster (z. B. per Win+D) /// wird zuerst wiederhergestellt. fn show_main_window(app: &AppHandle) { @@ -1009,15 +1044,36 @@ pub fn run() { tauri::webview::NewWindowResponse::Deny } }) - .on_download(|webview, event| { - if let tauri::webview::DownloadEvent::Requested { url, .. } = event { + .on_download(|webview, event| match event { + // Dateien, die die Seite selbst erzeugt (blob:/data:, z. B. + // Zertifikats-Downloads), kennt der System-Browser nicht -- + // Windows meldete „Holen Sie sich eine App, um diesen + // ‚blob‘-Link zu öffnen“ (VM 8233, 01.10.2026). Die speichert + // die App selbst im Ordner Downloads und meldet es danach. + tauri::webview::DownloadEvent::Requested { url, .. } => { + if saves_in_app(&url) { + return true; + } let _ = webview .app_handle() .opener() .open_url(url.to_string(), None::<&str>); - return false; + false } - true + tauri::webview::DownloadEvent::Finished { url, path, success } => { + if saves_in_app(&url) { + let (title, body) = download_notice(path.as_deref(), success); + let _ = webview + .app_handle() + .notification() + .builder() + .title(title) + .body(body) + .show(); + } + true + } + _ => true, }) .build()?; @@ -1500,6 +1556,33 @@ mod tests { ); } + #[test] + fn saves_in_app_nur_blob_und_data() { + for (url, expected) in [ + ("blob:http://localhost:3000/0c1d-11", true), + ("data:application/x-pem-file;base64,QUJD", true), + ("https://alpha.tessera.ctl.de/desktop/download/x.exe", false), + ("http://intranet.local/datei.pdf", false), + ] { + let parsed = tauri::Url::parse(url).unwrap(); + assert_eq!(saves_in_app(&parsed), expected, "{url}"); + } + } + + #[test] + fn download_notice_nennt_datei_und_ordner() { + let path = std::path::Path::new("/home/tessera/Downloads/www.example.de.crt"); + let (title, body) = download_notice(Some(path), true); + assert_eq!(title, "Download gespeichert"); + assert_eq!(body, "„www.example.de.crt“ liegt im Ordner Downloads."); + + let (title, _) = download_notice(None, false); + assert_eq!(title, "Download fehlgeschlagen"); + + let (_, body) = download_notice(None, true); + assert_eq!(body, "Die Datei wurde gespeichert."); + } + #[test] fn external_target_nur_http_und_https() { let https = tauri::Url::parse("https://www.google.com/search?q=tessera").unwrap();