fscrypt/
proxy_filename.rs1use anyhow::{anyhow, ensure, Error};
5use base64::engine::general_purpose::URL_SAFE_NO_PAD;
6use base64::Engine as _;
7use sha2::Digest;
8use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
9
10#[repr(C, packed)]
24#[derive(
25 Copy, Clone, Debug, Eq, PartialEq, FromBytes, Immutable, KnownLayout, IntoBytes, Unaligned,
26)]
27pub struct ProxyFilename {
28 pub hash_code: u64,
29 pub filename: [u8; 149],
30 pub sha256: [u8; 32],
31 len: usize,
41}
42
43const PROXY_FILENAME_MAX_SIZE: usize = 8 + 149 + 32;
45
46impl ProxyFilename {
47 pub fn new(hash_code: u64, raw_filename: &[u8]) -> Self {
48 let mut filename = [0u8; 149];
49 let mut sha256 = [0u8; 32];
50 let len = if raw_filename.len() <= filename.len() {
51 filename[..raw_filename.len()].copy_from_slice(raw_filename);
52 std::mem::size_of::<u64>() + raw_filename.len()
53 } else {
54 let len = filename.len();
55 filename.copy_from_slice(&raw_filename[..len]);
56 sha256 = sha2::Sha256::digest(&raw_filename[len..]).into();
57 PROXY_FILENAME_MAX_SIZE
58 };
59 Self { hash_code, filename, sha256, len }
60 }
61}
62
63impl Into<String> for ProxyFilename {
64 fn into(self) -> String {
65 URL_SAFE_NO_PAD.encode(&self.as_bytes()[..self.len])
66 }
67}
68
69impl TryFrom<&str> for ProxyFilename {
70 type Error = Error;
71 fn try_from(s: &str) -> Result<Self, Self::Error> {
72 let mut bytes = URL_SAFE_NO_PAD.decode(s).map_err(|_| anyhow!("Invalid proxy filename"))?;
73 ensure!(
74 (bytes.len() >= 8 && bytes.len() <= 8 + 149) || bytes.len() == PROXY_FILENAME_MAX_SIZE,
75 "Invalid proxy filename length {}",
76 bytes.len()
77 );
78 let len = bytes.len();
79 bytes.resize(std::mem::size_of::<ProxyFilename>(), 0);
80 let mut instance = Self::read_from_bytes(&bytes).unwrap();
81 instance.len = len;
82 Ok(instance)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 const PROXY_FILENAME_MAX_ENCODED_SIZE: usize = (PROXY_FILENAME_MAX_SIZE * 4).div_ceil(3);
92
93 #[test]
94 fn test_proxy_filename() {
95 let a = ProxyFilename::new(1, b"fo!$obar");
97 let encoded: String = a.into();
98 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
99 assert_eq!(a, b);
100
101 let a = ProxyFilename::new(1, b"foobar");
103 let encoded: String = a.into();
104 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
105 assert_eq!(a, b);
106
107 let a = ProxyFilename::new(1, &[0xff; 149]);
109 let encoded: String = a.into();
110 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
111 assert_eq!(a, b);
112 assert_eq!(encoded.len(), ((8 + 149) * 4usize).div_ceil(3));
114
115 let a = ProxyFilename::new(1, &[0; 150]);
118 let encoded: String = a.into();
119 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
120 assert_eq!(a, b);
121 assert_eq!(encoded.len(), PROXY_FILENAME_MAX_ENCODED_SIZE);
122
123 let a = ProxyFilename::new(1, &[b'a'; 255]);
125 let encoded: String = a.into();
126 let b: ProxyFilename = encoded.as_str().try_into().unwrap();
127 assert_eq!(a, b);
128 assert_eq!(encoded.len(), PROXY_FILENAME_MAX_ENCODED_SIZE);
129
130 assert!(ProxyFilename::try_from("$$dda123=").is_err());
132
133 assert_eq!(
134 URL_SAFE_NO_PAD.encode(&[0; PROXY_FILENAME_MAX_SIZE]).len(),
135 PROXY_FILENAME_MAX_ENCODED_SIZE
136 );
137
138 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 0]).as_str()).is_err());
142 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 7]).as_str()).is_err());
143 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 8]).as_str()).is_ok());
144 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 9]).as_str()).is_ok());
145 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 8 + 148]).as_str()).is_ok());
146 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 8 + 149]).as_str()).is_ok());
147 assert!(ProxyFilename::try_from(URL_SAFE_NO_PAD.encode(&[b'a'; 8 + 150]).as_str()).is_err());
148 assert!(ProxyFilename::try_from(
149 URL_SAFE_NO_PAD.encode(&[b'a'; PROXY_FILENAME_MAX_SIZE - 1]).as_str()
150 )
151 .is_err());
152 assert!(ProxyFilename::try_from(
153 URL_SAFE_NO_PAD.encode(&[b'a'; PROXY_FILENAME_MAX_SIZE]).as_str()
154 )
155 .is_ok());
156 assert!(ProxyFilename::try_from(
157 URL_SAFE_NO_PAD.encode(&[b'a'; PROXY_FILENAME_MAX_SIZE + 1]).as_str()
158 )
159 .is_err());
160 }
161}