From 488468991cee53520b4bf81d2566babd731b2635 Mon Sep 17 00:00:00 2001 From: xphoniex Date: Mon, 6 Mar 2023 17:23:08 +0000 Subject: [PATCH] httpd: Set a max allowed size of 4 MiB when serving files raw Signed-off-by: xphoniex --- radicle-httpd/src/error.rs | 4 ++ radicle-httpd/src/raw.rs | 98 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/radicle-httpd/src/error.rs b/radicle-httpd/src/error.rs index a722aa8e..96572bc8 100644 --- a/radicle-httpd/src/error.rs +++ b/radicle-httpd/src/error.rs @@ -35,6 +35,10 @@ pub enum Error { /// Surf error. #[error(transparent)] Surf(#[from] radicle_surf::Error), + + // Surf file error. + #[error(transparent)] + SurfFile(#[from] radicle_surf::fs::error::File), } impl Error { diff --git a/radicle-httpd/src/raw.rs b/radicle-httpd/src/raw.rs index 648defc9..9bc87a37 100644 --- a/radicle-httpd/src/raw.rs +++ b/radicle-httpd/src/raw.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use axum::extract::State; -use axum::http::header; +use axum::http::{header, StatusCode}; use axum::response::IntoResponse; use axum::routing::get; use axum::Router; @@ -15,6 +15,80 @@ use radicle_surf::{Oid, Repository}; use crate::axum_extra::Path; use crate::error::Error; +const MAX_BLOB_SIZE: usize = 4_194_304; + +static MIMES: &[(&str, &str)] = &[ + ("3gp", "video/3gpp"), + ("7z", "application/x-7z-compressed"), + ("aac", "audio/aac"), + ("avi", "video/x-msvideo"), + ("bin", "application/octet-stream"), + ("bmp", "image/bmp"), + ("bz", "application/x-bzip"), + ("bz2", "application/x-bzip2"), + ("csh", "application/x-csh"), + ("css", "text/css"), + ("csv", "text/csv"), + ("doc", "application/msword"), + ( + "docx", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ), + ("epub", "application/epub+zip"), + ("gz", "application/gzip"), + ("gif", "image/gif"), + ("htm", "text/html"), + ("html", "text/html"), + ("ico", "image/vnd.microsoft.icon"), + ("jar", "application/java-archive"), + ("jpeg", "image/jpeg"), + ("jpg", "image/jpeg"), + ("js", "text/javascript"), + ("json", "application/json"), + ("mjs", "text/javascript"), + ("mp3", "audio/mpeg"), + ("mp4", "video/mp4"), + ("mpeg", "video/mpeg"), + ("odp", "application/vnd.oasis.opendocument.presentation"), + ("ods", "application/vnd.oasis.opendocument.spreadsheet"), + ("odt", "application/vnd.oasis.opendocument.text"), + ("oga", "audio/ogg"), + ("ogv", "video/ogg"), + ("ogx", "application/ogg"), + ("otf", "font/otf"), + ("png", "image/png"), + ("pdf", "application/pdf"), + ("php", "application/x-httpd-php"), + ("ppt", "application/vnd.ms-powerpoint"), + ( + "pptx", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ), + ("rar", "application/vnd.rar"), + ("rtf", "application/rtf"), + ("sh", "application/x-sh"), + ("svg", "image/svg+xml"), + ("tar", "application/x-tar"), + ("tif", "image/tiff"), + ("tiff", "image/tiff"), + ("ttf", "font/ttf"), + ("txt", "text/plain"), + ("wav", "audio/wav"), + ("weba", "audio/webm"), + ("webm", "video/webm"), + ("webp", "image/webp"), + ("woff", "font/woff"), + ("woff2", "font/woff2"), + ("xhtml", "application/xhtml+xml"), + ("xls", "application/vnd.ms-excel"), + ( + "xlsx", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ), + ("xml", "application/xml"), + ("zip", "application/zip"), +]; + pub fn router(profile: Arc) -> Router { Router::new() .route("/:project/:sha/*path", get(file_handler)) @@ -27,12 +101,26 @@ async fn file_handler( ) -> impl IntoResponse { let storage = &profile.storage; let repo = Repository::open(paths::repository(storage, &project))?; - let blob = repo.blob(sha, &path)?; - let mut response_headers = HeaderMap::new(); - response_headers.insert(header::CONTENT_TYPE, "text; charset=utf-8".parse().unwrap()); - Ok::<_, Error>((response_headers, blob.content().to_owned())) + if repo.file(sha, &path)?.content(&repo)?.size() > MAX_BLOB_SIZE { + return Ok::<_, Error>((StatusCode::PAYLOAD_TOO_LARGE, response_headers, vec![])); + } + + let blob = repo.blob(sha, &path)?; + let mime = { + if let Some(ext) = path.split('.').last() { + MIMES + .binary_search_by(|(k, _)| k.cmp(&ext)) + .map(|k| MIMES[k].1) + .unwrap_or("text; charset=utf-8") + } else { + "application/octet-stream" + } + }; + response_headers.insert(header::CONTENT_TYPE, mime.parse().unwrap()); + + Ok::<_, Error>((StatusCode::OK, response_headers, blob.content().to_owned())) } #[cfg(test)]