Skip to main content

matrix_sdk_indexeddb/serializer/safe_encode/
traits.rs

1//! Helpers for wasm32/browser environments
2
3use base64::{
4    Engine, alphabet,
5    engine::{GeneralPurpose, general_purpose},
6};
7use indexed_db_futures::KeyRange;
8use matrix_sdk_store_encryption::StoreCipher;
9use ruma::{
10    DeviceId, EventId, MxcUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, TransactionId,
11    UserId,
12    events::{
13        GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, receipt::ReceiptType,
14    },
15};
16use wasm_bindgen::JsValue;
17
18/// ASCII Group Separator, for elements in the keys
19pub const KEY_SEPARATOR: &str = "\u{001D}";
20/// ASCII Record Separator is sure smaller than the Key Separator but smaller
21/// than regular characters
22pub const RANGE_END: &str = "\u{001E}";
23/// Using the literal escape character to escape KEY_SEPARATOR in regular keys
24/// (though super unlikely)
25pub const ESCAPED: &str = "\u{001E}\u{001D}";
26
27const STANDARD_NO_PAD: GeneralPurpose =
28    GeneralPurpose::new(&alphabet::STANDARD, general_purpose::NO_PAD);
29
30/// Encode value as String/JsValue/IdbKeyRange for the JS APIs in a
31/// safe, escaped manner.
32///
33/// Primary use is as a helper to escape potentially harmful opaque strings
34/// from UserId, RoomId, etc into keys that can be used (also for ranges)
35/// with the IndexedDB.
36pub trait SafeEncode {
37    /// Encode into a safe, escaped String
38    ///
39    /// It's the implementors responsibility to provide an encoded, safe
40    /// string where `KEY_SEPARATOR` is escaped with the `ESCAPED`.
41    /// The result will not be escaped again.
42    fn as_encoded_string(&self) -> String;
43
44    /// encode self securely for the given tablename with the given
45    /// `store_cipher` hash_key, returns the value as a base64 encoded
46    /// string without any padding.
47    fn as_secure_string(&self, table_name: &str, store_cipher: &StoreCipher) -> String {
48        STANDARD_NO_PAD
49            .encode(store_cipher.hash_key(table_name, self.as_encoded_string().as_bytes()))
50    }
51
52    /// Encode self into a IdbKeyRange for searching all keys that are
53    /// prefixed with this key, followed by `KEY_SEPARATOR`. Internally
54    /// uses `as_encoded_string` to ensure the given key is escaped properly.
55    fn encode_to_range(&self) -> KeyRange<JsValue> {
56        let key = self.as_encoded_string();
57        KeyRange::Bound(
58            JsValue::from([&key, KEY_SEPARATOR].concat()),
59            false,
60            JsValue::from([&key, RANGE_END].concat()),
61            false,
62        )
63    }
64
65    fn encode_to_range_secure(
66        &self,
67        table_name: &str,
68        store_cipher: &StoreCipher,
69    ) -> KeyRange<JsValue> {
70        let key = self.as_secure_string(table_name, store_cipher);
71        KeyRange::Bound(
72            JsValue::from([&key, KEY_SEPARATOR].concat()),
73            false,
74            JsValue::from([&key, RANGE_END].concat()),
75            false,
76        )
77    }
78}
79
80/// Implement SafeEncode for tuple of two elements, separating the escaped
81/// values with with `KEY_SEPARATOR`.
82impl<A, B> SafeEncode for (A, B)
83where
84    A: SafeEncode,
85    B: SafeEncode,
86{
87    fn as_encoded_string(&self) -> String {
88        [&self.0.as_encoded_string(), KEY_SEPARATOR, &self.1.as_encoded_string()].concat()
89    }
90
91    fn as_secure_string(&self, table_name: &str, store_cipher: &StoreCipher) -> String {
92        [
93            &STANDARD_NO_PAD
94                .encode(store_cipher.hash_key(table_name, self.0.as_encoded_string().as_bytes())),
95            KEY_SEPARATOR,
96            &STANDARD_NO_PAD
97                .encode(store_cipher.hash_key(table_name, self.1.as_encoded_string().as_bytes())),
98        ]
99        .concat()
100    }
101}
102
103/// Implement SafeEncode for tuple of three elements, separating the escaped
104/// values with with `KEY_SEPARATOR`.
105impl<A, B, C> SafeEncode for (A, B, C)
106where
107    A: SafeEncode,
108    B: SafeEncode,
109    C: SafeEncode,
110{
111    fn as_encoded_string(&self) -> String {
112        [
113            &self.0.as_encoded_string(),
114            KEY_SEPARATOR,
115            &self.1.as_encoded_string(),
116            KEY_SEPARATOR,
117            &self.2.as_encoded_string(),
118        ]
119        .concat()
120    }
121
122    fn as_secure_string(&self, table_name: &str, store_cipher: &StoreCipher) -> String {
123        [
124            &STANDARD_NO_PAD
125                .encode(store_cipher.hash_key(table_name, self.0.as_encoded_string().as_bytes())),
126            KEY_SEPARATOR,
127            &STANDARD_NO_PAD
128                .encode(store_cipher.hash_key(table_name, self.1.as_encoded_string().as_bytes())),
129            KEY_SEPARATOR,
130            &STANDARD_NO_PAD
131                .encode(store_cipher.hash_key(table_name, self.2.as_encoded_string().as_bytes())),
132        ]
133        .concat()
134    }
135}
136
137/// Implement SafeEncode for tuple of four elements, separating the escaped
138/// values with with `KEY_SEPARATOR`.
139impl<A, B, C, D> SafeEncode for (A, B, C, D)
140where
141    A: SafeEncode,
142    B: SafeEncode,
143    C: SafeEncode,
144    D: SafeEncode,
145{
146    fn as_encoded_string(&self) -> String {
147        [
148            &self.0.as_encoded_string(),
149            KEY_SEPARATOR,
150            &self.1.as_encoded_string(),
151            KEY_SEPARATOR,
152            &self.2.as_encoded_string(),
153            KEY_SEPARATOR,
154            &self.3.as_encoded_string(),
155        ]
156        .concat()
157    }
158
159    fn as_secure_string(&self, table_name: &str, store_cipher: &StoreCipher) -> String {
160        [
161            &STANDARD_NO_PAD
162                .encode(store_cipher.hash_key(table_name, self.0.as_encoded_string().as_bytes())),
163            KEY_SEPARATOR,
164            &STANDARD_NO_PAD
165                .encode(store_cipher.hash_key(table_name, self.1.as_encoded_string().as_bytes())),
166            KEY_SEPARATOR,
167            &STANDARD_NO_PAD
168                .encode(store_cipher.hash_key(table_name, self.2.as_encoded_string().as_bytes())),
169            KEY_SEPARATOR,
170            &STANDARD_NO_PAD
171                .encode(store_cipher.hash_key(table_name, self.3.as_encoded_string().as_bytes())),
172        ]
173        .concat()
174    }
175}
176
177/// Implement SafeEncode for tuple of five elements, separating the escaped
178/// values with with `KEY_SEPARATOR`.
179impl<A, B, C, D, E> SafeEncode for (A, B, C, D, E)
180where
181    A: SafeEncode,
182    B: SafeEncode,
183    C: SafeEncode,
184    D: SafeEncode,
185    E: SafeEncode,
186{
187    fn as_encoded_string(&self) -> String {
188        [
189            &self.0.as_encoded_string(),
190            KEY_SEPARATOR,
191            &self.1.as_encoded_string(),
192            KEY_SEPARATOR,
193            &self.2.as_encoded_string(),
194            KEY_SEPARATOR,
195            &self.3.as_encoded_string(),
196            KEY_SEPARATOR,
197            &self.4.as_encoded_string(),
198        ]
199        .concat()
200    }
201
202    fn as_secure_string(&self, table_name: &str, store_cipher: &StoreCipher) -> String {
203        [
204            &STANDARD_NO_PAD
205                .encode(store_cipher.hash_key(table_name, self.0.as_encoded_string().as_bytes())),
206            KEY_SEPARATOR,
207            &STANDARD_NO_PAD
208                .encode(store_cipher.hash_key(table_name, self.1.as_encoded_string().as_bytes())),
209            KEY_SEPARATOR,
210            &STANDARD_NO_PAD
211                .encode(store_cipher.hash_key(table_name, self.2.as_encoded_string().as_bytes())),
212            KEY_SEPARATOR,
213            &STANDARD_NO_PAD
214                .encode(store_cipher.hash_key(table_name, self.3.as_encoded_string().as_bytes())),
215            KEY_SEPARATOR,
216            &STANDARD_NO_PAD
217                .encode(store_cipher.hash_key(table_name, self.4.as_encoded_string().as_bytes())),
218        ]
219        .concat()
220    }
221}
222
223impl SafeEncode for String {
224    fn as_encoded_string(&self) -> String {
225        self.replace(KEY_SEPARATOR, ESCAPED)
226    }
227}
228
229impl SafeEncode for str {
230    fn as_encoded_string(&self) -> String {
231        self.replace(KEY_SEPARATOR, ESCAPED)
232    }
233}
234
235impl<T: SafeEncode + ?Sized> SafeEncode for &T {
236    fn as_encoded_string(&self) -> String {
237        (*self).as_encoded_string()
238    }
239}
240
241impl SafeEncode for TransactionId {
242    fn as_encoded_string(&self) -> String {
243        self.to_string().as_encoded_string()
244    }
245}
246
247impl SafeEncode for GlobalAccountDataEventType {
248    fn as_encoded_string(&self) -> String {
249        self.to_string().as_encoded_string()
250    }
251}
252
253impl SafeEncode for RoomAccountDataEventType {
254    fn as_encoded_string(&self) -> String {
255        self.to_string().as_encoded_string()
256    }
257}
258
259impl SafeEncode for StateEventType {
260    fn as_encoded_string(&self) -> String {
261        self.to_string().as_encoded_string()
262    }
263}
264
265impl SafeEncode for ReceiptType {
266    fn as_encoded_string(&self) -> String {
267        self.as_str().as_encoded_string()
268    }
269}
270
271impl SafeEncode for RoomId {
272    fn as_encoded_string(&self) -> String {
273        self.as_str().as_encoded_string()
274    }
275}
276
277impl SafeEncode for OwnedRoomId {
278    fn as_encoded_string(&self) -> String {
279        self.as_str().as_encoded_string()
280    }
281}
282
283impl SafeEncode for UserId {
284    fn as_encoded_string(&self) -> String {
285        self.as_str().as_encoded_string()
286    }
287}
288
289impl SafeEncode for OwnedUserId {
290    fn as_encoded_string(&self) -> String {
291        self.as_str().as_encoded_string()
292    }
293}
294
295impl SafeEncode for DeviceId {
296    fn as_encoded_string(&self) -> String {
297        self.as_str().as_encoded_string()
298    }
299}
300
301impl SafeEncode for EventId {
302    fn as_encoded_string(&self) -> String {
303        self.as_str().as_encoded_string()
304    }
305}
306
307impl SafeEncode for OwnedEventId {
308    fn as_encoded_string(&self) -> String {
309        self.as_str().as_encoded_string()
310    }
311}
312
313impl SafeEncode for MxcUri {
314    fn as_encoded_string(&self) -> String {
315        self.as_str().as_encoded_string()
316    }
317}
318
319impl SafeEncode for usize {
320    fn as_encoded_string(&self) -> String {
321        self.to_string()
322    }
323
324    fn as_secure_string(&self, _table_name: &str, _store_cipher: &StoreCipher) -> String {
325        self.to_string()
326    }
327}