matrix_sdk_indexeddb/
safe_encode.rs

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