]> Untitled Git - lemmy.git/blob - server/src/routes/images.rs
Merge remote-tracking branch 'weblate/main' into main
[lemmy.git] / server / src / routes / images.rs
1 use crate::rate_limit::RateLimit;
2 use actix::clock::Duration;
3 use actix_web::{body::BodyStream, http::StatusCode, *};
4 use awc::Client;
5 use lemmy_utils::settings::Settings;
6 use serde::{Deserialize, Serialize};
7
8 const THUMBNAIL_SIZES: &[u64] = &[256];
9
10 pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
11   let client = Client::build()
12     .header("User-Agent", "pict-rs-frontend, v0.1.0")
13     .timeout(Duration::from_secs(30))
14     .finish();
15
16   cfg
17     .data(client)
18     .service(
19       web::resource("/pictrs/image")
20         .wrap(rate_limit.image())
21         .route(web::post().to(upload)),
22     )
23     .service(web::resource("/pictrs/image/{filename}").route(web::get().to(full_res)))
24     .service(
25       web::resource("/pictrs/image/thumbnail{size}/{filename}").route(web::get().to(thumbnail)),
26     )
27     .service(web::resource("/pictrs/image/delete/{token}/{filename}").route(web::get().to(delete)));
28 }
29
30 #[derive(Debug, Serialize, Deserialize)]
31 pub struct Image {
32   file: String,
33   delete_token: String,
34 }
35
36 #[derive(Debug, Serialize, Deserialize)]
37 pub struct Images {
38   msg: String,
39   files: Option<Vec<Image>>,
40 }
41
42 async fn upload(
43   req: HttpRequest,
44   body: web::Payload,
45   client: web::Data<Client>,
46 ) -> Result<HttpResponse, Error> {
47   // TODO: check auth and rate limit here
48
49   let mut res = client
50     .request_from(format!("{}/image", Settings::get().pictrs_url), req.head())
51     .if_some(req.head().peer_addr, |addr, req| {
52       req.header("X-Forwarded-For", addr.to_string())
53     })
54     .send_stream(body)
55     .await?;
56
57   let images = res.json::<Images>().await?;
58
59   Ok(HttpResponse::build(res.status()).json(images))
60 }
61
62 async fn full_res(
63   filename: web::Path<String>,
64   req: HttpRequest,
65   client: web::Data<Client>,
66 ) -> Result<HttpResponse, Error> {
67   let url = format!(
68     "{}/image/{}",
69     Settings::get().pictrs_url,
70     &filename.into_inner()
71   );
72   image(url, req, client).await
73 }
74
75 async fn thumbnail(
76   parts: web::Path<(u64, String)>,
77   req: HttpRequest,
78   client: web::Data<Client>,
79 ) -> Result<HttpResponse, Error> {
80   let (size, file) = parts.into_inner();
81
82   if THUMBNAIL_SIZES.contains(&size) {
83     let url = format!(
84       "{}/image/thumbnail{}/{}",
85       Settings::get().pictrs_url,
86       size,
87       &file
88     );
89
90     return image(url, req, client).await;
91   }
92
93   Ok(HttpResponse::NotFound().finish())
94 }
95
96 async fn image(
97   url: String,
98   req: HttpRequest,
99   client: web::Data<Client>,
100 ) -> Result<HttpResponse, Error> {
101   let res = client
102     .request_from(url, req.head())
103     .if_some(req.head().peer_addr, |addr, req| {
104       req.header("X-Forwarded-For", addr.to_string())
105     })
106     .no_decompress()
107     .send()
108     .await?;
109
110   if res.status() == StatusCode::NOT_FOUND {
111     return Ok(HttpResponse::NotFound().finish());
112   }
113
114   let mut client_res = HttpResponse::build(res.status());
115
116   for (name, value) in res.headers().iter().filter(|(h, _)| *h != "connection") {
117     client_res.header(name.clone(), value.clone());
118   }
119
120   Ok(client_res.body(BodyStream::new(res)))
121 }
122
123 async fn delete(
124   components: web::Path<(String, String)>,
125   req: HttpRequest,
126   client: web::Data<Client>,
127 ) -> Result<HttpResponse, Error> {
128   let (token, file) = components.into_inner();
129
130   let url = format!(
131     "{}/image/delete/{}/{}",
132     Settings::get().pictrs_url,
133     &token,
134     &file
135   );
136   let res = client
137     .request_from(url, req.head())
138     .if_some(req.head().peer_addr, |addr, req| {
139       req.header("X-Forwarded-For", addr.to_string())
140     })
141     .no_decompress()
142     .send()
143     .await?;
144
145   Ok(HttpResponse::build(res.status()).body(BodyStream::new(res)))
146 }