fscrypt/
proxy_filename.rs1use anyhow::{Error, anyhow, ensure};
5use base64::Engine as _;
6use base64::engine::general_purpose::URL_SAFE_NO_PAD;
7use sha2::Digest;
8use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
9
10use crate::direntry;
11
12const MAX_FILENAME_LEN: usize = 149;
13
14#[repr(C, packed)]
28#[derive(
29 Copy, Clone, Debug, Eq, PartialEq, FromBytes, Immutable, KnownLayout, IntoBytes, Unaligned,
30)]
31pub struct ProxyFilename {
32 pub hash_code: u64,
33 pub filename: [u8; MAX_FILENAME_LEN],
34 pub sha256: [u8; 32],
35 len: usize,
45}
46
47const PROXY_FILENAME_MAX_SIZE: usize = 8 + 149 + 32;
49
50impl Default for ProxyFilename {
51 fn default() -> Self {
52 Self { hash_code: 0, filename: [0u8; 149], sha256: [0u8; 32], len: 0 }
53 }
54}
55
56impl ProxyFilename {
57 pub fn new(raw_filename: &[u8]) -> Self {
61 Self::new_with_hash_code(Self::compute_hash_code(raw_filename), raw_filename)
62 }
63
64 pub fn new_with_hash_code(hash_code: u64, raw_filename: &[u8]) -> Self {
67 let mut filename = [0u8; 149];
68 let mut sha256 = [0u8; 32];
69 let len = if raw_filename.len() <= filename.len() {
70 filename[..raw_filename.len()].copy_from_slice(raw_filename);
71 std::mem::size_of::<u64>() + raw_filename.len()
72 } else {
73 let len = filename.len();
74 filename.copy_from_slice(&raw_filename[..len]);
75 sha256 = Self::compute_sha256(raw_filename).into();
76 PROXY_FILENAME_MAX_SIZE
77 };
78 Self { hash_code, filename, sha256, len }
79 }
80
81 pub fn raw_filename(&self) -> &[u8] {
83 if self.len == PROXY_FILENAME_MAX_SIZE {
84 &self.filename
85 } else {
86 &self.filename[..self.len - std::mem::size_of::<u64>()]
87 }
88 }
89
90 pub fn compute_hash_code(raw_filename: &[u8]) -> u64 {
92 direntry::tea_hash_filename(raw_filename) as u64
93 }
94
95 pub fn compute_sha256(raw_filename: &[u8]) -> [u8; 32] {
102 sha2::Sha256::digest(&raw_filename[MAX_FILENAME_LEN..]).into()
103 }
104
105 pub fn is_truncated(&self) -> bool {
108 self.len == PROXY_FILENAME_MAX_SIZE
109 }
110}
111
112impl Into<String> for ProxyFilename {
113 fn into(self) -> String {
114 URL_SAFE_NO_PAD.encode(&self.as_bytes()[..self.len])
115 }
116}
117
118impl TryFrom<&str> for ProxyFilename {
119 type Error = Error;
120 fn try_from(s: &str) -> Result<Self, Self::Error> {
121 let mut bytes = URL_SAFE_NO_PAD.decode(s).map_err(|_| anyhow!("Invalid proxy filename"))?;
122 ensure!(
123 (bytes.len() >= 8 && bytes.len() <= 8 + 149) || bytes.len() == PROXY_FILENAME_MAX_SIZE,
124 "Invalid proxy filename length {}",
125 bytes.len()
126 );
127 let len = bytes.len();
128 bytes.resize(std::mem::size_of::<ProxyFilename>(), 0);
129 let mut instance = Self::read_from_bytes(&bytes).unwrap();
130 instance.len = len;
131 Ok(instance)
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 const PROXY_FILENAME_MAX_ENCODED_SIZE: usize = (PROXY_FILENAME_MAX_SIZE * 4).div_ceil(3);
141
142 #[test]
143 fn test_proxy_filename() {
144 let a = ProxyFilename::new(b"fo!$obar");
146 let encoded: String = a.into();
147 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
148 assert_eq!(a, b);
149
150 let a = ProxyFilename::new(b"foobar");
152 let encoded: String = a.into();
153 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
154 assert_eq!(a, b);
155
156 let a = ProxyFilename::new(&[0xff; 149]);
158 let encoded: String = a.into();
159 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
160 assert_eq!(a, b);
161 assert_eq!(encoded.len(), ((8 + 149) * 4usize).div_ceil(3));
163
164 let a = ProxyFilename::new(&[0; 150]);
167 let encoded: String = a.into();
168 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
169 assert_eq!(a, b);
170 assert_eq!(encoded.len(), PROXY_FILENAME_MAX_ENCODED_SIZE);
171
172 let a = ProxyFilename::new(&[b'a'; 255]);
174 let encoded: String = a.into();
175 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
176 assert_eq!(a, b);
177 assert_eq!(encoded.len(), PROXY_FILENAME_MAX_ENCODED_SIZE);
178
179 let a = ProxyFilename::new_with_hash_code(1, &[b'a'; 255]);
181 let encoded: String = a.into();
182 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
183 assert_eq!(a, b);
184 assert_eq!(encoded.len(), PROXY_FILENAME_MAX_ENCODED_SIZE);
185
186 assert!(ProxyFilename::try_from("$$dda123=").is_err());
188
189 assert_eq!(
190 URL_SAFE_NO_PAD.encode(&[0; PROXY_FILENAME_MAX_SIZE]).len(),
191 PROXY_FILENAME_MAX_ENCODED_SIZE
192 );
193
194 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 0]).as_str()).is_err());
198 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 7]).as_str()).is_err());
199 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 8]).as_str()).is_ok());
200 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 9]).as_str()).is_ok());
201 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 8 + 148]).as_str()).is_ok());
202 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 8 + 149]).as_str()).is_ok());
203 assert!(
204 ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 8 + 150]).as_str()).is_err()
205 );
206 assert!(
207 ProxyFilename::try_from(
208 URL_SAFE_NO_PAD.encode(&[b'a'; PROXY_FILENAME_MAX_SIZE - 1]).as_str()
209 )
210 .is_err()
211 );
212 assert!(
213 ProxyFilename::try_from(
214 URL_SAFE_NO_PAD.encode(&[b'a'; PROXY_FILENAME_MAX_SIZE]).as_str()
215 )
216 .is_ok()
217 );
218 assert!(
219 ProxyFilename::try_from(
220 URL_SAFE_NO_PAD.encode(&[b'a'; PROXY_FILENAME_MAX_SIZE + 1]).as_str()
221 )
222 .is_err()
223 );
224 }
225}