1use crate::Error;
2use jpeg_decoder::Decoder;
3
4const JPEG_SOI: [u8; 2] = [0xFF, 0xD8];
5const MARKER_COM: u8 = 0xFE;
6const MARKER_APP1: u8 = 0xE1;
7const MARKER_APP2: u8 = 0xE2;
8
9pub fn clean_metadata(data: &[u8]) -> Result<Vec<u8>, Error> {
23 if data.len() < 4 || data[0..2] != JPEG_SOI {
24 return Err(Error::InvalidFormat("Not a valid JPEG file".to_string()));
25 }
26
27 validate_jpeg_decode(data)?;
29
30 let mut output = Vec::new();
31 output.extend_from_slice(&JPEG_SOI);
32
33 let mut pos = 2;
34 let mut has_exif = false;
35 let mut orientation: Option<u16> = None;
36
37 while pos < data.len() - 1 {
39 if data[pos] != 0xFF {
40 return Err(Error::ParseError("Invalid JPEG marker".to_string()));
41 }
42
43 let marker = data[pos + 1];
44 pos += 2;
45
46 if marker == 0xDA {
48 output.extend_from_slice(&[0xFF, marker]);
49 output.extend_from_slice(&data[pos..]);
50 break;
51 }
52
53 if (0xD0..=0xD9).contains(&marker) {
55 output.extend_from_slice(&[0xFF, marker]);
56 continue;
57 }
58
59 if pos + 2 > data.len() {
61 return Err(Error::ParseError("Unexpected end of JPEG data".to_string()));
62 }
63
64 let segment_size = ((data[pos] as u16) << 8) | (data[pos + 1] as u16);
65 if segment_size < 2 {
66 return Err(Error::ParseError("Invalid segment size".to_string()));
67 }
68
69 let segment_end = pos + segment_size as usize;
70 if segment_end > data.len() {
71 return Err(Error::ParseError("Segment extends beyond file".to_string()));
72 }
73
74 let keep_segment = match marker {
76 0xC0..=0xC3 | 0xC5..=0xCF => true, 0xC4 => true, 0xDB => true, 0xDD => true, 0xE0 => true,
83 MARKER_APP1 => {
85 if !has_exif && segment_size > 8 && &data[pos + 2..pos + 6] == b"Exif" {
86 has_exif = true;
87 orientation = extract_orientation_from_exif(&data[pos + 8..segment_end]);
90 }
91 false
92 }
93 MARKER_APP2 => segment_size > 14 && &data[pos + 2..pos + 14] == b"ICC_PROFILE\0",
95 0xE3..=0xEF => false,
97 MARKER_COM => false,
99 _ => false,
100 };
101
102 if keep_segment {
103 output.extend_from_slice(&[0xFF, marker]);
104 output.extend_from_slice(&data[pos..segment_end]);
105 }
106
107 pos = segment_end;
108 }
109
110 if let Some(orientation_value) = orientation {
112 if (1..=8).contains(&orientation_value) {
113 let exif_data = create_minimal_exif(orientation_value)?;
114 let mut final_output = Vec::new();
116 let mut inserted = false;
117 let mut i = 0;
118
119 while i < output.len() - 1 {
120 if output[i] == 0xFF && output[i + 1] == 0xE0 && !inserted {
121 let marker_size = ((output[i + 2] as u16) << 8) | (output[i + 3] as u16);
123 let marker_end = i + 2 + marker_size as usize;
124 final_output.extend_from_slice(&output[i..marker_end]);
125 final_output.extend_from_slice(&exif_data);
126 inserted = true;
127 i = marker_end;
128 } else {
129 final_output.push(output[i]);
130 i += 1;
131 }
132 }
133 if i < output.len() {
134 final_output.push(output[i]);
135 }
136
137 if !inserted {
138 let mut temp = vec![0xFF, 0xD8];
140 temp.extend_from_slice(&exif_data);
141 temp.extend_from_slice(&output[2..]);
142 return Ok(temp);
143 }
144
145 return Ok(final_output);
146 }
147 }
148
149 validate_jpeg_decode(&output)?;
151
152 Ok(output)
153}
154
155fn create_minimal_exif(orientation: u16) -> Result<Vec<u8>, Error> {
157 let mut exif = Vec::new();
158
159 exif.extend_from_slice(&[0xFF, MARKER_APP1]);
161
162 exif.extend_from_slice(&[0x00, 0x00]);
164
165 exif.extend_from_slice(b"Exif\0\0");
167
168 exif.extend_from_slice(&[0x49, 0x49]); exif.extend_from_slice(&[0x2A, 0x00]); exif.extend_from_slice(&[0x08, 0x00, 0x00, 0x00]); exif.extend_from_slice(&[0x01, 0x00]); exif.extend_from_slice(&[0x12, 0x01]); exif.extend_from_slice(&[0x03, 0x00]); exif.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]); exif.extend_from_slice(&[orientation as u8, (orientation >> 8) as u8, 0x00, 0x00]); exif.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
184
185 let size = (exif.len() - 2) as u16;
187 exif[2] = (size >> 8) as u8;
188 exif[3] = size as u8;
189
190 Ok(exif)
191}
192
193pub fn read_comment(data: &[u8]) -> Result<Option<String>, Error> {
195 if data.len() < 4 || data[0..2] != JPEG_SOI {
196 return Err(Error::InvalidFormat("Not a valid JPEG file".to_string()));
197 }
198
199 validate_jpeg_decode(data)?;
201
202 let mut pos = 2;
203
204 while pos < data.len() - 1 {
205 if data[pos] != 0xFF {
206 return Err(Error::ParseError("Invalid JPEG marker".to_string()));
207 }
208
209 let marker = data[pos + 1];
210 pos += 2;
211
212 if marker == 0xDA {
214 break;
215 }
216
217 if (0xD0..=0xD9).contains(&marker) {
219 continue;
220 }
221
222 if pos + 2 > data.len() {
224 return Err(Error::ParseError("Unexpected end of JPEG data".to_string()));
225 }
226
227 let segment_size = ((data[pos] as u16) << 8) | (data[pos + 1] as u16);
228 if segment_size < 2 {
229 return Err(Error::ParseError("Invalid segment size".to_string()));
230 }
231
232 let segment_end = pos + segment_size as usize;
233 if segment_end > data.len() {
234 return Err(Error::ParseError("Segment extends beyond file".to_string()));
235 }
236
237 if marker == MARKER_COM {
239 if segment_size > 2 {
240 let comment_data = &data[pos + 2..segment_end];
241 let comment = String::from_utf8_lossy(comment_data).to_string();
242 return Ok(Some(comment));
243 } else {
244 return Ok(Some(String::new()));
246 }
247 }
248
249 pos = segment_end;
250 }
251
252 Ok(None)
253}
254
255fn extract_orientation_from_exif(exif_data: &[u8]) -> Option<u16> {
257 if exif_data.len() < 8 {
259 return None;
260 }
261
262 let endian = if &exif_data[0..2] == b"II" {
264 true
266 } else if &exif_data[0..2] == b"MM" {
267 false
269 } else {
270 return None;
271 };
272
273 let magic = if endian {
275 u16::from_le_bytes([exif_data[2], exif_data[3]])
276 } else {
277 u16::from_be_bytes([exif_data[2], exif_data[3]])
278 };
279
280 if magic != 42 {
281 return None;
282 }
283
284 let ifd0_offset = if endian {
286 u32::from_le_bytes([exif_data[4], exif_data[5], exif_data[6], exif_data[7]]) as usize
287 } else {
288 u32::from_be_bytes([exif_data[4], exif_data[5], exif_data[6], exif_data[7]]) as usize
289 };
290
291 if ifd0_offset + 2 > exif_data.len() {
292 return None;
293 }
294
295 let entry_count = if endian {
297 u16::from_le_bytes([exif_data[ifd0_offset], exif_data[ifd0_offset + 1]]) as usize
298 } else {
299 u16::from_be_bytes([exif_data[ifd0_offset], exif_data[ifd0_offset + 1]]) as usize
300 };
301
302 for i in 0..entry_count {
304 let entry_offset = ifd0_offset + 2 + (i * 12);
305 if entry_offset + 12 > exif_data.len() {
306 break;
307 }
308
309 let tag = if endian {
311 u16::from_le_bytes([exif_data[entry_offset], exif_data[entry_offset + 1]])
312 } else {
313 u16::from_be_bytes([exif_data[entry_offset], exif_data[entry_offset + 1]])
314 };
315
316 if tag == 0x0112 {
317 let value_offset = entry_offset + 8;
319 let orientation = if endian {
320 u16::from_le_bytes([exif_data[value_offset], exif_data[value_offset + 1]])
321 } else {
322 u16::from_be_bytes([exif_data[value_offset], exif_data[value_offset + 1]])
323 };
324
325 return Some(orientation);
326 }
327 }
328
329 None
330}
331
332fn validate_jpeg_decode(data: &[u8]) -> Result<(), Error> {
334 let mut decoder = Decoder::new(data);
335
336 match decoder.read_info() {
338 Ok(_) => {
339 let info = decoder.info();
341 if info.is_none() {
342 return Err(Error::InvalidFormat("Failed to get JPEG info".to_string()));
343 }
344
345 let info = info.unwrap();
347 if info.width == 0 || info.height == 0 {
348 return Err(Error::InvalidFormat("Invalid image dimensions".to_string()));
349 }
350
351 Ok(())
352 }
353 Err(e) => Err(Error::InvalidFormat(format!("Invalid JPEG: {e}"))),
354 }
355}
356
357pub fn write_comment(data: &[u8], comment: &str) -> Result<Vec<u8>, Error> {
359 if data.len() < 4 || data[0..2] != JPEG_SOI {
360 return Err(Error::InvalidFormat("Not a valid JPEG file".to_string()));
361 }
362
363 validate_jpeg_decode(data)?;
365
366 let comment_bytes = comment.as_bytes();
367 if comment_bytes.len() > 65533 {
368 return Err(Error::InvalidFormat("Comment too long".to_string()));
369 }
370
371 let mut output = Vec::new();
372 output.extend_from_slice(&JPEG_SOI);
373
374 let mut comment_segment = Vec::new();
376 comment_segment.extend_from_slice(&[0xFF, MARKER_COM]);
377 let segment_size = (comment_bytes.len() + 2) as u16;
378 comment_segment.push((segment_size >> 8) as u8);
379 comment_segment.push(segment_size as u8);
380 comment_segment.extend_from_slice(comment_bytes);
381
382 let mut pos = 2;
383 let mut comment_inserted = false;
384
385 while pos < data.len() - 1 {
387 if data[pos] != 0xFF {
388 return Err(Error::ParseError("Invalid JPEG marker".to_string()));
389 }
390
391 let marker = data[pos + 1];
392 pos += 2;
393
394 if !comment_inserted && (marker == 0xDA || marker == 0xDB) {
396 output.extend_from_slice(&comment_segment);
397 comment_inserted = true;
398 }
399
400 if marker == 0xDA {
402 output.extend_from_slice(&[0xFF, marker]);
403 output.extend_from_slice(&data[pos..]);
404 break;
405 }
406
407 if (0xD0..=0xD9).contains(&marker) {
409 output.extend_from_slice(&[0xFF, marker]);
410 continue;
411 }
412
413 if pos + 2 > data.len() {
415 return Err(Error::ParseError("Unexpected end of JPEG data".to_string()));
416 }
417
418 let segment_size = ((data[pos] as u16) << 8) | (data[pos + 1] as u16);
419 if segment_size < 2 {
420 return Err(Error::ParseError("Invalid segment size".to_string()));
421 }
422
423 let segment_end = pos + segment_size as usize;
424 if segment_end > data.len() {
425 return Err(Error::ParseError("Segment extends beyond file".to_string()));
426 }
427
428 if marker != MARKER_COM {
430 output.extend_from_slice(&[0xFF, marker]);
431 output.extend_from_slice(&data[pos..segment_end]);
432 }
433
434 pos = segment_end;
435 }
436
437 if !comment_inserted {
439 output.extend_from_slice(&comment_segment);
440 }
441
442 validate_jpeg_decode(&output)?;
444
445 Ok(output)
446}