use std::fs::File;
use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write};
pub fn strip_metadata(path: &str, out_path: Option<&str>) -> Result<(), String> {
let out_path_str = match out_path {
Some(p) => p.to_string(),
None => format!("{}.tmp", path),
};
let mut inp = File::open(path).map_err(|e| e.to_string())?;
let mut sig = [0u8; 8];
let n = inp.read(&mut sig).map_err(|e| e.to_string())?;
if n < 8 {
return Err("File too small".into());
}
let mut out = BufWriter::new(File::create(&out_path_str).map_err(|e| e.to_string())?);
let res = if sig[0..2] == [0xFF, 0xD8] {
strip_jpeg(&mut inp, &mut out, &sig[0..2])
} else if sig == [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] {
strip_png(&mut inp, &mut out, &sig)
} else if sig[0..4] == *b"RIFF" {
crate::webp::strip_metadata(&mut inp, &mut out)
} else {
Err("Stripping metadata is currently unsupported for this format (only JPEG, PNG, and WebP are supported).".into())
};
drop(out);
drop(inp);
if res.is_err() && out_path.is_none() {
let _ = std::fs::remove_file(&out_path_str);
}
res?;
if out_path.is_none() {
std::fs::rename(&out_path_str, path)
.map_err(|e| format!("Failed to overwrite original file: {}", e))?;
}
Ok(())
}
fn strip_jpeg(inp: &mut File, out: &mut BufWriter<File>, sig: &[u8]) -> Result<(), String> {
out.write_all(sig).map_err(|e| e.to_string())?;
inp.seek(SeekFrom::Start(2)).map_err(|e| e.to_string())?;
loop {
let mut byte = [0u8; 1];
if inp.read(&mut byte).map_err(|e| e.to_string())? == 0 {
break;
}
if byte[0] != 0xFF {
out.write_all(&byte).map_err(|e| e.to_string())?;
continue;
}
let mut marker = [0u8; 1];
if inp.read(&mut marker).map_err(|e| e.to_string())? == 0 {
out.write_all(&[0xFF]).map_err(|e| e.to_string())?;
break;
}
if marker[0] == 0xFF {
out.write_all(&[0xFF]).map_err(|e| e.to_string())?;
inp.seek(SeekFrom::Current(-1)).map_err(|e| e.to_string())?;
continue;
}
if marker[0] == 0x00 || marker[0] == 0x01 || (marker[0] >= 0xD0 && marker[0] <= 0xD9) {
out.write_all(&[0xFF, marker[0]])
.map_err(|e| e.to_string())?;
if marker[0] == 0xD9 {
break; }
continue;
}
let mut len_buf = [0u8; 2];
if inp.read_exact(&mut len_buf).is_err() {
out.write_all(&[0xFF, marker[0]])
.map_err(|e| e.to_string())?;
break;
}
let length = u16::from_be_bytes(len_buf) as usize;
let strip = marker[0] == 0xFE
|| (marker[0] >= 0xE1 && marker[0] <= 0xEF && marker[0] != 0xE2 && marker[0] != 0xEE);
if strip {
if length >= 2 {
inp.seek(SeekFrom::Current((length - 2) as i64))
.map_err(|e| e.to_string())?;
}
} else {
out.write_all(&[0xFF, marker[0], len_buf[0], len_buf[1]])
.map_err(|e| e.to_string())?;
if length >= 2 {
let mut data = vec![0; length - 2];
inp.read_exact(&mut data).map_err(|e| e.to_string())?;
out.write_all(&data).map_err(|e| e.to_string())?;
}
}
if marker[0] == 0xDA {
io::copy(inp, out).map_err(|e| e.to_string())?;
break;
}
}
Ok(())
}
fn strip_png(inp: &mut File, out: &mut BufWriter<File>, sig: &[u8]) -> Result<(), String> {
out.write_all(sig).map_err(|e| e.to_string())?;
loop {
let mut len_buf = [0u8; 4];
if inp.read_exact(&mut len_buf).is_err() {
break;
}
let length = u32::from_be_bytes(len_buf) as usize;
let mut type_buf = [0u8; 4];
inp.read_exact(&mut type_buf).map_err(|e| e.to_string())?;
let strip = matches!(&type_buf, b"eXIf" | b"tEXt" | b"zTXt" | b"iTXt" | b"tIME");
if strip {
inp.seek(SeekFrom::Current((length + 4) as i64))
.map_err(|e| e.to_string())?;
} else {
out.write_all(&len_buf).map_err(|e| e.to_string())?;
out.write_all(&type_buf).map_err(|e| e.to_string())?;
let mut remaining = length + 4;
let mut buf = [0u8; 8192];
while remaining > 0 {
let to_read = std::cmp::min(remaining, buf.len());
inp.read_exact(&mut buf[..to_read])
.map_err(|e| e.to_string())?;
out.write_all(&buf[..to_read]).map_err(|e| e.to_string())?;
remaining -= to_read;
}
}
if &type_buf == b"IEND" {
break;
}
}
Ok(())
}