Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src-tauri/src/commands/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@
use crate::error::AppError;
use crate::models::JobRegistry;

/// Longest job id the frontend sends (`crypto.randomUUID()` is 36 characters).
const JOB_ID_MAX: usize = 128;

/// The webview's job id names a work folder (`<temp>/work/<job_id>`) that the job deletes when it
/// ends: only `[A-Za-z0-9_-]{1,128}` is accepted, so it can never step out of the temp root.
pub(crate) fn check_job_id(job_id: &str) -> Result<(), AppError> {
let ok = !job_id.is_empty()
&& job_id.len() <= JOB_ID_MAX
&& job_id
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_');
if ok {
return Ok(());
}
Err(AppError::new(
"INVALID_JOB",
"Invalid job",
"OffPDF received a job id it does not accept.",
)
.with_details(format!("job id of {} bytes", job_id.len())))
}

/// Cancel a running job by id. Killing the child (if any) is handled by the
/// `JobHandle`; the worker thread reaps it and returns `AppError::cancelled`.
#[tauri::command]
Expand All @@ -15,3 +37,37 @@ pub async fn cancel_job(
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::check_job_id;

/// review-T5: a job id reaches `remove_dir_all(<temp>/work/<job_id>)`.
#[test]
fn job_ids_cannot_leave_the_work_folder() {
for ok in [
"3f2b8c1e-0d4a-4c6e-9a51-7b2f0e9d1c34",
"job-1700000000000-1a2b",
"a_b",
] {
assert!(check_job_id(ok).is_ok(), "{ok}");
}
let long = "a".repeat(129);
for bad in [
"",
"..",
"../x",
"a/b",
"a\\b",
"C:x",
"a b",
long.as_str(),
"x\0",
] {
let e = check_job_id(bad)
.err()
.unwrap_or_else(|| panic!("{bad:?} accepted"));
assert_eq!(e.code, "INVALID_JOB");
}
}
}
19 changes: 19 additions & 0 deletions src-tauri/src/commands/pdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub async fn merge_pdfs(
input_paths: Vec<String>,
output_path: String,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand All @@ -65,6 +66,7 @@ pub async fn split_pdf(
picks: Vec<PagePick>,
mode: SplitMode,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand All @@ -86,6 +88,7 @@ pub async fn assemble_pdf(
output_path: String,
groups: Vec<PageGroup>,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand All @@ -108,6 +111,7 @@ pub async fn edit_pdf(
groups: Vec<PageGroup>,
rotations: Vec<RotateGroup>,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand All @@ -130,6 +134,7 @@ pub async fn unlock_pdf(
output_path: String,
password: String,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand All @@ -153,6 +158,7 @@ pub async fn watermark_pdf(
text: String,
opacity: f64,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -187,6 +193,7 @@ pub async fn crop_pdf(
right: f64,
bottom: f64,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -223,6 +230,7 @@ pub async fn edit_pdf_overlays(
flatten_form: Option<bool>,
flatten_annotations: Option<bool>,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -278,6 +286,7 @@ pub async fn stamp_pdf(
color: [f64; 3],
size_pct: f64,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -316,6 +325,7 @@ pub async fn poster_pdf(
overlap: f64,
marks: bool,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -352,6 +362,7 @@ pub async fn nup_pdf(
sheet_w: f64,
sheet_h: f64,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -388,6 +399,7 @@ pub async fn add_page_numbers(
pad_width: Option<u32>,
with_date: Option<bool>,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -430,6 +442,7 @@ pub async fn protect_pdf(
user_password: String,
owner_password: String,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -460,6 +473,7 @@ pub async fn extract_pages(
output_path: String,
pages: String,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand All @@ -482,6 +496,7 @@ pub async fn delete_pages(
output_path: String,
pages: String,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand All @@ -505,6 +520,7 @@ pub async fn rotate_pages(
angle: i32,
rotate_pages: String,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -535,6 +551,7 @@ pub async fn reorder_pages(
output_path: String,
order: String,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand All @@ -559,6 +576,7 @@ pub async fn compress_pdf(
quality: u32,
target_bytes: Option<u64>,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -589,6 +607,7 @@ pub async fn optimize_pdf(
output_path: String,
groups: Vec<PageGroup>,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down
8 changes: 8 additions & 0 deletions src-tauri/src/commands/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub async fn pdf_to_images(
format: String,
dpi: u32,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -211,6 +212,7 @@ pub async fn ocr_pdf(
picks: Vec<PagePick>,
lang: String,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -258,6 +260,7 @@ pub async fn office_to_pdf_batch(
output_dir: String,
input_paths: Vec<String>,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -335,6 +338,7 @@ pub async fn pdfa_pdf(
output_path: String,
groups: Vec<PageGroup>,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -394,6 +398,7 @@ pub async fn detect_blank_pages(
input_path: String,
sensitivity: String,
) -> Result<Vec<u32>, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let jid = job_id.clone();
let res = tauri::async_runtime::spawn_blocking(move || {
Expand Down Expand Up @@ -431,6 +436,7 @@ pub async fn write_pdf_meta(
fields: crate::pdf_engine::metadata::PdfMeta,
clear_all: bool,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -468,6 +474,7 @@ pub async fn export_pdf_text(
first_page: Option<u32>,
last_page: Option<u32>,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down Expand Up @@ -504,6 +511,7 @@ pub async fn pdf_to_office(
groups: Vec<PageGroup>,
format: String,
) -> Result<JobResult, AppError> {
super::jobs::check_job_id(&job_id)?;
let handle = registry.register(&job_id);
let app2 = app.clone();
let jid = job_id.clone();
Expand Down
Loading