123 lines
3.7 KiB
Rust
123 lines
3.7 KiB
Rust
use crate::{config::Format, error::UploadError};
|
|
use actix_web::web;
|
|
use bytes::Bytes;
|
|
use image::{ImageDecoder, ImageEncoder, ImageFormat};
|
|
use std::io::Cursor;
|
|
use tracing::debug;
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub(crate) enum GifError {
|
|
#[error("Error decoding gif")]
|
|
Decode(#[from] gif::DecodingError),
|
|
|
|
#[error("Error reading bytes")]
|
|
Io(#[from] std::io::Error),
|
|
}
|
|
|
|
// import & export image using the image crate
|
|
pub(crate) async fn validate_image(
|
|
bytes: Bytes,
|
|
prescribed_format: Option<Format>,
|
|
) -> Result<(Bytes, mime::Mime), UploadError> {
|
|
let tup = web::block(move || {
|
|
if let Some(prescribed) = prescribed_format {
|
|
let img = image::load_from_memory(&bytes).map_err(UploadError::InvalidImage)?;
|
|
|
|
let mime = prescribed.to_mime();
|
|
|
|
let mut bytes = Cursor::new(vec![]);
|
|
img.write_to(&mut bytes, prescribed.to_image_format())?;
|
|
return Ok((Bytes::from(bytes.into_inner()), mime));
|
|
}
|
|
|
|
let format = image::guess_format(&bytes).map_err(UploadError::InvalidImage)?;
|
|
|
|
debug!("Validating {:?}", format);
|
|
let res = match format {
|
|
ImageFormat::Png => Ok((validate_png(bytes)?, mime::IMAGE_PNG)),
|
|
ImageFormat::Jpeg => Ok((validate_jpg(bytes)?, mime::IMAGE_JPEG)),
|
|
ImageFormat::Bmp => Ok((validate_bmp(bytes)?, mime::IMAGE_BMP)),
|
|
ImageFormat::Gif => Ok((validate_gif(bytes)?, mime::IMAGE_GIF)),
|
|
_ => Err(UploadError::UnsupportedFormat),
|
|
};
|
|
debug!("Validated");
|
|
res
|
|
})
|
|
.await?;
|
|
|
|
Ok(tup)
|
|
}
|
|
|
|
fn validate_png(bytes: Bytes) -> Result<Bytes, UploadError> {
|
|
let decoder = image::png::PngDecoder::new(Cursor::new(&bytes))?;
|
|
|
|
let mut bytes = Cursor::new(vec![]);
|
|
let encoder = image::png::PNGEncoder::new(&mut bytes);
|
|
validate_still_image(decoder, encoder)?;
|
|
|
|
Ok(Bytes::from(bytes.into_inner()))
|
|
}
|
|
|
|
fn validate_jpg(bytes: Bytes) -> Result<Bytes, UploadError> {
|
|
let decoder = image::jpeg::JpegDecoder::new(Cursor::new(&bytes))?;
|
|
|
|
let mut bytes = Cursor::new(vec![]);
|
|
let encoder = image::jpeg::JPEGEncoder::new(&mut bytes);
|
|
validate_still_image(decoder, encoder)?;
|
|
|
|
Ok(Bytes::from(bytes.into_inner()))
|
|
}
|
|
|
|
fn validate_bmp(bytes: Bytes) -> Result<Bytes, UploadError> {
|
|
let decoder = image::bmp::BmpDecoder::new(Cursor::new(&bytes))?;
|
|
|
|
let mut bytes = Cursor::new(vec![]);
|
|
let encoder = image::bmp::BMPEncoder::new(&mut bytes);
|
|
validate_still_image(decoder, encoder)?;
|
|
|
|
Ok(Bytes::from(bytes.into_inner()))
|
|
}
|
|
|
|
fn validate_gif(bytes: Bytes) -> Result<Bytes, GifError> {
|
|
use gif::{Parameter, SetParameter};
|
|
|
|
let mut decoder = gif::Decoder::new(Cursor::new(&bytes));
|
|
|
|
decoder.set(gif::ColorOutput::Indexed);
|
|
|
|
let mut reader = decoder.read_info()?;
|
|
|
|
let width = reader.width();
|
|
let height = reader.height();
|
|
let global_palette = reader.global_palette().unwrap_or(&[]);
|
|
|
|
let mut bytes = Cursor::new(vec![]);
|
|
{
|
|
let mut encoder = gif::Encoder::new(&mut bytes, width, height, global_palette)?;
|
|
|
|
gif::Repeat::Infinite.set_param(&mut encoder)?;
|
|
|
|
while let Some(frame) = reader.read_next_frame()? {
|
|
encoder.write_frame(frame)?;
|
|
}
|
|
}
|
|
|
|
Ok(Bytes::from(bytes.into_inner()))
|
|
}
|
|
|
|
fn validate_still_image<'a, D, E>(decoder: D, encoder: E) -> Result<(), UploadError>
|
|
where
|
|
D: ImageDecoder<'a>,
|
|
E: ImageEncoder,
|
|
{
|
|
let (width, height) = decoder.dimensions();
|
|
let color_type = decoder.color_type();
|
|
let total_bytes = decoder.total_bytes();
|
|
let mut decoded_bytes = vec![0u8; total_bytes as usize];
|
|
decoder.read_image(&mut decoded_bytes)?;
|
|
|
|
encoder.write_image(&decoded_bytes, width, height, color_type)?;
|
|
|
|
Ok(())
|
|
}
|