aven_core/attachments/
validation.rs1#![allow(dead_code)]
2
3use anyhow::{Result, bail};
4
5pub const MAX_BLOB_BYTES: usize = 25 * 1024 * 1024;
6pub const MAX_FILENAME_LEN: usize = 255;
7pub const MAX_ALT_TEXT_LEN: usize = 500;
8pub const SUPPORTED_MEDIA_TYPES: &[&str] = &["image/png", "image/jpeg", "image/gif", "image/webp"];
9
10pub fn validate_attachment_id(value: &str) -> Result<()> {
11 if value.len() != 16 || !value.bytes().all(|byte| crate::ids::BASE32.contains(&byte)) {
12 bail!("error invalid-attachment-id input={value}");
13 }
14 Ok(())
15}
16
17pub fn validate_sha256(value: &str) -> Result<()> {
18 if value.len() != 64
19 || !value
20 .bytes()
21 .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))
22 {
23 bail!("error invalid-sha256");
24 }
25 Ok(())
26}
27
28pub fn validate_media_type(value: &str) -> Result<()> {
29 if !SUPPORTED_MEDIA_TYPES.contains(&value) {
30 bail!("error unsupported-attachment-media-type input={value}");
31 }
32 Ok(())
33}
34
35pub fn validate_blob_size(byte_size: usize) -> Result<()> {
36 if byte_size == 0 || byte_size > MAX_BLOB_BYTES {
37 bail!("error invalid-attachment-size bytes={byte_size}");
38 }
39 Ok(())
40}
41
42pub fn validate_filename(value: Option<&str>) -> Result<()> {
43 let Some(value) = value else {
44 return Ok(());
45 };
46 if value.is_empty()
47 || value.len() > MAX_FILENAME_LEN
48 || value
49 .chars()
50 .any(|ch| ch.is_control() || ch == '/' || ch == '\\')
51 {
52 bail!("error invalid-attachment-filename");
53 }
54 Ok(())
55}
56
57pub fn validate_alt_text(value: Option<&str>) -> Result<()> {
58 let Some(value) = value else {
59 return Ok(());
60 };
61 if value.len() > MAX_ALT_TEXT_LEN || value.chars().any(char::is_control) {
62 bail!("error invalid-attachment-alt-text");
63 }
64 Ok(())
65}
66
67pub fn validate_dimensions(width: Option<i64>, height: Option<i64>) -> Result<()> {
68 let (Some(width), Some(height)) = (width, height) else {
69 bail!("error invalid-attachment-dimensions");
70 };
71 let Ok(width_u32) = u32::try_from(width) else {
72 bail!("error invalid-attachment-dimensions");
73 };
74 let Ok(height_u32) = u32::try_from(height) else {
75 bail!("error invalid-attachment-dimensions");
76 };
77 if width_u32 == 0
78 || height_u32 == 0
79 || width_u32 > crate::attachments::decode::MAX_IMAGE_EDGE
80 || height_u32 > crate::attachments::decode::MAX_IMAGE_EDGE
81 || u64::from(width_u32) * u64::from(height_u32)
82 > crate::attachments::decode::MAX_FRAME_PIXELS
83 {
84 bail!("error invalid-attachment-dimensions");
85 }
86 Ok(())
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn validates_attachment_id() {
95 assert!(validate_attachment_id("7KQ9A1X4MV2P8D6R").is_ok());
96 assert!(validate_attachment_id("short").is_err());
97 assert!(validate_attachment_id("7KQ9A1X4MV2P8D6U").is_err()); assert!(validate_attachment_id("7KQ9A1X4MV2P8D6r").is_err()); }
100
101 #[test]
102 fn validates_sha256() {
103 let valid = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789";
104 assert!(validate_sha256(valid).is_ok());
105 assert!(validate_sha256("short").is_err());
106 assert!(validate_sha256(&format!("{valid}g")).is_err());
107 assert!(validate_sha256(&valid.to_ascii_uppercase()).is_err());
108 }
109
110 #[test]
111 fn validates_media_type() {
112 assert!(validate_media_type("image/png").is_ok());
113 assert!(validate_media_type("image/jpeg").is_ok());
114 assert!(validate_media_type("image/gif").is_ok());
115 assert!(validate_media_type("image/webp").is_ok());
116 assert!(validate_media_type("image/svg+xml").is_err());
117 assert!(validate_media_type("").is_err());
118 }
119
120 #[test]
121 fn validates_blob_size() {
122 assert!(validate_blob_size(1).is_ok());
123 assert!(validate_blob_size(25 * 1024 * 1024).is_ok());
124 assert!(validate_blob_size(0).is_err());
125 assert!(validate_blob_size(25 * 1024 * 1024 + 1).is_err());
126 }
127
128 #[test]
129 fn validates_filename() {
130 assert!(validate_filename(Some("photo.png")).is_ok());
131 assert!(validate_filename(None).is_ok());
132 assert!(validate_filename(Some("")).is_err());
133 assert!(validate_filename(Some("a/photo.png")).is_err());
134 assert!(validate_filename(Some("a\\photo.png")).is_err());
135 let long = "a".repeat(256);
136 assert!(validate_filename(Some(&long)).is_err());
137 }
138
139 #[test]
140 fn validates_alt_text() {
141 assert!(validate_alt_text(Some("a photo of a cat")).is_ok());
142 assert!(validate_alt_text(None).is_ok());
143 assert!(validate_alt_text(Some("")).is_ok());
144 let long = "a".repeat(501);
145 assert!(validate_alt_text(Some(&long)).is_err());
146 assert!(validate_alt_text(Some("a\nb")).is_err());
147 }
148
149 #[test]
150 fn validates_dimensions() {
151 assert!(validate_dimensions(None, None).is_err());
152 assert!(validate_dimensions(Some(1920), Some(1080)).is_ok());
153 assert!(validate_dimensions(Some(0), Some(1080)).is_err());
154 assert!(validate_dimensions(Some(1920), None).is_err());
155 assert!(validate_dimensions(None, Some(1080)).is_err());
156 }
157}