162 lines
6.4 KiB
Rust
162 lines
6.4 KiB
Rust
use std::io::{Cursor, Read};
|
|
|
|
use exif::Reader;
|
|
use image::{ImageFormat, ImageReader};
|
|
use revolt_database::Metadata;
|
|
use revolt_result::{create_error, Result, ToRevoltError};
|
|
use tempfile::NamedTempFile;
|
|
use tokio::process::Command;
|
|
|
|
/// Strip EXIF data from given file and produce new file and metadata
|
|
pub async fn strip_metadata(
|
|
file: NamedTempFile,
|
|
buf: Vec<u8>,
|
|
metadata: Metadata,
|
|
mime: &str,
|
|
) -> Result<(Vec<u8>, Metadata)> {
|
|
match &metadata {
|
|
Metadata::Image { width, height } => match mime {
|
|
// // little_exif does not appear to parse JPEGs correctly? had 2/2 files fail
|
|
// "image/jpeg" | "image/png" => {
|
|
// // use little_exif to strip metadata except for orientation and colour profile
|
|
// // PNGs must also be re-encoded to mitigate CVE-2023-21036
|
|
// let metadata = revolt_little_exif::metadata::Metadata::new_from_path_with_filetype(
|
|
// file.path(),
|
|
// match mime {
|
|
// "image/jpeg" => revolt_little_exif::filetype::FileExtension::JPEG,
|
|
// "image/png" => revolt_little_exif::filetype::FileExtension::PNG {
|
|
// as_zTXt_chunk: true,
|
|
// },
|
|
// _ => unreachable!(),
|
|
// },
|
|
// )
|
|
// .unwrap();
|
|
// dbg!(metadata.data());
|
|
// todo!()
|
|
// }
|
|
// Apply orientation manually & strip all other EXIF data
|
|
"image/jpeg" | "image/png" | "image/avif" | "image/tiff" => {
|
|
// Create a reader
|
|
let mut cursor = Cursor::new(buf);
|
|
|
|
// Decode the image
|
|
let image = ImageReader::new(&mut cursor)
|
|
.with_guessed_format()
|
|
.to_internal_error()?
|
|
.decode()
|
|
.to_internal_error()?;
|
|
|
|
// Reset read position
|
|
cursor.set_position(0);
|
|
|
|
// Extract orientation data
|
|
let exif_reader = Reader::new();
|
|
let rotation = match exif_reader.read_from_container(&mut cursor) {
|
|
Ok(exif) => match exif.get_field(exif::Tag::Orientation, exif::In::PRIMARY) {
|
|
Some(orientation) => orientation.value.get_uint(0).unwrap_or_default(),
|
|
_ => 0,
|
|
},
|
|
_ => 0,
|
|
};
|
|
|
|
// Create a buffer to write to
|
|
let mut bytes: Vec<u8> = Vec::new();
|
|
let mut writer = Cursor::new(&mut bytes);
|
|
|
|
// Apply the EXIF rotation
|
|
// See https://jdhao.github.io/2019/07/31/image_rotation_exif_info/
|
|
match &rotation {
|
|
2 => image.fliph(),
|
|
3 => image.rotate180(),
|
|
4 => image.rotate180().fliph(),
|
|
5 => image.rotate90().fliph(),
|
|
6 => image.rotate90(),
|
|
7 => image.rotate270().fliph(),
|
|
8 => image.rotate270(),
|
|
_ => image,
|
|
}
|
|
.write_to(
|
|
&mut writer,
|
|
match mime {
|
|
"image/jpeg" => ImageFormat::Jpeg,
|
|
"image/png" => ImageFormat::Png,
|
|
"image/avif" => ImageFormat::Avif,
|
|
"image/tiff" => ImageFormat::Tiff,
|
|
_ => todo!(),
|
|
},
|
|
).to_internal_error()?;
|
|
|
|
// Calculate dimensions after rotation.
|
|
let (width, height) = match &rotation {
|
|
2 | 4 | 5 | 7 => (*height, *width),
|
|
_ => (*width, *height),
|
|
};
|
|
|
|
Ok((bytes, Metadata::Image { width, height }))
|
|
}
|
|
// JXLs store EXIF data but we don't have the ability to write them
|
|
"image/jxl" => Ok((buf, metadata)),
|
|
// All other images that cannot store EXIF data
|
|
_ => Ok((buf, metadata)),
|
|
},
|
|
// Use ffmpeg to copy video stream and probe new metadata
|
|
Metadata::Video { .. } => match mime {
|
|
// Strip EXIF data by copying video stream
|
|
"video/mp4" | "video/webm" | "video/quicktime" => {
|
|
// Pick the correct file format for ffmpeg
|
|
let ext = match mime {
|
|
"video/mp4" => "mp4",
|
|
"video/webm" => "webm",
|
|
"video/quicktime" => "mov",
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
// Temporary output file
|
|
let mut out_file = NamedTempFile::new().to_internal_error()?;
|
|
|
|
// Process the file with ffmpeg
|
|
Command::new("ffmpeg")
|
|
.args([
|
|
// Overwrite the temporary file
|
|
"-y",
|
|
// Read original uploaded file
|
|
"-i",
|
|
file.path().to_str().ok_or(create_error!(InternalError))?,
|
|
// Strip any metadata
|
|
"-map_metadata",
|
|
"-1",
|
|
// Copy video / audio data to new file
|
|
"-c:v",
|
|
"copy",
|
|
"-c:a",
|
|
"copy",
|
|
// Select correct file format
|
|
"-f",
|
|
ext,
|
|
// Save to new temporary file
|
|
out_file
|
|
.path()
|
|
.to_str()
|
|
.ok_or(create_error!(InternalError))?,
|
|
])
|
|
.output()
|
|
.await
|
|
.to_internal_error()?;
|
|
|
|
// Probe the file again
|
|
let metadata = crate::metadata::generate_metadata(&out_file, mime);
|
|
|
|
// Read the file from disk
|
|
let mut buf = Vec::<u8>::new();
|
|
out_file.read_to_end(&mut buf).to_internal_error()?;
|
|
|
|
Ok((buf, metadata))
|
|
}
|
|
// Assume all other video formats cannot store EXIF data
|
|
_ => Ok((buf, metadata)),
|
|
},
|
|
// all other file types don't store EXIF data
|
|
_ => Ok((buf, metadata)),
|
|
}
|
|
}
|