From 61eb2335d3edf4635f00d5e261aa305a099d2f28 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 12 Mar 2026 20:47:44 +0300 Subject: [PATCH] pinakes-server: validate GPS coordinate bounds; validate saved search fields and sort_order Signed-off-by: NotAShelf Change-Id: Idca86117aeeff4afd489ee00bb5c70a36a6a6964 --- crates/pinakes-server/src/routes/photos.rs | 8 +++++ .../src/routes/saved_searches.rs | 33 +++++++++++++++++++ crates/pinakes-server/src/routes/subtitles.rs | 7 ++++ crates/pinakes-server/src/routes/transcode.rs | 3 ++ 4 files changed, 51 insertions(+) diff --git a/crates/pinakes-server/src/routes/photos.rs b/crates/pinakes-server/src/routes/photos.rs index 4119774..c36b463 100644 --- a/crates/pinakes-server/src/routes/photos.rs +++ b/crates/pinakes-server/src/routes/photos.rs @@ -152,6 +152,14 @@ pub async fn get_map_photos( State(state): State, Query(query): Query, ) -> Result { + let valid_lat = |v: f64| v.is_finite() && (-90.0..=90.0).contains(&v); + let valid_lon = |v: f64| v.is_finite() && (-180.0..=180.0).contains(&v); + if !valid_lat(query.lat1) || !valid_lat(query.lat2) { + return Err(ApiError::bad_request("latitude must be in [-90, 90]")); + } + if !valid_lon(query.lon1) || !valid_lon(query.lon2) { + return Err(ApiError::bad_request("longitude must be in [-180, 180]")); + } // Validate bounding box let min_lat = query.lat1.min(query.lat2); let max_lat = query.lat1.max(query.lat2); diff --git a/crates/pinakes-server/src/routes/saved_searches.rs b/crates/pinakes-server/src/routes/saved_searches.rs index c5e6e23..ed103ab 100644 --- a/crates/pinakes-server/src/routes/saved_searches.rs +++ b/crates/pinakes-server/src/routes/saved_searches.rs @@ -22,10 +22,43 @@ pub struct SavedSearchResponse { pub created_at: chrono::DateTime, } +const VALID_SORT_ORDERS: &[&str] = &[ + "date_asc", + "date_desc", + "name_asc", + "name_desc", + "size_asc", + "size_desc", +]; + pub async fn create_saved_search( State(state): State, Json(req): Json, ) -> Result, ApiError> { + let name_len = req.name.chars().count(); + if name_len == 0 || name_len > 255 { + return Err(ApiError( + pinakes_core::error::PinakesError::InvalidOperation( + "name must be 1-255 characters".into(), + ), + )); + } + if req.query.is_empty() || req.query.len() > 2048 { + return Err(ApiError( + pinakes_core::error::PinakesError::InvalidOperation( + "query must be 1-2048 bytes".into(), + ), + )); + } + if let Some(ref sort) = req.sort_order + && !VALID_SORT_ORDERS.contains(&sort.as_str()) { + return Err(ApiError( + pinakes_core::error::PinakesError::InvalidOperation(format!( + "sort_order must be one of: {}", + VALID_SORT_ORDERS.join(", ") + )), + )); + } let id = uuid::Uuid::now_v7(); state .storage diff --git a/crates/pinakes-server/src/routes/subtitles.rs b/crates/pinakes-server/src/routes/subtitles.rs index 3e94770..b8be6ca 100644 --- a/crates/pinakes-server/src/routes/subtitles.rs +++ b/crates/pinakes-server/src/routes/subtitles.rs @@ -47,6 +47,13 @@ pub async fn add_subtitle( ), )); } + if req + .language + .as_ref() + .is_some_and(|l| l.is_empty() || l.len() > 64) + { + return Err(ApiError::bad_request("language must be 1-64 bytes")); + } let subtitle = Subtitle { id: Uuid::now_v7(), media_id: MediaId(id), diff --git a/crates/pinakes-server/src/routes/transcode.rs b/crates/pinakes-server/src/routes/transcode.rs index b98ad4f..c57becb 100644 --- a/crates/pinakes-server/src/routes/transcode.rs +++ b/crates/pinakes-server/src/routes/transcode.rs @@ -16,6 +16,9 @@ pub async fn start_transcode( Path(id): Path, Json(req): Json, ) -> Result, ApiError> { + if req.profile.is_empty() || req.profile.len() > 255 { + return Err(ApiError::bad_request("profile must be 1-255 bytes")); + } let job_id = state .job_queue .submit(pinakes_core::jobs::JobKind::Transcode {