1 use crate::types::ExifError;
2
3 use std::fmt::{self, Display};
4
5 #[derive(Copy, Clone, Eq, PartialEq)]
6 pub enum FileType {
7 Unknown,
8 JPEG,
9 TIFF,
10 }
11
12 impl Display for FileType {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result13 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14 f.write_str(self.as_str())
15 }
16 }
17
18 impl FileType {
as_str(&self) -> &'static str19 pub fn as_str(&self) -> &'static str {
20 match self {
21 Self::Unknown => "application/octet-stream",
22 Self::JPEG => "image/jpeg",
23 Self::TIFF => "image/tiff",
24 }
25 }
26 }
27
28 /// Detect the type of an image contained in a byte buffer
detect_type(contents: &[u8]) -> FileType29 pub(crate) fn detect_type(contents: &[u8]) -> FileType {
30 if contents.len() < 11 {
31 return FileType::Unknown;
32 }
33
34 if contents[0] == 0xff && contents[1] == 0xd8 && contents[2] == 0xff && // contents[3] == 0xe0 &&
35 contents[6] == b'J' && contents[7] == b'F' && contents[8] == b'I' && contents[9] == b'F' &&
36 contents[10] == 0
37 {
38 return FileType::JPEG;
39 }
40 if contents[0] == 0xff && contents[1] == 0xd8 && contents[2] == 0xff && // contents[3] == 0xe0
41 contents[6] == b'E' && contents[7] == b'x' && contents[8] == b'i' && contents[9] == b'f' &&
42 contents[10] == 0
43 {
44 return FileType::JPEG;
45 }
46 if contents[0] == b'I' && contents[1] == b'I' && contents[2] == 42 && contents[3] == 0 {
47 /* TIFF little-endian */
48 return FileType::TIFF;
49 }
50 if contents[0] == b'M' && contents[1] == b'M' && contents[2] == 0 && contents[3] == 42 {
51 /* TIFF big-endian */
52 return FileType::TIFF;
53 }
54 FileType::Unknown
55 }
56
57 /// Find the embedded TIFF in a JPEG image (that in turn contains the EXIF data)
find_embedded_tiff_in_jpeg(contents: &[u8]) -> Result<(usize, usize), ExifError>58 pub fn find_embedded_tiff_in_jpeg(contents: &[u8]) -> Result<(usize, usize), ExifError> {
59 let mut offset = 2 as usize;
60
61 while offset < contents.len() {
62 if contents.len() < (offset + 4) {
63 return Err(ExifError::JpegWithoutExif(
64 "JPEG truncated in marker header".to_string(),
65 ));
66 }
67
68 let marker: u16 = u16::from(contents[offset]) * 256 + u16::from(contents[offset + 1]);
69
70 if marker < 0xff00 {
71 return Err(ExifError::JpegWithoutExif(format!(
72 "Invalid marker {:x}",
73 marker
74 )));
75 }
76
77 offset += 2;
78 let size = (contents[offset] as usize) * 256 + (contents[offset + 1] as usize);
79
80 if size < 2 {
81 return Err(ExifError::JpegWithoutExif(
82 "JPEG marker size must be at least 2 (because of the size word)".to_string(),
83 ));
84 }
85 if contents.len() < (offset + size) {
86 return Err(ExifError::JpegWithoutExif(
87 "JPEG truncated in marker body".to_string(),
88 ));
89 }
90
91 if marker == 0xffe1 {
92 if size < 8 {
93 return Err(ExifError::JpegWithoutExif(
94 "EXIF preamble truncated".to_string(),
95 ));
96 }
97
98 if contents[offset + 2..offset + 8] != [b'E', b'x', b'i', b'f', 0, 0] {
99 return Err(ExifError::JpegWithoutExif(
100 "EXIF preamble unrecognized".to_string(),
101 ));
102 }
103
104 // The offset and size of the block, excluding size and 'Exif\0\0'.
105 return Ok((offset + 8, size - 8));
106 }
107 if marker == 0xffda {
108 // last marker
109 return Err(ExifError::JpegWithoutExif(
110 "Last mark found and no EXIF".to_string(),
111 ));
112 }
113 offset += size;
114 }
115
116 Err(ExifError::JpegWithoutExif(
117 "Scan past EOF and no EXIF found".to_string(),
118 ))
119 }
120