mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-11 02:12:00 +03:00
fix: replace write_text_file with backend dialog
Replaces `write_text_file` and `write_image_file` with backend commands `save_text_file_dialog` and `save_image_file_dialog`. Instead of allowing the frontend to pass an arbitrary absolute path, the Tauri backend now invokes the OS save dialog directly, eliminating the risk of arbitrary file write if the frontend is compromised. Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com>
This commit is contained in:
parent
984275711a
commit
21f7726316
6 changed files with 54 additions and 32 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -17,6 +17,7 @@
|
||||||
"@tauri-apps/api": "^2.5.0",
|
"@tauri-apps/api": "^2.5.0",
|
||||||
"@tauri-apps/plugin-autostart": "^2.3.0",
|
"@tauri-apps/plugin-autostart": "^2.3.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.7.1",
|
"@tauri-apps/plugin-dialog": "^2.7.1",
|
||||||
|
"@tauri-apps/plugin-fs": "^2.5.1",
|
||||||
"@tauri-apps/plugin-global-shortcut": "^2.2.0",
|
"@tauri-apps/plugin-global-shortcut": "^2.2.0",
|
||||||
"@tauri-apps/plugin-opener": "^2.2.0",
|
"@tauri-apps/plugin-opener": "^2.2.0",
|
||||||
"@tauri-apps/plugin-process": "^2.3.1",
|
"@tauri-apps/plugin-process": "^2.3.1",
|
||||||
|
|
@ -1218,6 +1219,15 @@
|
||||||
"@tauri-apps/api": "^2.11.0"
|
"@tauri-apps/api": "^2.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tauri-apps/plugin-fs": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-9Lz+Jopp6QyeEWhlpkMx4R/+P9HgR+AVAI4vOZhlT8Xaymtz8iVI/Ov984/XTqgJz/5gz5NretqPB/XEMS3NhQ==",
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tauri-apps/plugin-global-shortcut": {
|
"node_modules/@tauri-apps/plugin-global-shortcut": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-global-shortcut/-/plugin-global-shortcut-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-global-shortcut/-/plugin-global-shortcut-2.3.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"@tauri-apps/api": "^2.5.0",
|
"@tauri-apps/api": "^2.5.0",
|
||||||
"@tauri-apps/plugin-autostart": "^2.3.0",
|
"@tauri-apps/plugin-autostart": "^2.3.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.7.1",
|
"@tauri-apps/plugin-dialog": "^2.7.1",
|
||||||
|
"@tauri-apps/plugin-fs": "^2.5.1",
|
||||||
"@tauri-apps/plugin-global-shortcut": "^2.2.0",
|
"@tauri-apps/plugin-global-shortcut": "^2.2.0",
|
||||||
"@tauri-apps/plugin-opener": "^2.2.0",
|
"@tauri-apps/plugin-opener": "^2.2.0",
|
||||||
"@tauri-apps/plugin-process": "^2.3.1",
|
"@tauri-apps/plugin-process": "^2.3.1",
|
||||||
|
|
|
||||||
|
|
@ -38,20 +38,41 @@ fn safe_name(name: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn write_text_file(path: String, contents: String) -> Result<(), String> {
|
async fn save_text_file_dialog(app: tauri::AppHandle, contents: String, is_sum: bool) -> Result<bool, String> {
|
||||||
write_atomic(std::path::Path::new(&path), &contents)
|
use tauri_plugin_dialog::DialogExt;
|
||||||
|
let dialog = app.dialog().file();
|
||||||
|
let dialog = if is_sum {
|
||||||
|
dialog.add_filter("Summarum Sheet", &["sum"])
|
||||||
|
} else {
|
||||||
|
dialog.add_filter("Text file", &["txt"])
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(path) = dialog.blocking_save_file() {
|
||||||
|
let path = path.into_path().map_err(|_| "invalid path".to_string())?;
|
||||||
|
write_atomic(&path, &contents)?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn write_image_file(path: String, data_base64: String) -> Result<(), String> {
|
async fn save_image_file_dialog(app: tauri::AppHandle, data_base64: String) -> Result<bool, String> {
|
||||||
|
use tauri_plugin_dialog::DialogExt;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
if let Some(path) = app.dialog().file().add_filter("PNG Image", &["png"]).blocking_save_file() {
|
||||||
|
let path = path.into_path().map_err(|_| "invalid path".to_string())?;
|
||||||
let bytes = base64_decode(&data_base64)?;
|
let bytes = base64_decode(&data_base64)?;
|
||||||
let p = std::path::Path::new(&path);
|
let tmp = path.with_extension("tmp");
|
||||||
let tmp = p.with_extension("tmp");
|
|
||||||
std::fs::File::create(&tmp)
|
std::fs::File::create(&tmp)
|
||||||
.and_then(|mut f| f.write_all(&bytes))
|
.and_then(|mut f| f.write_all(&bytes))
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
fs::rename(&tmp, p).map_err(|e| e.to_string())
|
fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base64_decode(s: &str) -> Result<Vec<u8>, String> {
|
fn base64_decode(s: &str) -> Result<Vec<u8>, String> {
|
||||||
|
|
@ -712,8 +733,8 @@ fn main() {
|
||||||
read_text_file,
|
read_text_file,
|
||||||
exit_app,
|
exit_app,
|
||||||
is_hidden_launch,
|
is_hidden_launch,
|
||||||
write_text_file,
|
save_text_file_dialog,
|
||||||
write_image_file
|
save_image_file_dialog
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let handle = app.handle().clone();
|
let handle = app.handle().clone();
|
||||||
|
|
|
||||||
10
src/main.ts
10
src/main.ts
|
|
@ -907,17 +907,11 @@ async function boot(): Promise<void> {
|
||||||
} else if (action === "print") {
|
} else if (action === "print") {
|
||||||
window.print();
|
window.print();
|
||||||
} else if (isTauri()) {
|
} else if (isTauri()) {
|
||||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
|
||||||
const { invoke } = await import("@tauri-apps/api/core");
|
const { invoke } = await import("@tauri-apps/api/core");
|
||||||
const isSum = action === "save-sum";
|
const isSum = action === "save-sum";
|
||||||
const path = await save({
|
|
||||||
filters: isSum
|
|
||||||
? [{ name: "Summarum Sheet", extensions: ["sum"] }]
|
|
||||||
: [{ name: "Text file", extensions: ["txt"] }],
|
|
||||||
});
|
|
||||||
if (path) {
|
|
||||||
const content = isSum ? editor.getText() : editor.getSheetWithResults();
|
const content = isSum ? editor.getText() : editor.getSheetWithResults();
|
||||||
await invoke("write_text_file", { path, contents: content });
|
const saved = await invoke("save_text_file_dialog", { contents: content, isSum });
|
||||||
|
if (saved) {
|
||||||
toast(t("saved"));
|
toast(t("saved"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -234,9 +234,9 @@ export async function fetchHistoricalRates(date: string): Promise<Record<string,
|
||||||
|
|
||||||
// ---------- image export
|
// ---------- image export
|
||||||
|
|
||||||
export async function writeImageFile(path: string, dataBase64: string): Promise<void> {
|
export async function writeImageFile(dataBase64: string): Promise<boolean> {
|
||||||
if (!isTauri()) return;
|
if (!isTauri()) return false;
|
||||||
await invoke("write_image_file", { path, dataBase64 });
|
return await invoke<boolean>("save_image_file_dialog", { dataBase64 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- market data
|
// ---------- market data
|
||||||
|
|
|
||||||
|
|
@ -58,14 +58,10 @@ export async function exportSheetImage(deps: ExportImageDeps): Promise<void> {
|
||||||
} catch {
|
} catch {
|
||||||
// clipboard API failed — save to file
|
// clipboard API failed — save to file
|
||||||
try {
|
try {
|
||||||
const { save } = await import("@tauri-apps/plugin-dialog");
|
|
||||||
const path = await save({ filters: [{ name: "PNG Image", extensions: ["png"] }] });
|
|
||||||
if (path) {
|
|
||||||
const buf = await blob.arrayBuffer();
|
const buf = await blob.arrayBuffer();
|
||||||
const b64 = bytesToBase64(new Uint8Array(buf));
|
const b64 = bytesToBase64(new Uint8Array(buf));
|
||||||
await writeImageFile(path, b64);
|
const saved = await writeImageFile(b64);
|
||||||
toast(t("saved"));
|
if (saved) toast(t("saved"));
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
toast(t("imageFailed"));
|
toast(t("imageFailed"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue