diff --git a/Cargo.lock b/Cargo.lock index 0383133..9f45b00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3255,7 +3255,7 @@ dependencies = [ [[package]] name = "web-petting" -version = "0.1.6" +version = "0.1.7" dependencies = [ "chrono", "chrono-tz", diff --git a/src/public.rs b/src/public.rs index ca06acb..c224efe 100644 --- a/src/public.rs +++ b/src/public.rs @@ -395,32 +395,35 @@ async fn favicon(_request: Request) -> cot::Result { } async fn serve_static(_request: Request, Path(filename): Path) -> cot::Result { - let safe_name = filename.replace(['/', '\\', '.', ' '], ""); - // rebuild with original extension - let ext = filename.rsplit('.').next().unwrap_or(""); - let _stem = filename.rsplit('.').last().unwrap_or(""); - let _ = safe_name; // just for validation idea; use the path directly with whitelist - let path = format!("static/{}", filename); - match tokio::fs::read(&path).await { - Ok(data) => { - let content_type = match ext { - "png" => "image/png", - "jpg" | "jpeg" => "image/jpeg", - "webp" => "image/webp", - "svg" => "image/svg+xml", - "gif" => "image/gif", - _ => "application/octet-stream", - }; - let body = cot::Body::fixed(data); - let mut resp = Response::new(body); - resp.headers_mut() - .insert("content-type", content_type.parse().unwrap()); - resp.headers_mut() - .insert("cache-control", "public, max-age=604800".parse().unwrap()); - Ok(resp) - } - Err(_) => Html::new("404").into_response(), + // Only allow simple filenames (no path traversal) + if filename.contains('/') || filename.contains('\\') || filename.contains("..") { + return Html::new("404").into_response(); } + let ext = filename.rsplit('.').next().unwrap_or(""); + // Try relative path first, then /data/static/ (Docker) + let path = format!("static/{filename}"); + let data = match tokio::fs::read(&path).await { + Ok(d) => d, + Err(_) => match tokio::fs::read(format!("/data/static/{filename}")).await { + Ok(d) => d, + Err(_) => return Html::new("404").into_response(), + }, + }; + let content_type = match ext { + "png" => "image/png", + "jpg" | "jpeg" => "image/jpeg", + "webp" => "image/webp", + "svg" => "image/svg+xml", + "gif" => "image/gif", + _ => "application/octet-stream", + }; + let body = cot::Body::fixed(data); + let mut resp = Response::new(body); + resp.headers_mut() + .insert("content-type", content_type.parse().unwrap()); + resp.headers_mut() + .insert("cache-control", "public, max-age=604800".parse().unwrap()); + Ok(resp) } async fn robots_txt(_request: Request, db: Database) -> cot::Result { diff --git a/static/cat_bottom_left.png b/static/cat_bottom_left.png index 24e3cca..47d54b2 100644 Binary files a/static/cat_bottom_left.png and b/static/cat_bottom_left.png differ