bitwarden_encoding/
b64.rs1use std::str::FromStr;
2
3use data_encoding::BASE64;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use crate::{FromStrVisitor, chunked::chunked_encode};
8
9#[cfg(feature = "wasm")]
13#[derive(Debug, Serialize, Clone, Hash, PartialEq, Eq)]
14#[serde(into = "String")]
15#[bitwarden_ffi::wasm_record]
16pub struct B64(#[tsify(type = "string")] Vec<u8>);
17
18#[cfg(not(feature = "wasm"))]
22#[derive(Debug, Serialize, Clone, Hash, PartialEq, Eq)]
23#[serde(into = "String")]
24pub struct B64(Vec<u8>);
25
26impl B64 {
27 pub fn as_bytes(&self) -> &[u8] {
29 &self.0
30 }
31
32 pub fn into_bytes(self) -> Vec<u8> {
34 self.0
35 }
36}
37
38impl<'de> Deserialize<'de> for B64 {
40 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
41 where
42 D: serde::Deserializer<'de>,
43 {
44 deserializer.deserialize_str(FromStrVisitor::new())
45 }
46}
47
48impl From<Vec<u8>> for B64 {
49 fn from(src: Vec<u8>) -> Self {
50 Self(src)
51 }
52}
53impl From<&[u8]> for B64 {
54 fn from(src: &[u8]) -> Self {
55 Self(src.to_vec())
56 }
57}
58
59impl From<B64> for Vec<u8> {
60 fn from(src: B64) -> Self {
61 src.0
62 }
63}
64
65impl From<B64> for String {
66 fn from(src: B64) -> Self {
67 String::from(&src)
68 }
69}
70
71impl From<&B64> for String {
72 fn from(src: &B64) -> Self {
73 chunked_encode(&BASE64, &src.0)
74 }
75}
76
77impl std::fmt::Display for B64 {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 f.write_str(String::from(self).as_str())
80 }
81}
82
83#[derive(Debug, Error)]
85#[error("Data isn't base64 encoded")]
86pub struct NotB64EncodedError;
87
88const BASE64_PERMISSIVE: data_encoding::Encoding = data_encoding_macro::new_encoding! {
89 symbols: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
90 padding: None,
91 check_trailing_bits: false,
92};
93const BASE64_PADDING: &str = "=";
94
95impl TryFrom<String> for B64 {
96 type Error = NotB64EncodedError;
97
98 fn try_from(value: String) -> Result<Self, Self::Error> {
99 Self::try_from(value.as_str())
100 }
101}
102
103impl TryFrom<&str> for B64 {
104 type Error = NotB64EncodedError;
105
106 fn try_from(value: &str) -> Result<Self, Self::Error> {
107 let sane_string = value.trim_end_matches(BASE64_PADDING);
108 BASE64_PERMISSIVE
109 .decode(sane_string.as_bytes())
110 .map(Self)
111 .map_err(|_| NotB64EncodedError)
112 }
113}
114
115impl FromStr for B64 {
116 type Err = NotB64EncodedError;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 Self::try_from(s)
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_b64_from_vec() {
129 let data = vec![72, 101, 108, 108, 111];
130 let b64 = B64::from(data.clone());
131 assert_eq!(Vec::<u8>::from(b64), data);
132 }
133
134 #[test]
135 fn test_b64_from_slice() {
136 let data = b"Hello";
137 let b64 = B64::from(data.as_slice());
138 assert_eq!(b64.as_bytes(), data);
139 }
140
141 #[test]
142 fn test_b64_encoding_with_padding() {
143 let data = b"Hello, World!";
144 let b64 = B64::from(data.as_slice());
145 let encoded = String::from(&b64);
146 assert_eq!(encoded, "SGVsbG8sIFdvcmxkIQ==");
147 assert!(encoded.contains('='));
148 }
149
150 #[test]
151 fn test_b64_decoding_with_padding() {
152 let encoded_with_padding = "SGVsbG8sIFdvcmxkIQ==";
153 let b64 = B64::try_from(encoded_with_padding).unwrap();
154 assert_eq!(b64.as_bytes(), b"Hello, World!");
155 }
156
157 #[test]
158 fn test_b64_decoding_without_padding() {
159 let encoded_without_padding = "SGVsbG8sIFdvcmxkIQ";
160 let b64 = B64::try_from(encoded_without_padding).unwrap();
161 assert_eq!(b64.as_bytes(), b"Hello, World!");
162 }
163
164 #[test]
165 fn test_b64_round_trip_with_padding() {
166 let original = b"Test data that requires padding!";
167 let b64 = B64::from(original.as_slice());
168 let encoded = String::from(&b64);
169 let decoded = B64::try_from(encoded.as_str()).unwrap();
170 assert_eq!(decoded.as_bytes(), original);
171 }
172
173 #[test]
174 fn test_b64_round_trip_without_padding() {
175 let original = b"Test data";
176 let b64 = B64::from(original.as_slice());
177 let encoded = String::from(&b64);
178 let decoded = B64::try_from(encoded.as_str()).unwrap();
179 assert_eq!(decoded.as_bytes(), original);
180 }
181
182 #[test]
183 fn test_b64_display() {
184 let data = b"Hello";
185 let b64 = B64::from(data.as_slice());
186 assert_eq!(b64.to_string(), "SGVsbG8=");
187 }
188
189 #[test]
190 fn test_b64_invalid_encoding() {
191 let invalid_b64 = "This is not base64!@#$";
192 let result = B64::try_from(invalid_b64);
193 assert!(result.is_err());
194 }
195
196 #[test]
197 fn test_b64_empty_string() {
198 let empty = "";
199 let b64 = B64::try_from(empty).unwrap();
200 assert_eq!(b64.as_bytes().len(), 0);
201 }
202
203 #[test]
204 fn test_b64_padding_removal() {
205 let encoded_with_padding = "SGVsbG8sIFdvcmxkIQ==";
206 let b64 = B64::try_from(encoded_with_padding).unwrap();
207 assert_eq!(b64.as_bytes(), b"Hello, World!");
208 }
209
210 #[test]
211 fn test_b64_serialization() {
212 let data = b"serialization test";
213 let b64 = B64::from(data.as_slice());
214
215 let serialized = serde_json::to_string(&b64).unwrap();
216 assert_eq!(serialized, "\"c2VyaWFsaXphdGlvbiB0ZXN0\"");
217
218 let deserialized: B64 = serde_json::from_str(&serialized).unwrap();
219 assert_eq!(b64.as_bytes(), deserialized.as_bytes());
220 }
221
222 #[test]
223 fn test_not_b64_encoded_error_display() {
224 let error = NotB64EncodedError;
225 assert_eq!(error.to_string(), "Data isn't base64 encoded");
226 }
227
228 #[test]
229 fn test_b64_from_str() {
230 let encoded = "SGVsbG8sIFdvcmxkIQ==";
231 let b64: B64 = encoded.parse().unwrap();
232 assert_eq!(b64.as_bytes(), b"Hello, World!");
233 }
234
235 #[test]
236 fn test_b64_eq_and_hash() {
237 let data1 = b"test data";
238 let data2 = b"test data";
239 let data3 = b"different data";
240
241 let b64_1 = B64::from(data1.as_slice());
242 let b64_2 = B64::from(data2.as_slice());
243 let b64_3 = B64::from(data3.as_slice());
244
245 assert_eq!(b64_1, b64_2);
246 assert_ne!(b64_1, b64_3);
247
248 use std::{
249 collections::hash_map::DefaultHasher,
250 hash::{Hash, Hasher},
251 };
252
253 let mut hasher1 = DefaultHasher::new();
254 let mut hasher2 = DefaultHasher::new();
255
256 b64_1.hash(&mut hasher1);
257 b64_2.hash(&mut hasher2);
258
259 assert_eq!(hasher1.finish(), hasher2.finish());
260 }
261}