diff --git a/src/client_server.rs b/src/client_server.rs index b054204..06748af 100644 --- a/src/client_server.rs +++ b/src/client_server.rs @@ -4,12 +4,12 @@ use std::{ time::{Duration, SystemTime}, }; -use crate::{utils, Database, MatrixResult, Ruma}; +use crate::{utils, ConduitResult, Database, Error, Ruma}; use log::{debug, warn}; use rocket::{delete, get, options, post, put, State}; use ruma::{ api::client::{ - error::{Error, ErrorKind}, + error::ErrorKind, r0::{ account::{get_username_availability, register}, alias::{create_alias, delete_alias, get_alias}, @@ -53,7 +53,10 @@ use ruma::{ }, events::{ collections::only::Event as EduEvent, - room::{canonical_alias, guest_access, history_visibility, join_rules, member, redaction}, + room::{ + canonical_alias, guest_access, history_visibility, join_rules, member, name, redaction, + topic, + }, EventJson, EventType, }, identifiers::{RoomAliasId, RoomId, RoomVersionId, UserId}, @@ -67,66 +70,58 @@ const MXC_LENGTH: usize = 256; const SESSION_ID_LENGTH: usize = 256; #[get("/_matrix/client/versions")] -pub fn get_supported_versions_route() -> MatrixResult { - MatrixResult(Ok(get_supported_versions::Response { +pub fn get_supported_versions_route() -> ConduitResult { + Ok(get_supported_versions::Response { versions: vec!["r0.5.0".to_owned(), "r0.6.0".to_owned()], unstable_features: BTreeMap::new(), - })) + } + .into()) } #[get("/_matrix/client/r0/register/available", data = "")] pub fn get_register_available_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { // Validate user id - let user_id = - match UserId::parse_with_server_name(body.username.clone(), db.globals.server_name()) - .ok() - .filter(|user_id| !user_id.is_historical()) - { - None => { - debug!("Username invalid"); - return MatrixResult(Err(Error { - kind: ErrorKind::InvalidUsername, - message: "Username was invalid.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); - } - Some(user_id) => user_id, - }; + let user_id = UserId::parse_with_server_name(body.username.clone(), db.globals.server_name()) + .ok() + .filter(|user_id| { + !user_id.is_historical() && user_id.server_name() == db.globals.server_name() + }) + .ok_or(Error::BadRequest( + ErrorKind::InvalidUsername, + "Username is invalid.", + ))?; // Check if username is creative enough - if db.users.exists(&user_id).unwrap() { - debug!("ID already taken"); - return MatrixResult(Err(Error { - kind: ErrorKind::UserInUse, - message: "Desired user ID is already taken.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); + if db.users.exists(&user_id)? { + return Err(Error::BadRequest( + ErrorKind::UserInUse, + "Desired user ID is already taken.", + )); } // TODO add check for appservice namespaces // If no if check is true we have an username that's available to be used. - MatrixResult(Ok(get_username_availability::Response { available: true })) + Ok(get_username_availability::Response { available: true }.into()) } #[post("/_matrix/client/r0/register", data = "")] pub fn register_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { if db.globals.registration_disabled() { - return MatrixResult(Err(UiaaResponse::MatrixError(Error { - kind: ErrorKind::Unknown, - message: "Registration has been disabled.".to_owned(), - status_code: http::StatusCode::FORBIDDEN, - }))); + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "Registration has been disabled.", + )); } // Validate user id - let user_id = match UserId::parse_with_server_name( + let user_id = UserId::parse_with_server_name( body.username .clone() .unwrap_or_else(|| utils::random_string(GUEST_NAME_LENGTH)) @@ -134,65 +129,55 @@ pub fn register_route( db.globals.server_name(), ) .ok() - .filter(|user_id| !user_id.is_historical()) - { - None => { - debug!("Username invalid"); - return MatrixResult(Err(UiaaResponse::MatrixError(Error { - kind: ErrorKind::InvalidUsername, - message: "Username was invalid.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - }))); - } - Some(user_id) => user_id, - }; + .filter(|user_id| !user_id.is_historical() && user_id.server_name() == db.globals.server_name()) + .ok_or(Error::BadRequest( + ErrorKind::InvalidUsername, + "Username is invalid.", + ))?; // Check if username is creative enough - if db.users.exists(&user_id).unwrap() { - debug!("ID already taken"); - return MatrixResult(Err(UiaaResponse::MatrixError(Error { - kind: ErrorKind::UserInUse, - message: "Desired user ID is already taken.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - }))); + if db.users.exists(&user_id)? { + return Err(Error::BadRequest( + ErrorKind::UserInUse, + "Desired user ID is already taken.", + )); } // UIAA - let uiaainfo = UiaaInfo { + let mut uiaainfo = UiaaInfo { flows: vec![AuthFlow { stages: vec!["m.login.dummy".to_owned()], }], completed: Vec::new(), params: Default::default(), - session: Some(utils::random_string(SESSION_ID_LENGTH)), + session: None, auth_error: None, }; if let Some(auth) = &body.auth { - let (worked, uiaainfo) = db - .uiaa - .try_auth(&user_id, "", auth, &uiaainfo, &db.users, &db.globals) - .unwrap(); + let (worked, uiaainfo) = + db.uiaa + .try_auth(&user_id, "", auth, &uiaainfo, &db.users, &db.globals)?; if !worked { - return MatrixResult(Err(UiaaResponse::AuthResponse(uiaainfo))); + return Err(Error::Uiaa(uiaainfo)); } // Success! } else { - db.uiaa.create(&user_id, "", &uiaainfo).unwrap(); - return MatrixResult(Err(UiaaResponse::AuthResponse(uiaainfo))); + uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); + db.uiaa.create(&user_id, "", &uiaainfo)?; + return Err(Error::Uiaa(uiaainfo)); } let password = body.password.clone().unwrap_or_default(); if let Ok(hash) = utils::calculate_hash(&password) { // Create user - db.users.create(&user_id, &hash).unwrap(); + db.users.create(&user_id, &hash)?; } else { - return MatrixResult(Err(UiaaResponse::MatrixError(Error { - kind: ErrorKind::InvalidParam, - message: "Password did not met requirements".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - }))); + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "Password does not meet the requirements.", + )); } // Generate new device id if the user didn't specify one @@ -205,123 +190,95 @@ pub fn register_route( let token = utils::random_string(TOKEN_LENGTH); // Add device - db.users - .create_device( - &user_id, - &device_id, - &token, - body.initial_device_display_name.clone(), - ) - .unwrap(); + db.users.create_device( + &user_id, + &device_id, + &token, + body.initial_device_display_name.clone(), + )?; // Initial data - db.account_data - .update( - None, - &user_id, - &EventType::PushRules, - serde_json::to_value(ruma::events::push_rules::PushRulesEvent { - content: ruma::events::push_rules::PushRulesEventContent { - global: ruma::events::push_rules::Ruleset { - content: vec![], - override_: vec![ruma::events::push_rules::ConditionalPushRule { - actions: vec![ruma::events::push_rules::Action::DontNotify], - default: true, - enabled: false, - rule_id: ".m.rule.master".to_owned(), - conditions: vec![], + db.account_data.update( + None, + &user_id, + &EventType::PushRules, + serde_json::to_value(ruma::events::push_rules::PushRulesEvent { + content: ruma::events::push_rules::PushRulesEventContent { + global: ruma::events::push_rules::Ruleset { + content: vec![], + override_: vec![ruma::events::push_rules::ConditionalPushRule { + actions: vec![ruma::events::push_rules::Action::DontNotify], + default: true, + enabled: false, + rule_id: ".m.rule.master".to_owned(), + conditions: vec![], + }], + room: vec![], + sender: vec![], + underride: vec![ruma::events::push_rules::ConditionalPushRule { + actions: vec![ + ruma::events::push_rules::Action::Notify, + ruma::events::push_rules::Action::SetTweak(ruma::push::Tweak::Sound( + "default".to_owned(), + )), + ], + default: true, + enabled: true, + rule_id: ".m.rule.message".to_owned(), + conditions: vec![ruma::events::push_rules::PushCondition::EventMatch { + key: "type".to_owned(), + pattern: "m.room.message".to_owned(), }], - room: vec![], - sender: vec![], - underride: vec![ruma::events::push_rules::ConditionalPushRule { - actions: vec![ - ruma::events::push_rules::Action::Notify, - ruma::events::push_rules::Action::SetTweak( - ruma::push::Tweak::Sound("default".to_owned()), - ), - ], - default: true, - enabled: true, - rule_id: ".m.rule.message".to_owned(), - conditions: vec![ruma::events::push_rules::PushCondition::EventMatch { - key: "type".to_owned(), - pattern: "m.room.message".to_owned(), - }], - }], - }, + }], }, - }) - .unwrap() - .as_object_mut() - .unwrap(), - &db.globals, - ) - .unwrap(); + }, + }) + .expect("data is valid, we just created it") + .as_object_mut() + .expect("data is valid, we just created it"), + &db.globals, + )?; - MatrixResult(Ok(register::Response { + Ok(register::Response { access_token: Some(token), user_id, device_id: Some(device_id), - })) + } + .into()) } #[get("/_matrix/client/r0/login")] -pub fn get_login_route() -> MatrixResult { - MatrixResult(Ok(get_login_types::Response { +pub fn get_login_route() -> ConduitResult { + Ok(get_login_types::Response { flows: vec![get_login_types::LoginType::Password], - })) + } + .into()) } #[post("/_matrix/client/r0/login", data = "")] pub fn login_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { // Validate login method let user_id = // TODO: Other login methods if let (login::UserInfo::MatrixId(username), login::LoginInfo::Password { password }) = (body.user.clone(), body.login_info.clone()) { - if let Ok(user_id) = UserId::parse_with_server_name(username, db.globals.server_name()) { - if let Some(hash) = db.users.password_hash(&user_id).unwrap() { - let hash_matches = - argon2::verify_encoded(&hash, password.as_bytes()).unwrap_or(false); + let user_id = UserId::parse_with_server_name(username, db.globals.server_name()).map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?; + let hash = db.users.password_hash(&user_id)?.ok_or(Error::BadRequest(ErrorKind::Forbidden, "Wrong username or password."))?; - if hash_matches { - // Success! - user_id - } else { - debug!("Invalid password."); - return MatrixResult(Err(Error { - kind: ErrorKind::Forbidden, - message: "".to_owned(), - status_code: http::StatusCode::FORBIDDEN, - })); - } - } else { - debug!("UserId does not exist (has no assigned password). Can't log in."); - return MatrixResult(Err(Error { - kind: ErrorKind::Forbidden, - message: "".to_owned(), - status_code: http::StatusCode::FORBIDDEN, - })); - } - } else { - debug!("Invalid UserId."); - return MatrixResult(Err(Error { - kind: ErrorKind::InvalidUsername, - message: "Bad user id.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); + let hash_matches = + argon2::verify_encoded(&hash, password.as_bytes()).unwrap_or(false); + + if !hash_matches { + return Err(Error::BadRequest(ErrorKind::Forbidden, "Wrong username or password.")); } + + user_id } else { - debug!("Bad login type"); - return MatrixResult(Err(Error { - kind: ErrorKind::Unknown, - message: "Bad login type.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); + return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type.")); }; // Generate new device id if the user didn't specify one @@ -335,39 +292,38 @@ pub fn login_route( let token = utils::random_string(TOKEN_LENGTH); // Add device - db.users - .create_device( - &user_id, - &device_id, - &token, - body.initial_device_display_name.clone(), - ) - .unwrap(); + db.users.create_device( + &user_id, + &device_id, + &token, + body.initial_device_display_name.clone(), + )?; - MatrixResult(Ok(login::Response { + Ok(login::Response { user_id, access_token: token, home_server: Some(db.globals.server_name().to_owned()), device_id, well_known: None, - })) + } + .into()) } #[post("/_matrix/client/r0/logout", data = "")] pub fn logout_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); let device_id = body.device_id.as_ref().expect("user is authenticated"); - db.users.remove_device(&user_id, &device_id).unwrap(); + db.users.remove_device(&user_id, &device_id)?; - MatrixResult(Ok(logout::Response)) + Ok(logout::Response.into()) } #[get("/_matrix/client/r0/capabilities")] -pub fn get_capabilities_route() -> MatrixResult { +pub fn get_capabilities_route() -> ConduitResult { let mut available = BTreeMap::new(); available.insert( RoomVersionId::version_5(), @@ -378,7 +334,7 @@ pub fn get_capabilities_route() -> MatrixResult { get_capabilities::RoomVersionStability::Stable, ); - MatrixResult(Ok(get_capabilities::Response { + Ok(get_capabilities::Response { capabilities: get_capabilities::Capabilities { change_password: None, // None means it is possible room_versions: Some(get_capabilities::RoomVersionsCapability { @@ -387,31 +343,36 @@ pub fn get_capabilities_route() -> MatrixResult { }), custom_capabilities: BTreeMap::new(), }, - })) + } + .into()) } #[get("/_matrix/client/r0/pushrules", data = "")] pub fn get_pushrules_all_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if let Some(EduEvent::PushRules(pushrules)) = db + if let EduEvent::PushRules(pushrules) = db .account_data - .get(None, &user_id, &EventType::PushRules) - .unwrap() - .map(|edu| edu.deserialize().expect("PushRules event in db is valid")) + .get(None, &user_id, &EventType::PushRules)? + .ok_or(Error::BadRequest( + ErrorKind::NotFound, + "PushRules event not found.", + ))? + .deserialize() + .map_err(|_| Error::BadRequest( + ErrorKind::NotFound, + "PushRules event in db is invalid.", + ))? { - MatrixResult(Ok(get_pushrules_all::Response { + Ok(get_pushrules_all::Response { global: pushrules.content.global, - })) + } + .into()) } else { - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "PushRules event not found.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) + Err(Error::BadDatabase("Pushrules event has wrong content.")) } } @@ -425,10 +386,10 @@ pub fn set_pushrule_route( _scope: String, _kind: String, _rule_id: String, -) -> MatrixResult { +) -> ConduitResult { // TODO warn!("TODO: set_pushrule_route"); - MatrixResult(Ok(set_pushrule::Response)) + Ok(set_pushrule::Response.into()) } #[put("/_matrix/client/r0/pushrules/<_scope>/<_kind>/<_rule_id>/enabled")] @@ -436,19 +397,19 @@ pub fn set_pushrule_enabled_route( _scope: String, _kind: String, _rule_id: String, -) -> MatrixResult { +) -> ConduitResult { // TODO warn!("TODO: set_pushrule_enabled_route"); - MatrixResult(Ok(set_pushrule_enabled::Response)) + Ok(set_pushrule_enabled::Response.into()) } #[get("/_matrix/client/r0/user/<_user_id>/filter/<_filter_id>")] pub fn get_filter_route( _user_id: String, _filter_id: String, -) -> MatrixResult { +) -> ConduitResult { // TODO - MatrixResult(Ok(get_filter::Response { + Ok(get_filter::Response { filter: filter::FilterDefinition { event_fields: None, event_format: None, @@ -456,15 +417,17 @@ pub fn get_filter_route( room: None, presence: None, }, - })) + } + .into()) } #[post("/_matrix/client/r0/user/<_user_id>/filter")] -pub fn create_filter_route(_user_id: String) -> MatrixResult { +pub fn create_filter_route(_user_id: String) -> ConduitResult { // TODO - MatrixResult(Ok(create_filter::Response { + Ok(create_filter::Response { filter_id: utils::random_string(10), - })) + } + .into()) } #[put( @@ -476,22 +439,24 @@ pub fn set_global_account_data_route( body: Ruma, _user_id: String, _type: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - db.account_data - .update( - None, - user_id, - &EventType::try_from(&body.event_type).unwrap(), - json!({"content": serde_json::from_str::(body.data.get()).unwrap()}) - .as_object_mut() - .unwrap(), - &db.globals, + db.account_data.update( + None, + user_id, + &EventType::try_from(&body.event_type).expect("EventType::try_from can never fail"), + json!( + {"content": serde_json::from_str::(body.data.get()) + .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))? + } ) - .unwrap(); + .as_object_mut() + .expect("we just created a valid object"), + &db.globals, + )?; - MatrixResult(Ok(set_global_account_data::Response)) + Ok(set_global_account_data::Response.into()) } #[get( @@ -503,26 +468,19 @@ pub fn get_global_account_data_route( body: Ruma, _user_id: String, _type: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if let Some(data) = db + let data = db .account_data .get( None, user_id, - &EventType::try_from(&body.event_type).unwrap(), - ) - .unwrap() - { - MatrixResult(Ok(get_global_account_data::Response { account_data: data })) - } else { - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "Data not found.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) - } + &EventType::try_from(&body.event_type).expect("EventType::try_from can never fail"), + )? + .ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?; + + Ok(get_global_account_data::Response { account_data: data }.into()) } #[put("/_matrix/client/r0/profile/<_user_id>/displayname", data = "")] @@ -530,64 +488,64 @@ pub fn set_displayname_route( db: State<'_, Database>, body: Ruma, _user_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); db.users - .set_displayname(&user_id, body.displayname.clone()) - .unwrap(); + .set_displayname(&user_id, body.displayname.clone())?; // Send a new membership event into all joined rooms for room_id in db.rooms.rooms_joined(&user_id) { - let room_id = room_id.unwrap(); - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomMember, - serde_json::to_value(ruma::events::room::member::MemberEventContent { - displayname: body.displayname.clone(), - ..serde_json::from_value::>( - db.rooms - .room_state(&room_id) - .unwrap() - .get(&(EventType::RoomMember, user_id.to_string())) - .expect("user is part of the room") - .content - .clone(), - ) - .unwrap() - .deserialize() - .unwrap() - }) - .unwrap(), - None, - Some(user_id.to_string()), - None, - &db.globals, - ) - .unwrap(); + let room_id = room_id?; + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomMember, + serde_json::to_value(ruma::events::room::member::MemberEventContent { + displayname: body.displayname.clone(), + ..serde_json::from_value::>( + db.rooms + .room_state(&room_id)? + .get(&(EventType::RoomMember, user_id.to_string())) + .ok_or(Error::BadDatabase( + "Tried to send displayname update for user not in the room.", + ))? + .content + .clone(), + ) + .map_err(|_| Error::BadDatabase("Database contains invalid PDU."))? + .deserialize() + .map_err(|_| Error::BadDatabase("Database contains invalid PDU."))? + }) + .expect("event is valid, we just created it"), + None, + Some(user_id.to_string()), + None, + &db.globals, + )?; } // Presence update - db.global_edus - .update_presence( - ruma::events::presence::PresenceEvent { - content: ruma::events::presence::PresenceEventContent { - avatar_url: db.users.avatar_url(&user_id).unwrap(), - currently_active: None, - displayname: db.users.displayname(&user_id).unwrap(), - last_active_ago: Some(utils::millis_since_unix_epoch().try_into().unwrap()), - presence: ruma::events::presence::PresenceState::Online, - status_msg: None, - }, - sender: user_id.clone(), + db.global_edus.update_presence( + ruma::events::presence::PresenceEvent { + content: ruma::events::presence::PresenceEventContent { + avatar_url: db.users.avatar_url(&user_id)?, + currently_active: None, + displayname: db.users.displayname(&user_id)?, + last_active_ago: Some( + utils::millis_since_unix_epoch() + .try_into() + .expect("time is valid"), + ), + presence: ruma::events::presence::PresenceState::Online, + status_msg: None, }, - &db.globals, - ) - .unwrap(); + sender: user_id.clone(), + }, + &db.globals, + )?; - MatrixResult(Ok(set_display_name::Response)) + Ok(set_display_name::Response.into()) } #[get("/_matrix/client/r0/profile/<_user_id>/displayname", data = "")] @@ -595,11 +553,12 @@ pub fn get_displayname_route( db: State<'_, Database>, body: Ruma, _user_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.body.user_id.clone(); - MatrixResult(Ok(get_display_name::Response { - displayname: db.users.displayname(&user_id).unwrap(), - })) + Ok(get_display_name::Response { + displayname: db.users.displayname(&user_id)?, + } + .into()) } #[put("/_matrix/client/r0/profile/<_user_id>/avatar_url", data = "")] @@ -607,78 +566,75 @@ pub fn set_avatar_url_route( db: State<'_, Database>, body: Ruma, _user_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); if let Some(avatar_url) = &body.avatar_url { if !avatar_url.starts_with("mxc://") { - debug!("Request contains an invalid avatar_url."); - return MatrixResult(Err(Error { - kind: ErrorKind::InvalidParam, - message: "avatar_url has to start with mxc://.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "avatar_url has to start with mxc://.", + )); } // TODO in the future when we can handle media uploads make sure that this url is our own server // TODO also make sure this is valid mxc:// format (not only starting with it) } - db.users - .set_avatar_url(&user_id, body.avatar_url.clone()) - .unwrap(); + db.users.set_avatar_url(&user_id, body.avatar_url.clone())?; // Send a new membership event into all joined rooms for room_id in db.rooms.rooms_joined(&user_id) { - let room_id = room_id.unwrap(); - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomMember, - serde_json::to_value(ruma::events::room::member::MemberEventContent { - avatar_url: body.avatar_url.clone(), - ..serde_json::from_value::>( - db.rooms - .room_state(&room_id) - .unwrap() - .get(&(EventType::RoomMember, user_id.to_string())) - .expect("user should be part of the room") - .content - .clone(), - ) - .unwrap() - .deserialize() - .unwrap() - }) - .unwrap(), - None, - Some(user_id.to_string()), - None, - &db.globals, - ) - .unwrap(); + let room_id = room_id?; + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomMember, + serde_json::to_value(ruma::events::room::member::MemberEventContent { + avatar_url: body.avatar_url.clone(), + ..serde_json::from_value::>( + db.rooms + .room_state(&room_id)? + .get(&(EventType::RoomMember, user_id.to_string())) + .ok_or(Error::BadDatabase( + "Tried to send avatar url update for user not in the room.", + ))? + .content + .clone(), + ) + .map_err(|_| Error::BadDatabase("Database contains invalid PDU."))? + .deserialize() + .map_err(|_| Error::BadDatabase("Database contains invalid PDU."))? + }) + .expect("event is valid, we just created it"), + None, + Some(user_id.to_string()), + None, + &db.globals, + )?; } // Presence update - db.global_edus - .update_presence( - ruma::events::presence::PresenceEvent { - content: ruma::events::presence::PresenceEventContent { - avatar_url: db.users.avatar_url(&user_id).unwrap(), - currently_active: None, - displayname: db.users.displayname(&user_id).unwrap(), - last_active_ago: Some(utils::millis_since_unix_epoch().try_into().unwrap()), - presence: ruma::events::presence::PresenceState::Online, - status_msg: None, - }, - sender: user_id.clone(), + db.global_edus.update_presence( + ruma::events::presence::PresenceEvent { + content: ruma::events::presence::PresenceEventContent { + avatar_url: db.users.avatar_url(&user_id)?, + currently_active: None, + displayname: db.users.displayname(&user_id)?, + last_active_ago: Some( + utils::millis_since_unix_epoch() + .try_into() + .expect("time is valid"), + ), + presence: ruma::events::presence::PresenceState::Online, + status_msg: None, }, - &db.globals, - ) - .unwrap(); + sender: user_id.clone(), + }, + &db.globals, + )?; - MatrixResult(Ok(set_avatar_url::Response)) + Ok(set_avatar_url::Response.into()) } #[get("/_matrix/client/r0/profile/<_user_id>/avatar_url", data = "")] @@ -686,11 +642,12 @@ pub fn get_avatar_url_route( db: State<'_, Database>, body: Ruma, _user_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.body.user_id.clone(); - MatrixResult(Ok(get_avatar_url::Response { - avatar_url: db.users.avatar_url(&user_id).unwrap(), - })) + Ok(get_avatar_url::Response { + avatar_url: db.users.avatar_url(&user_id)?, + } + .into()) } #[get("/_matrix/client/r0/profile/<_user_id>", data = "")] @@ -698,25 +655,24 @@ pub fn get_profile_route( db: State<'_, Database>, body: Ruma, _user_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.body.user_id.clone(); - let avatar_url = db.users.avatar_url(&user_id).unwrap(); - let displayname = db.users.displayname(&user_id).unwrap(); + let avatar_url = db.users.avatar_url(&user_id)?; + let displayname = db.users.displayname(&user_id)?; - if avatar_url.is_some() || displayname.is_some() { - return MatrixResult(Ok(get_profile::Response { - avatar_url, - displayname, - })); + if avatar_url.is_none() && displayname.is_none() { + // Return 404 if we don't have a profile for this id + return Err(Error::BadRequest( + ErrorKind::NotFound, + "Profile was not found.", + )); } - // Return 404 if we don't have a profile for this id - debug!("Profile was not found."); - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "Profile was not found.".to_owned(), - status_code: http::StatusCode::NOT_FOUND, - })) + Ok(get_profile::Response { + avatar_url, + displayname, + } + .into()) } #[put("/_matrix/client/r0/presence/<_user_id>/status", data = "")] @@ -724,76 +680,73 @@ pub fn set_presence_route( db: State<'_, Database>, body: Ruma, _user_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - db.global_edus - .update_presence( - ruma::events::presence::PresenceEvent { - content: ruma::events::presence::PresenceEventContent { - avatar_url: db.users.avatar_url(&user_id).unwrap(), - currently_active: None, - displayname: db.users.displayname(&user_id).unwrap(), - last_active_ago: Some(utils::millis_since_unix_epoch().try_into().unwrap()), - presence: body.presence, - status_msg: body.status_msg.clone(), - }, - sender: user_id.clone(), + db.global_edus.update_presence( + ruma::events::presence::PresenceEvent { + content: ruma::events::presence::PresenceEventContent { + avatar_url: db.users.avatar_url(&user_id)?, + currently_active: None, + displayname: db.users.displayname(&user_id)?, + last_active_ago: Some( + utils::millis_since_unix_epoch() + .try_into() + .expect("time is valid"), + ), + presence: body.presence, + status_msg: body.status_msg.clone(), }, - &db.globals, - ) - .unwrap(); + sender: user_id.clone(), + }, + &db.globals, + )?; - MatrixResult(Ok(set_presence::Response)) + Ok(set_presence::Response.into()) } #[post("/_matrix/client/r0/keys/upload", data = "")] pub fn upload_keys_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); let device_id = body.device_id.as_ref().expect("user is authenticated"); if let Some(one_time_keys) = &body.one_time_keys { for (key_key, key_value) in one_time_keys { db.users - .add_one_time_key(user_id, device_id, key_key, key_value) - .unwrap(); + .add_one_time_key(user_id, device_id, key_key, key_value)?; } } if let Some(device_keys) = &body.device_keys { db.users - .add_device_keys(user_id, device_id, device_keys, &db.globals) - .unwrap(); + .add_device_keys(user_id, device_id, device_keys, &db.globals)?; } - MatrixResult(Ok(upload_keys::Response { - one_time_key_counts: db.users.count_one_time_keys(user_id, device_id).unwrap(), - })) + Ok(upload_keys::Response { + one_time_key_counts: db.users.count_one_time_keys(user_id, device_id)?, + } + .into()) } #[post("/_matrix/client/r0/keys/query", data = "")] pub fn get_keys_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { let mut device_keys = BTreeMap::new(); for (user_id, device_ids) in &body.device_keys { if device_ids.is_empty() { let mut container = BTreeMap::new(); - for (device_id, mut keys) in db - .users - .all_device_keys(&user_id.clone()) - .map(|r| r.unwrap()) - { - let metadata = db - .users - .get_device_metadata(user_id, &device_id) - .unwrap() - .expect("this device should exist"); + for result in db.users.all_device_keys(&user_id.clone()) { + let (device_id, mut keys) = result?; + + let metadata = db.users.get_device_metadata(user_id, &device_id)?.ok_or( + Error::BadDatabase("all_device_keys contained nonexistent device."), + )?; keys.unsigned = Some(keys::UnsignedDeviceInfo { device_display_name: metadata.display_name, @@ -806,12 +759,13 @@ pub fn get_keys_route( for device_id in device_ids { let mut container = BTreeMap::new(); for keys in db.users.get_device_keys(&user_id.clone(), &device_id) { - let mut keys = keys.unwrap(); - let metadata = db - .users - .get_device_metadata(user_id, &device_id) - .unwrap() - .expect("this device should exist"); + let mut keys = keys?; + let metadata = db.users.get_device_metadata(user_id, &device_id)?.ok_or( + Error::BadRequest( + ErrorKind::InvalidParam, + "Tried to get keys for nonexistent device.", + ), + )?; keys.unsigned = Some(keys::UnsignedDeviceInfo { device_display_name: metadata.display_name, @@ -824,25 +778,25 @@ pub fn get_keys_route( } } - MatrixResult(Ok(get_keys::Response { + Ok(get_keys::Response { failures: BTreeMap::new(), device_keys, - })) + } + .into()) } #[post("/_matrix/client/r0/keys/claim", data = "")] pub fn claim_keys_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { let mut one_time_keys = BTreeMap::new(); for (user_id, map) in &body.one_time_keys { let mut container = BTreeMap::new(); for (device_id, key_algorithm) in map { - if let Some(one_time_keys) = db - .users - .take_one_time_key(user_id, device_id, key_algorithm) - .unwrap() + if let Some(one_time_keys) = + db.users + .take_one_time_key(user_id, device_id, key_algorithm)? { let mut c = BTreeMap::new(); c.insert(one_time_keys.0, one_time_keys.1); @@ -852,10 +806,11 @@ pub fn claim_keys_route( one_time_keys.insert(user_id.clone(), container); } - MatrixResult(Ok(claim_keys::Response { + Ok(claim_keys::Response { failures: BTreeMap::new(), one_time_keys, - })) + } + .into()) } #[post("/_matrix/client/r0/rooms/<_room_id>/read_markers", data = "")] @@ -863,39 +818,34 @@ pub fn set_read_marker_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - db.account_data - .update( - Some(&body.room_id), - &user_id, - &EventType::FullyRead, - serde_json::to_value(ruma::events::fully_read::FullyReadEvent { - content: ruma::events::fully_read::FullyReadEventContent { - event_id: body.fully_read.clone(), - }, - room_id: Some(body.room_id.clone()), - }) - .unwrap() - .as_object_mut() - .unwrap(), - &db.globals, - ) - .unwrap(); + db.account_data.update( + Some(&body.room_id), + &user_id, + &EventType::FullyRead, + serde_json::to_value(ruma::events::fully_read::FullyReadEvent { + content: ruma::events::fully_read::FullyReadEventContent { + event_id: body.fully_read.clone(), + }, + room_id: Some(body.room_id.clone()), + }) + .expect("we just created a valid event") + .as_object_mut() + .expect("we just created a valid event"), + &db.globals, + )?; if let Some(event) = &body.read_receipt { - db.rooms - .edus - .room_read_set( - &body.room_id, - &user_id, - db.rooms - .get_pdu_count(event) - .unwrap() - .expect("TODO: what if a client specifies an invalid event"), - ) - .unwrap(); + db.rooms.edus.room_read_set( + &body.room_id, + &user_id, + db.rooms.get_pdu_count(event)?.ok_or(Error::BadRequest( + ErrorKind::InvalidParam, + "Event does not exist.", + ))?, + )?; let mut user_receipts = BTreeMap::new(); user_receipts.insert( @@ -912,20 +862,17 @@ pub fn set_read_marker_route( }, ); - db.rooms - .edus - .roomlatest_update( - &user_id, - &body.room_id, - EduEvent::Receipt(ruma::events::receipt::ReceiptEvent { - content: receipt_content, - room_id: None, // None because it can be inferred - }), - &db.globals, - ) - .unwrap(); + db.rooms.edus.roomlatest_update( + &user_id, + &body.room_id, + EduEvent::Receipt(ruma::events::receipt::ReceiptEvent { + content: receipt_content, + room_id: None, // None because it can be inferred + }), + &db.globals, + )?; } - MatrixResult(Ok(set_read_marker::Response)) + Ok(set_read_marker::Response.into()) } #[put( @@ -937,106 +884,94 @@ pub fn create_typing_event_route( body: Ruma, _room_id: String, _user_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); if body.typing { - db.rooms - .edus - .roomactive_add( - &user_id, - &body.room_id, - body.timeout.map(|d| d.as_millis() as u64).unwrap_or(30000) - + utils::millis_since_unix_epoch().try_into().unwrap_or(0), - &db.globals, - ) - .unwrap(); + db.rooms.edus.roomactive_add( + &user_id, + &body.room_id, + body.timeout.map(|d| d.as_millis() as u64).unwrap_or(30000) + + utils::millis_since_unix_epoch(), + &db.globals, + )?; } else { db.rooms .edus - .roomactive_remove(&user_id, &body.room_id, &db.globals) - .unwrap(); + .roomactive_remove(&user_id, &body.room_id, &db.globals)?; } - MatrixResult(Ok(create_typing_event::Response)) + Ok(create_typing_event::Response.into()) } #[post("/_matrix/client/r0/createRoom", data = "")] pub fn create_room_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { - let room_id = RoomId::new(db.globals.server_name()).expect("host is valid"); +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - let alias = if let Some(localpart) = &body.room_alias_name { - // TODO: Check for invalid characters and maximum length - if let Ok(alias) = - RoomAliasId::try_from(format!("#{}:{}", localpart, db.globals.server_name())) - { - if db.rooms.id_from_alias(&alias).unwrap().is_some() { - return MatrixResult(Err(Error { - kind: ErrorKind::Unknown, - message: "Alias already exists.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); - } + let room_id = RoomId::new(db.globals.server_name()) + .map_err(|_| Error::BadDatabase("Server name is invalid."))?; - Some(alias) - } else { - return MatrixResult(Err(Error { - kind: ErrorKind::Unknown, - message: "Invalid alias.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); - } - } else { - None - }; + let alias = body + .room_alias_name + .as_ref() + .map_or(Ok(None), |localpart| { + // TODO: Check for invalid characters and maximum length + let alias = + RoomAliasId::try_from(format!("#{}:{}", localpart, db.globals.server_name())) + .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid alias."))?; + + if db.rooms.id_from_alias(&alias)?.is_some() { + Err(Error::BadRequest( + ErrorKind::RoomInUse, + "Room alias already exists.", + )) + } else { + Ok(Some(alias)) + } + })?; // 1. The room create event - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomCreate, - serde_json::to_value(ruma::events::room::create::CreateEventContent { - creator: user_id.clone(), - federate: body.creation_content.as_ref().map_or(true, |c| c.federate), - predecessor: body - .creation_content - .as_ref() - .and_then(|c| c.predecessor.clone()), - room_version: RoomVersionId::version_6(), - }) - .unwrap(), - None, - Some("".to_owned()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomCreate, + serde_json::to_value(ruma::events::room::create::CreateEventContent { + creator: user_id.clone(), + federate: body.creation_content.as_ref().map_or(true, |c| c.federate), + predecessor: body + .creation_content + .as_ref() + .and_then(|c| c.predecessor.clone()), + room_version: RoomVersionId::version_6(), + }) + .expect("event is valid, we just created it"), + None, + Some("".to_owned()), + None, + &db.globals, + )?; // 2. Let the room creator join - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomMember, - serde_json::to_value(member::MemberEventContent { - membership: member::MembershipState::Join, - displayname: db.users.displayname(&user_id).unwrap(), - avatar_url: db.users.avatar_url(&user_id).unwrap(), - is_direct: body.is_direct, - third_party_invite: None, - }) - .unwrap(), - None, - Some(user_id.to_string()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomMember, + serde_json::to_value(member::MemberEventContent { + membership: member::MembershipState::Join, + displayname: db.users.displayname(&user_id)?, + avatar_url: db.users.avatar_url(&user_id)?, + is_direct: body.is_direct, + third_party_invite: None, + }) + .expect("event is valid, we just created it"), + None, + Some(user_id.to_string()), + None, + &db.globals, + )?; // Figure out preset. We need it for power levels and preset specific events let visibility = body.visibility.unwrap_or(room::Visibility::Private); @@ -1053,8 +988,9 @@ pub fn create_room_route( } let power_levels_content = if let Some(power_levels) = &body.power_level_content_override { - serde_json::from_str(power_levels.json().get()) - .expect("TODO: handle. we hope the client sends a valid power levels json") + serde_json::from_str(power_levels.json().get()).map_err(|_| { + Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override.") + })? } else { serde_json::to_value(ruma::events::room::power_levels::PowerLevelsEventContent { ban: 50.into(), @@ -1070,88 +1006,80 @@ pub fn create_room_route( room: 50.into(), }, }) - .unwrap() + .expect("event is valid, we just created it") }; - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomPowerLevels, - power_levels_content, - None, - Some("".to_owned()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomPowerLevels, + power_levels_content, + None, + Some("".to_owned()), + None, + &db.globals, + )?; // 4. Events set by preset // 4.1 Join Rules - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomJoinRules, - match preset { - create_room::RoomPreset::PublicChat => { - serde_json::to_value(join_rules::JoinRulesEventContent { - join_rule: join_rules::JoinRule::Public, - }) - .unwrap() - } - _ => serde_json::to_value(join_rules::JoinRulesEventContent { - join_rule: join_rules::JoinRule::Invite, + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomJoinRules, + match preset { + create_room::RoomPreset::PublicChat => { + serde_json::to_value(join_rules::JoinRulesEventContent { + join_rule: join_rules::JoinRule::Public, }) - .unwrap(), - }, - None, - Some("".to_owned()), - None, - &db.globals, - ) - .unwrap(); + .expect("event is valid, we just created it") + } + _ => serde_json::to_value(join_rules::JoinRulesEventContent { + join_rule: join_rules::JoinRule::Invite, + }) + .expect("event is valid, we just created it"), + }, + None, + Some("".to_owned()), + None, + &db.globals, + )?; // 4.2 History Visibility - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomHistoryVisibility, - serde_json::to_value(history_visibility::HistoryVisibilityEventContent { - history_visibility: history_visibility::HistoryVisibility::Shared, - }) - .unwrap(), - None, - Some("".to_owned()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomHistoryVisibility, + serde_json::to_value(history_visibility::HistoryVisibilityEventContent { + history_visibility: history_visibility::HistoryVisibility::Shared, + }) + .expect("event is valid, we just created it"), + None, + Some("".to_owned()), + None, + &db.globals, + )?; // 4.3 Guest Access - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomGuestAccess, - match preset { - create_room::RoomPreset::PublicChat => { - serde_json::to_value(guest_access::GuestAccessEventContent { - guest_access: guest_access::GuestAccess::Forbidden, - }) - .unwrap() - } - _ => serde_json::to_value(guest_access::GuestAccessEventContent { - guest_access: guest_access::GuestAccess::CanJoin, + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomGuestAccess, + match preset { + create_room::RoomPreset::PublicChat => { + serde_json::to_value(guest_access::GuestAccessEventContent { + guest_access: guest_access::GuestAccess::Forbidden, }) - .unwrap(), - }, - None, - Some("".to_owned()), - None, - &db.globals, - ) - .unwrap(); + .expect("event is valid, we just created it") + } + _ => serde_json::to_value(guest_access::GuestAccessEventContent { + guest_access: guest_access::GuestAccess::CanJoin, + }) + .expect("event is valid, we just created it"), + }, + None, + Some("".to_owned()), + None, + &db.globals, + )?; // 5. Events listed in initial_state for create_room::InitialStateEvent { @@ -1160,92 +1088,85 @@ pub fn create_room_route( content, } in &body.initial_state { - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - event_type.clone(), - serde_json::from_str(content.get()).unwrap(), - None, - state_key.clone(), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + event_type.clone(), + serde_json::from_str(content.get()).map_err(|_| { + Error::BadRequest(ErrorKind::BadJson, "Invalid initial_state content.") + })?, + None, + state_key.clone(), + None, + &db.globals, + )?; } // 6. Events implied by name and topic if let Some(name) = &body.name { - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomName, - serde_json::to_value( - ruma::events::room::name::NameEventContent::new(name.clone()).unwrap(), - ) - .unwrap(), - None, - Some("".to_owned()), - None, - &db.globals, + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomName, + serde_json::to_value( + name::NameEventContent::new(name.clone()) + .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Name is invalid."))?, ) - .unwrap(); + .expect("event is valid, we just created it"), + None, + Some("".to_owned()), + None, + &db.globals, + )?; } if let Some(topic) = &body.topic { - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomTopic, - serde_json::to_value(ruma::events::room::topic::TopicEventContent { - topic: topic.clone(), - }) - .unwrap(), - None, - Some("".to_owned()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomTopic, + serde_json::to_value(topic::TopicEventContent { + topic: topic.clone(), + }) + .expect("event is valid, we just created it"), + None, + Some("".to_owned()), + None, + &db.globals, + )?; } // 7. Events implied by invite (and TODO: invite_3pid) for user in &body.invite { - db.rooms - .append_pdu( - room_id.clone(), - user_id.clone(), - EventType::RoomMember, - serde_json::to_value(member::MemberEventContent { - membership: member::MembershipState::Invite, - displayname: db.users.displayname(&user).unwrap(), - avatar_url: db.users.avatar_url(&user).unwrap(), - is_direct: body.is_direct, - third_party_invite: None, - }) - .unwrap(), - None, - Some(user.to_string()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + room_id.clone(), + user_id.clone(), + EventType::RoomMember, + serde_json::to_value(member::MemberEventContent { + membership: member::MembershipState::Invite, + displayname: db.users.displayname(&user)?, + avatar_url: db.users.avatar_url(&user)?, + is_direct: body.is_direct, + third_party_invite: None, + }) + .expect("event is valid, we just created it"), + None, + Some(user.to_string()), + None, + &db.globals, + )?; } // Homeserver specific stuff if let Some(alias) = alias { - db.rooms - .set_alias(&alias, Some(&room_id), &db.globals) - .unwrap(); + db.rooms.set_alias(&alias, Some(&room_id), &db.globals)?; } if let Some(room::Visibility::Public) = body.visibility { - db.rooms.set_public(&room_id, true).unwrap(); + db.rooms.set_public(&room_id, true)?; } - MatrixResult(Ok(create_room::Response { room_id })) + Ok(create_room::Response { room_id }.into()) } #[put( @@ -1258,30 +1179,24 @@ pub fn redact_event_route( _room_id: String, _event_id: String, _txn_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if let Ok(event_id) = db.rooms.append_pdu( + let event_id = db.rooms.append_pdu( body.room_id.clone(), user_id.clone(), EventType::RoomRedaction, serde_json::to_value(redaction::RedactionEventContent { reason: body.reason.clone(), }) - .unwrap(), + .expect("event is valid, we just created it"), None, None, Some(body.event_id.clone()), &db.globals, - ) { - MatrixResult(Ok(redact_event::Response { event_id })) - } else { - MatrixResult(Err(Error { - kind: ErrorKind::Unknown, - message: "Failed to redact event.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) - } + )?; + + Ok(redact_event::Response { event_id }.into()) } #[put("/_matrix/client/r0/directory/room/<_room_alias>", data = "")] @@ -1289,20 +1204,15 @@ pub fn create_alias_route( db: State<'_, Database>, body: Ruma, _room_alias: String, -) -> MatrixResult { - if db.rooms.id_from_alias(&body.room_alias).unwrap().is_some() { - return MatrixResult(Err(Error { - kind: ErrorKind::Unknown, - message: "Alias already exists".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); +) -> ConduitResult { + if db.rooms.id_from_alias(&body.room_alias)?.is_some() { + return Err(Error::Conflict("Alias already exists.")); } db.rooms - .set_alias(&body.room_alias, Some(&body.room_id), &db.globals) - .unwrap(); + .set_alias(&body.room_alias, Some(&body.room_id), &db.globals)?; - MatrixResult(Ok(create_alias::Response)) + Ok(create_alias::Response.into()) } #[delete("/_matrix/client/r0/directory/room/<_room_alias>", data = "")] @@ -1310,12 +1220,10 @@ pub fn delete_alias_route( db: State<'_, Database>, body: Ruma, _room_alias: String, -) -> MatrixResult { - db.rooms - .set_alias(&body.room_alias, None, &db.globals) - .unwrap(); +) -> ConduitResult { + db.rooms.set_alias(&body.room_alias, None, &db.globals)?; - MatrixResult(Ok(delete_alias::Response)) + Ok(delete_alias::Response.into()) } #[get("/_matrix/client/r0/directory/room/<_room_alias>", data = "")] @@ -1323,24 +1231,24 @@ pub fn get_alias_route( db: State<'_, Database>, body: Ruma, _room_alias: String, -) -> MatrixResult { - if body.room_alias.server_name() == db.globals.server_name() { - if let Some(room_id) = db.rooms.id_from_alias(&body.room_alias).unwrap() { - MatrixResult(Ok(get_alias::Response { - room_id, - servers: vec![db.globals.server_name().to_owned()], - })) - } else { - debug!("Room alias not found."); - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "Room with alias not found.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) - } - } else { +) -> ConduitResult { + if body.room_alias.server_name() != db.globals.server_name() { todo!("ask remote server"); } + + let room_id = db + .rooms + .id_from_alias(&body.room_alias)? + .ok_or(Error::BadRequest( + ErrorKind::NotFound, + "Room with alias not found.", + ))?; + + Ok(get_alias::Response { + room_id, + servers: vec![db.globals.server_name().to_owned()], + } + .into()) } #[post("/_matrix/client/r0/rooms/<_room_id>/join", data = "")] @@ -1348,58 +1256,56 @@ pub fn join_room_by_id_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); // TODO: Ask a remote server if we don't have this room let event = db .rooms - .room_state(&body.room_id) - .unwrap() + .room_state(&body.room_id)? .get(&(EventType::RoomMember, user_id.to_string())) .map_or_else( || { // There was no existing membership event - member::MemberEventContent { + Ok::<_, Error>(member::MemberEventContent { membership: member::MembershipState::Join, - displayname: db.users.displayname(&user_id).unwrap(), - avatar_url: db.users.avatar_url(&user_id).unwrap(), + displayname: db.users.displayname(&user_id)?, + avatar_url: db.users.avatar_url(&user_id)?, is_direct: None, third_party_invite: None, - } + }) }, |pdu| { // We change the existing membership event let mut event = serde_json::from_value::>( pdu.content.clone(), ) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid member event in db."))? .deserialize() - .unwrap(); + .map_err(|_| Error::BadDatabase("Invalid member event in db."))?; event.membership = member::MembershipState::Join; - event.displayname = db.users.displayname(&user_id).unwrap(); - event.avatar_url = db.users.avatar_url(&user_id).unwrap(); - event + event.displayname = db.users.displayname(&user_id)?; + event.avatar_url = db.users.avatar_url(&user_id)?; + Ok(event) }, - ); + )?; - db.rooms - .append_pdu( - body.room_id.clone(), - user_id.clone(), - EventType::RoomMember, - serde_json::to_value(event).unwrap(), - None, - Some(user_id.to_string()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + body.room_id.clone(), + user_id.clone(), + EventType::RoomMember, + serde_json::to_value(event).expect("event is valid, we just created it"), + None, + Some(user_id.to_string()), + None, + &db.globals, + )?; - MatrixResult(Ok(join_room_by_id::Response { + Ok(join_room_by_id::Response { room_id: body.room_id.clone(), - })) + } + .into()) } #[post("/_matrix/client/r0/join/<_room_id_or_alias>", data = "")] @@ -1407,27 +1313,13 @@ pub fn join_room_by_id_or_alias_route( db: State<'_, Database>, body: Ruma, _room_id_or_alias: String, -) -> MatrixResult { - let room_id = match RoomId::try_from(body.room_id_or_alias.clone()) { - Ok(room_id) => room_id, - Err(_) => { - if let Some(room_id) = db - .rooms - .id_from_alias(&body.room_id_or_alias.clone().try_into().unwrap()) - .unwrap() - { - room_id - } else { - // Ask creator server of the room to join TODO ask someone else when not available - //server_server::send_request(data, destination, request) - return MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "Room alias not found.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); - } - } - }; +) -> ConduitResult { + let room_id = RoomId::try_from(body.room_id_or_alias.clone()).or_else(|alias| { + Ok::<_, Error>(db.rooms.id_from_alias(&alias)?.ok_or(Error::BadRequest( + ErrorKind::NotFound, + "Room not found (TODO: Federation).", + ))?) + })?; let body = Ruma { user_id: body.user_id.clone(), @@ -1438,12 +1330,11 @@ pub fn join_room_by_id_or_alias_route( third_party_signed: body.third_party_signed.clone(), }, }; - MatrixResult(match join_room_by_id_route(db, body, "".to_owned()).0 { - Ok(response) => Ok(join_room_by_id_or_alias::Response { - room_id: response.room_id, - }), - Err(e) => Err(e), - }) + + Ok(join_room_by_id_or_alias::Response { + room_id: join_room_by_id_route(db, body, "".to_owned())?.0.room_id, + } + .into()) } #[post("/_matrix/client/r0/rooms/<_room_id>/leave", data = "")] @@ -1451,38 +1342,38 @@ pub fn leave_room_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - let state = db.rooms.room_state(&body.room_id).unwrap(); + let state = db.rooms.room_state(&body.room_id)?; - let mut event = - serde_json::from_value::>( - state - .get(&(EventType::RoomMember, user_id.to_string())) - .unwrap() // TODO: error handling - .content - .clone(), - ) - .unwrap() - .deserialize() - .unwrap(); + let mut event = serde_json::from_value::>( + state + .get(&(EventType::RoomMember, user_id.to_string())) + .ok_or(Error::BadRequest( + ErrorKind::BadState, + "Cannot leave a room you are not a member of.", + ))? + .content + .clone(), + ) + .map_err(|_| Error::BadDatabase("Invalid member event in database."))? + .deserialize() + .map_err(|_| Error::BadDatabase("Invalid member event in database."))?; - event.membership = ruma::events::room::member::MembershipState::Leave; + event.membership = member::MembershipState::Leave; - db.rooms - .append_pdu( - body.room_id.clone(), - user_id.clone(), - EventType::RoomMember, - serde_json::to_value(event).unwrap(), - None, - Some(user_id.to_string()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + body.room_id.clone(), + user_id.clone(), + EventType::RoomMember, + serde_json::to_value(event).expect("event is valid, we just created it"), + None, + Some(user_id.to_string()), + None, + &db.globals, + )?; - MatrixResult(Ok(leave_room::Response)) + Ok(leave_room::Response.into()) } #[post("/_matrix/client/r0/rooms/<_room_id>/kick", data = "")] @@ -1490,39 +1381,40 @@ pub fn kick_user_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - let state = db.rooms.room_state(&body.room_id).unwrap(); + let state = db.rooms.room_state(&body.room_id)?; let mut event = serde_json::from_value::>( state .get(&(EventType::RoomMember, user_id.to_string())) - .unwrap() // TODO: error handling + .ok_or(Error::BadRequest( + ErrorKind::BadState, + "Cannot kick member that's not in the room.", + ))? .content .clone(), ) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid member event in database."))? .deserialize() - .unwrap(); + .map_err(|_| Error::BadDatabase("Invalid member event in database."))?; event.membership = ruma::events::room::member::MembershipState::Leave; // TODO: reason - db.rooms - .append_pdu( - body.room_id.clone(), - user_id.clone(), // Sender - EventType::RoomMember, - serde_json::to_value(event).unwrap(), - None, - Some(body.body.user_id.to_string()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + body.room_id.clone(), + user_id.clone(), // Sender + EventType::RoomMember, + serde_json::to_value(event).expect("event is valid, we just created it"), + None, + Some(body.body.user_id.to_string()), + None, + &db.globals, + )?; - MatrixResult(Ok(kick_user::Response)) + Ok(kick_user::Response.into()) } #[post("/_matrix/client/r0/rooms/<_room_id>/ban", data = "")] @@ -1530,39 +1422,46 @@ pub fn ban_user_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - let state = db.rooms.room_state(&body.room_id).unwrap(); + let state = db.rooms.room_state(&body.room_id)?; - let mut event = - serde_json::from_value::>( - state - .get(&(EventType::RoomMember, user_id.to_string())) - .unwrap() // TODO: error handling - .content - .clone(), - ) - .unwrap() - .deserialize() - .unwrap(); - - event.membership = ruma::events::room::member::MembershipState::Ban; // TODO: reason - db.rooms - .append_pdu( - body.room_id.clone(), - user_id.clone(), // Sender - EventType::RoomMember, - serde_json::to_value(event).unwrap(), - None, - Some(body.body.user_id.to_string()), - None, - &db.globals, - ) - .unwrap(); + let event = state + .get(&(EventType::RoomMember, user_id.to_string())) + .map_or( + Ok::<_, Error>(member::MemberEventContent { + membership: member::MembershipState::Ban, + displayname: db.users.displayname(&user_id)?, + avatar_url: db.users.avatar_url(&user_id)?, + is_direct: None, + third_party_invite: None, + }), + |event| { + let mut event = serde_json::from_value::>( + event.content.clone(), + ) + .map_err(|_| Error::BadDatabase("Invalid member event in database."))? + .deserialize() + .map_err(|_| Error::BadDatabase("Invalid member event in database."))?; + event.membership = ruma::events::room::member::MembershipState::Ban; + Ok(event) + }, + )?; - MatrixResult(Ok(ban_user::Response)) + db.rooms.append_pdu( + body.room_id.clone(), + user_id.clone(), // Sender + EventType::RoomMember, + serde_json::to_value(event).expect("event is valid, we just created it"), + None, + Some(body.body.user_id.to_string()), + None, + &db.globals, + )?; + + Ok(ban_user::Response.into()) } #[post("/_matrix/client/r0/rooms/<_room_id>/unban", data = "")] @@ -1570,38 +1469,39 @@ pub fn unban_user_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - let state = db.rooms.room_state(&body.room_id).unwrap(); + let state = db.rooms.room_state(&body.room_id)?; let mut event = serde_json::from_value::>( state .get(&(EventType::RoomMember, user_id.to_string())) - .unwrap() // TODO: error handling + .ok_or(Error::BadRequest( + ErrorKind::BadState, + "Cannot unban a user who is not banned.", + ))? .content .clone(), ) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid member event in database."))? .deserialize() - .unwrap(); + .map_err(|_| Error::BadDatabase("Invalid member event in database."))?; event.membership = ruma::events::room::member::MembershipState::Leave; - db.rooms - .append_pdu( - body.room_id.clone(), - user_id.clone(), // Sender - EventType::RoomMember, - serde_json::to_value(event).unwrap(), - None, - Some(body.body.user_id.to_string()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + body.room_id.clone(), + user_id.clone(), // Sender + EventType::RoomMember, + serde_json::to_value(event).expect("event is valid, we just created it"), + None, + Some(body.body.user_id.to_string()), + None, + &db.globals, + )?; - MatrixResult(Ok(unban_user::Response)) + Ok(unban_user::Response.into()) } #[post("/_matrix/client/r0/rooms/<_room_id>/forget", data = "")] @@ -1609,12 +1509,12 @@ pub fn forget_room_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - db.rooms.forget(&body.room_id, &user_id).unwrap(); + db.rooms.forget(&body.room_id, &user_id)?; - MatrixResult(Ok(forget_room::Response)) + Ok(forget_room::Response.into()) } #[post("/_matrix/client/r0/rooms/<_room_id>/invite", data = "")] @@ -1622,35 +1522,29 @@ pub fn invite_user_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { if let invite_user::InvitationRecipient::UserId { user_id } = &body.recipient { - db.rooms - .append_pdu( - body.room_id.clone(), - body.user_id.clone().expect("user is authenticated"), - EventType::RoomMember, - serde_json::to_value(member::MemberEventContent { - membership: member::MembershipState::Invite, - displayname: db.users.displayname(&user_id).unwrap(), - avatar_url: db.users.avatar_url(&user_id).unwrap(), - is_direct: None, - third_party_invite: None, - }) - .unwrap(), - None, - Some(user_id.to_string()), - None, - &db.globals, - ) - .unwrap(); + db.rooms.append_pdu( + body.room_id.clone(), + body.user_id.clone().expect("user is authenticated"), + EventType::RoomMember, + serde_json::to_value(member::MemberEventContent { + membership: member::MembershipState::Invite, + displayname: db.users.displayname(&user_id)?, + avatar_url: db.users.avatar_url(&user_id)?, + is_direct: None, + third_party_invite: None, + }) + .expect("event is valid, we just created it"), + None, + Some(user_id.to_string()), + None, + &db.globals, + )?; - MatrixResult(Ok(invite_user::Response)) + Ok(invite_user::Response.into()) } else { - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "User not found.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) + Err(Error::BadRequest(ErrorKind::NotFound, "User not found.")) } } @@ -1659,13 +1553,13 @@ pub async fn set_room_visibility_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { match body.visibility { - room::Visibility::Public => db.rooms.set_public(&body.room_id, true).unwrap(), - room::Visibility::Private => db.rooms.set_public(&body.room_id, false).unwrap(), + room::Visibility::Public => db.rooms.set_public(&body.room_id, true)?, + room::Visibility::Private => db.rooms.set_public(&body.room_id, false)?, } - MatrixResult(Ok(set_room_visibility::Response)) + Ok(set_room_visibility::Response.into()) } #[get("/_matrix/client/r0/directory/list/room/<_room_id>", data = "")] @@ -1673,21 +1567,22 @@ pub async fn get_room_visibility_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { - MatrixResult(Ok(get_room_visibility::Response { - visibility: if db.rooms.is_public_room(&body.room_id).unwrap() { +) -> ConduitResult { + Ok(get_room_visibility::Response { + visibility: if db.rooms.is_public_room(&body.room_id)? { room::Visibility::Public } else { room::Visibility::Private }, - })) + } + .into()) } #[get("/_matrix/client/r0/publicRooms", data = "")] pub async fn get_public_rooms_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { let Ruma { body: get_public_rooms::Request { @@ -1700,7 +1595,12 @@ pub async fn get_public_rooms_route( json_body, } = body; - let response = get_public_rooms_filtered_route( + let get_public_rooms_filtered::Response { + chunk, + prev_batch, + next_batch, + total_room_count_estimate, + } = get_public_rooms_filtered_route( db, Ruma { body: get_public_rooms_filtered::Request { @@ -1715,99 +1615,94 @@ pub async fn get_public_rooms_route( json_body, }, ) - .await; + .await? + .0; - MatrixResult(match response.0 { - Ok(get_public_rooms_filtered::Response { - chunk, - prev_batch, - next_batch, - total_room_count_estimate, - }) => Ok(get_public_rooms::Response { - chunk, - prev_batch, - next_batch, - total_room_count_estimate, - }), - Err(e) => Err(e), - }) + Ok(get_public_rooms::Response { + chunk, + prev_batch, + next_batch, + total_room_count_estimate, + } + .into()) } #[post("/_matrix/client/r0/publicRooms", data = "")] pub async fn get_public_rooms_filtered_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { let mut chunk = db .rooms .public_rooms() .map(|room_id| { - let room_id = room_id.unwrap(); + let room_id = room_id?; - let state = db.rooms.room_state(&room_id).unwrap(); + let state = db.rooms.room_state(&room_id)?; - directory::PublicRoomsChunk { + let chunk = directory::PublicRoomsChunk { aliases: Vec::new(), - canonical_alias: state.get(&(EventType::RoomCanonicalAlias, "".to_owned())).and_then(|s| { - serde_json::from_value::< + canonical_alias: state.get(&(EventType::RoomCanonicalAlias, "".to_owned())).map_or(Ok::<_, Error>(None), |s| { + Ok(serde_json::from_value::< EventJson, >(s.content.clone()) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid canonical alias event in database."))? .deserialize() - .unwrap() - .alias - }), - name: state.get(&(EventType::RoomName, "".to_owned())).map(|s| { - serde_json::from_value::>( + .map_err(|_| Error::BadDatabase("Invalid canonical alias event in database."))? + .alias) + })?, + name: state.get(&(EventType::RoomName, "".to_owned())).map_or(Ok::<_, Error>(None), |s| { + Ok(serde_json::from_value::>( s.content.clone(), ) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid room name event in database."))? .deserialize() - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid room name event in database."))? .name() - .unwrap() - .to_owned() - }), + .map(|n| n.to_owned())) + })?, num_joined_members: (db.rooms.room_members(&room_id).count() as u32).into(), room_id, - topic: state.get(&(EventType::RoomTopic, "".to_owned())).map(|s| { - serde_json::from_value::< + topic: state.get(&(EventType::RoomTopic, "".to_owned())).map_or(Ok::<_, Error>(None), |s| { + Ok(Some(serde_json::from_value::< EventJson, >(s.content.clone()) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid room topic event in database."))? .deserialize() - .unwrap() - .topic - }), - world_readable: state.get(&(EventType::RoomHistoryVisibility, "".to_owned())).map_or(false, |s| { - serde_json::from_value::< + .map_err(|_| Error::BadDatabase("Invalid room topic event in database."))? + .topic)) + })?, + world_readable: state.get(&(EventType::RoomHistoryVisibility, "".to_owned())).map_or(Ok::<_, Error>(false), |s| { + Ok(serde_json::from_value::< EventJson, >(s.content.clone()) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid room history visibility event in database."))? .deserialize() - .unwrap() - .history_visibility == history_visibility::HistoryVisibility::WorldReadable - }), - guest_can_join: state.get(&(EventType::RoomGuestAccess, "".to_owned())).map_or(false, |s| { - serde_json::from_value::< + .map_err(|_| Error::BadDatabase("Invalid room history visibility event in database."))? + .history_visibility == history_visibility::HistoryVisibility::WorldReadable) + })?, + guest_can_join: state.get(&(EventType::RoomGuestAccess, "".to_owned())).map_or(Ok::<_, Error>(false), |s| { + Ok(serde_json::from_value::< EventJson, >(s.content.clone()) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid room guest access event in database."))? .deserialize() - .unwrap() - .guest_access == guest_access::GuestAccess::CanJoin - }), - avatar_url: state.get(&(EventType::RoomAvatar, "".to_owned())).map(|s| { - serde_json::from_value::< + .map_err(|_| Error::BadDatabase("Invalid room guest access event in database."))? + .guest_access == guest_access::GuestAccess::CanJoin) + })?, + avatar_url: state.get(&(EventType::RoomAvatar, "".to_owned())).map_or( Ok::<_, Error>(None),|s| { + Ok(Some(serde_json::from_value::< EventJson, >(s.content.clone()) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid room avatar event in database."))? .deserialize() - .unwrap() - .url - }), - } + .map_err(|_| Error::BadDatabase("Invalid room avatar event in database."))? + .url)) + })?, + }; + Ok::<_, Error>(chunk) }) + .filter_map(|r| r.ok()) // Filter out buggy rooms .collect::>(); chunk.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members)); @@ -1824,38 +1719,42 @@ pub async fn get_public_rooms_filtered_route( }, ) .await - .unwrap() + ? .chunk .into_iter() - .map(|c| serde_json::from_str(&serde_json::to_string(&c).unwrap()).unwrap()) + .map(|c| serde_json::from_str(&serde_json::to_string(&c)?)?) .collect::>(), ); */ let total_room_count_estimate = (chunk.len() as u32).into(); - MatrixResult(Ok(get_public_rooms_filtered::Response { + Ok(get_public_rooms_filtered::Response { chunk, prev_batch: None, next_batch: None, total_room_count_estimate: Some(total_room_count_estimate), - })) + } + .into()) } #[post("/_matrix/client/r0/user_directory/search", data = "")] pub fn search_users_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { - MatrixResult(Ok(search_users::Response { +) -> ConduitResult { + Ok(search_users::Response { results: db .users .iter() - .map(Result::unwrap) - .map(|user_id| search_users::User { - user_id: user_id.clone(), - display_name: db.users.displayname(&user_id).unwrap(), - avatar_url: db.users.avatar_url(&user_id).unwrap(), + .filter_map(|user_id| { + // Filter out buggy users (they should not exist, but you never know...) + let user_id = user_id.ok()?; + Some(search_users::User { + user_id: user_id.clone(), + display_name: db.users.displayname(&user_id).ok()?, + avatar_url: db.users.avatar_url(&user_id).ok()?, + }) }) .filter(|user| { user.user_id.to_string().contains(&body.search_term) @@ -1867,21 +1766,23 @@ pub fn search_users_route( }) .collect(), limited: false, - })) + } + .into()) } #[get("/_matrix/client/r0/rooms/<_room_id>/members")] -pub fn get_member_events_route(_room_id: String) -> MatrixResult { +pub fn get_member_events_route(_room_id: String) -> ConduitResult { warn!("TODO: get_member_events_route"); - MatrixResult(Ok(get_member_events::Response { chunk: Vec::new() })) + Ok(get_member_events::Response { chunk: Vec::new() }.into()) } #[get("/_matrix/client/r0/thirdparty/protocols")] -pub fn get_protocols_route() -> MatrixResult { +pub fn get_protocols_route() -> ConduitResult { warn!("TODO: get_protocols_route"); - MatrixResult(Ok(get_protocols::Response { + Ok(get_protocols::Response { protocols: BTreeMap::new(), - })) + } + .into()) } #[put( @@ -1894,30 +1795,29 @@ pub fn create_message_event_route( _room_id: String, _event_type: String, _txn_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); let mut unsigned = serde_json::Map::new(); unsigned.insert("transaction_id".to_owned(), body.txn_id.clone().into()); - if let Ok(event_id) = db.rooms.append_pdu( + let event_id = db.rooms.append_pdu( body.room_id.clone(), user_id.clone(), body.event_type.clone(), - serde_json::from_str(body.json_body.unwrap().get()).unwrap(), + serde_json::from_str( + body.json_body + .ok_or(Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))? + .get(), + ) + .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?, Some(unsigned), None, None, &db.globals, - ) { - MatrixResult(Ok(create_message_event::Response { event_id })) - } else { - MatrixResult(Err(Error { - kind: ErrorKind::Unknown, - message: "Failed to send message.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) - } + )?; + + Ok(create_message_event::Response { event_id }.into()) } #[put( @@ -1930,19 +1830,24 @@ pub fn create_state_event_for_key_route( _room_id: String, _event_type: String, _state_key: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - let content = - serde_json::from_str::(body.json_body.clone().unwrap().get()).unwrap(); + let content = serde_json::from_str::( + body.json_body + .as_ref() + .ok_or(Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))? + .get(), + ) + .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?; if body.event_type == EventType::RoomCanonicalAlias { let canonical_alias = serde_json::from_value::< EventJson, >(content.clone()) - .unwrap() + .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid canonical alias."))? .deserialize() - .unwrap(); + .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid canonical alias."))?; let mut aliases = canonical_alias.alt_aliases; @@ -1954,21 +1859,16 @@ pub fn create_state_event_for_key_route( if alias.server_name() != db.globals.server_name() || db .rooms - .id_from_alias(&alias) - .unwrap() + .id_from_alias(&alias)? .filter(|room| room == &body.room_id) // Make sure it's the right room .is_none() { - return MatrixResult(Err(Error { - kind: ErrorKind::Unknown, - message: "You are only allowed to send canonical_alias events when it's aliases already exists".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); + return Err(Error::BadRequest(ErrorKind::Forbidden, "You are only allowed to send canonical_alias events when it's aliases already exists")); } } } - if let Ok(event_id) = db.rooms.append_pdu( + let event_id = db.rooms.append_pdu( body.room_id.clone(), user_id.clone(), body.event_type.clone(), @@ -1977,15 +1877,9 @@ pub fn create_state_event_for_key_route( Some(body.state_key.clone()), None, &db.globals, - ) { - MatrixResult(Ok(create_state_event_for_key::Response { event_id })) - } else { - MatrixResult(Err(Error { - kind: ErrorKind::Unknown, - message: "Failed to send event.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) - } + )?; + + Ok(create_state_event_for_key::Response { event_id }.into()) } #[put( @@ -1997,7 +1891,7 @@ pub fn create_state_event_for_empty_key_route( body: Ruma, _room_id: String, _event_type: String, -) -> MatrixResult { +) -> ConduitResult { // This just calls create_state_event_for_key_route let Ruma { body: @@ -2011,30 +1905,28 @@ pub fn create_state_event_for_empty_key_route( json_body, } = body; - let response = create_state_event_for_key_route( - db, - Ruma { - body: create_state_event_for_key::Request { - room_id, - event_type, - data, - state_key: "".to_owned(), + Ok(create_state_event_for_empty_key::Response { + event_id: create_state_event_for_key_route( + db, + Ruma { + body: create_state_event_for_key::Request { + room_id, + event_type, + data, + state_key: "".to_owned(), + }, + user_id, + device_id, + json_body, }, - user_id, - device_id, - json_body, - }, - _room_id, - _event_type, - "".to_owned(), - ); - - MatrixResult(match response.0 { - Ok(create_state_event_for_key::Response { event_id }) => { - Ok(create_state_event_for_empty_key::Response { event_id }) - } - Err(e) => Err(e), - }) + _room_id, + _event_type, + "".to_owned(), + )? + .0 + .event_id, + } + .into()) } #[get("/_matrix/client/r0/rooms/<_room_id>/state", data = "")] @@ -2042,26 +1934,25 @@ pub fn get_state_events_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if !db.rooms.is_joined(user_id, &body.room_id).unwrap() { - return MatrixResult(Err(Error { - kind: ErrorKind::Forbidden, - message: "You don't have permission to view the room state.".to_owned(), - status_code: http::StatusCode::FORBIDDEN, - })); + if !db.rooms.is_joined(user_id, &body.room_id)? { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "You don't have permission to view the room state.", + )); } - MatrixResult(Ok(get_state_events::Response { + Ok(get_state_events::Response { room_state: db .rooms - .room_state(&body.room_id) - .unwrap() + .room_state(&body.room_id)? .values() .map(|pdu| pdu.to_state_event()) .collect(), - })) + } + .into()) } #[get( @@ -2074,33 +1965,30 @@ pub fn get_state_events_for_key_route( _room_id: String, _event_type: String, _state_key: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if !db.rooms.is_joined(user_id, &body.room_id).unwrap() { - return MatrixResult(Err(Error { - kind: ErrorKind::Forbidden, - message: "You don't have permission to view the room state.".to_owned(), - status_code: http::StatusCode::FORBIDDEN, - })); + if !db.rooms.is_joined(user_id, &body.room_id)? { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "You don't have permission to view the room state.", + )); } - if let Some(event) = db - .rooms - .room_state(&body.room_id) - .unwrap() + let state = db.rooms.room_state(&body.room_id)?; + + let event = state .get(&(body.event_type.clone(), body.state_key.clone())) - { - MatrixResult(Ok(get_state_events_for_key::Response { - content: serde_json::value::to_raw_value(&event.content).unwrap(), - })) - } else { - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "State event not found.".to_owned(), - status_code: http::StatusCode::NOT_FOUND, - })) + .ok_or(Error::BadRequest( + ErrorKind::NotFound, + "State event not found.", + ))?; + + Ok(get_state_events_for_key::Response { + content: serde_json::value::to_raw_value(&event.content) + .map_err(|_| Error::BadDatabase("Invalid event content in database"))?, } + .into()) } #[get( @@ -2112,45 +2000,42 @@ pub fn get_state_events_for_empty_key_route( body: Ruma, _room_id: String, _event_type: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if !db.rooms.is_joined(user_id, &body.room_id).unwrap() { - return MatrixResult(Err(Error { - kind: ErrorKind::Forbidden, - message: "You don't have permission to view the room state.".to_owned(), - status_code: http::StatusCode::FORBIDDEN, - })); + if !db.rooms.is_joined(user_id, &body.room_id)? { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "You don't have permission to view the room state.", + )); } - if let Some(event) = db - .rooms - .room_state(&body.room_id) - .unwrap() + let state = db.rooms.room_state(&body.room_id)?; + + let event = state .get(&(body.event_type.clone(), "".to_owned())) - { - MatrixResult(Ok(get_state_events_for_key::Response { - content: serde_json::value::to_raw_value(event).unwrap(), - })) - } else { - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "State event not found.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) + .ok_or(Error::BadRequest( + ErrorKind::NotFound, + "State event not found.", + ))?; + + Ok(get_state_events_for_empty_key::Response { + content: serde_json::value::to_raw_value(event) + .map_err(|_| Error::BadDatabase("Invalid event content in database"))?, } + .into()) } #[get("/_matrix/client/r0/sync", data = "")] pub fn sync_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { std::thread::sleep(Duration::from_millis(1000)); let user_id = body.user_id.as_ref().expect("user is authenticated"); let device_id = body.device_id.as_ref().expect("user is authenticated"); - let next_batch = db.globals.current_count().unwrap().to_string(); + let next_batch = db.globals.current_count()?.to_string(); let mut joined_rooms = BTreeMap::new(); let since = body @@ -2160,13 +2045,12 @@ pub fn sync_route( .unwrap_or(0); for room_id in db.rooms.rooms_joined(&user_id) { - let room_id = room_id.unwrap(); + let room_id = room_id?; let mut pdus = db .rooms - .pdus_since(&room_id, since) - .unwrap() - .map(|r| r.unwrap()) + .pdus_since(&room_id, since)? + .filter_map(|r| r.ok()) // Filter out buggy events .collect::>(); let mut send_member_count = false; @@ -2180,9 +2064,9 @@ pub fn sync_route( let content = serde_json::from_value::< EventJson, >(pdu.content.clone()) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid PDU in database."))? .deserialize() - .unwrap(); + .map_err(|_| Error::BadDatabase("Invalid PDU in database."))?; if content.membership == ruma::events::room::member::MembershipState::Join { joined_since_last_sync = true; // Both send_member_count and joined_since_last_sync are set. There's nothing more @@ -2193,7 +2077,7 @@ pub fn sync_route( } } - let state = db.rooms.room_state(&room_id).unwrap(); + let state = db.rooms.room_state(&room_id)?; let (joined_member_count, invited_member_count, heroes) = if send_member_count { let joined_member_count = db.rooms.room_members(&room_id).count(); @@ -2208,51 +2092,54 @@ pub fn sync_route( for hero in db .rooms - .all_pdus(&room_id) - .unwrap() + .all_pdus(&room_id)? .filter_map(|pdu| pdu.ok()) // Ignore all broken pdus .filter(|pdu| pdu.kind == EventType::RoomMember) - .filter_map(|pdu| { + .map(|pdu| { let content = serde_json::from_value::< EventJson, >(pdu.content.clone()) - .unwrap() + .map_err(|_| Error::BadDatabase("Invalid member event in database."))? .deserialize() - .unwrap(); + .map_err(|_| Error::BadDatabase("Invalid member event in database."))?; - let current_content = serde_json::from_value::< - EventJson, - >( - state - .get(&( - EventType::RoomMember, - pdu.state_key.clone().expect( - "TODO: error handling. Is it really a state event?", - ), - )) - .expect("a user that joined once will always have a member event") - .content - .clone(), - ) - .unwrap() - .deserialize() - .unwrap(); + if let Some(state_key) = &pdu.state_key { + let current_content = serde_json::from_value::< + EventJson, + >( + state + .get(&(EventType::RoomMember, state_key.clone())) + .ok_or(Error::BadDatabase( + "A user that joined once has no member event anymore.", + ))? + .content + .clone(), + ) + .map_err(|_| Error::BadDatabase("Invalid member event in database."))? + .deserialize() + .map_err(|_| Error::BadDatabase("Invalid member event in database."))?; - // The membership was and still is invite or join - if matches!( - content.membership, - ruma::events::room::member::MembershipState::Join - | ruma::events::room::member::MembershipState::Invite - ) && matches!( - current_content.membership, - ruma::events::room::member::MembershipState::Join - | ruma::events::room::member::MembershipState::Invite - ) { - Some(pdu.state_key.unwrap()) + // The membership was and still is invite or join + if matches!( + content.membership, + ruma::events::room::member::MembershipState::Join + | ruma::events::room::member::MembershipState::Invite + ) && matches!( + current_content.membership, + ruma::events::room::member::MembershipState::Join + | ruma::events::room::member::MembershipState::Invite + ) { + Ok::<_, Error>(Some(state_key.clone())) + } else { + Ok(None) + } } else { - None + Ok(None) } }) + .filter_map(|u| u.ok()) // Filter out buggy users + // Filter for possible heroes + .filter_map(|u| u) { if heroes.contains(&hero) || hero == user_id.to_string() { continue; @@ -2272,14 +2159,14 @@ pub fn sync_route( }; let notification_count = if send_notification_counts { - if let Some(last_read) = db.rooms.edus.room_read_get(&room_id, &user_id).unwrap() { + if let Some(last_read) = db.rooms.edus.room_read_get(&room_id, &user_id)? { Some( (db.rooms - .pdus_since(&room_id, last_read) - .unwrap() + .pdus_since(&room_id, last_read)? + .filter_map(|pdu| pdu.ok()) // Filter out buggy events .filter(|pdu| { matches!( - pdu.as_ref().unwrap().kind.clone(), + pdu.kind.clone(), EventType::RoomMessage | EventType::RoomEncrypted ) }) @@ -2301,10 +2188,14 @@ pub fn sync_route( 0 })); - let prev_batch = pdus - .first() - .and_then(|e| db.rooms.get_pdu_count(&e.event_id).unwrap()) - .map(|c| c.to_string()); + let prev_batch = pdus.first().map_or(Ok::<_, Error>(None), |e| { + Ok(Some( + db.rooms + .get_pdu_count(&e.event_id)? + .ok_or(Error::BadDatabase("Can't find count from event in db."))? + .to_string(), + )) + })?; let room_events = pdus .into_iter() @@ -2314,26 +2205,24 @@ pub fn sync_route( let mut edus = db .rooms .edus - .roomlatests_since(&room_id, since) - .unwrap() - .map(|r| r.unwrap()) + .roomlatests_since(&room_id, since)? + .filter_map(|r| r.ok()) // Filter out buggy events .collect::>(); if db .rooms .edus - .last_roomactive_update(&room_id, &db.globals) - .unwrap() + .last_roomactive_update(&room_id, &db.globals)? > since { edus.push( serde_json::from_str( &serde_json::to_string(&EduEvent::Typing( - db.rooms.edus.roomactives_all(&room_id).unwrap(), + db.rooms.edus.roomactives_all(&room_id)?, )) - .unwrap(), + .expect("event is valid, we just created it"), ) - .unwrap(), + .expect("event is valid, we just created it"), ); } @@ -2341,8 +2230,7 @@ pub fn sync_route( account_data: sync_events::AccountData { events: db .account_data - .changes_since(Some(&room_id), &user_id, since) - .unwrap() + .changes_since(Some(&room_id), &user_id, since)? .into_iter() .map(|(_, v)| v) .collect(), @@ -2380,40 +2268,41 @@ pub fn sync_route( }; if !joined_room.is_empty() { - joined_rooms.insert(room_id.clone().try_into().unwrap(), joined_room); + joined_rooms.insert(room_id.clone(), joined_room); } } let mut left_rooms = BTreeMap::new(); for room_id in db.rooms.rooms_left(&user_id) { - let room_id = room_id.unwrap(); - let pdus = db.rooms.pdus_since(&room_id, since).unwrap(); - let room_events = pdus.map(|pdu| pdu.unwrap().to_room_event()).collect(); + let room_id = room_id?; + let pdus = db.rooms.pdus_since(&room_id, since)?; + let room_events = pdus + .filter_map(|pdu| pdu.ok()) // Filter out buggy events + .map(|pdu| pdu.to_room_event()) + .collect(); // TODO: Only until leave point let mut edus = db .rooms .edus - .roomlatests_since(&room_id, since) - .unwrap() - .map(|r| r.unwrap()) + .roomlatests_since(&room_id, since)? + .filter_map(|r| r.ok()) // Filter out buggy events .collect::>(); if db .rooms .edus - .last_roomactive_update(&room_id, &db.globals) - .unwrap() + .last_roomactive_update(&room_id, &db.globals)? > since { edus.push( serde_json::from_str( &serde_json::to_string(&EduEvent::Typing( - db.rooms.edus.roomactives_all(&room_id).unwrap(), + db.rooms.edus.roomactives_all(&room_id)?, )) - .unwrap(), + .expect("event is valid, we just created it"), ) - .unwrap(), + .expect("event is valid, we just created it"), ); } @@ -2428,20 +2317,19 @@ pub fn sync_route( }; if !left_room.is_empty() { - left_rooms.insert(room_id.clone().try_into().unwrap(), left_room); + left_rooms.insert(room_id.clone(), left_room); } } let mut invited_rooms = BTreeMap::new(); for room_id in db.rooms.rooms_invited(&user_id) { - let room_id = room_id.unwrap(); + let room_id = room_id?; let invited_room = sync_events::InvitedRoom { invite_state: sync_events::InviteState { events: db .rooms - .room_state(&room_id) - .unwrap() + .room_state(&room_id)? .into_iter() .map(|(_, pdu)| pdu.to_stripped_state_event()) .collect(), @@ -2453,7 +2341,7 @@ pub fn sync_route( } } - MatrixResult(Ok(sync_events::Response { + Ok(sync_events::Response { next_batch, rooms: sync_events::Rooms { leave: left_rooms, @@ -2463,24 +2351,27 @@ pub fn sync_route( presence: sync_events::Presence { events: db .global_edus - .presence_since(since) - .unwrap() + .presence_since(since)? .map(|edu| { - let mut edu = edu.unwrap().deserialize().unwrap(); - let timestamp = edu.content.last_active_ago.unwrap(); - let last_active_ago = js_int::UInt::try_from(utils::millis_since_unix_epoch()) - .unwrap() - - timestamp; - edu.content.last_active_ago = Some(last_active_ago); - edu.into() + let mut edu = edu? + .deserialize() + .map_err(|_| Error::BadDatabase("EDU in database is invalid."))?; + if let Some(timestamp) = edu.content.last_active_ago { + let last_active_ago = + js_int::UInt::try_from(utils::millis_since_unix_epoch()) + .expect("time is valid") + - timestamp; + edu.content.last_active_ago = Some(last_active_ago); + } + Ok::<_, Error>(edu.into()) }) + .filter_map(|edu| edu.ok()) // Filter out buggy events .collect(), }, account_data: sync_events::AccountData { events: db .account_data - .changes_since(None, &user_id, since) - .unwrap() + .changes_since(None, &user_id, since)? .into_iter() .map(|(_, v)| v) .collect(), @@ -2489,8 +2380,8 @@ pub fn sync_route( changed: if since != 0 { db.users .device_keys_changed(since) - .map(|u| u.unwrap()) - .collect() + .filter_map(|u| u.ok()) + .collect() // Filter out buggy events } else { Vec::new() }, @@ -2498,12 +2389,10 @@ pub fn sync_route( }, device_one_time_keys_count: Default::default(), // TODO to_device: sync_events::ToDevice { - events: db - .users - .take_to_device_events(user_id, device_id, 100) - .unwrap(), + events: db.users.take_to_device_events(user_id, device_id, 100)?, }, - })) + } + .into()) } #[get( @@ -2515,81 +2404,96 @@ pub fn get_context_route( body: Ruma, _room_id: String, _event_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if !db.rooms.is_joined(user_id, &body.room_id).unwrap() { - return MatrixResult(Err(Error { - kind: ErrorKind::Forbidden, - message: "You don't have permission to view this room.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); + if !db.rooms.is_joined(user_id, &body.room_id)? { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "You don't have permission to view this room.", + )); } - if let Some(base_event) = db.rooms.get_pdu(&body.event_id).unwrap() { - let base_event = base_event.to_room_event(); + let base_event = db + .rooms + .get_pdu(&body.event_id)? + .ok_or(Error::BadRequest( + ErrorKind::NotFound, + "Base event not found.", + ))? + .to_room_event(); - let base_token = db + let base_token = db + .rooms + .get_pdu_count(&body.event_id)? + .expect("event still exists"); + + let events_before = db + .rooms + .pdus_until(&body.room_id, base_token) + .take( + u32::try_from(body.limit).map_err(|_| { + Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.") + })? as usize + / 2, + ) + .filter_map(|r| r.ok()) // Remove buggy events + .collect::>(); + + let start_token = events_before.last().map_or(Ok(None), |e| { + Ok::<_, Error>(Some( + db.rooms + .get_pdu_count(&e.event_id)? + .ok_or(Error::BadDatabase("Can't find count from event in db."))? + .to_string(), + )) + })?; + + let events_before = events_before + .into_iter() + .map(|pdu| pdu.to_room_event()) + .collect::>(); + + let events_after = db + .rooms + .pdus_after(&body.room_id, base_token) + .take( + u32::try_from(body.limit).map_err(|_| { + Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.") + })? as usize + / 2, + ) + .filter_map(|r| r.ok()) // Remove buggy events + .collect::>(); + + let end_token = events_after.last().map_or(Ok(None), |e| { + Ok::<_, Error>(Some( + db.rooms + .get_pdu_count(&e.event_id)? + .ok_or(Error::BadDatabase("Can't find count from event in db."))? + .to_string(), + )) + })?; + + let events_after = events_after + .into_iter() + .map(|pdu| pdu.to_room_event()) + .collect::>(); + + Ok(get_context::Response { + start: start_token, + end: end_token, + events_before, + event: Some(base_event), + events_after, + state: db // TODO: State at event .rooms - .get_pdu_count(&body.event_id) - .unwrap() - .expect("event exists, so count should exist too"); - - let events_before = db - .rooms - .pdus_until(&body.room_id, base_token) - .take(u32::try_from(body.limit).unwrap() as usize / 2) - .map(|r| r.unwrap()) - .collect::>(); - - let start_token = events_before - .last() - .and_then(|e| db.rooms.get_pdu_count(&e.event_id).unwrap()) - .map(|c| c.to_string()); - - let events_before = events_before - .into_iter() - .map(|pdu| pdu.to_room_event()) - .collect::>(); - - let events_after = db - .rooms - .pdus_after(&body.room_id, base_token) - .take(u32::try_from(body.limit).unwrap() as usize / 2) - .map(|r| r.unwrap()) - .collect::>(); - - let end_token = events_after - .last() - .and_then(|e| db.rooms.get_pdu_count(&e.event_id).unwrap()) - .map(|c| c.to_string()); - - let events_after = events_after - .into_iter() - .map(|pdu| pdu.to_room_event()) - .collect::>(); - - MatrixResult(Ok(get_context::Response { - start: start_token, - end: end_token, - events_before, - event: Some(base_event), - events_after, - state: db // TODO: State at event - .rooms - .room_state(&body.room_id) - .unwrap() - .values() - .map(|pdu| pdu.to_state_event()) - .collect(), - })) - } else { - MatrixResult(Err(Error { - kind: ErrorKind::Unknown, - message: "Invalid base event.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) + .room_state(&body.room_id)? + .values() + .map(|pdu| pdu.to_state_event()) + .collect(), } + .into()) } #[get("/_matrix/client/r0/rooms/<_room_id>/messages", data = "")] @@ -2597,96 +2501,109 @@ pub fn get_message_events_route( db: State<'_, Database>, body: Ruma, _room_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if !db.rooms.is_joined(user_id, &body.room_id).unwrap() { - return MatrixResult(Err(Error { - kind: ErrorKind::Forbidden, - message: "You don't have permission to view this room.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })); + if !db.rooms.is_joined(user_id, &body.room_id)? { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "You don't have permission to view this room.", + )); } - if let Ok(from) = body.from.clone().parse() { - match body.dir { - get_message_events::Direction::Forward => { - let events_after = db - .rooms - .pdus_after(&body.room_id, from) - .take(body.limit.map(|l| l.try_into().unwrap()).unwrap_or(10_u32) as usize) - .map(|r| r.unwrap()) - .collect::>(); + let from = body + .from + .clone() + .parse() + .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from` value."))?; + match body.dir { + get_message_events::Direction::Forward => { + let events_after = db + .rooms + .pdus_after(&body.room_id, from) + // Use limit or else 10 + .take(body.limit.map_or(Ok::<_, Error>(10_usize), |l| { + Ok(u32::try_from(l).map_err(|_| { + Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.") + })? as usize) + })?) + .filter_map(|r| r.ok()) // Filter out buggy events + .collect::>(); - let end_token = events_after - .last() - .and_then(|e| db.rooms.get_pdu_count(&e.event_id).unwrap()) - .map(|c| c.to_string()); + let end_token = events_after.last().map_or(Ok::<_, Error>(None), |e| { + Ok(Some( + db.rooms + .get_pdu_count(&e.event_id)? + .ok_or(Error::BadDatabase("Can't find count from event in db."))? + .to_string(), + )) + })?; - let events_after = events_after - .into_iter() - .map(|pdu| pdu.to_room_event()) - .collect::>(); + let events_after = events_after + .into_iter() + .map(|pdu| pdu.to_room_event()) + .collect::>(); - MatrixResult(Ok(get_message_events::Response { - start: Some(body.from.clone()), - end: end_token, - chunk: events_after, - state: Vec::new(), - })) - } - get_message_events::Direction::Backward => { - let events_before = db - .rooms - .pdus_until(&body.room_id, from) - .take(body.limit.map(|l| l.try_into().unwrap()).unwrap_or(10_u32) as usize) - .map(|r| r.unwrap()) - .collect::>(); - - let start_token = events_before - .last() - .and_then(|e| db.rooms.get_pdu_count(&e.event_id).unwrap()) - .map(|c| c.to_string()); - - let events_before = events_before - .into_iter() - .map(|pdu| pdu.to_room_event()) - .collect::>(); - - MatrixResult(Ok(get_message_events::Response { - start: Some(body.from.clone()), - end: start_token, - chunk: events_before, - state: Vec::new(), - })) + Ok(get_message_events::Response { + start: Some(body.from.clone()), + end: end_token, + chunk: events_after, + state: Vec::new(), } + .into()) + } + get_message_events::Direction::Backward => { + let events_before = db + .rooms + .pdus_until(&body.room_id, from) + // Use limit or else 10 + .take(body.limit.map_or(Ok::<_, Error>(10_usize), |l| { + Ok(u32::try_from(l).map_err(|_| { + Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.") + })? as usize) + })?) + .filter_map(|r| r.ok()) // Filter out buggy events + .collect::>(); + + let start_token = events_before.last().map_or(Ok::<_, Error>(None), |e| { + Ok(Some( + db.rooms + .get_pdu_count(&e.event_id)? + .ok_or(Error::BadDatabase("Can't find count from event in db."))? + .to_string(), + )) + })?; + + let events_before = events_before + .into_iter() + .map(|pdu| pdu.to_room_event()) + .collect::>(); + + Ok(get_message_events::Response { + start: Some(body.from.clone()), + end: start_token, + chunk: events_before, + state: Vec::new(), + } + .into()) } - } else { - MatrixResult(Err(Error { - kind: ErrorKind::Unknown, - message: "Invalid from.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) } } #[get("/_matrix/client/r0/voip/turnServer")] -pub fn turn_server_route() -> MatrixResult { - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "There is no turn server yet.".to_owned(), - status_code: http::StatusCode::NOT_FOUND, - })) +pub fn turn_server_route() -> ConduitResult { + Err(Error::BadRequest( + ErrorKind::NotFound, + "There is no turn server yet.", + )) } #[post("/_matrix/client/r0/publicised_groups")] -pub fn publicised_groups_route() -> MatrixResult { - warn!("TODO: publicised_groups_route"); - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "There are no publicised groups yet.".to_owned(), - status_code: http::StatusCode::NOT_FOUND, - })) +pub fn publicised_groups_route() -> ConduitResult { + Err(Error::BadRequest( + ErrorKind::NotFound, + "There are not publicised groups yet.", + )) } #[put( @@ -2698,72 +2615,72 @@ pub fn send_event_to_device_route( body: Ruma, _event_type: String, _txn_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); for (target_user_id, map) in &body.messages { for (target_device_id_maybe, event) in map { match target_device_id_maybe { - to_device::DeviceIdOrAllDevices::DeviceId(target_device_id) => db - .users - .add_to_device_event( + to_device::DeviceIdOrAllDevices::DeviceId(target_device_id) => { + db.users.add_to_device_event( user_id, &target_user_id, &target_device_id, &body.event_type, - serde_json::from_str(event.get()).unwrap(), + serde_json::from_str(event.get()).map_err(|_| { + Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid") + })?, &db.globals, - ) - .unwrap(), + )? + } to_device::DeviceIdOrAllDevices::AllDevices => { for target_device_id in db.users.all_device_ids(&target_user_id) { - db.users - .add_to_device_event( - user_id, - &target_user_id, - &target_device_id.unwrap(), - &body.event_type, - serde_json::from_str(event.get()).unwrap(), - &db.globals, - ) - .unwrap(); + db.users.add_to_device_event( + user_id, + &target_user_id, + &target_device_id?, + &body.event_type, + serde_json::from_str(event.get()).map_err(|_| { + Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid") + })?, + &db.globals, + )?; } } } } } - MatrixResult(Ok(send_event_to_device::Response)) + Ok(send_event_to_device::Response.into()) } #[get("/_matrix/media/r0/config")] -pub fn get_media_config_route() -> MatrixResult { - MatrixResult(Ok(get_media_config::Response { +pub fn get_media_config_route() -> ConduitResult { + Ok(get_media_config::Response { upload_size: (20_u32 * 1024 * 1024).into(), // 20 MB - })) + } + .into()) } #[post("/_matrix/media/r0/upload", data = "")] pub fn create_content_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { let mxc = format!( "mxc://{}/{}", db.globals.server_name(), utils::random_string(MXC_LENGTH) ); - db.media - .create( - mxc.clone(), - body.filename.as_ref(), - &body.content_type, - &body.file, - ) - .unwrap(); + db.media.create( + mxc.clone(), + body.filename.as_ref(), + &body.content_type, + &body.file, + )?; - MatrixResult(Ok(create_content::Response { content_uri: mxc })) + Ok(create_content::Response { content_uri: mxc }.into()) } #[get( @@ -2775,23 +2692,19 @@ pub fn get_content_route( body: Ruma, _server_name: String, _media_id: String, -) -> MatrixResult { +) -> ConduitResult { if let Some((filename, content_type, file)) = db .media - .get(format!("mxc://{}/{}", body.server_name, body.media_id)) - .unwrap() + .get(format!("mxc://{}/{}", body.server_name, body.media_id))? { - MatrixResult(Ok(get_content::Response { + Ok(get_content::Response { file, content_type, content_disposition: filename.unwrap_or_default(), // TODO: Spec says this should be optional - })) + } + .into()) } else { - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "Media not found.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) + Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")) } } @@ -2804,23 +2717,19 @@ pub fn get_content_thumbnail_route( body: Ruma, _server_name: String, _media_id: String, -) -> MatrixResult { - if let Some((_, content_type, file)) = db - .media - .get_thumbnail( - format!("mxc://{}/{}", body.server_name, body.media_id), - body.width.try_into().unwrap(), - body.height.try_into().unwrap(), - ) - .unwrap() - { - MatrixResult(Ok(get_content_thumbnail::Response { file, content_type })) +) -> ConduitResult { + if let Some((_, content_type, file)) = db.media.get_thumbnail( + format!("mxc://{}/{}", body.server_name, body.media_id), + body.width + .try_into() + .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?, + body.height + .try_into() + .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?, + )? { + Ok(get_content_thumbnail::Response { file, content_type }.into()) } else { - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "Media not found.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) + Err(Error::BadRequest(ErrorKind::NotFound, "Media not found.")) } } @@ -2828,16 +2737,16 @@ pub fn get_content_thumbnail_route( pub fn get_devices_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); let devices = db .users .all_devices_metadata(user_id) - .map(|r| r.unwrap()) + .filter_map(|r| r.ok()) // Filter out buggy devices .collect::>(); - MatrixResult(Ok(get_devices::Response { devices })) + Ok(get_devices::Response { devices }.into()) } #[get("/_matrix/client/r0/devices/<_device_id>", data = "")] @@ -2845,22 +2754,15 @@ pub fn get_device_route( db: State<'_, Database>, body: Ruma, _device_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); let device = db .users - .get_device_metadata(&user_id, &body.body.device_id) - .unwrap(); + .get_device_metadata(&user_id, &body.body.device_id)? + .ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?; - match device { - None => MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "Device not found".to_string(), - status_code: http::StatusCode::NOT_FOUND, - })), - Some(device) => MatrixResult(Ok(get_device::Response { device })), - } + Ok(get_device::Response { device }.into()) } #[put("/_matrix/client/r0/devices/<_device_id>", data = "")] @@ -2868,30 +2770,20 @@ pub fn update_device_route( db: State<'_, Database>, body: Ruma, _device_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - let device = db + let mut device = db .users - .get_device_metadata(&user_id, &body.body.device_id) - .unwrap(); + .get_device_metadata(&user_id, &body.body.device_id)? + .ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?; - match device { - None => MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "Device not found".to_string(), - status_code: http::StatusCode::NOT_FOUND, - })), - Some(mut device) => { - device.display_name = body.display_name.clone(); + device.display_name = body.display_name.clone(); - db.users - .update_device_metadata(&user_id, &body.body.device_id, &device) - .unwrap(); + db.users + .update_device_metadata(&user_id, &body.body.device_id, &device)?; - MatrixResult(Ok(update_device::Response)) - } - } + Ok(update_device::Response.into()) } #[delete("/_matrix/client/r0/devices/<_device_id>", data = "")] @@ -2899,7 +2791,7 @@ pub fn delete_device_route( db: State<'_, Database>, body: Ruma, _device_id: String, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); let device_id = body.device_id.as_ref().expect("user is authenticated"); @@ -2915,84 +2807,77 @@ pub fn delete_device_route( }; if let Some(auth) = &body.auth { - let (worked, uiaainfo) = db - .uiaa - .try_auth( - &user_id, - &device_id, - auth, - &uiaainfo, - &db.users, - &db.globals, - ) - .unwrap(); + let (worked, uiaainfo) = db.uiaa.try_auth( + &user_id, + &device_id, + auth, + &uiaainfo, + &db.users, + &db.globals, + )?; if !worked { - return MatrixResult(Err(UiaaResponse::AuthResponse(uiaainfo))); + return Err(Error::Uiaa(uiaainfo)); } // Success! } else { uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); - db.uiaa.create(&user_id, &device_id, &uiaainfo).unwrap(); - return MatrixResult(Err(UiaaResponse::AuthResponse(uiaainfo))); + db.uiaa.create(&user_id, &device_id, &uiaainfo)?; + return Err(Error::Uiaa(uiaainfo)); } - db.users - .remove_device(&user_id, &body.body.device_id) - .unwrap(); + db.users.remove_device(&user_id, &body.body.device_id)?; - MatrixResult(Ok(delete_device::Response)) + Ok(delete_device::Response.into()) } #[post("/_matrix/client/r0/delete_devices", data = "")] pub fn delete_devices_route( db: State<'_, Database>, body: Ruma, -) -> MatrixResult { +) -> ConduitResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); let device_id = body.device_id.as_ref().expect("user is authenticated"); // UIAA - let uiaainfo = UiaaInfo { + let mut uiaainfo = UiaaInfo { flows: vec![AuthFlow { stages: vec!["m.login.password".to_owned()], }], completed: Vec::new(), params: Default::default(), - session: Some(utils::random_string(SESSION_ID_LENGTH)), + session: None, auth_error: None, }; if let Some(auth) = &body.auth { - let (worked, uiaainfo) = db - .uiaa - .try_auth( - &user_id, - &device_id, - auth, - &uiaainfo, - &db.users, - &db.globals, - ) - .unwrap(); + let (worked, uiaainfo) = db.uiaa.try_auth( + &user_id, + &device_id, + auth, + &uiaainfo, + &db.users, + &db.globals, + )?; if !worked { - return MatrixResult(Err(UiaaResponse::AuthResponse(uiaainfo))); + return Err(Error::Uiaa(uiaainfo)); } // Success! } else { - db.uiaa.create(&user_id, &device_id, &uiaainfo).unwrap(); - return MatrixResult(Err(UiaaResponse::AuthResponse(uiaainfo))); + uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); + db.uiaa.create(&user_id, &device_id, &uiaainfo)?; + return Err(Error::Uiaa(uiaainfo)); } for device_id in &body.devices { - db.users.remove_device(&user_id, &device_id).unwrap() + db.users.remove_device(&user_id, &device_id)? } - MatrixResult(Ok(delete_devices::Response)) + Ok(delete_devices::Response.into()) } #[options("/<_segments..>")] pub fn options_route( _segments: rocket::http::uri::Segments<'_>, -) -> MatrixResult { - MatrixResult(Ok(send_event_to_device::Response)) +) -> ConduitResult { + Ok(send_event_to_device::Response.into()) } diff --git a/src/database.rs b/src/database.rs index 34af8fc..2cc01ea 100644 --- a/src/database.rs +++ b/src/database.rs @@ -6,6 +6,7 @@ pub(self) mod rooms; pub(self) mod uiaa; pub(self) mod users; +use crate::{Error, Result}; use directories::ProjectDirs; use log::info; use std::fs::remove_dir_all; @@ -25,84 +26,92 @@ pub struct Database { impl Database { /// Tries to remove the old database but ignores all errors. - pub fn try_remove(server_name: &str) { + pub fn try_remove(server_name: &str) -> Result<()> { let mut path = ProjectDirs::from("xyz", "koesters", "conduit") - .unwrap() + .ok_or(Error::BadConfig( + "The OS didn't return a valid home directory path.", + ))? .data_dir() .to_path_buf(); path.push(server_name); let _ = remove_dir_all(path); + + Ok(()) } /// Load an existing database or create a new one. - pub fn load_or_create(config: &Config) -> Self { + pub fn load_or_create(config: &Config) -> Result { let server_name = config.get_str("server_name").unwrap_or("localhost"); let path = config .get_str("database_path") - .map(|x| x.to_owned()) + .map(|x| Ok::<_, Error>(x.to_owned())) .unwrap_or_else(|_| { let path = ProjectDirs::from("xyz", "koesters", "conduit") - .unwrap() + .ok_or(Error::BadConfig( + "The OS didn't return a valid home directory path.", + ))? .data_dir() .join(server_name); - path.to_str().unwrap().to_owned() - }); - let db = sled::open(&path).unwrap(); + Ok(path + .to_str() + .ok_or(Error::BadConfig("Database path contains invalid unicode."))? + .to_owned()) + })?; + + let db = sled::open(&path)?; info!("Opened sled database at {}", path); - Self { - globals: globals::Globals::load(db.open_tree("global").unwrap(), config), + Ok(Self { + globals: globals::Globals::load(db.open_tree("global")?, config)?, users: users::Users { - userid_password: db.open_tree("userid_password").unwrap(), - userid_displayname: db.open_tree("userid_displayname").unwrap(), - userid_avatarurl: db.open_tree("userid_avatarurl").unwrap(), - userdeviceid_token: db.open_tree("userdeviceid_token").unwrap(), - userdeviceid_metadata: db.open_tree("userdeviceid_metadata").unwrap(), - token_userdeviceid: db.open_tree("token_userdeviceid").unwrap(), - onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys").unwrap(), - userdeviceid_devicekeys: db.open_tree("userdeviceid_devicekeys").unwrap(), - devicekeychangeid_userid: db.open_tree("devicekeychangeid_userid").unwrap(), - todeviceid_events: db.open_tree("todeviceid_events").unwrap(), + userid_password: db.open_tree("userid_password")?, + userid_displayname: db.open_tree("userid_displayname")?, + userid_avatarurl: db.open_tree("userid_avatarurl")?, + userdeviceid_token: db.open_tree("userdeviceid_token")?, + userdeviceid_metadata: db.open_tree("userdeviceid_metadata")?, + token_userdeviceid: db.open_tree("token_userdeviceid")?, + onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys")?, + userdeviceid_devicekeys: db.open_tree("userdeviceid_devicekeys")?, + devicekeychangeid_userid: db.open_tree("devicekeychangeid_userid")?, + todeviceid_events: db.open_tree("todeviceid_events")?, }, uiaa: uiaa::Uiaa { - userdeviceid_uiaainfo: db.open_tree("userdeviceid_uiaainfo").unwrap(), + userdeviceid_uiaainfo: db.open_tree("userdeviceid_uiaainfo")?, }, rooms: rooms::Rooms { edus: rooms::RoomEdus { - roomuserid_lastread: db.open_tree("roomuserid_lastread").unwrap(), // "Private" read receipt - roomlatestid_roomlatest: db.open_tree("roomlatestid_roomlatest").unwrap(), // Read receipts - roomactiveid_userid: db.open_tree("roomactiveid_userid").unwrap(), // Typing notifs - roomid_lastroomactiveupdate: db - .open_tree("roomid_lastroomactiveupdate") - .unwrap(), + roomuserid_lastread: db.open_tree("roomuserid_lastread")?, // "Private" read receipt + roomlatestid_roomlatest: db.open_tree("roomlatestid_roomlatest")?, // Read receipts + roomactiveid_userid: db.open_tree("roomactiveid_userid")?, // Typing notifs + roomid_lastroomactiveupdate: db.open_tree("roomid_lastroomactiveupdate")?, }, - pduid_pdu: db.open_tree("pduid_pdu").unwrap(), - eventid_pduid: db.open_tree("eventid_pduid").unwrap(), - roomid_pduleaves: db.open_tree("roomid_pduleaves").unwrap(), - roomstateid_pdu: db.open_tree("roomstateid_pdu").unwrap(), + pduid_pdu: db.open_tree("pduid_pdu")?, + eventid_pduid: db.open_tree("eventid_pduid")?, + roomid_pduleaves: db.open_tree("roomid_pduleaves")?, + roomstateid_pdu: db.open_tree("roomstateid_pdu")?, - alias_roomid: db.open_tree("alias_roomid").unwrap(), - aliasid_alias: db.open_tree("alias_roomid").unwrap(), - publicroomids: db.open_tree("publicroomids").unwrap(), + alias_roomid: db.open_tree("alias_roomid")?, + aliasid_alias: db.open_tree("alias_roomid")?, + publicroomids: db.open_tree("publicroomids")?, - userroomid_joined: db.open_tree("userroomid_joined").unwrap(), - roomuserid_joined: db.open_tree("roomuserid_joined").unwrap(), - userroomid_invited: db.open_tree("userroomid_invited").unwrap(), - roomuserid_invited: db.open_tree("roomuserid_invited").unwrap(), - userroomid_left: db.open_tree("userroomid_left").unwrap(), + userroomid_joined: db.open_tree("userroomid_joined")?, + roomuserid_joined: db.open_tree("roomuserid_joined")?, + userroomid_invited: db.open_tree("userroomid_invited")?, + roomuserid_invited: db.open_tree("roomuserid_invited")?, + userroomid_left: db.open_tree("userroomid_left")?, }, account_data: account_data::AccountData { - roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata").unwrap(), + roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata")?, }, global_edus: global_edus::GlobalEdus { - presenceid_presence: db.open_tree("presenceid_presence").unwrap(), // Presence + presenceid_presence: db.open_tree("presenceid_presence")?, // Presence }, media: media::Media { - mediaid_file: db.open_tree("mediaid_file").unwrap(), + mediaid_file: db.open_tree("mediaid_file")?, }, _db: db, - } + }) } } diff --git a/src/database/account_data.rs b/src/database/account_data.rs index f09b4c5..f7c564d 100644 --- a/src/database/account_data.rs +++ b/src/database/account_data.rs @@ -1,5 +1,6 @@ use crate::{utils, Error, Result}; use ruma::{ + api::client::error::ErrorKind, events::{collections::only::Event as EduEvent, EventJson, EventType}, identifiers::{RoomId, UserId}, }; @@ -20,7 +21,10 @@ impl AccountData { globals: &super::globals::Globals, ) -> Result<()> { if json.get("content").is_none() { - return Err(Error::BadRequest("json needs to have a content field")); + return Err(Error::BadRequest( + ErrorKind::BadJson, + "Json needs to have a content field.", + )); } json.insert("type".to_owned(), kind.to_string().into()); @@ -62,9 +66,10 @@ impl AccountData { key.push(0xff); key.extend_from_slice(kind.to_string().as_bytes()); - self.roomuserdataid_accountdata - .insert(key, &*serde_json::to_string(&json)?) - .unwrap(); + self.roomuserdataid_accountdata.insert( + key, + &*serde_json::to_string(&json).expect("Map::to_string always works"), + )?; Ok(()) } @@ -109,17 +114,22 @@ impl AccountData { .take_while(move |(k, _)| k.starts_with(&prefix)) .map(|(k, v)| { Ok::<_, Error>(( - EventType::try_from(utils::string_from_bytes( - k.rsplit(|&b| b == 0xff) - .next() - .ok_or(Error::BadDatabase("roomuserdataid is invalid"))?, - )?) - .map_err(|_| Error::BadDatabase("roomuserdataid is invalid"))?, - serde_json::from_slice::>(&v).unwrap(), + EventType::try_from( + utils::string_from_bytes( + k.rsplit(|&b| b == 0xff) + .next() + .ok_or(Error::BadDatabase("RoomUserData ID in db is invalid."))?, + ) + .map_err(|_| Error::BadDatabase("RoomUserData ID in db is invalid."))?, + ) + .map_err(|_| Error::BadDatabase("RoomUserData ID in db is invalid."))?, + serde_json::from_slice::>(&v).map_err(|_| { + Error::BadDatabase("Database contains invalid account data.") + })?, )) }) { - let (kind, data) = r.unwrap(); + let (kind, data) = r?; userdata.insert(kind, data); } diff --git a/src/database/global_edus.rs b/src/database/global_edus.rs index e9c6d23..7d1ac20 100644 --- a/src/database/global_edus.rs +++ b/src/database/global_edus.rs @@ -1,4 +1,4 @@ -use crate::Result; +use crate::{Error, Result}; use ruma::events::EventJson; pub struct GlobalEdus { @@ -21,7 +21,10 @@ impl GlobalEdus { .rev() .filter_map(|r| r.ok()) .find(|key| { - key.rsplit(|&b| b == 0xff).next().unwrap() == presence.sender.to_string().as_bytes() + key.rsplit(|&b| b == 0xff) + .next() + .expect("rsplit always returns an element") + == presence.sender.to_string().as_bytes() }) { // This is the old global_latest @@ -32,8 +35,10 @@ impl GlobalEdus { presence_id.push(0xff); presence_id.extend_from_slice(&presence.sender.to_string().as_bytes()); - self.presenceid_presence - .insert(presence_id, &*serde_json::to_string(&presence)?)?; + self.presenceid_presence.insert( + presence_id, + &*serde_json::to_string(&presence).expect("PresenceEvent can be serialized"), + )?; Ok(()) } @@ -50,6 +55,9 @@ impl GlobalEdus { .presenceid_presence .range(&*first_possible_edu..) .filter_map(|r| r.ok()) - .map(|(_, v)| Ok(serde_json::from_slice(&v)?))) + .map(|(_, v)| { + Ok(serde_json::from_slice(&v) + .map_err(|_| Error::BadDatabase("Invalid presence event in db."))?) + })) } } diff --git a/src/database/globals.rs b/src/database/globals.rs index 08ab411..32cddd8 100644 --- a/src/database/globals.rs +++ b/src/database/globals.rs @@ -1,4 +1,4 @@ -use crate::{utils, Result}; +use crate::{utils, Error, Result}; pub const COUNTER: &str = "c"; @@ -11,17 +11,16 @@ pub struct Globals { } impl Globals { - pub fn load(globals: sled::Tree, config: &rocket::Config) -> Self { + pub fn load(globals: sled::Tree, config: &rocket::Config) -> Result { let keypair = ruma::signatures::Ed25519KeyPair::new( &*globals - .update_and_fetch("keypair", utils::generate_keypair) - .unwrap() - .unwrap(), + .update_and_fetch("keypair", utils::generate_keypair)? + .expect("utils::generate_keypair always returns Some"), "key1".to_owned(), ) - .unwrap(); + .map_err(|_| Error::BadDatabase("Private or public keys are invalid."))?; - Self { + Ok(Self { globals, keypair, reqwest_client: reqwest::Client::new(), @@ -30,7 +29,7 @@ impl Globals { .unwrap_or("localhost") .to_owned(), registration_disabled: config.get_bool("registration_disabled").unwrap_or(false), - } + }) } /// Returns this server's keypair. @@ -49,14 +48,15 @@ impl Globals { .globals .update_and_fetch(COUNTER, utils::increment)? .expect("utils::increment will always put in a value"), - )) + ) + .map_err(|_| Error::BadDatabase("Count has invalid bytes."))?) } pub fn current_count(&self) -> Result { - Ok(self - .globals - .get(COUNTER)? - .map_or(0_u64, |bytes| utils::u64_from_bytes(&bytes))) + self.globals.get(COUNTER)?.map_or(Ok(0_u64), |bytes| { + Ok(utils::u64_from_bytes(&bytes) + .map_err(|_| Error::BadDatabase("Count has invalid bytes."))?) + }) } pub fn server_name(&self) -> &str { diff --git a/src/database/media.rs b/src/database/media.rs index c64fd0b..f70e924 100644 --- a/src/database/media.rs +++ b/src/database/media.rs @@ -43,16 +43,21 @@ impl Media { let content_type = utils::string_from_bytes( parts .next() - .ok_or(Error::BadDatabase("mediaid is invalid"))?, - )?; + .ok_or(Error::BadDatabase("Invalid Media ID in db"))?, + ) + .map_err(|_| Error::BadDatabase("Invalid content type in db."))?; let filename_bytes = parts .next() - .ok_or(Error::BadDatabase("mediaid is invalid"))?; + .ok_or(Error::BadDatabase("Media ID in db is invalid."))?; + let filename = if filename_bytes.is_empty() { None } else { - Some(utils::string_from_bytes(filename_bytes)?) + Some( + utils::string_from_bytes(filename_bytes) + .map_err(|_| Error::BadDatabase("Filename in db is invalid."))?, + ) }; Ok(Some((filename, content_type, file.to_vec()))) @@ -89,16 +94,21 @@ impl Media { let content_type = utils::string_from_bytes( parts .next() - .ok_or(Error::BadDatabase("mediaid is invalid"))?, - )?; + .ok_or(Error::BadDatabase("Invalid Media ID in db"))?, + ) + .map_err(|_| Error::BadDatabase("Invalid content type in db."))?; let filename_bytes = parts .next() - .ok_or(Error::BadDatabase("mediaid is invalid"))?; + .ok_or(Error::BadDatabase("Media ID in db is invalid."))?; + let filename = if filename_bytes.is_empty() { None } else { - Some(utils::string_from_bytes(filename_bytes)?) + Some( + utils::string_from_bytes(filename_bytes) + .map_err(|_| Error::BadDatabase("Filename in db is invalid."))?, + ) }; Ok(Some((filename, content_type, file.to_vec()))) @@ -110,16 +120,21 @@ impl Media { let content_type = utils::string_from_bytes( parts .next() - .ok_or(Error::BadDatabase("mediaid is invalid"))?, - )?; + .ok_or(Error::BadDatabase("Media ID in db is invalid"))?, + ) + .map_err(|_| Error::BadDatabase("Invalid content type in db."))?; let filename_bytes = parts .next() - .ok_or(Error::BadDatabase("mediaid is invalid"))?; + .ok_or(Error::BadDatabase("Media ID in db is invalid"))?; + let filename = if filename_bytes.is_empty() { None } else { - Some(utils::string_from_bytes(filename_bytes)?) + Some( + utils::string_from_bytes(filename_bytes) + .map_err(|_| Error::BadDatabase("Filename in db is invalid."))?, + ) }; if let Ok(image) = image::load_from_memory(&file) { diff --git a/src/database/rooms.rs b/src/database/rooms.rs index fa422de..799a7cb 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -5,6 +5,7 @@ pub use edus::RoomEdus; use crate::{utils, Error, PduEvent, Result}; use log::error; use ruma::{ + api::client::error::ErrorKind, events::{ room::{ join_rules, member, @@ -61,30 +62,34 @@ impl Rooms { .roomstateid_pdu .scan_prefix(&room_id.to_string().as_bytes()) .values() - .map(|value| Ok::<_, Error>(serde_json::from_slice::(&value?)?)) + .map(|value| { + Ok::<_, Error>( + serde_json::from_slice::(&value?) + .map_err(|_| Error::BadDatabase("Invalid PDU in db."))?, + ) + }) { let pdu = pdu?; - hashmap.insert( - ( - pdu.kind.clone(), - pdu.state_key - .clone() - .expect("state events have a state key"), - ), - pdu, - ); + let state_key = pdu.state_key.clone().ok_or(Error::BadDatabase( + "Room state contains event without state_key.", + ))?; + hashmap.insert((pdu.kind.clone(), state_key), pdu); } Ok(hashmap) } /// Returns the `count` of this pdu's id. pub fn get_pdu_count(&self, event_id: &EventId) -> Result> { - Ok(self - .eventid_pduid + self.eventid_pduid .get(event_id.to_string().as_bytes())? - .map(|pdu_id| { - utils::u64_from_bytes(&pdu_id[pdu_id.len() - mem::size_of::()..pdu_id.len()]) - })) + .map_or(Ok(None), |pdu_id| { + Ok(Some( + utils::u64_from_bytes( + &pdu_id[pdu_id.len() - mem::size_of::()..pdu_id.len()], + ) + .map_err(|_| Error::BadDatabase("PDU has invalid count bytes."))?, + )) + }) } /// Returns the json of a pdu. @@ -92,11 +97,12 @@ impl Rooms { self.eventid_pduid .get(event_id.to_string().as_bytes())? .map_or(Ok(None), |pdu_id| { - Ok(Some(serde_json::from_slice( - &self.pduid_pdu.get(pdu_id)?.ok_or(Error::BadDatabase( - "eventid_pduid points to nonexistent pdu", - ))?, - )?)) + Ok(Some( + serde_json::from_slice(&self.pduid_pdu.get(pdu_id)?.ok_or( + Error::BadDatabase("eventid_pduid points to nonexistent pdu."), + )?) + .map_err(|_| Error::BadDatabase("Invalid PDU in db."))?, + )) }) } @@ -112,28 +118,37 @@ impl Rooms { self.eventid_pduid .get(event_id.to_string().as_bytes())? .map_or(Ok(None), |pdu_id| { - Ok(Some(serde_json::from_slice( - &self.pduid_pdu.get(pdu_id)?.ok_or(Error::BadDatabase( - "eventid_pduid points to nonexistent pdu", - ))?, - )?)) + Ok(Some( + serde_json::from_slice(&self.pduid_pdu.get(pdu_id)?.ok_or( + Error::BadDatabase("eventid_pduid points to nonexistent pdu."), + )?) + .map_err(|_| Error::BadDatabase("Invalid PDU in db."))?, + )) }) } /// Returns the pdu. pub fn get_pdu_from_id(&self, pdu_id: &IVec) -> Result> { - self.pduid_pdu - .get(pdu_id)? - .map_or(Ok(None), |pdu| Ok(Some(serde_json::from_slice(&pdu)?))) + self.pduid_pdu.get(pdu_id)?.map_or(Ok(None), |pdu| { + Ok(Some( + serde_json::from_slice(&pdu) + .map_err(|_| Error::BadDatabase("Invalid PDU in db."))?, + )) + }) } - /// Returns the pdu. - pub fn replace_pdu(&self, pdu_id: &IVec, pdu: &PduEvent) -> Result<()> { + /// Removes a pdu and creates a new one with the same id. + fn replace_pdu(&self, pdu_id: &IVec, pdu: &PduEvent) -> Result<()> { if self.pduid_pdu.get(&pdu_id)?.is_some() { - self.pduid_pdu - .insert(&pdu_id, &*serde_json::to_string(pdu)?)?; + self.pduid_pdu.insert( + &pdu_id, + &*serde_json::to_string(pdu).expect("PduEvent::to_string always works"), + )?; Ok(()) } else { - Err(Error::BadRequest("pdu does not exist")) + Err(Error::BadRequest( + ErrorKind::NotFound, + "PDU does not exist.", + )) } } @@ -148,7 +163,12 @@ impl Rooms { .roomid_pduleaves .scan_prefix(prefix) .values() - .map(|bytes| Ok::<_, Error>(EventId::try_from(&*utils::string_from_bytes(&bytes?)?)?)) + .map(|bytes| { + Ok::<_, Error>( + serde_json::from_slice(&bytes?) + .map_err(|_| Error::BadDatabase("Invalid EventID in roomid_pduleaves."))?, + ) + }) { events.push(event?); } @@ -214,174 +234,203 @@ impl Rooms { Ok( serde_json::from_value::>( power_levels.content.clone(), - )? - .deserialize()?, + ) + .expect("EventJson::from_value always works.") + .deserialize() + .map_err(|_| Error::BadDatabase("Invalid PowerLevels event in db."))?, ) }, )?; - { - let sender_membership = self - .room_state(&room_id)? - .get(&(EventType::RoomMember, sender.to_string())) - .map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { - Ok( - serde_json::from_value::>( - pdu.content.clone(), - )? - .deserialize()? - .membership, + let sender_membership = self + .room_state(&room_id)? + .get(&(EventType::RoomMember, sender.to_string())) + .map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { + Ok( + serde_json::from_value::>( + pdu.content.clone(), + ) + .expect("EventJson::from_value always works.") + .deserialize() + .map_err(|_| Error::BadDatabase("Invalid Member event in db."))? + .membership, + ) + })?; + + let sender_power = power_levels.users.get(&sender).map_or_else( + || { + if sender_membership != member::MembershipState::Join { + None + } else { + Some(&power_levels.users_default) + } + }, + // If it's okay, wrap with Some(_) + Some, + ); + + if !match event_type { + EventType::RoomMember => { + let target_user_id = UserId::try_from(&**state_key).map_err(|_| { + Error::BadRequest( + ErrorKind::InvalidParam, + "State key of member event does not contain user id.", ) })?; - let sender_power = power_levels.users.get(&sender).map_or_else( - || { - if sender_membership != member::MembershipState::Join { - None - } else { - Some(&power_levels.users_default) - } - }, - // If it's okay, wrap with Some(_) - Some, - ); + let current_membership = self + .room_state(&room_id)? + .get(&(EventType::RoomMember, target_user_id.to_string())) + .map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { + Ok( + serde_json::from_value::>( + pdu.content.clone(), + ) + .expect("EventJson::from_value always works.") + .deserialize() + .map_err(|_| Error::BadDatabase("Invalid Member event in db."))? + .membership, + ) + })?; - if !match event_type { - EventType::RoomMember => { - let target_user_id = UserId::try_from(&**state_key)?; + let target_membership = serde_json::from_value::< + EventJson, + >(content.clone()) + .expect("EventJson::from_value always works.") + .deserialize() + .map_err(|_| Error::BadDatabase("Invalid Member event in db."))? + .membership; - let current_membership = self - .room_state(&room_id)? - .get(&(EventType::RoomMember, target_user_id.to_string())) - .map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| { - Ok(serde_json::from_value::< - EventJson, - >(pdu.content.clone())? - .deserialize()? - .membership) - })?; + let target_power = power_levels.users.get(&target_user_id).map_or_else( + || { + if target_membership != member::MembershipState::Join { + None + } else { + Some(&power_levels.users_default) + } + }, + // If it's okay, wrap with Some(_) + Some, + ); - let target_membership = serde_json::from_value::< - EventJson, - >(content.clone())? - .deserialize()? - .membership; - - let target_power = power_levels.users.get(&target_user_id).map_or_else( - || { - if target_membership != member::MembershipState::Join { - None - } else { - Some(&power_levels.users_default) - } - }, - // If it's okay, wrap with Some(_) - Some, - ); - - let join_rules = self - .room_state(&room_id)? + let join_rules = + self.room_state(&room_id)? .get(&(EventType::RoomJoinRules, "".to_owned())) - .map_or(join_rules::JoinRule::Public, |pdu| { - serde_json::from_value::< + .map_or(Ok::<_, Error>(join_rules::JoinRule::Public), |pdu| { + Ok(serde_json::from_value::< EventJson, >(pdu.content.clone()) - .unwrap() + .expect("EventJson::from_value always works.") .deserialize() - .unwrap() - .join_rule - }); + .map_err(|_| { + Error::BadDatabase("Database contains invalid JoinRules event") + })? + .join_rule) + })?; - let authorized = if target_membership == member::MembershipState::Join { - let mut prev_events = prev_events.iter(); - let prev_event = self - .get_pdu(prev_events.next().ok_or(Error::BadRequest( - "membership can't be the first event", - ))?)? - .ok_or(Error::BadDatabase("pdu leave points to valid event"))?; - if prev_event.kind == EventType::RoomCreate - && prev_event.prev_events.is_empty() - { - true - } else if sender != target_user_id { - false - } else if let member::MembershipState::Ban = current_membership { - false - } else { - join_rules == join_rules::JoinRule::Invite - && (current_membership == member::MembershipState::Join - || current_membership == member::MembershipState::Invite) - || join_rules == join_rules::JoinRule::Public - } - } else if target_membership == member::MembershipState::Invite { - if let Some(third_party_invite_json) = content.get("third_party_invite") - { - if current_membership == member::MembershipState::Ban { - false - } else { - let _third_party_invite = - serde_json::from_value::( - third_party_invite_json.clone(), - )?; - todo!("handle third party invites"); - } - } else if sender_membership != member::MembershipState::Join - || current_membership == member::MembershipState::Join - || current_membership == member::MembershipState::Ban - { - false - } else { - sender_power - .filter(|&p| p >= &power_levels.invite) - .is_some() - } - } else if target_membership == member::MembershipState::Leave { - if sender == target_user_id { - current_membership == member::MembershipState::Join - || current_membership == member::MembershipState::Invite - } else if sender_membership != member::MembershipState::Join - || current_membership == member::MembershipState::Ban - && sender_power.filter(|&p| p < &power_levels.ban).is_some() - { - false - } else { - sender_power.filter(|&p| p >= &power_levels.kick).is_some() - && target_power < sender_power - } - } else if target_membership == member::MembershipState::Ban { - if sender_membership != member::MembershipState::Join { - false - } else { - sender_power.filter(|&p| p >= &power_levels.ban).is_some() - && target_power < sender_power - } - } else { + let authorized = if target_membership == member::MembershipState::Join { + let mut prev_events = prev_events.iter(); + let prev_event = self + .get_pdu(prev_events.next().ok_or(Error::BadRequest( + ErrorKind::Unknown, + "Membership can't be the first event", + ))?)? + .ok_or(Error::BadDatabase("PDU leaf points to invalid event!"))?; + if prev_event.kind == EventType::RoomCreate + && prev_event.prev_events.is_empty() + { + true + } else if sender != target_user_id { false - }; - - if authorized { - // Update our membership info - self.update_membership(&room_id, &target_user_id, &target_membership)?; + } else if let member::MembershipState::Ban = current_membership { + false + } else { + join_rules == join_rules::JoinRule::Invite + && (current_membership == member::MembershipState::Join + || current_membership == member::MembershipState::Invite) + || join_rules == join_rules::JoinRule::Public } + } else if target_membership == member::MembershipState::Invite { + if let Some(third_party_invite_json) = content.get("third_party_invite") { + if current_membership == member::MembershipState::Ban { + false + } else { + let _third_party_invite = + serde_json::from_value::( + third_party_invite_json.clone(), + ) + .map_err(|_| { + Error::BadRequest( + ErrorKind::InvalidParam, + "ThirdPartyInvite is invalid", + ) + })?; + todo!("handle third party invites"); + } + } else if sender_membership != member::MembershipState::Join + || current_membership == member::MembershipState::Join + || current_membership == member::MembershipState::Ban + { + false + } else { + sender_power + .filter(|&p| p >= &power_levels.invite) + .is_some() + } + } else if target_membership == member::MembershipState::Leave { + if sender == target_user_id { + current_membership == member::MembershipState::Join + || current_membership == member::MembershipState::Invite + } else if sender_membership != member::MembershipState::Join + || current_membership == member::MembershipState::Ban + && sender_power.filter(|&p| p < &power_levels.ban).is_some() + { + false + } else { + sender_power.filter(|&p| p >= &power_levels.kick).is_some() + && target_power < sender_power + } + } else if target_membership == member::MembershipState::Ban { + if sender_membership != member::MembershipState::Join { + false + } else { + sender_power.filter(|&p| p >= &power_levels.ban).is_some() + && target_power < sender_power + } + } else { + false + }; - authorized + if authorized { + // Update our membership info + self.update_membership(&room_id, &target_user_id, &target_membership)?; } - EventType::RoomCreate => prev_events.is_empty(), - // Not allow any of the following events if the sender is not joined. - _ if sender_membership != member::MembershipState::Join => false, - _ => { - // TODO - sender_power.unwrap_or(&power_levels.users_default) - >= &power_levels.state_default - } - } { - error!("Unauthorized"); - // Not authorized - return Err(Error::BadRequest("event not authorized")); + authorized } + EventType::RoomCreate => prev_events.is_empty(), + // Not allow any of the following events if the sender is not joined. + _ if sender_membership != member::MembershipState::Join => false, + + _ => { + // TODO + sender_power.unwrap_or(&power_levels.users_default) + >= &power_levels.state_default + } + } { + error!("Unauthorized"); + // Not authorized + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "Event is not authorized", + )); } } else if !self.is_joined(&sender, &room_id)? { - return Err(Error::BadRequest("event not authorized")); + // TODO: auth rules apply to all events, not only those with a state key + error!("Unauthorized"); + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "Event is not authorized", + )); } // Our depth is the maximum depth of prev_events + 1 @@ -410,14 +459,14 @@ impl Rooms { origin: globals.server_name().to_owned(), origin_server_ts: utils::millis_since_unix_epoch() .try_into() - .expect("this only fails many years in the future"), + .expect("time is valid"), kind: event_type.clone(), content: content.clone(), state_key, prev_events, depth: depth .try_into() - .expect("depth can overflow and should be deprecated..."), + .map_err(|_| Error::BadDatabase("Depth is invalid"))?, auth_events: Vec::new(), redacts: redacts.clone(), unsigned, @@ -430,18 +479,20 @@ impl Rooms { // Generate event id pdu.event_id = EventId::try_from(&*format!( "${}", - ruma::signatures::reference_hash(&serde_json::to_value(&pdu)?) - .expect("ruma can calculate reference hashes") + ruma::signatures::reference_hash( + &serde_json::to_value(&pdu).expect("event is valid, we just created it") + ) + .expect("ruma can calculate reference hashes") )) - .expect("ruma's reference hashes are correct"); + .expect("ruma's reference hashes are valid event ids"); - let mut pdu_json = serde_json::to_value(&pdu)?; + let mut pdu_json = serde_json::to_value(&pdu).expect("event is valid, we just created it"); ruma::signatures::hash_and_sign_event( globals.server_name(), globals.keypair(), &mut pdu_json, ) - .expect("our new event can be hashed and signed"); + .expect("event is valid, we just created it"); self.replace_pdu_leaves(&room_id, &pdu.event_id)?; @@ -473,8 +524,15 @@ impl Rooms { // TODO: Reason let _reason = serde_json::from_value::< EventJson, - >(content)? - .deserialize()? + >(content) + .expect("EventJson::from_value always works.") + .deserialize() + .map_err(|_| { + Error::BadRequest( + ErrorKind::InvalidParam, + "Invalid redaction event content.", + ) + })? .reason; self.redact_pdu(&redact_id)?; @@ -528,7 +586,10 @@ impl Rooms { }) .filter_map(|r| r.ok()) .take_while(move |(k, _)| k.starts_with(&prefix)) - .map(|(_, v)| Ok(serde_json::from_slice(&v)?))) + .map(|(_, v)| { + Ok(serde_json::from_slice(&v) + .map_err(|_| Error::BadDatabase("PDU in db is invalid."))?) + })) } /// Returns an iterator over all events in a room that happened before the event with id @@ -552,7 +613,10 @@ impl Rooms { .rev() .filter_map(|r| r.ok()) .take_while(move |(k, _)| k.starts_with(&prefix)) - .map(|(_, v)| Ok(serde_json::from_slice(&v)?)) + .map(|(_, v)| { + Ok(serde_json::from_slice(&v) + .map_err(|_| Error::BadDatabase("PDU in db is invalid."))?) + }) } /// Returns an iterator over all events in a room that happened after the event with id @@ -575,7 +639,10 @@ impl Rooms { .range(current..) .filter_map(|r| r.ok()) .take_while(move |(k, _)| k.starts_with(&prefix)) - .map(|(_, v)| Ok(serde_json::from_slice(&v)?)) + .map(|(_, v)| { + Ok(serde_json::from_slice(&v) + .map_err(|_| Error::BadDatabase("PDU in db is invalid."))?) + }) } /// Replace a PDU with the redacted form. @@ -583,12 +650,15 @@ impl Rooms { if let Some(pdu_id) = self.get_pdu_id(event_id)? { let mut pdu = self .get_pdu_from_id(&pdu_id)? - .ok_or(Error::BadDatabase("pduid points to invalid pdu"))?; - pdu.redact(); + .ok_or(Error::BadDatabase("PDU ID points to invalid PDU."))?; + pdu.redact()?; self.replace_pdu(&pdu_id, &pdu)?; Ok(()) } else { - Err(Error::BadRequest("eventid does not exist")) + Err(Error::BadRequest( + ErrorKind::NotFound, + "Event ID does not exist.", + )) } } @@ -664,7 +734,10 @@ impl Rooms { let room_id = self .alias_roomid .remove(alias.alias())? - .ok_or(Error::BadRequest("Alias does not exist"))?; + .ok_or(Error::BadRequest( + ErrorKind::NotFound, + "Alias does not exist.", + ))?; for key in self.aliasid_alias.scan_prefix(room_id).keys() { self.aliasid_alias.remove(key?)?; @@ -678,7 +751,9 @@ impl Rooms { self.alias_roomid .get(alias.alias())? .map_or(Ok(None), |bytes| { - Ok(Some(RoomId::try_from(utils::string_from_bytes(&bytes)?)?)) + Ok(Some(serde_json::from_slice(&bytes).map_err(|_| { + Error::BadDatabase("Room ID in alias_roomid is invalid.") + })?)) }) } @@ -689,7 +764,10 @@ impl Rooms { self.aliasid_alias .scan_prefix(prefix) .values() - .map(|bytes| Ok(RoomAliasId::try_from(utils::string_from_bytes(&bytes?)?)?)) + .map(|bytes| { + Ok(serde_json::from_slice(&bytes?) + .map_err(|_| Error::BadDatabase("Alias in aliasid_alias is invalid."))?) + }) } pub fn set_public(&self, room_id: &RoomId, public: bool) -> Result<()> { @@ -707,10 +785,10 @@ impl Rooms { } pub fn public_rooms(&self) -> impl Iterator> { - self.publicroomids - .iter() - .keys() - .map(|bytes| Ok(RoomId::try_from(utils::string_from_bytes(&bytes?)?)?)) + self.publicroomids.iter().keys().map(|bytes| { + Ok(serde_json::from_slice(&bytes?) + .map_err(|_| Error::BadDatabase("Room ID in publicroomids is invalid."))?) + }) } /// Returns an iterator over all rooms a user joined. @@ -719,12 +797,13 @@ impl Rooms { .scan_prefix(room_id.to_string()) .values() .map(|key| { - Ok(UserId::try_from(&*utils::string_from_bytes( + Ok(serde_json::from_slice( &key? .rsplit(|&b| b == 0xff) .next() - .ok_or(Error::BadDatabase("userroomid is invalid"))?, - )?)?) + .ok_or(Error::BadDatabase("RoomUser ID is invalid."))?, + ) + .map_err(|_| Error::BadDatabase("Invalid User ID in db."))?) }) } @@ -734,12 +813,13 @@ impl Rooms { .scan_prefix(room_id.to_string()) .keys() .map(|key| { - Ok(UserId::try_from(&*utils::string_from_bytes( + Ok(serde_json::from_slice( &key? .rsplit(|&b| b == 0xff) .next() - .ok_or(Error::BadDatabase("userroomid is invalid"))?, - )?)?) + .ok_or(Error::BadDatabase("RoomUser ID is invalid."))?, + ) + .map_err(|_| Error::BadDatabase("Invalid User ID in db."))?) }) } @@ -749,12 +829,13 @@ impl Rooms { .scan_prefix(user_id.to_string()) .keys() .map(|key| { - Ok(RoomId::try_from(&*utils::string_from_bytes( + Ok(serde_json::from_slice( &key? .rsplit(|&b| b == 0xff) .next() - .ok_or(Error::BadDatabase("userroomid is invalid"))?, - )?)?) + .ok_or(Error::BadDatabase("UserRoom ID is invalid."))?, + ) + .map_err(|_| Error::BadDatabase("Invalid Room ID in db."))?) }) } @@ -764,12 +845,13 @@ impl Rooms { .scan_prefix(&user_id.to_string()) .keys() .map(|key| { - Ok(RoomId::try_from(&*utils::string_from_bytes( + Ok(serde_json::from_slice( &key? .rsplit(|&b| b == 0xff) .next() - .ok_or(Error::BadDatabase("userroomid is invalid"))?, - )?)?) + .ok_or(Error::BadDatabase("UserRoom ID is invalid."))?, + ) + .map_err(|_| Error::BadDatabase("Invalid Room ID in db."))?) }) } @@ -779,12 +861,13 @@ impl Rooms { .scan_prefix(&user_id.to_string()) .keys() .map(|key| { - Ok(RoomId::try_from(&*utils::string_from_bytes( + Ok(serde_json::from_slice( &key? .rsplit(|&b| b == 0xff) .next() - .ok_or(Error::BadDatabase("userroomid is invalid"))?, - )?)?) + .ok_or(Error::BadDatabase("UserRoom ID is invalid."))?, + ) + .map_err(|_| Error::BadDatabase("Invalid Room ID in db."))?) }) } diff --git a/src/database/rooms/edus.rs b/src/database/rooms/edus.rs index 5d04639..c8b03aa 100644 --- a/src/database/rooms/edus.rs +++ b/src/database/rooms/edus.rs @@ -33,7 +33,10 @@ impl RoomEdus { .filter_map(|r| r.ok()) .take_while(|key| key.starts_with(&prefix)) .find(|key| { - key.rsplit(|&b| b == 0xff).next().unwrap() == user_id.to_string().as_bytes() + key.rsplit(|&b| b == 0xff) + .next() + .expect("rsplit always returns an element") + == user_id.to_string().as_bytes() }) { // This is the old room_latest @@ -45,8 +48,10 @@ impl RoomEdus { room_latest_id.push(0xff); room_latest_id.extend_from_slice(&user_id.to_string().as_bytes()); - self.roomlatestid_roomlatest - .insert(room_latest_id, &*serde_json::to_string(&event)?)?; + self.roomlatestid_roomlatest.insert( + room_latest_id, + &*serde_json::to_string(&event).expect("EduEvent::to_string always works"), + )?; Ok(()) } @@ -68,7 +73,10 @@ impl RoomEdus { .range(&*first_possible_edu..) .filter_map(|r| r.ok()) .take_while(move |(k, _)| k.starts_with(&prefix)) - .map(|(_, v)| Ok(serde_json::from_slice(&v)?))) + .map(|(_, v)| { + Ok(serde_json::from_slice(&v) + .map_err(|_| Error::BadDatabase("Read receipt in db is invalid."))?) + })) } /// Sets a user as typing until the timeout timestamp is reached or roomactive_remove is @@ -152,17 +160,21 @@ impl RoomEdus { .roomactiveid_userid .scan_prefix(&prefix) .keys() - .filter_map(|r| r.ok()) - .take_while(|k| { - utils::u64_from_bytes( - k.split(|&c| c == 0xff) - .nth(1) - .expect("roomactive has valid timestamp and delimiters"), - ) < current_timestamp + .map(|key| { + let key = key?; + Ok::<_, Error>(( + key.clone(), + utils::u64_from_bytes(key.split(|&b| b == 0xff).nth(1).ok_or( + Error::BadDatabase("RoomActive has invalid timestamp or delimiters."), + )?) + .map_err(|_| Error::BadDatabase("RoomActive has invalid timestamp bytes."))?, + )) }) + .filter_map(|r| r.ok()) + .take_while(|&(_, timestamp)| timestamp < current_timestamp) { // This is an outdated edu (time > timestamp) - self.roomactiveid_userid.remove(outdated_edu)?; + self.roomactiveid_userid.remove(outdated_edu.0)?; found_outdated = true; } @@ -187,7 +199,11 @@ impl RoomEdus { Ok(self .roomid_lastroomactiveupdate .get(&room_id.to_string().as_bytes())? - .map(|bytes| utils::u64_from_bytes(&bytes)) + .map_or(Ok::<_, Error>(None), |bytes| { + Ok(Some( + utils::u64_from_bytes(&bytes).map_err(|_| Error::BadDatabase(""))?, + )) + })? .unwrap_or(0)) } @@ -202,7 +218,11 @@ impl RoomEdus { .roomactiveid_userid .scan_prefix(prefix) .values() - .map(|user_id| Ok::<_, Error>(UserId::try_from(utils::string_from_bytes(&user_id?)?)?)) + .map(|user_id| { + Ok::<_, Error>(serde_json::from_slice(&user_id?).map_err(|_| { + Error::BadDatabase("User ID in roomactiveid_userid is invalid.") + })?) + }) { user_ids.push(user_id?); } @@ -230,9 +250,10 @@ impl RoomEdus { key.push(0xff); key.extend_from_slice(&user_id.to_string().as_bytes()); - Ok(self - .roomuserid_lastread - .get(key)? - .map(|v| utils::u64_from_bytes(&v))) + self.roomuserid_lastread.get(key)?.map_or(Ok(None), |v| { + Ok(Some(utils::u64_from_bytes(&v).map_err(|_| { + Error::BadDatabase("Invalid private read marker bytes") + })?)) + }) } } diff --git a/src/database/uiaa.rs b/src/database/uiaa.rs index 9851e84..0ae2ea4 100644 --- a/src/database/uiaa.rs +++ b/src/database/uiaa.rs @@ -43,15 +43,51 @@ impl Uiaa { // Find out what the user completed match &**kind { "m.login.password" => { - if auth_parameters["identifier"]["type"] != "m.id.user" { - panic!("identifier not supported"); + let identifier = auth_parameters.get("identifier").ok_or(Error::BadRequest( + ErrorKind::MissingParam, + "m.login.password needs identifier.", + ))?; + + let identifier_type = identifier.get("type").ok_or(Error::BadRequest( + ErrorKind::MissingParam, + "Identifier needs a type.", + ))?; + + if identifier_type != "m.id.user" { + return Err(Error::BadRequest( + ErrorKind::Unrecognized, + "Identifier type not recognized.", + )); } - let user_id = UserId::parse_with_server_name( - auth_parameters["identifier"]["user"].as_str().unwrap(), - globals.server_name(), - )?; - let password = auth_parameters["password"].as_str().unwrap(); + let username = identifier + .get("user") + .ok_or(Error::BadRequest( + ErrorKind::MissingParam, + "Identifier needs user field.", + ))? + .as_str() + .ok_or(Error::BadRequest( + ErrorKind::BadJson, + "User is not a string.", + ))?; + + let user_id = UserId::parse_with_server_name(username, globals.server_name()) + .map_err(|_| { + Error::BadRequest(ErrorKind::InvalidParam, "User ID is invalid.") + })?; + + let password = auth_parameters + .get("password") + .ok_or(Error::BadRequest( + ErrorKind::MissingParam, + "Password is missing.", + ))? + .as_str() + .ok_or(Error::BadRequest( + ErrorKind::BadJson, + "Password is not a string.", + ))?; // Check if password is correct if let Some(hash) = users.password_hash(&user_id)? { @@ -59,7 +95,6 @@ impl Uiaa { argon2::verify_encoded(&hash, password.as_bytes()).unwrap_or(false); if !hash_matches { - debug!("Invalid password."); uiaainfo.auth_error = Some(ruma::api::client::error::ErrorBody { kind: ErrorKind::Forbidden, message: "Invalid username or password.".to_owned(), @@ -113,8 +148,10 @@ impl Uiaa { userdeviceid.extend_from_slice(device_id.as_bytes()); if let Some(uiaainfo) = uiaainfo { - self.userdeviceid_uiaainfo - .insert(&userdeviceid, &*serde_json::to_string(&uiaainfo)?)?; + self.userdeviceid_uiaainfo.insert( + &userdeviceid, + &*serde_json::to_string(&uiaainfo).expect("UiaaInfo::to_string always works"), + )?; } else { self.userdeviceid_uiaainfo.remove(&userdeviceid)?; } @@ -136,8 +173,12 @@ impl Uiaa { &self .userdeviceid_uiaainfo .get(&userdeviceid)? - .ok_or(Error::BadRequest("session does not exist"))?, - )?; + .ok_or(Error::BadRequest( + ErrorKind::Forbidden, + "UIAA session does not exist.", + ))?, + ) + .map_err(|_| Error::BadDatabase("UiaaInfo in userdeviceid_uiaainfo is invalid."))?; if uiaainfo .session @@ -145,7 +186,10 @@ impl Uiaa { .filter(|&s| s == session) .is_none() { - return Err(Error::BadRequest("wrong session token")); + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "UIAA session token invalid.", + )); } Ok(uiaainfo) diff --git a/src/database/users.rs b/src/database/users.rs index 5c47455..b70c2de 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -43,24 +43,29 @@ impl Users { .get(token)? .map_or(Ok(None), |bytes| { let mut parts = bytes.split(|&b| b == 0xff); - let user_bytes = parts - .next() - .ok_or(Error::BadDatabase("token_userdeviceid value invalid"))?; - let device_bytes = parts - .next() - .ok_or(Error::BadDatabase("token_userdeviceid value invalid"))?; + let user_bytes = parts.next().ok_or(Error::BadDatabase( + "token_userdeviceid value in db is invalid.", + ))?; + let device_bytes = parts.next().ok_or(Error::BadDatabase( + "token_userdeviceid value in db is invalid.", + ))?; Ok(Some(( - UserId::try_from(utils::string_from_bytes(&user_bytes)?)?, - utils::string_from_bytes(&device_bytes)?, + serde_json::from_slice(&user_bytes).map_err(|_| { + Error::BadDatabase("User ID in token_userdeviceid is invalid.") + })?, + utils::string_from_bytes(&device_bytes).map_err(|_| { + Error::BadDatabase("Device ID in token_userdeviceid is invalid.") + })?, ))) }) } /// Returns an iterator over all users on this homeserver. pub fn iter(&self) -> impl Iterator> { - self.userid_password.iter().keys().map(|r| { - utils::string_from_bytes(&r?).and_then(|string| Ok(UserId::try_from(&*string)?)) + self.userid_password.iter().keys().map(|bytes| { + Ok(serde_json::from_slice(&bytes?) + .map_err(|_| Error::BadDatabase("User ID bytes in db are invalid."))?) }) } @@ -68,14 +73,22 @@ impl Users { pub fn password_hash(&self, user_id: &UserId) -> Result> { self.userid_password .get(user_id.to_string())? - .map_or(Ok(None), |bytes| utils::string_from_bytes(&bytes).map(Some)) + .map_or(Ok(None), |bytes| { + Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| { + Error::BadDatabase("Password hash in db is not valid string.") + })?)) + }) } /// Returns the displayname of a user on this homeserver. pub fn displayname(&self, user_id: &UserId) -> Result> { self.userid_displayname .get(user_id.to_string())? - .map_or(Ok(None), |bytes| utils::string_from_bytes(&bytes).map(Some)) + .map_or(Ok(None), |bytes| { + Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| { + Error::BadDatabase("Displayname in db is invalid.") + })?)) + }) } /// Sets a new displayname or removes it if displayname is None. You still need to nofify all rooms of this change. @@ -94,7 +107,11 @@ impl Users { pub fn avatar_url(&self, user_id: &UserId) -> Result> { self.userid_avatarurl .get(user_id.to_string())? - .map_or(Ok(None), |bytes| utils::string_from_bytes(&bytes).map(Some)) + .map_or(Ok(None), |bytes| { + Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| { + Error::BadDatabase("Avatar URL in db is invalid.") + })?)) + }) } /// Sets a new avatar_url or removes it if avatar_url is None. @@ -117,11 +134,8 @@ impl Users { token: &str, initial_device_display_name: Option, ) -> Result<()> { - if !self.exists(user_id)? { - return Err(Error::BadRequest( - "tried to create device for nonexistent user", - )); - } + // This method should never be called for nonexistent users. + assert!(self.exists(user_id)?); let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); userdeviceid.push(0xff); @@ -134,7 +148,8 @@ impl Users { display_name: initial_device_display_name, last_seen_ip: None, // TODO last_seen_ts: Some(SystemTime::now()), - })? + }) + .expect("Device::to_string never fails.") .as_bytes(), )?; @@ -185,23 +200,22 @@ impl Users { &*bytes? .rsplit(|&b| b == 0xff) .next() - .ok_or(Error::BadDatabase("userdeviceid is invalid"))?, - )?) + .ok_or(Error::BadDatabase("UserDevice ID in db is invalid."))?, + ) + .map_err(|_| { + Error::BadDatabase("Device ID in userdeviceid_metadata is invalid.") + })?) }) } /// Replaces the access token of one device. - pub fn set_token(&self, user_id: &UserId, device_id: &str, token: &str) -> Result<()> { + fn set_token(&self, user_id: &UserId, device_id: &str, token: &str) -> Result<()> { let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); userdeviceid.push(0xff); userdeviceid.extend_from_slice(device_id.as_bytes()); // All devices have metadata - if self.userdeviceid_metadata.get(&userdeviceid)?.is_none() { - return Err(Error::BadRequest( - "Tried to set token for nonexistent device", - )); - } + assert!(self.userdeviceid_metadata.get(&userdeviceid)?.is_some()); // Remove old token if let Some(old_token) = self.userdeviceid_token.get(&userdeviceid)? { @@ -228,19 +242,23 @@ impl Users { key.extend_from_slice(device_id.as_bytes()); // All devices have metadata - if self.userdeviceid_metadata.get(&key)?.is_none() { - return Err(Error::BadRequest( - "Tried to set token for nonexistent device", - )); - } + // Only existing devices should be able to call this. + assert!(self.userdeviceid_metadata.get(&key)?.is_some()); key.push(0xff); // TODO: Use AlgorithmAndDeviceId::to_string when it's available (and update everything, // because there are no wrapping quotation marks anymore) - key.extend_from_slice(&serde_json::to_string(one_time_key_key)?.as_bytes()); + key.extend_from_slice( + &serde_json::to_string(one_time_key_key) + .expect("AlgorithmAndDeviceId::to_string always works") + .as_bytes(), + ); - self.onetimekeyid_onetimekeys - .insert(&key, &*serde_json::to_string(&one_time_key_value)?)?; + self.onetimekeyid_onetimekeys.insert( + &key, + &*serde_json::to_string(&one_time_key_value) + .expect("OneTimeKey::to_string always works"), + )?; Ok(()) } @@ -271,9 +289,11 @@ impl Users { &*key .rsplit(|&b| b == 0xff) .next() - .ok_or(Error::BadDatabase("onetimekeyid is invalid"))?, - )?, - serde_json::from_slice(&*value)?, + .ok_or(Error::BadDatabase("OneTimeKeyId in db is invalid."))?, + ) + .map_err(|_| Error::BadDatabase("OneTimeKeyId in db is invalid."))?, + serde_json::from_slice(&*value) + .map_err(|_| Error::BadDatabase("OneTimeKeys in db are invalid."))?, )) }) .transpose() @@ -300,8 +320,9 @@ impl Users { &*bytes? .rsplit(|&b| b == 0xff) .next() - .ok_or(Error::BadDatabase("onetimekeyid is invalid"))?, - )? + .ok_or(Error::BadDatabase("OneTimeKey ID in db is invalid."))?, + ) + .map_err(|_| Error::BadDatabase("AlgorithmAndDeviceID in db is invalid."))? .0, ) }) @@ -323,8 +344,10 @@ impl Users { userdeviceid.push(0xff); userdeviceid.extend_from_slice(device_id.as_bytes()); - self.userdeviceid_devicekeys - .insert(&userdeviceid, &*serde_json::to_string(&device_keys)?)?; + self.userdeviceid_devicekeys.insert( + &userdeviceid, + &*serde_json::to_string(&device_keys).expect("DeviceKeys::to_string always works"), + )?; self.devicekeychangeid_userid .insert(globals.next_count()?.to_be_bytes(), &*user_id.to_string())?; @@ -344,14 +367,21 @@ impl Users { self.userdeviceid_devicekeys .scan_prefix(key) .values() - .map(|bytes| Ok(serde_json::from_slice(&bytes?)?)) + .map(|bytes| { + Ok(serde_json::from_slice(&bytes?) + .map_err(|_| Error::BadDatabase("DeviceKeys in db are invalid."))?) + }) } pub fn device_keys_changed(&self, since: u64) -> impl Iterator> { self.devicekeychangeid_userid .range(since.to_be_bytes()..) .values() - .map(|bytes| Ok(UserId::try_from(utils::string_from_bytes(&bytes?)?)?)) + .map(|bytes| { + Ok(serde_json::from_slice(&bytes?).map_err(|_| { + Error::BadDatabase("User ID in devicekeychangeid_userid is invalid.") + })?) + }) } pub fn all_device_keys( @@ -366,9 +396,14 @@ impl Users { let userdeviceid = utils::string_from_bytes( key.rsplit(|&b| b == 0xff) .next() - .ok_or(Error::BadDatabase("userdeviceid is invalid"))?, - )?; - Ok((userdeviceid, serde_json::from_slice(&*value)?)) + .ok_or(Error::BadDatabase("UserDeviceID in db is invalid."))?, + ) + .map_err(|_| Error::BadDatabase("UserDeviceId in db is invalid."))?; + Ok(( + userdeviceid, + serde_json::from_slice(&*value) + .map_err(|_| Error::BadDatabase("DeviceKeys in db are invalid."))?, + )) }) } @@ -392,8 +427,10 @@ impl Users { json.insert("sender".to_owned(), sender.to_string().into()); json.insert("content".to_owned(), content); - self.todeviceid_events - .insert(&key, &*serde_json::to_string(&json)?)?; + self.todeviceid_events.insert( + &key, + &*serde_json::to_string(&json).expect("Map::to_string always works"), + )?; Ok(()) } @@ -413,7 +450,10 @@ impl Users { for result in self.todeviceid_events.scan_prefix(&prefix).take(max) { let (key, value) = result?; - events.push(serde_json::from_slice(&*value)?); + events.push( + serde_json::from_slice(&*value) + .map_err(|_| Error::BadDatabase("Event in todeviceid_events is invalid."))?, + ); self.todeviceid_events.remove(key)?; } @@ -430,12 +470,15 @@ impl Users { userdeviceid.push(0xff); userdeviceid.extend_from_slice(device_id.as_bytes()); - if self.userdeviceid_metadata.get(&userdeviceid)?.is_none() { - return Err(Error::BadRequest("device does not exist")); - } + // Only existing devices should be able to call this. + assert!(self.userdeviceid_metadata.get(&userdeviceid)?.is_some()); - self.userdeviceid_metadata - .insert(userdeviceid, serde_json::to_string(device)?.as_bytes())?; + self.userdeviceid_metadata.insert( + userdeviceid, + serde_json::to_string(device) + .expect("Device::to_string always works") + .as_bytes(), + )?; Ok(()) } @@ -448,7 +491,11 @@ impl Users { self.userdeviceid_metadata .get(&userdeviceid)? - .map_or(Ok(None), |bytes| Ok(Some(serde_json::from_slice(&bytes)?))) + .map_or(Ok(None), |bytes| { + Ok(Some(serde_json::from_slice(&bytes).map_err(|_| { + Error::BadDatabase("Metadata in userdeviceid_metadata is invalid.") + })?)) + }) } pub fn all_devices_metadata(&self, user_id: &UserId) -> impl Iterator> { @@ -458,6 +505,10 @@ impl Users { self.userdeviceid_metadata .scan_prefix(key) .values() - .map(|bytes| Ok(serde_json::from_slice::(&bytes?)?)) + .map(|bytes| { + Ok(serde_json::from_slice::(&bytes?).map_err(|_| { + Error::BadDatabase("Device in userdeviceid_metadata is invalid.") + })?) + }) } } diff --git a/src/error.rs b/src/error.rs index 3561d9e..3652f0a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,41 +1,79 @@ +use crate::RumaResponse; +use http::StatusCode; +use rocket::{ + response::{self, Responder}, + Request, +}; +use ruma::api::client::{ + error::{Error as RumaError, ErrorKind}, + r0::uiaa::{UiaaInfo, UiaaResponse}, +}; use thiserror::Error; pub type Result = std::result::Result; #[derive(Error, Debug)] pub enum Error { - #[error("problem with the database")] + #[error("There was a problem with the connection to the database.")] SledError { #[from] source: sled::Error, }, - #[error("tried to parse invalid string")] - StringFromBytesError { - #[from] - source: std::string::FromUtf8Error, - }, - #[error("tried to parse invalid identifier")] - SerdeJsonError { - #[from] - source: serde_json::Error, - }, - #[error("tried to parse invalid identifier")] - RumaIdentifierError { - #[from] - source: ruma::identifiers::Error, - }, - #[error("tried to parse invalid event")] - RumaEventError { - #[from] - source: ruma::events::InvalidEvent, - }, - #[error("could not generate image")] + #[error("Could not generate an image.")] ImageError { #[from] source: image::error::ImageError, }, - #[error("bad request")] - BadRequest(&'static str), - #[error("problem in that database")] + #[error("{0}")] + BadConfig(&'static str), + #[error("{0}")] BadDatabase(&'static str), + #[error("uiaa")] + Uiaa(UiaaInfo), + + #[error("{0}: {1}")] + BadRequest(ErrorKind, &'static str), + #[error("{0}")] + Conflict(&'static str), // This is only needed for when a room alias already exists +} + +#[rocket::async_trait] +impl<'r> Responder<'r> for Error { + async fn respond_to(self, r: &'r Request<'_>) -> response::Result<'r> { + if let Self::Uiaa(uiaainfo) = &self { + return RumaResponse::from(UiaaResponse::AuthResponse(uiaainfo.clone())) + .respond_to(r) + .await; + } + + let message = format!("{}", self); + + use ErrorKind::*; + let (kind, status_code) = match self { + Self::BadRequest(kind, _) => ( + kind, + match kind { + Forbidden | GuestAccessForbidden | ThreepidAuthFailed | ThreepidDenied => { + StatusCode::FORBIDDEN + } + Unauthorized | UnknownToken | MissingToken => StatusCode::UNAUTHORIZED, + NotFound => StatusCode::NOT_FOUND, + LimitExceeded => StatusCode::TOO_MANY_REQUESTS, + UserDeactivated => StatusCode::FORBIDDEN, + TooLarge => StatusCode::PAYLOAD_TOO_LARGE, + _ => StatusCode::BAD_REQUEST, + }, + ), + Self::Conflict(_) => (Unknown, StatusCode::CONFLICT), + _ => (Unknown, StatusCode::INTERNAL_SERVER_ERROR), + }; + + RumaResponse::from(RumaError { + kind, + message, + status_code, + }) + .respond_to(r) + .await + } } diff --git a/src/main.rs b/src/main.rs index ad9aeda..bd0f8b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ mod utils; pub use database::Database; pub use error::{Error, Result}; pub use pdu::PduEvent; -pub use ruma_wrapper::{MatrixResult, Ruma}; +pub use ruma_wrapper::{ConduitResult, Ruma, RumaResponse}; use rocket::{fairing::AdHoc, routes}; @@ -95,7 +95,7 @@ fn setup_rocket() -> rocket::Rocket { ], ) .attach(AdHoc::on_attach("Config", |rocket| { - let data = Database::load_or_create(&rocket.config()); + let data = Database::load_or_create(&rocket.config()).expect("valid config"); Ok(rocket.manage(data)) })) diff --git a/src/pdu.rs b/src/pdu.rs index 454d27f..8c8423a 100644 --- a/src/pdu.rs +++ b/src/pdu.rs @@ -1,3 +1,4 @@ +use crate::{Error, Result}; use js_int::UInt; use ruma::{ api::federation::pdu::EventHash, @@ -36,7 +37,7 @@ pub struct PduEvent { } impl PduEvent { - pub fn redact(&mut self) { + pub fn redact(&mut self) -> Result<()> { self.unsigned.clear(); let allowed = match self.kind { EventType::RoomMember => vec!["membership"], @@ -56,7 +57,11 @@ impl PduEvent { _ => vec![], }; - let old_content = self.content.as_object_mut().unwrap(); // TODO error + let old_content = self + .content + .as_object_mut() + .ok_or(Error::BadDatabase("PDU has invalid content"))?; + let mut new_content = serde_json::Map::new(); for key in allowed { @@ -71,21 +76,23 @@ impl PduEvent { ); self.content = new_content.into(); + + Ok(()) } pub fn to_room_event(&self) -> EventJson { - // Can only fail in rare circumstances that won't ever happen here, see - // https://docs.rs/serde_json/1.0.50/serde_json/fn.to_string.html - let json = serde_json::to_string(&self).unwrap(); - // EventJson's deserialize implementation always returns `Ok(...)` - serde_json::from_str::>(&json).unwrap() + let json = serde_json::to_string(&self).expect("PDUs are always valid"); + serde_json::from_str::>(&json) + .expect("EventJson::from_str always works") } pub fn to_state_event(&self) -> EventJson { - let json = serde_json::to_string(&self).unwrap(); - serde_json::from_str::>(&json).unwrap() + let json = serde_json::to_string(&self).expect("PDUs are always valid"); + serde_json::from_str::>(&json) + .expect("EventJson::from_str always works") } pub fn to_stripped_state_event(&self) -> EventJson { - let json = serde_json::to_string(&self).unwrap(); - serde_json::from_str::>(&json).unwrap() + let json = serde_json::to_string(&self).expect("PDUs are always valid"); + serde_json::from_str::>(&json) + .expect("EventJson::from_str always works") } } diff --git a/src/ruma_wrapper.rs b/src/ruma_wrapper.rs index 47c8967..8be5c47 100644 --- a/src/ruma_wrapper.rs +++ b/src/ruma_wrapper.rs @@ -1,4 +1,4 @@ -use crate::utils; +use crate::{utils, Error}; use log::warn; use rocket::{ data::{Data, FromData, FromDataFuture, Transform, TransformFuture, Transformed}, @@ -42,7 +42,7 @@ impl<'a, T: Endpoint> FromData<'a> for Ruma { let data = rocket::try_outcome!(outcome.owned()); let (user_id, device_id) = if T::METADATA.requires_authentication { - let db = request.guard::>().await.unwrap(); + let db = request.guard::>().await.expect("database was loaded"); // Get token from header or query value let token = match request @@ -108,32 +108,24 @@ impl Deref for Ruma { } /// This struct converts ruma responses into rocket http responses. -pub struct MatrixResult(pub std::result::Result); +pub type ConduitResult = std::result::Result, Error>; -impl TryInto>> for MatrixResult -where - T: TryInto>>, - E: Into>>, -{ - type Error = T::Error; +pub struct RumaResponse>>>(pub T); - fn try_into(self) -> Result>, T::Error> { - match self.0 { - Ok(t) => t.try_into(), - Err(e) => Ok(e.into()), - } +impl>>> From for RumaResponse { + fn from(t: T) -> Self { + Self(t) } } #[rocket::async_trait] -impl<'r, T, E> Responder<'r> for MatrixResult +impl<'r, T> Responder<'r> for RumaResponse where T: Send + TryInto>>, T::Error: Send, - E: Into>> + Send, { async fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> { - let http_response: Result, _> = self.try_into(); + let http_response: Result, _> = self.0.try_into(); match http_response { Ok(http_response) => { let mut response = rocket::response::Response::build(); @@ -165,11 +157,3 @@ where } } } - -impl Deref for MatrixResult { - type Target = Result; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/src/utils.rs b/src/utils.rs index 8f3b4ad..0ab3bfa 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,3 @@ -use crate::Result; use argon2::{Config, Variant}; use rand::prelude::*; use std::{ @@ -9,39 +8,38 @@ use std::{ pub fn millis_since_unix_epoch() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) - .unwrap() + .expect("time is valid") .as_millis() as u64 } pub fn increment(old: Option<&[u8]>) -> Option> { - let number = match old { - Some(bytes) => { - let array: [u8; 8] = bytes.try_into().unwrap(); - let number = u64::from_be_bytes(array); + let number = match old.map(|bytes| bytes.try_into()) { + Some(Ok(bytes)) => { + let number = u64::from_be_bytes(bytes); number + 1 } - None => 1, // Start at one. since 0 should return the first event in the db + _ => 1, // Start at one. since 0 should return the first event in the db }; Some(number.to_be_bytes().to_vec()) } pub fn generate_keypair(old: Option<&[u8]>) -> Option> { - Some( - old.map(|s| s.to_vec()) - .unwrap_or_else(|| ruma::signatures::Ed25519KeyPair::generate().unwrap()), - ) + Some(old.map(|s| s.to_vec()).unwrap_or_else(|| { + ruma::signatures::Ed25519KeyPair::generate() + .expect("Ed25519KeyPair generation always works (?)") + })) } /// Parses the bytes into an u64. -pub fn u64_from_bytes(bytes: &[u8]) -> u64 { - let array: [u8; 8] = bytes.try_into().expect("bytes are valid u64"); - u64::from_be_bytes(array) +pub fn u64_from_bytes(bytes: &[u8]) -> Result { + let array: [u8; 8] = bytes.try_into()?; + Ok(u64::from_be_bytes(array)) } /// Parses the bytes into a string. -pub fn string_from_bytes(bytes: &[u8]) -> Result { - Ok(String::from_utf8(bytes.to_vec())?) +pub fn string_from_bytes(bytes: &[u8]) -> Result { + String::from_utf8(bytes.to_vec()) } pub fn random_string(length: usize) -> String { @@ -52,7 +50,7 @@ pub fn random_string(length: usize) -> String { } /// Calculate a new hash for the given password -pub fn calculate_hash(password: &str) -> std::result::Result { +pub fn calculate_hash(password: &str) -> Result { let hashing_config = Config { variant: Variant::Argon2id, ..Default::default()