2021-01-04 17:34:31 +00:00
|
|
|
use actix_web::{body::BodyStream, client::Client, dev::Payload, HttpRequest, HttpResponse};
|
2021-01-06 08:21:17 +00:00
|
|
|
use std::{fmt, sync::Arc, time::Duration};
|
2021-01-04 17:34:31 +00:00
|
|
|
use url::Url;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct Serde<T> {
|
|
|
|
inner: T,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> serde::Serialize for Serde<T>
|
|
|
|
where
|
|
|
|
T: std::fmt::Display,
|
|
|
|
{
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
where
|
|
|
|
S: serde::Serializer,
|
|
|
|
{
|
|
|
|
let s = self.inner.to_string();
|
|
|
|
serde::Serialize::serialize(s.as_str(), serializer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'de, T> serde::Deserialize<'de> for Serde<T>
|
|
|
|
where
|
|
|
|
T: std::str::FromStr,
|
|
|
|
<T as std::str::FromStr>::Err: std::fmt::Display,
|
|
|
|
{
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
where
|
|
|
|
D: serde::Deserializer<'de>,
|
|
|
|
{
|
|
|
|
let s: String = serde::Deserialize::deserialize(deserializer)?;
|
|
|
|
let inner = s
|
|
|
|
.parse::<T>()
|
|
|
|
.map_err(|e| serde::de::Error::custom(e.to_string()))?;
|
|
|
|
|
|
|
|
Ok(Serde { inner })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
|
|
struct Details {
|
|
|
|
width: usize,
|
|
|
|
height: usize,
|
|
|
|
content_type: Serde<mime::Mime>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
|
|
pub struct Image {
|
|
|
|
file: String,
|
|
|
|
delete_token: String,
|
|
|
|
details: Details,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Image {
|
|
|
|
pub(crate) fn key(&self) -> &str {
|
|
|
|
&self.file
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn token(&self) -> &str {
|
|
|
|
&self.delete_token
|
|
|
|
}
|
2021-01-05 02:23:17 +00:00
|
|
|
|
|
|
|
pub(crate) fn width(&self) -> usize {
|
|
|
|
self.details.width
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn height(&self) -> usize {
|
|
|
|
self.details.height
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn mime(&self) -> mime::Mime {
|
|
|
|
self.details.content_type.inner.clone()
|
|
|
|
}
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
|
|
pub struct Images {
|
|
|
|
msg: String,
|
|
|
|
files: Option<Vec<Image>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Images {
|
2021-01-05 02:23:17 +00:00
|
|
|
fn is_err(&self) -> bool {
|
2021-01-04 17:34:31 +00:00
|
|
|
self.files.is_none()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn images(&self) -> impl DoubleEndedIterator<Item = &Image> {
|
|
|
|
self.files.iter().flat_map(|v| v.iter())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub enum UploadError {
|
|
|
|
#[error("Failed to send request")]
|
|
|
|
Request,
|
|
|
|
|
|
|
|
#[error("Failed to parse image json")]
|
|
|
|
Json,
|
2021-01-05 02:23:17 +00:00
|
|
|
|
|
|
|
#[error("Error in pict-rs: {0}")]
|
|
|
|
Other(String),
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
pub enum DeleteError {
|
|
|
|
#[error("Failed to send request")]
|
|
|
|
Request,
|
|
|
|
|
|
|
|
#[error("Status was not 2XX")]
|
|
|
|
Status,
|
|
|
|
}
|
2021-01-04 17:34:31 +00:00
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
pub trait ImageInfo {
|
2021-01-04 17:34:31 +00:00
|
|
|
fn image_url(&self, key: &str) -> Url;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
|
|
|
|
pub enum ImageType {
|
|
|
|
#[serde(rename = "jpg")]
|
|
|
|
Jpeg,
|
|
|
|
|
|
|
|
#[serde(rename = "png")]
|
|
|
|
Png,
|
|
|
|
|
|
|
|
#[serde(rename = "webp")]
|
|
|
|
Webp,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct State {
|
|
|
|
upstream: Url,
|
2021-01-05 02:23:17 +00:00
|
|
|
pub(super) info: Arc<dyn ImageInfo + Send + Sync>,
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl State {
|
2021-01-22 05:43:38 +00:00
|
|
|
pub(super) fn new(upstream: Url, image_info: impl ImageInfo + Send + Sync + 'static) -> Self {
|
2021-01-04 17:34:31 +00:00
|
|
|
State {
|
|
|
|
upstream,
|
2021-01-05 02:23:17 +00:00
|
|
|
info: Arc::new(image_info),
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
pub async fn serve_thumbnail(
|
2021-01-04 17:34:31 +00:00
|
|
|
&self,
|
|
|
|
req: &HttpRequest,
|
|
|
|
key: &str,
|
|
|
|
kind: ImageType,
|
|
|
|
size: u64,
|
2021-01-22 05:43:38 +00:00
|
|
|
client: &Client,
|
2021-01-04 17:34:31 +00:00
|
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
2021-01-22 05:43:38 +00:00
|
|
|
self.serve_image(self.thumbnail(key, kind, size), req, client)
|
|
|
|
.await
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
pub async fn serve_banner(
|
2021-01-04 17:34:31 +00:00
|
|
|
&self,
|
|
|
|
req: &HttpRequest,
|
|
|
|
key: &str,
|
|
|
|
kind: ImageType,
|
|
|
|
size: u64,
|
2021-01-22 05:43:38 +00:00
|
|
|
client: &Client,
|
2021-01-04 17:34:31 +00:00
|
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
2021-01-22 05:43:38 +00:00
|
|
|
self.serve_image(self.banner(key, kind, size), req, client)
|
|
|
|
.await
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
pub async fn serve_icon(
|
2021-01-04 17:34:31 +00:00
|
|
|
&self,
|
|
|
|
req: &HttpRequest,
|
|
|
|
key: &str,
|
|
|
|
kind: ImageType,
|
|
|
|
size: u64,
|
2021-01-22 05:43:38 +00:00
|
|
|
client: &Client,
|
2021-01-04 17:34:31 +00:00
|
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
2021-01-22 05:43:38 +00:00
|
|
|
self.serve_image(self.icon(key, kind, size), req, client)
|
|
|
|
.await
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
pub async fn serve_full(
|
2021-01-04 17:34:31 +00:00
|
|
|
&self,
|
|
|
|
req: &HttpRequest,
|
|
|
|
key: &str,
|
2021-01-22 05:43:38 +00:00
|
|
|
client: &Client,
|
2021-01-04 17:34:31 +00:00
|
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
2021-01-22 05:43:38 +00:00
|
|
|
self.serve_image(self.full_size(key), req, client).await
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
async fn serve_image(
|
|
|
|
&self,
|
|
|
|
url: Url,
|
|
|
|
req: &HttpRequest,
|
2021-01-22 05:43:38 +00:00
|
|
|
client: &Client,
|
2021-01-05 02:23:17 +00:00
|
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
2021-01-22 05:43:38 +00:00
|
|
|
let client_req = client
|
2021-01-06 08:21:17 +00:00
|
|
|
.request_from(url.as_str(), req.head())
|
|
|
|
.timeout(Duration::from_secs(30));
|
2021-01-05 02:23:17 +00:00
|
|
|
let client_req = if let Some(addr) = req.head().peer_addr {
|
|
|
|
client_req.header("X-Forwarded-For", addr.to_string())
|
|
|
|
} else {
|
|
|
|
client_req
|
|
|
|
};
|
|
|
|
|
|
|
|
let res = client_req.no_decompress().send().await?;
|
|
|
|
|
|
|
|
let mut client_res = HttpResponse::build(res.status());
|
|
|
|
|
|
|
|
for (name, value) in res.headers().iter().filter(|(h, _)| *h != "connection") {
|
|
|
|
client_res.header(name.clone(), value.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(client_res.body(BodyStream::new(res)))
|
|
|
|
}
|
|
|
|
|
2021-01-22 05:43:38 +00:00
|
|
|
pub(super) async fn delete_image(
|
|
|
|
&self,
|
|
|
|
key: &str,
|
|
|
|
token: &str,
|
|
|
|
client: &Client,
|
|
|
|
) -> Result<(), DeleteError> {
|
|
|
|
let res = client
|
2021-01-05 02:23:17 +00:00
|
|
|
.delete(self.delete(key, token).as_str())
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.map_err(|e| {
|
|
|
|
log::error!("{}", e);
|
|
|
|
DeleteError::Request
|
|
|
|
})?;
|
|
|
|
|
|
|
|
if !res.status().is_success() {
|
|
|
|
return Err(DeleteError::Status);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-01-22 05:43:38 +00:00
|
|
|
pub(super) async fn proxy_upload<'a>(
|
|
|
|
&'a self,
|
2021-01-04 17:34:31 +00:00
|
|
|
req: HttpRequest,
|
|
|
|
body: Payload,
|
2021-01-22 05:43:38 +00:00
|
|
|
client: &'a Client,
|
2021-01-04 17:34:31 +00:00
|
|
|
) -> Result<Images, UploadError> {
|
2021-01-22 05:43:38 +00:00
|
|
|
let client_req = client
|
2021-01-06 08:21:17 +00:00
|
|
|
.request_from(self.upload().as_str(), req.head())
|
|
|
|
.timeout(Duration::from_secs(30));
|
2021-01-04 17:34:31 +00:00
|
|
|
|
|
|
|
let client_req = if let Some(addr) = req.head().peer_addr {
|
|
|
|
client_req.header("X-Forwarded-For", addr.to_string())
|
|
|
|
} else {
|
|
|
|
client_req
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut res = client_req
|
|
|
|
.send_stream(body)
|
|
|
|
.await
|
|
|
|
.map_err(|_| UploadError::Request)?;
|
|
|
|
|
|
|
|
let images = res.json::<Images>().await.map_err(|_| UploadError::Json)?;
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
if images.is_err() {
|
|
|
|
return Err(UploadError::Other(images.msg));
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:34:31 +00:00
|
|
|
Ok(images)
|
|
|
|
}
|
|
|
|
|
2021-01-22 05:43:38 +00:00
|
|
|
pub(super) async fn download_image(
|
|
|
|
&self,
|
|
|
|
url: &Url,
|
|
|
|
client: &Client,
|
|
|
|
) -> Result<Images, UploadError> {
|
|
|
|
let mut res = client
|
2021-01-05 02:23:17 +00:00
|
|
|
.get(self.download(url).as_str())
|
2021-01-06 08:21:17 +00:00
|
|
|
.timeout(Duration::from_secs(30))
|
2021-01-05 02:23:17 +00:00
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.map_err(|e| {
|
|
|
|
log::error!("Error in download: {}", e);
|
|
|
|
UploadError::Request
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let images: Images = res.json().await.map_err(|e| {
|
|
|
|
log::error!("Error in download: {}", e);
|
|
|
|
UploadError::Json
|
|
|
|
})?;
|
|
|
|
|
|
|
|
if images.is_err() {
|
|
|
|
return Err(UploadError::Other(images.msg));
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
Ok(images)
|
2021-01-04 17:34:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
fn upload(&self) -> Url {
|
2021-01-04 17:34:31 +00:00
|
|
|
let mut url = self.upstream.clone();
|
|
|
|
url.set_path("/image");
|
|
|
|
url
|
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
fn full_size(&self, key: &str) -> Url {
|
2021-01-04 17:34:31 +00:00
|
|
|
let mut url = self.upstream.clone();
|
|
|
|
url.set_path(&format!("/image/original/{}", key));
|
|
|
|
url
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process(&self, kind: ImageType) -> Url {
|
|
|
|
let mut url = self.upstream.clone();
|
|
|
|
url.set_path(&format!("/image/process.{}", kind));
|
|
|
|
url
|
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
fn icon(&self, key: &str, kind: ImageType, size: u64) -> Url {
|
2021-01-04 17:34:31 +00:00
|
|
|
let mut url = self.process(kind);
|
|
|
|
url.set_query(Some(&format!("src={}&crop=1x1&resize={}", key, size)));
|
|
|
|
url
|
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
fn banner(&self, key: &str, kind: ImageType, size: u64) -> Url {
|
2021-01-04 17:34:31 +00:00
|
|
|
let mut url = self.process(kind);
|
|
|
|
url.set_query(Some(&format!("src={}&crop=3x1&resize={}", key, size)));
|
|
|
|
url
|
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
fn thumbnail(&self, key: &str, kind: ImageType, size: u64) -> Url {
|
2021-01-04 17:34:31 +00:00
|
|
|
let mut url = self.process(kind);
|
|
|
|
url.set_query(Some(&format!("src={}&resize={}", key, size)));
|
|
|
|
url
|
|
|
|
}
|
|
|
|
|
2021-01-05 02:23:17 +00:00
|
|
|
fn download(&self, image_url: &Url) -> Url {
|
|
|
|
let mut url = self.upstream.clone();
|
|
|
|
url.set_path("/image/download");
|
|
|
|
url.set_query(Some(&format!("url={}", image_url)));
|
|
|
|
url
|
|
|
|
}
|
|
|
|
|
|
|
|
fn delete(&self, key: &str, token: &str) -> Url {
|
2021-01-04 17:34:31 +00:00
|
|
|
let mut url = self.upstream.clone();
|
|
|
|
url.set_path(&format!("/image/delete/{}/{}", token, key));
|
|
|
|
url
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for ImageType {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
let s = match self {
|
|
|
|
Self::Png => "png",
|
|
|
|
Self::Jpeg => "jpg",
|
|
|
|
Self::Webp => "webp",
|
|
|
|
};
|
|
|
|
|
|
|
|
write!(f, "{}", s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for State {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
f.debug_struct("State")
|
|
|
|
.field("upstream", &self.upstream)
|
|
|
|
.field("image_info", &"Rc<dyn ImageInfo>")
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|