kanidmd_lib/valueset/image/
png.rs
1use super::{ImageValidationError, MAX_IMAGE_HEIGHT, MAX_IMAGE_WIDTH};
2use crate::prelude::*;
3static PNG_PRELUDE: &[u8] = &[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
4static PNG_CHUNK_END: &[u8; 4] = b"IEND";
5
6#[derive(Debug)]
7enum PngChunkStatus {
10 SeenEnd { has_trailer: bool },
11 MoreChunks,
12}
13
14fn png_consume_chunks_until_iend(
16 buf: &[u8],
17) -> Result<(PngChunkStatus, &[u8]), ImageValidationError> {
18 if buf.len() < 12 {
20 return Err(ImageValidationError::InvalidImage(format!(
21 "PNG file is too short to be valid, got {} bytes",
22 buf.len()
23 )));
24 } else {
25 #[cfg(any(debug_assertions, test))]
26 trace!("input buflen: {}", buf.len());
27 }
28 let (length_bytes, buf) = buf.split_at(4);
29 let (chunk_type, buf) = buf.split_at(4);
30
31 let length = u32::from_be_bytes(
33 length_bytes
34 .try_into()
35 .map_err(|_| ImageValidationError::InvalidImage("PNG corrupt!".to_string()))?,
36 );
37 #[cfg(any(debug_assertions, test))]
38 trace!(
39 "length_bytes: {:?} length: {} chunk_type: {:?} buflen: {}",
40 length_bytes,
41 &length,
42 &chunk_type,
43 &buf.len()
44 );
45
46 if buf.len() < (length + 4) as usize {
47 return Err(ImageValidationError::InvalidImage(format!(
48 "PNG file is too short to be valid, failed to split at the chunk length {}, had {} bytes",
49 length,
50 buf.len(),
51 )));
52 }
53 let (_, buf) = buf.split_at(length as usize);
54 #[cfg(any(debug_assertions, test))]
55 trace!("new buflen: {}", &buf.len());
56
57 let (_checksum, buf) = buf.split_at(4);
58 #[cfg(any(debug_assertions, test))]
59 trace!("post-checksum buflen: {}", &buf.len());
60
61 if chunk_type == PNG_CHUNK_END {
62 if buf.is_empty() {
63 Ok((PngChunkStatus::SeenEnd { has_trailer: false }, buf))
64 } else {
65 Ok((PngChunkStatus::SeenEnd { has_trailer: true }, buf))
66 }
67 } else {
68 Ok((PngChunkStatus::MoreChunks, buf))
69 }
70}
71
72pub fn png_has_trailer(contents: &Vec<u8>) -> Result<bool, ImageValidationError> {
74 let buf = contents.as_slice();
75 let (magic, buf) = buf.split_at(PNG_PRELUDE.len());
77
78 let buf = buf.to_owned();
79 let mut buf = buf.as_slice();
80
81 if magic != PNG_PRELUDE {
82 return Err(ImageValidationError::InvalidPngPrelude);
83 }
84
85 loop {
86 let (status, new_buf) = png_consume_chunks_until_iend(buf)?;
87 buf = match status {
88 PngChunkStatus::SeenEnd { has_trailer } => return Ok(has_trailer),
89 PngChunkStatus::MoreChunks => new_buf,
90 };
91 }
92}
93
94pub fn png_lodepng_validate(
96 contents: &Vec<u8>,
97 filename: &str,
98) -> Result<(), ImageValidationError> {
99 match lodepng::decode32(contents) {
100 Ok(val) => {
101 if val.width > MAX_IMAGE_WIDTH as usize || val.height > MAX_IMAGE_HEIGHT as usize {
102 admin_debug!(
103 "PNG validation failed for {} {}",
104 filename,
105 ImageValidationError::ExceedsMaxWidth
106 );
107 Err(ImageValidationError::ExceedsMaxWidth)
108 } else if val.height > MAX_IMAGE_HEIGHT as usize {
109 admin_debug!(
110 "PNG validation failed for {} {}",
111 filename,
112 ImageValidationError::ExceedsMaxHeight
113 );
114 Err(ImageValidationError::ExceedsMaxHeight)
115 } else {
116 Ok(())
117 }
118 }
119 Err(err) => {
120 Err(ImageValidationError::InvalidImage(format!("{:?}", err)))
122 }
123 }
124}
125
126#[test]
127fn test_png_consume_chunks_until_iend() {
129 let mut testchunks = vec![0, 0, 0, 1]; testchunks.extend(PNG_CHUNK_END); testchunks.push(1); testchunks.extend([0, 0, 0, 1]); let expected: [u8; 0] = [];
135 let testchunks_slice = testchunks.as_slice();
136 let res = png_consume_chunks_until_iend(testchunks_slice);
137
138 match res {
140 Ok((result, buf)) => {
141 if let PngChunkStatus::MoreChunks = result {
142 panic!("Shouldn't have more chunks!");
143 }
144 assert_eq!(buf, &expected);
145 }
146 Err(err) => panic!("Error: {:?}", err),
147 };
148
149 let mut x = 11;
151 while x > 0 {
152 let newslice = &testchunks_slice[0..=x];
153 let res = png_consume_chunks_until_iend(newslice);
154 trace!("chunkstatus at size {} {:?}", x, &res);
155 assert!(res.is_err());
156 x -= 1;
157 }
158}