use std::fs;
use std::path::Path;
fn is_metadata_marker(marker: u8) -> bool {
(0xE0..=0xEF).contains(&marker) || marker == 0xFE
}
fn keep_application_segment(marker: u8, payload: &[u8]) -> bool {
(marker == 0xE0 && (payload.starts_with(b"JFIF\x00") || payload.starts_with(b"JFXX\x00")))
|| (marker == 0xE2 && payload.starts_with(b"ICC_PROFILE\x00"))
|| (marker == 0xEE && payload.starts_with(b"Adobe"))
}
pub fn orientation_segment(payload: &[u8]) -> Result<Vec<u8>, String> {
if payload.len() + 2 > 0xFFFF {
return Err("orientation EXIF segment is too large".into());
}
let mut out = vec![0xFF, 0xE1];
out.extend_from_slice(&((payload.len() + 2) as u16).to_be_bytes());
out.extend_from_slice(payload);
Ok(out)
}
pub fn strip(src: &Path, dst: &Path, orientation: Option<&[u8]>) -> Result<u64, String> {
let data = fs::read(src).map_err(|e| format!("{}: {e}", src.display()))?;
if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
return Err("not a JPEG".into());
}
let mut out: Vec<u8> = vec![0xFF, 0xD8];
let mut i = 2usize;
let mut inserted_orientation = false;
let mut saw_scan = false;
while i + 1 < data.len() {
if data[i] != 0xFF {
return Err("malformed JPEG marker stream".into());
}
let marker_start = i;
while i < data.len() && data[i] == 0xFF {
i += 1;
}
if i >= data.len() {
return Err("truncated JPEG marker".into());
}
let marker = data[i];
i += 1;
if marker == 0x00 {
return Err("unexpected stuffed byte before JPEG scan".into());
}
if matches!(marker, 0x01 | 0xD8 | 0xD9) || (0xD0..=0xD7).contains(&marker) {
out.extend_from_slice(&data[marker_start..i]);
continue;
}
if i + 2 > data.len() {
return Err("truncated JPEG segment length".into());
}
let seg_len = u16::from_be_bytes([data[i], data[i + 1]]) as usize;
if seg_len < 2 {
return Err("invalid JPEG segment length".into());
}
let segment_end = i + seg_len;
if segment_end > data.len() {
return Err("truncated JPEG segment".into());
}
if marker == 0xDA {
if let Some(seg) = orientation {
if !inserted_orientation {
out.extend_from_slice(seg);
}
}
if !contains(&data[segment_end..], &[0xFF, 0xD9]) {
return Err("JPEG scan has no end marker".into());
}
out.extend_from_slice(&data[marker_start..]);
saw_scan = true;
break;
}
let payload = &data[i + 2..segment_end];
let keep = !is_metadata_marker(marker) || keep_application_segment(marker, payload);
if let Some(seg) = orientation {
if !inserted_orientation && marker != 0xE0 {
out.extend_from_slice(seg);
inserted_orientation = true;
}
}
if keep {
out.extend_from_slice(&data[marker_start..segment_end]);
}
i = segment_end;
}
if !saw_scan {
return Err("JPEG has no image scan".into());
}
fs::write(dst, &out).map_err(|e| format!("{}: {e}", dst.display()))?;
Ok((data.len() - out.len()) as u64)
}
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
haystack.windows(needle.len()).any(|w| w == needle)
}