diff --git a/crates/api_common/src/context.rs b/crates/api_common/src/context.rs index b578914d1f..1c423d156a 100644 --- a/crates/api_common/src/context.rs +++ b/crates/api_common/src/context.rs @@ -15,6 +15,9 @@ use std::sync::Arc; pub struct LemmyContext { pool: ActualDbPool, client: Arc, + /// Pictrs requests must bypass proxy. Unfortunately no_proxy can only be set on ClientBuilder + /// and not on RequestBuilder, so we need a separate client here. + pictrs_client: Arc, secret: Arc, rate_limit_cell: RateLimitCell, } @@ -23,12 +26,14 @@ impl LemmyContext { pub fn create( pool: ActualDbPool, client: ClientWithMiddleware, + pictrs_client: ClientWithMiddleware, secret: Secret, rate_limit_cell: RateLimitCell, ) -> LemmyContext { LemmyContext { pool, client: Arc::new(client), + pictrs_client: Arc::new(pictrs_client), secret: Arc::new(secret), rate_limit_cell, } @@ -42,6 +47,9 @@ impl LemmyContext { pub fn client(&self) -> &ClientWithMiddleware { &self.client } + pub fn pictrs_client(&self) -> &ClientWithMiddleware { + &self.pictrs_client + } pub fn settings(&self) -> &'static Settings { &SETTINGS } @@ -70,7 +78,13 @@ impl LemmyContext { let rate_limit_cell = RateLimitCell::with_test_config(); - let context = LemmyContext::create(pool, client, secret, rate_limit_cell.clone()); + let context = LemmyContext::create( + pool, + client.clone(), + client, + secret, + rate_limit_cell.clone(), + ); FederationConfig::builder() .domain(context.settings().hostname.clone()) diff --git a/crates/api_common/src/request.rs b/crates/api_common/src/request.rs index 756d9517f3..5428c4c4b4 100644 --- a/crates/api_common/src/request.rs +++ b/crates/api_common/src/request.rs @@ -319,7 +319,7 @@ struct PictrsPurgeResponse { /// - It might not be an image /// - Pictrs might not be set up pub async fn purge_image_from_pictrs(image_url: &Url, context: &LemmyContext) -> LemmyResult<()> { - is_image_content_type(context.client(), image_url).await?; + is_image_content_type(context.pictrs_client(), image_url).await?; let alias = image_url .path_segments() @@ -334,7 +334,7 @@ pub async fn purge_image_from_pictrs(image_url: &Url, context: &LemmyContext) -> .api_key .ok_or(LemmyErrorType::PictrsApiKeyNotProvided)?; let response = context - .client() + .pictrs_client() .post(&purge_url) .timeout(REQWEST_TIMEOUT) .header("x-api-token", pictrs_api_key) @@ -361,7 +361,7 @@ pub async fn delete_image_from_pictrs( pictrs_config.url, &delete_token, &alias ); context - .client() + .pictrs_client() .delete(&url) .timeout(REQWEST_TIMEOUT) .send() @@ -384,7 +384,6 @@ async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> L }; // fetch remote non-pictrs images for persistent thumbnail link - // TODO: should limit size once supported by pictrs let fetch_url = format!( "{}image/download?url={}&resize={}", pictrs_config.url, @@ -393,7 +392,7 @@ async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> L ); let res = context - .client() + .pictrs_client() .get(&fetch_url) .timeout(REQWEST_TIMEOUT) .send() @@ -439,7 +438,7 @@ pub async fn fetch_pictrs_proxied_image_details( let proxy_url = format!("{pictrs_url}image/original?proxy={encoded_image_url}"); context - .client() + .pictrs_client() .get(&proxy_url) .timeout(REQWEST_TIMEOUT) .send() @@ -450,7 +449,7 @@ pub async fn fetch_pictrs_proxied_image_details( let details_url = format!("{pictrs_url}image/details/original?proxy={encoded_image_url}"); let res = context - .client() + .pictrs_client() .get(&details_url) .timeout(REQWEST_TIMEOUT) .send() diff --git a/crates/routes/src/images/delete.rs b/crates/routes/src/images/delete.rs index 5499027a83..b28c87c6c2 100644 --- a/crates/routes/src/images/delete.rs +++ b/crates/routes/src/images/delete.rs @@ -17,7 +17,6 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::error::LemmyResult; -use reqwest_middleware::ClientWithMiddleware; pub async fn delete_site_icon( context: Data, @@ -126,7 +125,6 @@ pub async fn delete_user_banner( pub async fn delete_image( data: Json, context: Data, - client: Data, // require login _local_user_view: LocalUserView, ) -> LemmyResult> { @@ -136,7 +134,12 @@ pub async fn delete_image( pictrs_config.url, &data.token, &data.filename ); - client.delete(url).send().await?.error_for_status()?; + context + .pictrs_client() + .delete(url) + .send() + .await? + .error_for_status()?; LocalImage::delete_by_alias(&mut context.pool(), &data.filename).await?; diff --git a/crates/routes/src/images/download.rs b/crates/routes/src/images/download.rs index 0c6f97eb51..76f09a8d12 100644 --- a/crates/routes/src/images/download.rs +++ b/crates/routes/src/images/download.rs @@ -14,7 +14,6 @@ use lemmy_api_common::{ use lemmy_db_schema::source::{images::RemoteImage, local_site::LocalSite}; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::error::LemmyResult; -use reqwest_middleware::ClientWithMiddleware; use url::Url; pub async fn get_image( @@ -23,7 +22,6 @@ pub async fn get_image( req: HttpRequest, local_user_view: Option, context: Data, - client: Data, ) -> LemmyResult { // block access to images if instance is private if local_user_view.is_none() { @@ -48,13 +46,12 @@ pub async fn get_image( url }; - do_get_image(processed_url, req, client).await + do_get_image(processed_url, req, &context).await } pub async fn image_proxy( Query(params): Query, req: HttpRequest, - client: Data, context: Data, ) -> LemmyResult, HttpResponse>> { let url = Url::parse(¶ms.url)?; @@ -89,7 +86,7 @@ pub async fn image_proxy( } else { // Proxy the image data through Lemmy Ok(Either::Right( - do_get_image(processed_url, req, client).await?, + do_get_image(processed_url, req, &context).await?, )) } } @@ -97,9 +94,9 @@ pub async fn image_proxy( pub(super) async fn do_get_image( url: String, req: HttpRequest, - client: Data, + context: &LemmyContext, ) -> LemmyResult { - let mut client_req = adapt_request(&req, url, client); + let mut client_req = adapt_request(&req, url, context); if let Some(addr) = req.head().peer_addr { client_req = client_req.header("X-Forwarded-For", addr.to_string()); diff --git a/crates/routes/src/images/mod.rs b/crates/routes/src/images/mod.rs index 57dd290831..aefe428312 100644 --- a/crates/routes/src/images/mod.rs +++ b/crates/routes/src/images/mod.rs @@ -1,21 +1,22 @@ use actix_web::web::*; use lemmy_api_common::{context::LemmyContext, SuccessResponse}; use lemmy_utils::error::LemmyResult; -use reqwest_middleware::ClientWithMiddleware; pub mod delete; pub mod download; pub mod upload; mod utils; -pub async fn pictrs_health( - client: Data, - context: Data, -) -> LemmyResult> { +pub async fn pictrs_health(context: Data) -> LemmyResult> { let pictrs_config = context.settings().pictrs()?; let url = format!("{}healthz", pictrs_config.url); - client.get(url).send().await?.error_for_status()?; + context + .pictrs_client() + .get(url) + .send() + .await? + .error_for_status()?; Ok(Json(SuccessResponse::default())) } diff --git a/crates/routes/src/images/upload.rs b/crates/routes/src/images/upload.rs index 8b7008d0d4..8f77b5a7a8 100644 --- a/crates/routes/src/images/upload.rs +++ b/crates/routes/src/images/upload.rs @@ -20,7 +20,6 @@ use lemmy_db_schema::{ use lemmy_db_views::structs::LocalUserView; use lemmy_utils::error::LemmyResult; use reqwest::Body; -use reqwest_middleware::ClientWithMiddleware; use std::time::Duration; use UploadType::*; @@ -34,7 +33,6 @@ pub async fn upload_image( req: HttpRequest, body: Payload, local_user_view: LocalUserView, - client: Data, context: Data, ) -> LemmyResult> { if context.settings().pictrs()?.image_upload_disabled { @@ -42,7 +40,7 @@ pub async fn upload_image( } Ok(Json( - do_upload_image(req, body, Other, &local_user_view, client, &context).await?, + do_upload_image(req, body, Other, &local_user_view, &context).await?, )) } @@ -50,10 +48,9 @@ pub async fn upload_user_avatar( req: HttpRequest, body: Payload, local_user_view: LocalUserView, - client: Data, context: Data, ) -> LemmyResult> { - let image = do_upload_image(req, body, Avatar, &local_user_view, client, &context).await?; + let image = do_upload_image(req, body, Avatar, &local_user_view, &context).await?; delete_old_image(&local_user_view.person.avatar, &context).await?; let form = PersonUpdateForm { @@ -69,10 +66,9 @@ pub async fn upload_user_banner( req: HttpRequest, body: Payload, local_user_view: LocalUserView, - client: Data, context: Data, ) -> LemmyResult> { - let image = do_upload_image(req, body, Banner, &local_user_view, client, &context).await?; + let image = do_upload_image(req, body, Banner, &local_user_view, &context).await?; delete_old_image(&local_user_view.person.banner, &context).await?; let form = PersonUpdateForm { @@ -89,13 +85,12 @@ pub async fn upload_community_icon( query: Query, body: Payload, local_user_view: LocalUserView, - client: Data, context: Data, ) -> LemmyResult> { let community: Community = Community::read(&mut context.pool(), query.id).await?; is_mod_or_admin(&mut context.pool(), &local_user_view.person, community.id).await?; - let image = do_upload_image(req, body, Avatar, &local_user_view, client, &context).await?; + let image = do_upload_image(req, body, Avatar, &local_user_view, &context).await?; delete_old_image(&community.icon, &context).await?; let form = CommunityUpdateForm { @@ -112,13 +107,12 @@ pub async fn upload_community_banner( query: Query, body: Payload, local_user_view: LocalUserView, - client: Data, context: Data, ) -> LemmyResult> { let community: Community = Community::read(&mut context.pool(), query.id).await?; is_mod_or_admin(&mut context.pool(), &local_user_view.person, community.id).await?; - let image = do_upload_image(req, body, Banner, &local_user_view, client, &context).await?; + let image = do_upload_image(req, body, Banner, &local_user_view, &context).await?; delete_old_image(&community.banner, &context).await?; let form = CommunityUpdateForm { @@ -134,13 +128,12 @@ pub async fn upload_site_icon( req: HttpRequest, body: Payload, local_user_view: LocalUserView, - client: Data, context: Data, ) -> LemmyResult> { is_admin(&local_user_view)?; let site = Site::read_local(&mut context.pool()).await?; - let image = do_upload_image(req, body, Avatar, &local_user_view, client, &context).await?; + let image = do_upload_image(req, body, Avatar, &local_user_view, &context).await?; delete_old_image(&site.icon, &context).await?; let form = SiteUpdateForm { @@ -156,13 +149,12 @@ pub async fn upload_site_banner( req: HttpRequest, body: Payload, local_user_view: LocalUserView, - client: Data, context: Data, ) -> LemmyResult> { is_admin(&local_user_view)?; let site = Site::read_local(&mut context.pool()).await?; - let image = do_upload_image(req, body, Banner, &local_user_view, client, &context).await?; + let image = do_upload_image(req, body, Banner, &local_user_view, &context).await?; delete_old_image(&site.banner, &context).await?; let form = SiteUpdateForm { @@ -179,13 +171,12 @@ pub async fn do_upload_image( body: Payload, upload_type: UploadType, local_user_view: &LocalUserView, - client: Data, context: &Data, ) -> LemmyResult { let pictrs = context.settings().pictrs()?; let image_url = format!("{}image", pictrs.url); - let mut client_req = adapt_request(&req, image_url, client); + let mut client_req = adapt_request(&req, image_url, &context); client_req = match upload_type { Avatar => { diff --git a/crates/routes/src/images/utils.rs b/crates/routes/src/images/utils.rs index 064ee0311e..80108360ca 100644 --- a/crates/routes/src/images/utils.rs +++ b/crates/routes/src/images/utils.rs @@ -11,17 +11,18 @@ use http::HeaderValue; use lemmy_api_common::{context::LemmyContext, request::delete_image_from_pictrs}; use lemmy_db_schema::{newtypes::DbUrl, source::images::LocalImage}; use lemmy_utils::{error::LemmyResult, REQWEST_TIMEOUT}; -use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; +use reqwest_middleware::RequestBuilder; pub(super) fn adapt_request( request: &HttpRequest, url: String, - client: Data, + context: &LemmyContext, ) -> RequestBuilder { // remove accept-encoding header so that pictrs doesn't compress the response const INVALID_HEADERS: &[HeaderName] = &[ACCEPT_ENCODING, HOST]; - let client_request = client + let client_request = context + .pictrs_client() .request(convert_method(request.method()), url) .timeout(REQWEST_TIMEOUT); diff --git a/src/lib.rs b/src/lib.rs index 065a035136..bd84d02640 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -195,9 +195,13 @@ pub async fn start_lemmy_server(args: CmdArgs) -> LemmyResult<()> { let client = ClientBuilder::new(client_builder(&SETTINGS).build()?) .with(TracingMiddleware::default()) .build(); + let pictrs_client = ClientBuilder::new(client_builder(&SETTINGS).no_proxy().build()?) + .with(TracingMiddleware::default()) + .build(); let context = LemmyContext::create( pool.clone(), client.clone(), + pictrs_client, secret.clone(), rate_limit_cell.clone(), ); @@ -330,11 +334,6 @@ fn create_http_server( .build() .map_err(|e| LemmyErrorType::Unknown(format!("Should always be buildable: {e}")))?; - // Pictrs cannot use proxy - let pictrs_client = ClientBuilder::new(client_builder(&SETTINGS).no_proxy().build()?) - .with(TracingMiddleware::default()) - .build(); - // Create Http server let bind = (settings.bind, settings.port); let server = HttpServer::new(move || { @@ -355,7 +354,6 @@ fn create_http_server( .wrap(ErrorHandlers::new().default_handler(jsonify_plain_text_errors)) .app_data(Data::new(context.clone())) .app_data(Data::new(rate_limit_cell.clone())) - .app_data(Data::new(pictrs_client.clone())) .wrap(FederationMiddleware::new(federation_config.clone())) .wrap(SessionMiddleware::new(context.clone())) .wrap(Condition::new(