diff --git a/README.md b/README.md index 0f63d51..12af0f0 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ insist on writing my own containers). _How hard can it be_ I thought and went on hacking something together. The result does at most one tenth of what cops can do but luckily enough it is the part I need for myself. -![Screenshot](screenshot.png) +![Screenshot](screenshot.jpg) # Building diff --git a/little-hesinde/src/handlers/books.rs b/little-hesinde/src/handlers/books.rs index 9944821..eace2ab 100644 --- a/little-hesinde/src/handlers/books.rs +++ b/little-hesinde/src/handlers/books.rs @@ -4,12 +4,20 @@ use std::sync::Arc; use calibre_db::data::pagination::SortOrder; use poem::{ + error::NotFoundError, handler, web::{Data, Path}, Response, }; +use tokio::fs::File; -use crate::{app_state::AppState, Accept}; +use crate::{ + app_state::AppState, + data::book::{Book, Format}, + handlers::error::HandlerError, + opds::media_type::MediaType, + Accept, +}; /// Handle a request for multiple books, starting at the first. #[handler] @@ -31,6 +39,31 @@ pub async fn handler( books(&accept, &state, Some(&cursor), &sort_order).await } +/// Handle a request for a book with id `id` in format `format`. +#[handler] +pub async fn handler_download( + Path((id, format)): Path<(u64, String)>, + state: Data<&Arc>, +) -> Result { + let book = state + .calibre + .scalar_book(id) + .map_err(HandlerError::DataError)?; + let book = Book::full_book(&book, &state).ok_or(NotFoundError)?; + let format = Format(format); + let file_name = book.formats.get(&format).ok_or(NotFoundError)?; + let file_path = state + .config + .library_path + .join(book.data.path) + .join(file_name); + let mut file = File::open(file_path).await.map_err(|_| NotFoundError)?; + let content_type: MediaType = format.into(); + let content_type = format!("{content_type}"); + + crate::handlers::download::handler(file_name, file, &content_type).await +} + async fn books( accept: &Accept, state: &Arc, diff --git a/little-hesinde/src/handlers/cover.rs b/little-hesinde/src/handlers/cover.rs index 4a0696d..a97514b 100644 --- a/little-hesinde/src/handlers/cover.rs +++ b/little-hesinde/src/handlers/cover.rs @@ -1,31 +1,24 @@ //! Handle requests for cover images. -use std::{fs::File, io::Read, sync::Arc}; +use std::sync::Arc; +use crate::{app_state::AppState, handlers::error::HandlerError}; use poem::{ error::NotFoundError, handler, - web::{headers::ContentType, Data, Path, WithContentType}, - IntoResponse, + web::{headers::ContentType, Data, Path}, + Response, }; - -use crate::{app_state::AppState, handlers::error::HandlerError}; +use tokio::fs::File; /// Handle a request for the cover image of book with id `id`. #[handler] -pub async fn handler( - id: Path, - state: Data<&Arc>, -) -> Result>, poem::Error> { +pub async fn handler(id: Path, state: Data<&Arc>) -> Result { let book = state .calibre .scalar_book(*id) .map_err(HandlerError::DataError)?; let cover_path = state.config.library_path.join(book.path).join("cover.jpg"); - let mut cover = File::open(cover_path).map_err(|_| NotFoundError)?; - - let mut data = Vec::new(); - cover.read_to_end(&mut data).map_err(|_| NotFoundError)?; - - Ok(data.with_content_type(ContentType::jpeg().to_string())) + let mut cover = File::open(cover_path).await.map_err(|_| NotFoundError)?; + crate::handlers::download::handler("cover.jpg", cover, &ContentType::jpeg().to_string()).await } diff --git a/little-hesinde/src/handlers/download.rs b/little-hesinde/src/handlers/download.rs index 1f25c56..ef9290a 100644 --- a/little-hesinde/src/handlers/download.rs +++ b/little-hesinde/src/handlers/download.rs @@ -1,50 +1,23 @@ //! Handle requests for specific formats of a book. -use std::sync::Arc; +use tokio::io::AsyncRead; -use tokio::fs::File; - -use poem::{ - error::NotFoundError, - handler, - web::{Data, Path}, - Body, IntoResponse, Response, -}; +use poem::{Body, IntoResponse, Response}; use tokio_util::io::ReaderStream; -use crate::{ - app_state::AppState, - data::book::{Book, Format}, - handlers::error::HandlerError, - opds::media_type::MediaType, -}; - -/// Handle a request for a book with id `id` in format `format`. -#[handler] -pub async fn handler( - Path((id, format)): Path<(u64, String)>, - state: Data<&Arc>, +/// Handle a request for file. +/// +/// Must not be used directly from a route as that makes it vulnerable to path traversal attacks. +pub async fn handler( + file_name: &str, + reader: A, + content_type: &str, ) -> Result { - let book = state - .calibre - .scalar_book(id) - .map_err(HandlerError::DataError)?; - let book = Book::full_book(&book, &state).ok_or(NotFoundError)?; - let format = Format(format); - let file_name = book.formats.get(&format).ok_or(NotFoundError)?; - let file_path = state - .config - .library_path - .join(book.data.path) - .join(file_name); - - let mut file = File::open(file_path).await.map_err(|_| NotFoundError)?; - let stream = ReaderStream::new(file); + let stream = ReaderStream::new(reader); let body = Body::from_bytes_stream(stream); - let content_type: MediaType = format.into(); Ok(body - .with_content_type(format!("{content_type}")) + .with_content_type(content_type) .with_header("Content-Disposition", format!("filename=\"{file_name}\"")) .into_response()) } diff --git a/little-hesinde/src/handlers/source_archive.rs b/little-hesinde/src/handlers/source_archive.rs index 4fa0498..7cbffa4 100644 --- a/little-hesinde/src/handlers/source_archive.rs +++ b/little-hesinde/src/handlers/source_archive.rs @@ -1,20 +1,11 @@ use crate::{APP_NAME, VERSION}; -use poem::{handler, Body, IntoResponse, Response}; -use tokio_util::io::ReaderStream; +use poem::{handler, Response}; const SOURCE_ARCHIVE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/archive.zip")); /// Handle a request for source code of the server.. #[handler] pub async fn handler() -> Result { - let stream = ReaderStream::new(SOURCE_ARCHIVE); - let body = Body::from_bytes_stream(stream); - - Ok(body - .with_content_type("application/zip") - .with_header( - "Content-Disposition", - format!("filename=\"{APP_NAME}-{VERSION}.zip\""), - ) - .into_response()) + let file_name = format!("{APP_NAME}-{VERSION}.zip"); + crate::handlers::download::handler(&file_name, SOURCE_ARCHIVE, "application/zip").await } diff --git a/little-hesinde/src/lib.rs b/little-hesinde/src/lib.rs index 4983245..89b9394 100644 --- a/little-hesinde/src/lib.rs +++ b/little-hesinde/src/lib.rs @@ -109,7 +109,7 @@ pub async fn run(config: Config) -> Result<(), std::io::Error> { get(handlers::authors::handler), ) .at("/cover/:id", get(handlers::cover::handler)) - .at("/book/:id/:format", get(handlers::download::handler)) + .at("/book/:id/:format", get(handlers::books::handler_download)) .at("/archive", get(handlers::source_archive::handler)) .nest("/static", EmbeddedFilesEndpoint::::new()) .data(Accept::Html); diff --git a/screenshot.jpg b/screenshot.jpg new file mode 100644 index 0000000..3e4a18c Binary files /dev/null and b/screenshot.jpg differ diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index da98654..0000000 Binary files a/screenshot.png and /dev/null differ