1use crate::check::Check;
5use crate::container::CHUNK_FLAG_ENCRYPTED;
6use crate::error::{Error, Result};
7
8pub const PNG_SIGNATURE: [u8; 8] = [0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];
10
11pub const PREVIEW_ROLE_MASK: u32 = 0x0F;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub enum PreviewRole {
17 #[default]
19 Unspecified = 0,
20 Large = 1,
22 Small = 2,
24 Icon = 3,
26}
27
28impl PreviewRole {
29 pub fn from_flags(flags: u32) -> Result<PreviewRole> {
31 let reserved = !(PREVIEW_ROLE_MASK | CHUNK_FLAG_ENCRYPTED);
34 if flags & reserved != 0 {
35 return Err(Error::new(
36 Check::PrevFlags,
37 format!("preview flags {flags:#010x} set a reserved bit"),
38 ));
39 }
40 match flags & PREVIEW_ROLE_MASK {
41 0 => Ok(PreviewRole::Unspecified),
42 1 => Ok(PreviewRole::Large),
43 2 => Ok(PreviewRole::Small),
44 3 => Ok(PreviewRole::Icon),
45 role => Err(Error::new(
46 Check::PrevFlags,
47 format!("preview role {role} is reserved"),
48 )),
49 }
50 }
51
52 pub fn to_flags(self) -> u32 {
54 self as u32
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Preview {
61 pub role: PreviewRole,
63 pub png: Vec<u8>,
65}
66
67pub fn is_png(payload: &[u8]) -> bool {
69 payload.len() >= 8 && payload[..8] == PNG_SIGNATURE
70}
71
72pub fn check_png(payload: &[u8]) -> Result<()> {
74 if payload.len() < 25 || !is_png(payload) {
77 return Err(Error::new(
78 Check::PrevPngSignature,
79 format!(
80 "a {} byte payload does not begin with the PNG signature",
81 payload.len()
82 ),
83 ));
84 }
85 let declared = u32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
86 if declared != 13 {
87 return Err(Error::new(
88 Check::PrevPngSignature,
89 format!("the first PNG chunk declares {declared} bytes; IHDR is 13"),
90 ));
91 }
92 if &payload[12..16] != b"IHDR" {
93 return Err(Error::new(
94 Check::PrevPngSignature,
95 "the first PNG chunk is not IHDR",
96 ));
97 }
98 Ok(())
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 fn minimal_png() -> Vec<u8> {
107 let mut png = Vec::from(PNG_SIGNATURE);
108 png.extend_from_slice(&13u32.to_be_bytes());
109 png.extend_from_slice(b"IHDR");
110 png.extend_from_slice(&1u32.to_be_bytes()); png.extend_from_slice(&1u32.to_be_bytes()); png.extend_from_slice(&[8, 6, 0, 0, 0]); png
114 }
115
116 #[test]
117 fn check_png_accepts_a_minimal_png_and_rejects_other_bytes() {
118 assert!(check_png(&minimal_png()).is_ok());
119 assert_eq!(
120 check_png(b"this is not a PNG at all, not even close")
121 .unwrap_err()
122 .check(),
123 Check::PrevPngSignature
124 );
125 let mut wrong_type = minimal_png();
127 wrong_type[12..16].copy_from_slice(b"IDAT");
128 assert_eq!(
129 check_png(&wrong_type).unwrap_err().check(),
130 Check::PrevPngSignature
131 );
132 let mut wrong_len = minimal_png();
134 wrong_len[8..12].copy_from_slice(&14u32.to_be_bytes());
135 assert_eq!(
136 check_png(&wrong_len).unwrap_err().check(),
137 Check::PrevPngSignature
138 );
139 }
140
141 #[test]
142 fn preview_roles_reject_reserved_values_but_allow_sealing() {
143 assert_eq!(PreviewRole::from_flags(1).unwrap(), PreviewRole::Large);
144 assert_eq!(
146 PreviewRole::from_flags(4).unwrap_err().check(),
147 Check::PrevFlags
148 );
149 assert_eq!(
150 PreviewRole::from_flags(15).unwrap_err().check(),
151 Check::PrevFlags
152 );
153 assert_eq!(
155 PreviewRole::from_flags(1 | (1 << 5)).unwrap_err().check(),
156 Check::PrevFlags
157 );
158 let flags = PreviewRole::Icon.to_flags() | CHUNK_FLAG_ENCRYPTED;
160 assert_eq!(PreviewRole::from_flags(flags).unwrap(), PreviewRole::Icon);
161 }
162}