use crate::encrypted_media::types::{EncryptedMediaError, MediaMetadata, MediaProcessingOptions};
pub fn extract_and_process_metadata(
data: &[u8],
mime_type: &str,
options: &MediaProcessingOptions,
) -> Result<(Vec<u8>, MediaMetadata), EncryptedMediaError> {
use crate::media_processing::metadata::{
is_safe_raster_format, preflight_dimension_check, strip_exif_and_return_image,
};
let mut metadata = MediaMetadata {
mime_type: mime_type.to_string(),
dimensions: None,
blurhash: None,
thumbhash: None,
original_size: data.len() as u64,
};
let processed_data: Vec<u8>;
if mime_type.starts_with("image/") {
if options.sanitize_exif {
if is_safe_raster_format(mime_type) {
match preflight_dimension_check(data, options) {
Ok(_) => {
match strip_exif_and_return_image(data, mime_type) {
Ok((cleaned_data, decoded_img)) => {
let image_metadata = crate::media_processing::metadata::extract_metadata_from_decoded_image(
&decoded_img,
options,
options.generate_blurhash,
options.generate_thumbhash,
)?;
metadata.dimensions = image_metadata.dimensions;
metadata.blurhash = image_metadata.blurhash;
metadata.thumbhash = image_metadata.thumbhash;
processed_data = cleaned_data;
}
Err(e) => {
tracing::warn!(
"Failed to sanitize {} despite preflight passing: {} - using original data",
mime_type,
e
);
let image_metadata = crate::media_processing::metadata::extract_metadata_from_encoded_image(
data,
options,
options.generate_blurhash,
options.generate_thumbhash,
)?;
metadata.dimensions = image_metadata.dimensions;
metadata.blurhash = image_metadata.blurhash;
metadata.thumbhash = image_metadata.thumbhash;
processed_data = data.to_vec();
}
}
}
Err(e) => {
tracing::warn!(
"Preflight dimension check failed for {}: {} - rejecting image",
mime_type,
e
);
return Err(e.into());
}
}
} else {
tracing::info!(
"Skipping EXIF sanitization for {} - not a safe raster format, using original data",
mime_type
);
let image_metadata =
crate::media_processing::metadata::extract_metadata_from_encoded_image(
data,
options,
options.generate_blurhash,
options.generate_thumbhash,
)?;
metadata.dimensions = image_metadata.dimensions;
metadata.blurhash = image_metadata.blurhash;
metadata.thumbhash = image_metadata.thumbhash;
processed_data = data.to_vec();
}
} else {
let image_metadata =
crate::media_processing::metadata::extract_metadata_from_encoded_image(
data,
options,
options.generate_blurhash,
options.generate_thumbhash,
)?;
metadata.dimensions = image_metadata.dimensions;
metadata.blurhash = image_metadata.blurhash;
metadata.thumbhash = image_metadata.thumbhash;
processed_data = data.to_vec();
}
} else {
processed_data = data.to_vec();
}
Ok((processed_data, metadata))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::media_processing::metadata::{is_safe_raster_format, preflight_dimension_check};
#[test]
fn test_animated_format_fallback() {
use crate::encrypted_media::types::MediaProcessingOptions;
let gif_data = vec![
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3B, ];
let options = MediaProcessingOptions {
generate_blurhash: false,
generate_thumbhash: false,
sanitize_exif: true, max_dimension: Some(100),
max_file_size: None,
max_filename_length: None,
};
let result = extract_and_process_metadata(&gif_data, "image/gif", &options);
assert!(
result.is_ok(),
"GIF processing should succeed with fallback"
);
let (processed_data, metadata) = result.unwrap();
assert_eq!(processed_data, gif_data, "Should return original GIF data");
assert_eq!(metadata.mime_type, "image/gif");
assert_eq!(metadata.original_size, gif_data.len() as u64);
let result = extract_and_process_metadata(&gif_data, "image/webp", &options);
assert!(
result.is_ok(),
"WebP processing should succeed with fallback"
);
let (processed_data, _) = result.unwrap();
assert_eq!(processed_data, gif_data, "Should return original WebP data");
}
#[test]
fn test_animated_format_without_sanitize() {
use crate::encrypted_media::types::MediaProcessingOptions;
let gif_data = vec![
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3B, ];
let options = MediaProcessingOptions {
generate_blurhash: false,
generate_thumbhash: false,
sanitize_exif: false, max_dimension: Some(100),
max_file_size: None,
max_filename_length: None,
};
let result = extract_and_process_metadata(&gif_data, "image/gif", &options);
assert!(
result.is_ok(),
"GIF processing without sanitization should succeed"
);
let (processed_data, metadata) = result.unwrap();
assert_eq!(processed_data, gif_data, "Should return original GIF data");
assert_eq!(metadata.mime_type, "image/gif");
}
#[test]
fn test_safe_raster_format_detection() {
assert!(is_safe_raster_format("image/jpeg"));
assert!(is_safe_raster_format("image/png"));
assert!(!is_safe_raster_format("image/gif"));
assert!(!is_safe_raster_format("image/webp"));
assert!(!is_safe_raster_format("image/svg+xml"));
assert!(!is_safe_raster_format("image/bmp"));
assert!(!is_safe_raster_format("image/tiff"));
}
#[test]
fn test_svg_passthrough_with_sanitize_requested() {
use crate::encrypted_media::types::MediaProcessingOptions;
let svg_data =
b"<svg xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"10\" height=\"10\"/></svg>";
let options = MediaProcessingOptions {
generate_blurhash: false,
generate_thumbhash: false,
sanitize_exif: true, max_dimension: Some(100),
max_file_size: None,
max_filename_length: None,
};
let result = extract_and_process_metadata(svg_data, "image/svg+xml", &options);
match result {
Ok((processed_data, _)) => {
assert_eq!(
processed_data, svg_data,
"SVG should pass through unchanged"
);
}
Err(_) => {
}
}
}
#[test]
fn test_preflight_rejects_oversized_image() {
use crate::encrypted_media::types::MediaProcessingOptions;
let huge_png_header = vec![
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let options = MediaProcessingOptions {
generate_blurhash: false,
generate_thumbhash: false,
sanitize_exif: true,
max_dimension: Some(16384), max_file_size: None,
max_filename_length: None,
};
let result = preflight_dimension_check(&huge_png_header, &options);
assert!(
result.is_err(),
"Preflight should reject oversized image dimensions"
);
let result = extract_and_process_metadata(&huge_png_header, "image/png", &options);
assert!(
result.is_err(),
"Should reject oversized PNG during preflight, before attempting decode"
);
}
}