From 5ffbf3bcf859eaf3c1402c56968a45c48de8be79 Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Wed, 13 Nov 2024 11:24:08 -0600 Subject: [PATCH] Relax some size-critical dependency versions Signed-off-by: eternal-flame-AD --- Cargo.lock | 12 --------- Cargo.toml | 17 +++++++----- README.md | 2 ++ src/fetch/mod.rs | 8 +++--- src/lib.rs | 36 ++++++++++++++----------- src/post_process/image_processing.rs | 29 +++++++++++++-------- src/post_process/mod.rs | 39 ++++++++++++++++++---------- 7 files changed, 81 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1ca8b4..809d703 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -695,7 +695,6 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", - "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -718,17 +717,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.31" diff --git a/Cargo.toml b/Cargo.toml index c96e70c..2996385 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,16 +16,19 @@ lto = true strip = true opt-level = "z" codegen-units = 1 +panic = "abort" [profile.release-local] inherits = "release" opt-level = 3 strip = false +panic = "unwind" [features] default = [] env-local = ["axum/tokio", "axum/http1", "axum/http2", "reqwest", "tokio", "env_logger", "governor", "clap", "toml", "image/rayon"] -cf-worker = ["dep:worker", "dep:worker-macros", "dep:console_error_panic_hook"] +cf-worker = ["dep:worker", "dep:worker-macros"] +panic-console-error = ["dep:console_error_panic_hook"] apparmor = ["dep:rand_core", "dep:siphasher"] reqwest = ["dep:reqwest"] svg-text = ["resvg/text"] @@ -37,10 +40,10 @@ governor = ["dep:governor"] worker = { version="0.4.2", features=['http', 'axum'], optional = true } worker-macros = { version="0.4.2", features=['http'], optional = true } axum = { version = "0.7", default-features = false, features = ["query", "json"] } -tower-service = "0.3.2" +tower-service = "0.3" console_error_panic_hook = { version = "0.1.1", optional = true } -serde = { version = "1.0.214", features = ["derive"] } -futures = "0.3.31" +serde = { version = "1", features = ["derive"] } +futures = { version = "0.3.31", default-features = false, features = ["std"] } image = { version = "0.25.5", default-features = false, features = ["avif", "bmp", "gif", "ico", "jpeg", "png", "webp"] } reqwest = { version = "0.12.9", features = ["brotli", "gzip", "stream", "zstd"], optional = true } rand_core = { version = "0.6.4", features = ["getrandom"], optional = true } @@ -48,11 +51,11 @@ siphasher = { version = "1.0.1", optional = true } tokio = { version = "1.41.1", features = ["rt", "rt-multi-thread", "macros"], optional = true } clap = { version = "4.5.20", features = ["derive"], optional = true } toml = { version = "0.8", optional = true } -log = "0.4.22" -env_logger = { version = "0.11.5", optional = true } +log = "0.4" +env_logger = { version = "0.11", optional = true } governor = { version = "0.7.0", features = ["dashmap"], optional = true } resvg = { version = "0.44.0", default-features = false, features = ["gif", "image-webp"] } -thiserror = "2.0.3" +thiserror = "2.0" [build-dependencies] chumsky = "0.9.3" diff --git a/README.md b/README.md index 2f95abc..629db3d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Work in progress! Currently to do: - [X] HTTPs only mode and X-Forwarded-Proto reflection - [X] Cache-Control header - [X] Rate-limiting on local deployment (untested) +- [ ] Read config from Cloudflare +- [ ] Handle all possible panics ## Demo diff --git a/src/fetch/mod.rs b/src/fetch/mod.rs index 108e56f..4b139d5 100644 --- a/src/fetch/mod.rs +++ b/src/fetch/mod.rs @@ -4,7 +4,6 @@ use axum::{ extract::FromRequestParts, http::{request::Parts, HeaderMap}, }; -use futures::stream::TryStreamExt; use std::{borrow::Cow, collections::HashSet, convert::Infallible, pin::Pin}; /// Default maximum number of redirects to follow @@ -18,6 +17,7 @@ pub struct RequestCtx<'a> { } const fn http_version_to_via(v: axum::http::Version) -> &'static str { + #[allow(clippy::match_same_arms)] match v { axum::http::Version::HTTP_09 => "0.9", axum::http::Version::HTTP_10 => "1.0", @@ -58,6 +58,7 @@ pub struct IncomingInfo { impl IncomingInfo { /// Check if the request is potentially looping + #[must_use] pub fn looping(&self, self_via: &str) -> bool { if self.user_agent.is_empty() { return true; @@ -136,10 +137,11 @@ pub trait UpstreamClient { pub mod reqwest { use super::{ http_version_to_via, Cow, ErrorResponse, HTTPResponse, HeaderMap, Pin, RequestCtx, - TryStreamExt, UpstreamClient, MAX_SIZE, + UpstreamClient, MAX_SIZE, }; use ::reqwest::{redirect::Policy, ClientBuilder, Url}; use axum::body::Bytes; + use futures::TryStreamExt; use reqwest::dns::Resolve; use std::{sync::Arc, time::Duration}; @@ -362,7 +364,7 @@ pub mod cf_worker { UpstreamClient, MAX_SIZE, }; use axum::http::{HeaderName, HeaderValue}; - use futures::{FutureExt, Stream, TryFutureExt}; + use futures::{Stream, TryFutureExt}; use worker::{ AbortController, ByteStream, CfProperties, Fetch, Headers, Method, PolishConfig, Request, RequestInit, RequestRedirect, Url, diff --git a/src/lib.rs b/src/lib.rs index 8239c5c..285078b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,9 @@ #![warn(missing_docs)] #![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)] -use std::{ - borrow::Cow, fmt::Display, marker::PhantomData, net::SocketAddr, num::NonZero, sync::Arc, -}; +use std::{borrow::Cow, fmt::Display, marker::PhantomData, sync::Arc}; +#[cfg(feature = "governor")] +use std::{net::SocketAddr, num::NonZero}; #[cfg(feature = "governor")] use axum::extract::ConnectInfo; @@ -185,13 +185,13 @@ pub fn router(config: Conf where <::Response as HTTPResponse>::BodyStream: Unpin, { - use std::time::Duration; - use axum::middleware; #[cfg(feature = "governor")] use governor::{ clock::SystemClock, middleware::StateInformationMiddleware, Quota, RateLimiter, }; + #[cfg(feature = "governor")] + use std::time::Duration; let state = AppState { #[cfg(feature = "governor")] @@ -199,7 +199,7 @@ where Quota::with_period(Duration::from_millis(config.rate_limit.replenish_every)) .unwrap() .allow_burst(config.rate_limit.burst), - SystemClock::default(), + SystemClock, ) .with_middleware::(), client: Upstream::new(&config.fetch), @@ -328,7 +328,7 @@ pub async fn rate_limit_middleware( headers.insert("X-RateLimit-Remaining", "0".parse().unwrap()); headers.insert( "Retry-After", - err.wait_time_from(SystemTime::now().into()) + err.wait_time_from(SystemTime::now()) .as_secs() .to_string() .parse() @@ -350,9 +350,9 @@ async fn fetch( use fetch::cf_worker::CfWorkerClient; use tower_service::Service; - #[cfg(all(feature = "cf-worker", target_arch = "wasm32"))] + #[cfg(feature = "panic-console-error")] console_error_panic_hook::set_once(); - Ok(router::(Default::default()) + Ok(router::(Config::default()) .call(req) .await?) } @@ -404,6 +404,7 @@ pub struct ImageOptions { impl ImageOptions { /// Whether post-processing is requested + #[must_use] pub fn requested_postprocess(&self) -> bool { self.format.is_some() || self.avatar.is_some() @@ -467,6 +468,7 @@ impl std::error::Error for ErrorResponse {} impl ErrorResponse { /// Method not allowed + #[must_use] pub const fn method_not_allowed() -> Self { Self { status: StatusCode::METHOD_NOT_ALLOWED, @@ -486,7 +488,7 @@ impl ErrorResponse { pub fn unexpected_status(url: &str, status: u16) -> Self { Self { status: StatusCode::BAD_GATEWAY, - message: format!("Unexpected status code when accessing {}: {}", url, status).into(), + message: format!("Unexpected status code when accessing {url}: {status}").into(), } } /// Insecure request @@ -676,7 +678,7 @@ impl App { { let mut options = query.image_options; if let Some(filename) = filename { - options.apply_filename(&filename); + options.apply_filename(filename); } match method { http::Method::GET => {} @@ -684,9 +686,10 @@ impl App { let mut resp = Response::new(Body::empty()); resp.headers_mut().insert( "Content-Type", + #[allow(clippy::match_same_arms)] match options.format.as_deref() { Some("png") => "image/png", - Some("jpeg") | Some("jpg") => "image/jpeg", + Some("jpeg" | "jpg") => "image/jpeg", Some("webp") => "image/webp", _ => "image/webp", } @@ -705,9 +708,12 @@ impl App { .request_upstream(&info, &query.url, false, true, DEFAULT_MAX_REDIRECTS) .await?; - let media = - MediaResponse::from_upstream_response(resp, &state.config.post_process, options) - .await?; + let media = Box::pin(MediaResponse::from_upstream_response( + resp, + &state.config.post_process, + options, + )) + .await?; Ok(media.into_response()) } diff --git a/src/post_process/image_processing.rs b/src/post_process/image_processing.rs index e46a600..c196665 100644 --- a/src/post_process/image_processing.rs +++ b/src/post_process/image_processing.rs @@ -7,7 +7,7 @@ use image::{ use crate::ImageOptions; -pub const fn clamp_width(input: (u32, u32), max_width: u32) -> (u32, u32) { +const fn clamp_width(input: (u32, u32), max_width: u32) -> (u32, u32) { if input.0 > max_width { (max_width, input.1 * max_width / input.0) } else { @@ -15,7 +15,7 @@ pub const fn clamp_width(input: (u32, u32), max_width: u32) -> (u32, u32) { } } -pub const fn clamp_height(input: (u32, u32), max_height: u32) -> (u32, u32) { +const fn clamp_height(input: (u32, u32), max_height: u32) -> (u32, u32) { if input.1 > max_height { (input.0 * max_height / input.1, max_height) } else { @@ -23,12 +23,13 @@ pub const fn clamp_height(input: (u32, u32), max_height: u32) -> (u32, u32) { } } -pub const fn clamp_dimensions(input: (u32, u32), max_width: u32, max_height: u32) -> (u32, u32) { +const fn clamp_dimensions(input: (u32, u32), max_width: u32, max_height: u32) -> (u32, u32) { clamp_height(clamp_width(input, max_width), max_height) } // All constants are following https://github.com/misskey-dev/media-proxy/blob/master/SPECIFICATION.md +/// Postprocesses an WebP image using the given options pub fn postprocess_webp_image( data: &[u8], opt: &ImageOptions, @@ -36,7 +37,7 @@ pub fn postprocess_webp_image( let dec = WebPDecoder::new(Cursor::new(data))?; if !dec.has_animation() { - return Ok(Some(postprocess_static_image(data, &opt)?)); + return Ok(Some(postprocess_static_image(data, opt)?)); } if opt.static_ == Some(true) { @@ -53,11 +54,12 @@ pub fn postprocess_webp_image( Ok(None) } +/// Postprocesses an PNG image using the given options pub fn postprocess_png_image(data: &[u8], opt: &ImageOptions) -> ImageResult> { let dec = PngDecoder::new(Cursor::new(data))?; if dec.is_apng()? { - return Ok(Some(postprocess_static_image(data, &opt)?)); + return Ok(Some(postprocess_static_image(data, opt)?)); } if opt.static_ == Some(true) { @@ -75,13 +77,17 @@ pub fn postprocess_png_image(data: &[u8], opt: &ImageOptions) -> ImageResult ImageResult { Ok(process_static_image_impl( image::load_from_memory(data)?, - &opt, + opt, )) } diff --git a/src/post_process/mod.rs b/src/post_process/mod.rs index 92d3770..95022be 100644 --- a/src/post_process/mod.rs +++ b/src/post_process/mod.rs @@ -68,24 +68,22 @@ where // svg need special handling so we deal with it first let is_svg = claimed_ct .as_deref() - .map(|ct| ct.starts_with("image/svg")) - .unwrap_or(false); + .is_some_and(|ct| ct.starts_with("image/svg")); // first if the media type is not something we can handle if !is_svg && (!options.requested_postprocess() || claimed_ct - .map(|ct| ct.starts_with("video/") || ct.starts_with("audio/")) - .unwrap_or(false)) + .is_some_and(|ct| ct.starts_with("video/") || ct.starts_with("audio/"))) { if config.enable_redirects && options.origin != Some(true) && content_length.map_or(false, |cl| cl > 1 << 20) { return Ok(MediaResponse::Redirect(response.request().url.to_string())); - } else { - return Ok(MediaResponse::probe_then_through(response).await?); } + + return MediaResponse::probe_then_through(response).await; } let is_https = response.request().secure; @@ -152,10 +150,17 @@ where let header = header.into_inner(); let mut buf = if let Some(cl) = content_length { let mut ret = Vec::with_capacity(cl); - ret.extend_from_slice(&header[..header_len as usize]); + ret.extend_from_slice( + &header[..header_len + .try_into() + .map_err(|_| ErrorResponse::payload_too_large())?], + ); ret } else { - header[..header_len as usize].to_vec() + header[..header_len + .try_into() + .map_err(|_| ErrorResponse::payload_too_large())?] + .to_vec() }; while let Some(Ok(bytes)) = remaining_body.next().await { if buf.len() + bytes.as_ref().len() > SLURP_LIMIT { @@ -241,8 +246,11 @@ where })) } else { Ok(MediaResponse::PassThru(PassThru { - header_len: header.position() as _, - header: header.into_inner(), + header_len: header + .position() + .try_into() + .map_err(|_| ErrorResponse::payload_too_large())?, + header: Box::new(header.into_inner()), remaining_body, content_type: Some(mime.to_string()), is_https, @@ -250,8 +258,11 @@ where } } None => Ok(MediaResponse::PassThru(PassThru { - header_len: header.position() as _, - header: header.into_inner(), + header_len: header + .position() + .try_into() + .map_err(|_| ErrorResponse::payload_too_large())?, + header: Box::new(header.into_inner()), remaining_body, content_type: None, is_https, @@ -335,7 +346,7 @@ where .position() .try_into() .map_err(|_| ErrorResponse::payload_too_large())?, - header: header.into_inner(), + header: Box::new(header.into_inner()), remaining_body: body, content_type, is_https, @@ -348,7 +359,7 @@ where /// Pass through the response pub struct PassThru { - header: [u8; MTU_BUFFER_SIZE], + header: Box<[u8; MTU_BUFFER_SIZE]>, header_len: usize, content_type: Option, is_https: bool,