Skip to content

Commit 9707d73

Browse files
zecakehjplatte
authored andcommitted
fix(indexeddb): Fix broken raw redacted state events
A fix for (de)serialization of redacted state events in Ruma made old events undeserializable. Bump the DB version. Signed-off-by: Kévin Commaille <[email protected]>
1 parent 5ae84a9 commit 9707d73

File tree

1 file changed

+140
-21
lines changed

1 file changed

+140
-21
lines changed

crates/matrix-sdk-indexeddb/src/state_store.rs

Lines changed: 140 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use ruma::{
4646
CanonicalJsonObject, EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, RoomVersionId, UserId,
4747
};
4848
use serde::{de::DeserializeOwned, Deserialize, Serialize};
49+
use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
4950
use tracing::{debug, warn};
5051
use wasm_bindgen::JsValue;
5152
use web_sys::IdbKeyRange;
@@ -109,7 +110,7 @@ impl From<IndexeddbStateStoreError> for StoreError {
109110
mod KEYS {
110111
// STORES
111112

112-
pub const CURRENT_DB_VERSION: f64 = 1.1;
113+
pub const CURRENT_DB_VERSION: f64 = 1.2;
113114
pub const CURRENT_META_DB_VERSION: f64 = 2.0;
114115

115116
pub const INTERNAL_STATE: &str = "matrix-sdk-state";
@@ -237,6 +238,74 @@ async fn backup(source: &IdbDatabase, meta: &IdbDatabase) -> Result<()> {
237238
Ok(())
238239
}
239240

241+
fn serialize_event(store_cipher: Option<&StoreCipher>, event: &impl Serialize) -> Result<JsValue> {
242+
Ok(match store_cipher {
243+
Some(cipher) => JsValue::from_serde(&cipher.encrypt_value_typed(event)?)?,
244+
None => JsValue::from_serde(event)?,
245+
})
246+
}
247+
248+
fn deserialize_event<T: DeserializeOwned>(
249+
store_cipher: Option<&StoreCipher>,
250+
event: JsValue,
251+
) -> Result<T> {
252+
match store_cipher {
253+
Some(cipher) => Ok(cipher.decrypt_value_typed(event.into_serde()?)?),
254+
None => Ok(event.into_serde()?),
255+
}
256+
}
257+
258+
async fn v1_2_fix_store(
259+
store: &IdbObjectStore<'_>,
260+
store_cipher: Option<&StoreCipher>,
261+
) -> Result<()> {
262+
fn maybe_fix_json(raw_json: &RawJsonValue) -> Result<Option<JsonValue>> {
263+
let json = raw_json.get();
264+
265+
if json.contains(r#""content":null"#) {
266+
let mut value: JsonValue = serde_json::from_str(json)?;
267+
if let Some(content) = value.get_mut("content") {
268+
if matches!(content, JsonValue::Null) {
269+
*content = JsonValue::Object(Default::default());
270+
return Ok(Some(value));
271+
}
272+
}
273+
}
274+
275+
Ok(None)
276+
}
277+
278+
let cursor = store.open_cursor()?.await?;
279+
280+
if let Some(cursor) = cursor {
281+
loop {
282+
let raw_json: Box<RawJsonValue> = deserialize_event(store_cipher, cursor.value())?;
283+
284+
if let Some(fixed_json) = maybe_fix_json(&raw_json)? {
285+
cursor.update(&serialize_event(store_cipher, &fixed_json)?)?.await?;
286+
}
287+
288+
if !cursor.continue_cursor()?.await? {
289+
break;
290+
}
291+
}
292+
}
293+
294+
Ok(())
295+
}
296+
297+
async fn migrate_to_v1_2(db: &IdbDatabase, store_cipher: Option<&StoreCipher>) -> Result<()> {
298+
let tx = db.transaction_on_multi_with_mode(
299+
&[KEYS::ROOM_STATE, KEYS::ROOM_INFOS],
300+
IdbTransactionMode::Readwrite,
301+
)?;
302+
303+
v1_2_fix_store(&tx.object_store(KEYS::ROOM_STATE)?, store_cipher).await?;
304+
v1_2_fix_store(&tx.object_store(KEYS::ROOM_INFOS)?, store_cipher).await?;
305+
306+
tx.await.into_result().map_err(|e| e.into())
307+
}
308+
240309
#[derive(Builder, Debug, PartialEq, Eq)]
241310
#[builder(name = "IndexeddbStateStoreBuilder", build_fn(skip))]
242311
pub struct IndexeddbStateStoreBuilderConfig {
@@ -311,7 +380,8 @@ impl IndexeddbStateStoreBuilder {
311380
None
312381
};
313382

314-
let recreate_stores = {
383+
let mut recreate_stores = false;
384+
{
315385
// checkup up in a separate call, whether we have to backup or do anything else
316386
// to the db. Unfortunately the set_on_upgrade_needed doesn't allow async fn
317387
// which we need to execute the backup.
@@ -337,15 +407,16 @@ impl IndexeddbStateStoreBuilder {
337407
let old_version = pre_db.version();
338408

339409
if created.load(Ordering::Relaxed) {
340-
// this is a fresh DB, return
341-
false
410+
// this is a fresh DB, nothing to do
342411
} else if old_version == 1.0 && has_store_cipher {
343412
match migration_strategy {
344413
MigrationConflictStrategy::BackupAndDrop => {
345414
backup(&pre_db, &meta_db).await?;
346-
true
415+
recreate_stores = true;
416+
}
417+
MigrationConflictStrategy::Drop => {
418+
recreate_stores = true;
347419
}
348-
MigrationConflictStrategy::Drop => true,
349420
MigrationConflictStrategy::Raise => {
350421
return Err(IndexeddbStateStoreError::MigrationConflict {
351422
name,
@@ -354,11 +425,12 @@ impl IndexeddbStateStoreBuilder {
354425
})
355426
}
356427
}
428+
} else if old_version < 1.2 {
429+
migrate_to_v1_2(&pre_db, store_cipher.as_deref()).await?;
357430
} else {
358431
// Nothing to be done
359-
false
360432
}
361-
};
433+
}
362434

363435
let mut db_req: OpenDbRequest = IdbDatabase::open_f64(&name, KEYS::CURRENT_DB_VERSION)?;
364436
db_req.set_on_upgrade_needed(Some(
@@ -421,17 +493,11 @@ impl IndexeddbStateStore {
421493
}
422494

423495
fn serialize_event(&self, event: &impl Serialize) -> Result<JsValue> {
424-
Ok(match &self.store_cipher {
425-
Some(cipher) => JsValue::from_serde(&cipher.encrypt_value_typed(event)?)?,
426-
None => JsValue::from_serde(event)?,
427-
})
496+
serialize_event(self.store_cipher.as_deref(), event)
428497
}
429498

430499
fn deserialize_event<T: DeserializeOwned>(&self, event: JsValue) -> Result<T> {
431-
match &self.store_cipher {
432-
Some(cipher) => Ok(cipher.decrypt_value_typed(event.into_serde()?)?),
433-
None => Ok(event.into_serde()?),
434-
}
500+
deserialize_event(self.store_cipher.as_deref(), event)
435501
}
436502

437503
fn encode_key<T>(&self, table_name: &str, key: T) -> JsValue
@@ -1451,15 +1517,21 @@ mod migration_tests {
14511517

14521518
use indexed_db_futures::prelude::*;
14531519
use matrix_sdk_test::async_test;
1520+
use ruma::{
1521+
events::{AnySyncStateEvent, StateEventType},
1522+
room_id,
1523+
};
1524+
use serde_json::json;
14541525
use uuid::Uuid;
14551526
use wasm_bindgen::JsValue;
14561527

14571528
use super::{
1458-
IndexeddbStateStore, IndexeddbStateStoreError, MigrationConflictStrategy, Result,
1459-
ALL_STORES,
1529+
serialize_event, IndexeddbStateStore, IndexeddbStateStoreError, MigrationConflictStrategy,
1530+
Result, ALL_STORES, KEYS,
14601531
};
1532+
use crate::safe_encode::SafeEncode;
14611533

1462-
pub async fn create_fake_db(name: &str, version: f64) -> Result<()> {
1534+
pub async fn create_fake_db(name: &str, version: f64) -> Result<IdbDatabase> {
14631535
let mut db_req: OpenDbRequest = IdbDatabase::open_f64(name, version)?;
14641536
db_req.set_on_upgrade_needed(Some(
14651537
move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
@@ -1471,8 +1543,7 @@ mod migration_tests {
14711543
Ok(())
14721544
},
14731545
));
1474-
db_req.into_future().await?;
1475-
Ok(())
1546+
db_req.into_future().await.map_err(Into::into)
14761547
}
14771548

14781549
#[async_test]
@@ -1565,4 +1636,52 @@ mod migration_tests {
15651636
}
15661637
Ok(())
15671638
}
1639+
1640+
#[async_test]
1641+
pub async fn test_migrating_to_v1_2() -> Result<()> {
1642+
let name = format!("migrating-1.2-{}", Uuid::new_v4().as_hyphenated().to_string());
1643+
// An event that fails to deserialize.
1644+
let wrong_redacted_state_event = json!({
1645+
"content": null,
1646+
"event_id": "$wrongevent",
1647+
"origin_server_ts": 1673887516047_u64,
1648+
"sender": "@example:localhost",
1649+
"state_key": "",
1650+
"type": "m.room.topic",
1651+
"unsigned": {
1652+
"redacted_because": {
1653+
"type": "m.room.redaction",
1654+
"sender": "@example:localhost",
1655+
"content": {},
1656+
"redacts": "$wrongevent",
1657+
"origin_server_ts": 1673893816047_u64,
1658+
"unsigned": {},
1659+
"event_id": "$redactionevent",
1660+
},
1661+
},
1662+
});
1663+
serde_json::from_value::<AnySyncStateEvent>(wrong_redacted_state_event.clone())
1664+
.unwrap_err();
1665+
1666+
let room_id = room_id!("!some_room:localhost");
1667+
1668+
// Populate DB with wrong event.
1669+
{
1670+
let db = create_fake_db(&name, 1.1).await?;
1671+
let tx =
1672+
db.transaction_on_one_with_mode(KEYS::ROOM_STATE, IdbTransactionMode::Readwrite)?;
1673+
let state = tx.object_store(KEYS::ROOM_STATE)?;
1674+
let key = (room_id, StateEventType::RoomTopic, "").encode();
1675+
state.put_key_val(&key, &serialize_event(None, &wrong_redacted_state_event)?)?;
1676+
tx.await.into_result()?;
1677+
}
1678+
1679+
// this transparently migrates to the latest version
1680+
let store = IndexeddbStateStore::builder().name(name).build().await?;
1681+
let event =
1682+
store.get_state_event(room_id, StateEventType::RoomTopic, "").await.unwrap().unwrap();
1683+
event.deserialize().unwrap();
1684+
1685+
Ok(())
1686+
}
15681687
}

0 commit comments

Comments
 (0)