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