kanidmd_lib/valueset/image/
jpg.rs
1use std::io::Cursor;
2
3use image::codecs::jpeg::JpegDecoder;
4use image::ImageDecoder;
5use sketching::*;
6
7use super::ImageValidationError;
8
9const JPEG_MAGIC: [u8; 2] = [0xff, 0xd8];
10const EOI_MAGIC: [u8; 2] = [0xff, 0xd9];
11const SOS_MARKER: [u8; 2] = [0xff, 0xda];
12
13pub fn check_jpg_header(contents: &[u8]) -> Result<(), ImageValidationError> {
15 if !contents.starts_with(&JPEG_MAGIC) {
16 return Err(ImageValidationError::InvalidImage(
17 "Failed to parse JPEG file, invalid magic bytes".to_string(),
18 ));
19 }
20 Ok(())
21}
22
23pub fn has_trailer(contents: &Vec<u8>) -> Result<bool, ImageValidationError> {
27 let buf = contents.as_slice();
28
29 let mut pos = JPEG_MAGIC.len();
30
31 while pos < buf.len() {
32 let marker = &buf[pos..pos + 2];
33 pos += 2;
34
35 let segment_size_bytes: &[u8] = &buf[pos..pos + 2];
36 let segment_size = u16::from_be_bytes(segment_size_bytes.try_into().map_err(|_| {
37 ImageValidationError::InvalidImage("JPEG segment size bytes were invalid!".to_string())
38 })?);
39 pos += segment_size as usize;
41
42 if marker == SOS_MARKER {
43 break;
44 }
45 }
46
47 let mut eoi_index = buf.len() * 2;
49 trace!("buffer length: {}", buf.len());
50
51 for i in pos..=(buf.len() - EOI_MAGIC.len()) {
53 if buf[i..(i + EOI_MAGIC.len())] == EOI_MAGIC {
54 eoi_index = i;
55 break;
56 }
57 }
58
59 if eoi_index > buf.len() {
60 Err(ImageValidationError::InvalidImage(
61 "End of image magic bytes not found in JPEG".to_string(),
62 ))
63 } else if (eoi_index + 2) < buf.len() {
64 debug!(
66 "we're at pos: {} and buf len is {}, is not OK",
67 eoi_index,
68 buf.len()
69 );
70 Ok(true)
71 } else {
72 debug!(
73 "we're at pos: {} and buf len is {}, is OK",
74 eoi_index,
75 buf.len()
76 );
77 Ok(false)
78 }
79}
80
81pub fn validate_decoding(
82 filename: &str,
83 contents: &[u8],
84 limits: image::Limits,
85) -> Result<(), ImageValidationError> {
86 let mut decoder = match JpegDecoder::new(Cursor::new(contents)) {
87 Ok(val) => val,
88 Err(err) => {
89 return Err(ImageValidationError::InvalidImage(format!(
90 "Failed to parse {} as JPG: {:?}",
91 filename, err
92 )))
93 }
94 };
95
96 match decoder.set_limits(limits) {
97 Err(err) => {
98 sketching::admin_warn!(
99 "Image validation failed while validating {}: {:?}",
100 filename,
101 err
102 );
103 Err(ImageValidationError::ExceedsMaxDimensions)
104 }
105 Ok(_) => Ok(()),
106 }
107}
108
109#[test]
110fn test_jpg_has_trailer() {
111 let file_contents = std::fs::read(format!(
112 "{}/src/valueset/image/test_images/oversize_dimensions.jpg",
113 env!("CARGO_MANIFEST_DIR")
114 ))
115 .expect("Failed to read file");
116 assert!(!has_trailer(&file_contents).expect("Failed to check for JPEG trailer"));
117
118 let file_contents = std::fs::read(format!(
120 "{}/src/valueset/image/test_images/windows11_3_cropped.jpg",
121 env!("CARGO_MANIFEST_DIR")
122 ))
123 .expect("Failed to read file");
124 assert!(has_trailer(&file_contents).expect("Failed to check for JPEG trailer"));
127}