From 8a49ae236165d085a0b79b49819f8cfccec52de5 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 18 Aug 2019 00:37:25 +0200 Subject: [PATCH] refactor: save lot implementation and follow up refactors rewrote qr code to match the now safe lot --- Xargo.toml | 6 + deltachat-ffi/deltachat.h | 2 - deltachat-ffi/src/lib.rs | 32 +- examples/repl/cmdline.rs | 37 +- examples/simple.rs | 13 +- src/chat.rs | 30 +- src/chatlist.rs | 8 +- src/constants.rs | 37 +- src/contact.rs | 9 +- src/dc_job.rs | 6 +- src/dc_msg.rs | 177 +++++++-- src/dc_qr.rs | 737 +++++++++++++++++++++++--------------- src/dc_receive_imf.rs | 20 +- src/dc_securejoin.rs | 214 ++++++----- src/error.rs | 8 + src/key.rs | 2 +- src/lot.rs | 175 ++++----- src/param.rs | 10 +- src/x.rs | 21 -- tests/stress.rs | 103 +++--- 20 files changed, 944 insertions(+), 703 deletions(-) create mode 100644 Xargo.toml diff --git a/Xargo.toml b/Xargo.toml new file mode 100644 index 000000000..afdcfe4f4 --- /dev/null +++ b/Xargo.toml @@ -0,0 +1,6 @@ +[dependencies.std] +features = ["panic-unwind"] + +# if using `cargo test` +[dependencies.test] +stage = 1 \ No newline at end of file diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index a0db09d09..f0e3d2f98 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -594,8 +594,6 @@ int dc_contact_is_verified (dc_contact_t*); #define DC_TEXT1_SELF 3 -dc_lot_t* dc_lot_new (void); -void dc_lot_empty (dc_lot_t*); void dc_lot_unref (dc_lot_t*); char* dc_lot_get_text1 (const dc_lot_t*); char* dc_lot_get_text2 (const dc_lot_t*); diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 049cf3e53..a6bb41772 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -991,7 +991,7 @@ pub unsafe extern "C" fn dc_check_qr( assert!(!qr.is_null()); let context = &*context; - let lot = dc_qr::dc_check_qr(context, qr); + let lot = dc_qr::dc_check_qr(context, as_str(qr)); Box::into_raw(Box::new(lot)) } @@ -1453,7 +1453,7 @@ pub unsafe extern "C" fn dc_msg_get_viewtype(msg: *mut dc_msg::dc_msg_t) -> libc pub unsafe extern "C" fn dc_msg_get_state(msg: *mut dc_msg::dc_msg_t) -> libc::c_int { assert!(!msg.is_null()); - dc_msg::dc_msg_get_state(msg) + dc_msg::dc_msg_get_state(msg) as libc::c_int } #[no_mangle] @@ -1794,19 +1794,6 @@ pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> l #[no_mangle] pub type dc_lot_t = lot::Lot; -#[no_mangle] -pub unsafe extern "C" fn dc_lot_new() -> *mut dc_lot_t { - Box::into_raw(Box::new(lot::Lot::new())) -} - -#[no_mangle] -pub unsafe extern "C" fn dc_lot_empty(lot: *mut dc_lot_t) { - assert!(!lot.is_null()); - - let _lot = Box::from_raw(lot); - *lot = lot::Lot::new(); -} - #[no_mangle] pub unsafe extern "C" fn dc_lot_unref(lot: *mut dc_lot_t) { assert!(!lot.is_null()); @@ -1819,7 +1806,7 @@ pub unsafe extern "C" fn dc_lot_get_text1(lot: *mut dc_lot_t) -> *mut libc::c_ch assert!(!lot.is_null()); let lot = &*lot; - lot.get_text1() + strdup_opt(lot.get_text1()) } #[no_mangle] @@ -1827,7 +1814,7 @@ pub unsafe extern "C" fn dc_lot_get_text2(lot: *mut dc_lot_t) -> *mut libc::c_ch assert!(!lot.is_null()); let lot = &*lot; - lot.get_text2() + strdup_opt(lot.get_text2()) } #[no_mangle] @@ -1835,7 +1822,7 @@ pub unsafe extern "C" fn dc_lot_get_text1_meaning(lot: *mut dc_lot_t) -> libc::c assert!(!lot.is_null()); let lot = &*lot; - lot.get_text1_meaning() + lot.get_text1_meaning() as libc::c_int } #[no_mangle] @@ -1843,7 +1830,7 @@ pub unsafe extern "C" fn dc_lot_get_state(lot: *mut dc_lot_t) -> libc::c_int { assert!(!lot.is_null()); let lot = &*lot; - lot.get_state() + lot.get_state().to_i64().expect("impossible") as libc::c_int } #[no_mangle] @@ -1897,3 +1884,10 @@ impl ResultExt for Result { } } } + +unsafe fn strdup_opt(s: Option>) -> *mut libc::c_char { + match s { + Some(s) => s.as_ref().strdup(), + None => ptr::null_mut(), + } +} diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index d391bbf0c..d0d016f53 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -16,6 +16,7 @@ use deltachat::dc_msg::*; use deltachat::dc_qr::*; use deltachat::dc_receive_imf::*; use deltachat::dc_tools::*; +use deltachat::lot::LotState; use deltachat::peerstate::*; use deltachat::sql; use deltachat::types::*; @@ -216,10 +217,10 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef, msg: *mut dc_msg_t let contact_id = contact.get_id(); let statestr = match dc_msg_get_state(msg) { - DC_STATE_OUT_PENDING => " o", - DC_STATE_OUT_DELIVERED => " √", - DC_STATE_OUT_MDN_RCVD => " √√", - DC_STATE_OUT_FAILED => " !!", + MessageState::OutPending => " o", + MessageState::OutDelivered => " √", + MessageState::OutMdnRcvd => " √√", + MessageState::OutFailed => " !!", _ => "", }; let temp2 = dc_timestamp_to_str(dc_msg_get_timestamp(msg)); @@ -242,9 +243,9 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef, msg: *mut dc_msg_t if dc_msg_is_starred(msg) { "★" } else { "" }, if dc_msg_get_from_id(msg) == 1 as libc::c_uint { "" - } else if dc_msg_get_state(msg) == DC_STATE_IN_SEEN { + } else if dc_msg_get_state(msg) == MessageState::InSeen { "[SEEN]" - } else if dc_msg_get_state(msg) == DC_STATE_IN_NOTICED { + } else if dc_msg_get_state(msg) == MessageState::InNoticed { "[NOTICED]" } else { "[FRESH]" @@ -621,10 +622,10 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E " [Archived]" } else { match lot.get_state() { - 20 => " o", - 26 => " √", - 28 => " √√", - 24 => " !!", + LotState::MsgOutPending => " o", + LotState::MsgOutDelivered => " √", + LotState::MsgOutMdnRcvd => " √√", + LotState::MsgOutFailed => " !!", _ => "", } }; @@ -635,9 +636,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E context, 0, "{}{}{}{} [{}]{}", - to_string(text1), - if !text1.is_null() { ": " } else { "" }, - to_string(text2), + text1.unwrap_or(""), + if text1.is_some() { ": " } else { "" }, + text2.unwrap_or(""), statestr, ×tr, if chat.is_sending_locations() { @@ -646,8 +647,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E "" }, ); - free(text1 as *mut libc::c_void); - free(text2 as *mut libc::c_void); info!( context, 0, "================================================================================" @@ -1049,13 +1048,13 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E } "checkqr" => { ensure!(!arg1.is_empty(), "Argument missing."); - let res = dc_check_qr(context, arg1_c); + let res = dc_check_qr(context, arg1); println!( - "state={}, id={}, text1={}, text2={}", + "state={}, id={}, text1={:?}, text2={:?}", res.get_state(), res.get_id(), - to_string(res.get_text1()), - to_string(res.get_text2()) + res.get_text1(), + res.get_text2() ); } "event" => { diff --git a/examples/simple.rs b/examples/simple.rs index 32a9203f3..9b39fbfdf 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -105,18 +105,7 @@ fn main() { let summary = chats.get_summary(0, None); let text1 = summary.get_text1(); let text2 = summary.get_text2(); - - let text1_s = if !text1.is_null() { - Some(CStr::from_ptr(text1)) - } else { - None - }; - let text2_s = if !text2.is_null() { - Some(CStr::from_ptr(text2)) - } else { - None - }; - println!("chat: {} - {:?} - {:?}", i, text1_s, text2_s,); + println!("chat: {} - {:?} - {:?}", i, text1, text2,); } thread::sleep(duration); diff --git a/src/chat.rs b/src/chat.rs index 564eca8b8..c17d4bde0 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -730,7 +730,7 @@ pub fn prepare_msg<'a>( "Cannot prepare message for special chat" ); - unsafe { (*msg).state = DC_STATE_OUT_PREPARING }; + unsafe { (*msg).state = MessageState::OutPreparing }; let msg_id = prepare_msg_common(context, chat_id, msg)?; context.call_cb( Event::MSGS_CHANGED, @@ -778,7 +778,7 @@ fn prepare_msg_common<'a>( let mut path_filename = path_filename.unwrap().to_string(); - if msg.state == DC_STATE_OUT_PREPARING && !dc_is_blobdir_path(context, &path_filename) { + if msg.state == MessageState::OutPreparing && !dc_is_blobdir_path(context, &path_filename) { bail!("Files must be created in the blob-directory."); } @@ -817,8 +817,8 @@ fn prepare_msg_common<'a>( unarchive(context, chat_id)?; let mut chat = Chat::load_from_db(context, chat_id)?; - if msg.state != DC_STATE_OUT_PREPARING { - msg.state = DC_STATE_OUT_PENDING; + if msg.state != MessageState::OutPreparing { + msg.state = MessageState::OutPending; } msg.id = unsafe { chat.prepare_msg_raw(context, msg, dc_create_smeared_timestamp(context))? }; @@ -886,7 +886,7 @@ pub unsafe fn send_msg<'a>( ) -> Result { ensure!(!msg.is_null(), "Invalid message"); - if (*msg).state != DC_STATE_OUT_PREPARING { + if (*msg).state != MessageState::OutPreparing { // automatically prepare normal messages prepare_msg_common(context, chat_id, msg)?; } else { @@ -895,7 +895,7 @@ pub unsafe fn send_msg<'a>( chat_id == 0 || chat_id == (*msg).chat_id, "Inconsistent chat ID" ); - dc_update_msg_state(context, (*msg).id, DC_STATE_OUT_PENDING); + dc_update_msg_state(context, (*msg).id, MessageState::OutPending); } ensure!( @@ -1004,7 +1004,7 @@ unsafe fn set_draft_raw(context: &Context, chat_id: u32, msg: *mut dc_msg_t) -> 1, time(), (*msg).type_0, - DC_STATE_OUT_DRAFT, + MessageState::OutDraft, (*msg).text.as_ref().map(String::as_str).unwrap_or(""), (*msg).param.to_string(), 1, @@ -1026,7 +1026,7 @@ fn get_draft_msg_id(context: &Context, chat_id: u32) -> u32 { .query_row_col::<_, i32>( context, "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![chat_id as i32, DC_STATE_OUT_DRAFT], + params![chat_id as i32, MessageState::OutDraft], 0, ) .unwrap_or_default() as u32 @@ -1154,7 +1154,7 @@ pub fn get_fresh_msg_cnt(context: &Context, chat_id: u32) -> usize { pub fn marknoticed_chat(context: &Context, chat_id: u32) -> Result<(), Error> { if !context.sql.exists( "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![chat_id as i32, DC_STATE_IN_FRESH], + params![chat_id as i32, MessageState::InFresh], )? { return Ok(()); } @@ -1294,7 +1294,11 @@ pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Err context, &context.sql, "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", - params![DC_STATE_IN_NOTICED, chat_id as i32, DC_STATE_IN_FRESH], + params![ + MessageState::InNoticed, + chat_id as i32, + MessageState::InFresh + ], )?; } @@ -1909,7 +1913,7 @@ pub unsafe fn forward_msgs( (*msg).param.remove(Param::Cmd); let new_msg_id: u32; - if (*msg).state == DC_STATE_OUT_PREPARING { + if (*msg).state == MessageState::OutPreparing { let fresh9 = curr_timestamp; curr_timestamp = curr_timestamp + 1; new_msg_id = chat @@ -1931,7 +1935,7 @@ pub unsafe fn forward_msgs( dc_msg_save_param_to_disk(msg); (*msg).param = save_param; } else { - (*msg).state = DC_STATE_OUT_PENDING; + (*msg).state = MessageState::OutPending; let fresh10 = curr_timestamp; curr_timestamp = curr_timestamp + 1; new_msg_id = chat @@ -2030,7 +2034,7 @@ pub fn add_device_msg(context: &Context, chat_id: u32, text: impl AsRef) { 2, dc_create_smeared_timestamp(context), Viewtype::Text, - DC_STATE_IN_NOTICED, + MessageState::InNoticed, text.as_ref(), as_str(rfc724_mid), ] diff --git a/src/chatlist.rs b/src/chatlist.rs index 721a769dd..79848fa81 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -3,11 +3,9 @@ use crate::constants::*; use crate::contact::*; use crate::context::*; use crate::dc_msg::*; -use crate::dc_tools::*; use crate::error::Result; use crate::lot::Lot; use crate::stock::StockMessage; -use std::ptr; /// An object representing a single chatlist in memory. /// @@ -257,7 +255,7 @@ impl<'a> Chatlist<'a> { let mut ret = Lot::new(); if index >= self.ids.len() { - ret.text2 = "ErrBadChatlistIndex".strdup(); + ret.text2 = Some("ErrBadChatlistIndex".to_string()); return ret; } @@ -291,9 +289,9 @@ impl<'a> Chatlist<'a> { }; if chat.id == DC_CHAT_ID_ARCHIVED_LINK as u32 { - ret.text2 = dc_strdup(ptr::null()) + ret.text2 = None; } else if lastmsg.is_null() || (*lastmsg).from_id == DC_CONTACT_ID_UNDEFINED as u32 { - ret.text2 = self.context.stock_str(StockMessage::NoMessages).strdup(); + ret.text2 = Some(self.context.stock_str(StockMessage::NoMessages).to_string()); } else { ret.fill(lastmsg, chat, lastcontact.as_ref(), self.context); } diff --git a/src/constants.rs b/src/constants.rs index 102636a8c..062632fdf 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -64,27 +64,8 @@ const DC_IMEX_EXPORT_BACKUP: usize = 11; /// param1 is the file with the backup to import const DC_IMEX_IMPORT_BACKUP: usize = 12; -/// id=contact -const DC_QR_ASK_VERIFYCONTACT: usize = 200; -/// text1=groupname -const DC_QR_ASK_VERIFYGROUP: usize = 202; -/// id=contact -const DC_QR_FPR_OK: usize = 210; -/// id=contact -const DC_QR_FPR_MISMATCH: usize = 220; -/// test1=formatted fingerprint -const DC_QR_FPR_WITHOUT_ADDR: usize = 230; -/// id=contact -const DC_QR_ADDR: usize = 320; -/// text1=text -const DC_QR_TEXT: usize = 330; -/// text1=URL -const DC_QR_URL: usize = 332; -/// text1=error string -const DC_QR_ERROR: usize = 400; - /// virtual chat showing all messages belonging to chats flagged with chats.blocked=2 -const DC_CHAT_ID_DEADDROP: usize = 1; +pub(crate) const DC_CHAT_ID_DEADDROP: usize = 1; /// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again) pub const DC_CHAT_ID_TRASH: usize = 3; /// a message is just in creation but not yet assigned to a chat (eg. we may need the message ID to set up blobs; this avoids unready message to be sent and shown) @@ -129,18 +110,6 @@ pub const DC_MSG_ID_MARKER1: usize = 1; const DC_MSG_ID_DAYMARKER: usize = 9; pub const DC_MSG_ID_LAST_SPECIAL: usize = 9; -const DC_STATE_UNDEFINED: i32 = 0; -pub const DC_STATE_IN_FRESH: i32 = 10; -pub const DC_STATE_IN_NOTICED: i32 = 13; -pub const DC_STATE_IN_SEEN: i32 = 16; -pub const DC_STATE_OUT_PREPARING: i32 = 18; -pub const DC_STATE_OUT_DRAFT: i32 = 19; -pub const DC_STATE_OUT_PENDING: i32 = 20; -pub const DC_STATE_OUT_FAILED: i32 = 24; -/// to check if a mail was sent, use dc_msg_is_sent() -pub const DC_STATE_OUT_DELIVERED: i32 = 26; -pub const DC_STATE_OUT_MDN_RCVD: i32 = 28; - /// approx. max. length returned by dc_msg_get_text() const DC_MAX_GET_TEXT_LEN: usize = 30000; /// approx. max. length returned by dc_get_msg_info() @@ -151,10 +120,6 @@ pub const DC_CONTACT_ID_SELF: usize = 1; const DC_CONTACT_ID_DEVICE: usize = 2; pub const DC_CONTACT_ID_LAST_SPECIAL: usize = 9; -const DC_TEXT1_DRAFT: usize = 1; -const DC_TEXT1_USERNAME: usize = 2; -const DC_TEXT1_SELF: usize = 3; - pub const DC_CREATE_MVBOX: usize = 1; // Flags for configuring IMAP and SMTP servers. diff --git a/src/contact.rs b/src/contact.rs index 799256d3f..8bb9dee95 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -9,6 +9,7 @@ use crate::constants::*; use crate::context::Context; use crate::dc_e2ee::*; use crate::dc_loginparam::*; +use crate::dc_msg::MessageState; use crate::dc_tools::*; use crate::error::Result; use crate::key::*; @@ -250,7 +251,7 @@ impl<'a> Contact<'a> { context, &context.sql, "UPDATE msgs SET state=? WHERE from_id=? AND state=?;", - params![DC_STATE_IN_NOTICED, id as i32, DC_STATE_IN_FRESH], + params![MessageState::InNoticed, id as i32, MessageState::InFresh], ) .is_ok() { @@ -327,7 +328,7 @@ impl<'a> Contact<'a> { "" }, ); - bail!("Bad address supplied"); + bail!("Bad address supplied: {:?}", addr); } let mut update_addr = false; @@ -909,7 +910,9 @@ fn get_first_name<'a>(full_name: &'a str) -> &'a str { /// Returns false if addr is an invalid address, otherwise true. pub fn may_be_valid_addr(addr: &str) -> bool { - addr.parse::().is_ok() + let res = addr.parse::(); + println!("{:?}", res); + res.is_ok() } pub fn addr_normalize(addr: &str) -> &str { diff --git a/src/dc_job.rs b/src/dc_job.rs index fd17c285e..b26dc51de 100644 --- a/src/dc_job.rs +++ b/src/dc_job.rs @@ -363,7 +363,11 @@ unsafe fn dc_job_do_DC_JOB_SEND(context: &Context, job: &mut dc_job_t) { } else { dc_delete_file(context, filename_s); if 0 != job.foreign_id { - dc_update_msg_state(context, job.foreign_id, DC_STATE_OUT_DELIVERED); + dc_update_msg_state( + context, + job.foreign_id, + MessageState::OutDelivered, + ); let chat_id: i32 = context .sql .query_row_col( diff --git a/src/dc_msg.rs b/src/dc_msg.rs index 3c5da7365..4f374f92d 100644 --- a/src/dc_msg.rs +++ b/src/dc_msg.rs @@ -2,6 +2,7 @@ use std::ffi::CString; use std::path::Path; use std::ptr; +use deltachat_derive::{FromSql, ToSql}; use phf::phf_map; use crate::chat::{self, Chat}; @@ -10,7 +11,7 @@ use crate::contact::*; use crate::context::*; use crate::dc_job::*; use crate::dc_tools::*; -use crate::lot::Lot; +use crate::lot::{Lot, LotState, Meaning}; use crate::param::*; use crate::pgp::*; use crate::sql; @@ -18,6 +19,115 @@ use crate::stock::StockMessage; use crate::types::*; use crate::x::*; +/// In practice, the user additionally cuts the string himself pixel-accurate. +const SUMMARY_CHARACTERS: usize = 160; + +#[repr(i32)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] +pub enum MessageState { + Undefined = 0, + InFresh = 10, + InNoticed = 13, + InSeen = 16, + OutPreparing = 18, + OutDraft = 19, + OutPending = 20, + OutFailed = 24, + OutDelivered = 26, + OutMdnRcvd = 28, +} + +impl From for LotState { + fn from(s: MessageState) -> Self { + use MessageState::*; + match s { + Undefined => LotState::Undefined, + InFresh => LotState::MsgInFresh, + InNoticed => LotState::MsgInNoticed, + InSeen => LotState::MsgInSeen, + OutPreparing => LotState::MsgOutPreparing, + OutDraft => LotState::MsgOutDraft, + OutPending => LotState::MsgOutPending, + OutFailed => LotState::MsgOutFailed, + OutDelivered => LotState::MsgOutDelivered, + OutMdnRcvd => LotState::MsgOutMdnRcvd, + } + } +} + +impl MessageState { + pub fn can_fail(self) -> bool { + match self { + MessageState::OutPreparing | MessageState::OutPending | MessageState::OutDelivered => { + true + } + _ => false, + } + } +} + +impl Lot { + /* library-internal */ + /* in practice, the user additionally cuts the string himself pixel-accurate */ + pub fn fill( + &mut self, + msg: *mut dc_msg_t, + chat: &Chat, + contact: Option<&Contact>, + context: &Context, + ) { + if msg.is_null() { + return; + } + + let msg = unsafe { &mut *msg }; + + if msg.state == MessageState::OutDraft { + self.text1 = Some(context.stock_str(StockMessage::Draft).to_owned().into()); + self.text1_meaning = Meaning::Text1Draft; + } else if msg.from_id == DC_CONTACT_ID_SELF as u32 { + if 0 != unsafe { dc_msg_is_info(msg) } || chat.is_self_talk() { + self.text1 = None; + self.text1_meaning = Meaning::None; + } else { + self.text1 = Some(context.stock_str(StockMessage::SelfMsg).to_owned().into()); + self.text1_meaning = Meaning::Text1Self; + } + } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { + if 0 != unsafe { dc_msg_is_info(msg) } || contact.is_none() { + self.text1 = None; + self.text1_meaning = Meaning::None; + } else { + if chat.id == DC_CHAT_ID_DEADDROP as u32 { + if let Some(contact) = contact { + self.text1 = Some(contact.get_display_name().into()); + } else { + self.text1 = None; + } + } else { + if let Some(contact) = contact { + self.text1 = Some(contact.get_first_name().into()); + } else { + self.text1 = None; + } + } + self.text1_meaning = Meaning::Text1Username; + } + } + + self.text2 = Some(dc_msg_get_summarytext_by_raw( + msg.type_0, + msg.text.as_ref(), + &mut msg.param, + SUMMARY_CHARACTERS, + context, + )); + + self.timestamp = unsafe { dc_msg_get_timestamp(msg) }; + self.state = msg.state.into(); + } +} + /* * the structure behind dc_msg_t */ #[derive(Clone)] #[repr(C)] @@ -28,7 +138,7 @@ pub struct dc_msg_t<'a> { pub chat_id: uint32_t, pub move_state: MoveState, pub type_0: Viewtype, - pub state: libc::c_int, + pub state: MessageState, pub hidden: libc::c_int, pub timestamp_sort: i64, pub timestamp_sent: i64, @@ -124,15 +234,16 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch .unwrap(); // TODO: better error handling ret += "State: "; + use MessageState::*; match (*msg).state { - DC_STATE_IN_FRESH => ret += "Fresh", - DC_STATE_IN_NOTICED => ret += "Noticed", - DC_STATE_IN_SEEN => ret += "Seen", - DC_STATE_OUT_DELIVERED => ret += "Delivered", - DC_STATE_OUT_FAILED => ret += "Failed", - DC_STATE_OUT_MDN_RCVD => ret += "Read", - DC_STATE_OUT_PENDING => ret += "Pending", - DC_STATE_OUT_PREPARING => ret += "Preparing", + InFresh => ret += "Fresh", + InNoticed => ret += "Noticed", + InSeen => ret += "Seen", + OutDelivered => ret += "Delivered", + OutFailed => ret += "Failed", + OutMdnRcvd => ret += "Read", + OutPending => ret += "Pending", + OutPreparing => ret += "Preparing", _ => ret += &format!("{}", (*msg).state), } @@ -229,7 +340,7 @@ pub unsafe fn dc_msg_new<'a>(context: &'a Context, viewtype: Viewtype) -> *mut d chat_id: 0, move_state: MoveState::Undefined, type_0: viewtype, - state: 0, + state: MessageState::Undefined, hidden: 0, timestamp_sort: 0, timestamp_sent: 0, @@ -518,7 +629,7 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize) for i in 0..msg_cnt { let id = unsafe { *msg_ids.offset(i as isize) }; let query_res = stmt.query_row(params![id as i32], |row| { - Ok((row.get::<_, i32>(0)?, row.get::<_, Option>(1)?.unwrap_or_default())) + Ok((row.get::<_, MessageState>(0)?, row.get::<_, Option>(1)?.unwrap_or_default())) }); if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res { continue; @@ -538,16 +649,16 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize) let msgs = msgs.unwrap(); for (id, curr_state, curr_blocked) in msgs.into_iter() { - if curr_blocked == 0 { - if curr_state == 10 || curr_state == 13 { - dc_update_msg_state(context, id, DC_STATE_IN_SEEN); + if curr_blocked == Blocked::Not { + if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed { + dc_update_msg_state(context, id, MessageState::InSeen); info!(context, 0, "Seen message #{}.", id); unsafe { dc_job_add(context, 130, id as i32, Params::new(), 0) }; send_event = true; } - } else if curr_state == DC_STATE_IN_FRESH { - dc_update_msg_state(context, id, DC_STATE_IN_NOTICED); + } else if curr_state == MessageState::InFresh { + dc_update_msg_state(context, id, MessageState::InNoticed); send_event = true; } } @@ -559,7 +670,7 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize) true } -pub fn dc_update_msg_state(context: &Context, msg_id: uint32_t, state: libc::c_int) -> bool { +pub fn dc_update_msg_state(context: &Context, msg_id: uint32_t, state: MessageState) -> bool { sql::execute( context, &context.sql, @@ -639,9 +750,9 @@ pub unsafe fn dc_msg_get_viewtype(msg: *const dc_msg_t) -> Viewtype { (*msg).type_0 } -pub unsafe fn dc_msg_get_state(msg: *const dc_msg_t) -> libc::c_int { +pub unsafe fn dc_msg_get_state(msg: *const dc_msg_t) -> MessageState { if msg.is_null() { - return 0i32; + return MessageState::Undefined; } (*msg).state @@ -868,7 +979,7 @@ pub unsafe fn dc_msg_is_sent(msg: *const dc_msg_t) -> libc::c_int { if msg.is_null() { return 0; } - if (*msg).state >= DC_STATE_OUT_DELIVERED { + if (*msg).state as i32 >= MessageState::OutDelivered as i32 { 1 } else { 0 @@ -916,7 +1027,7 @@ pub unsafe fn dc_msg_is_increation(msg: *const dc_msg_t) -> libc::c_int { return 0; } - if chat::msgtype_has_file((*msg).type_0) && (*msg).state == DC_STATE_OUT_PREPARING { + if chat::msgtype_has_file((*msg).type_0) && (*msg).state == MessageState::OutPreparing { 1 } else { 0 @@ -1124,18 +1235,12 @@ pub fn dc_update_msg_move_state( .is_ok() } -fn msgstate_can_fail(state: i32) -> bool { - DC_STATE_OUT_PREPARING == state - || DC_STATE_OUT_PENDING == state - || DC_STATE_OUT_DELIVERED == state -} - pub unsafe fn dc_set_msg_failed(context: &Context, msg_id: uint32_t, error: *const libc::c_char) { let mut msg = dc_msg_new_untyped(context); if dc_msg_load_from_db(msg, context, msg_id) { - if msgstate_can_fail((*msg).state) { - (*msg).state = DC_STATE_OUT_FAILED; + if (*msg).state.can_fail() { + (*msg).state = MessageState::OutFailed; } if !error.is_null() { (*msg).param.set(Param::Error, as_str(error)); @@ -1192,8 +1297,8 @@ pub unsafe fn dc_mdn_from_ext( Ok(( row.get::<_, i32>(0)?, row.get::<_, i32>(1)?, - row.get::<_, i32>(2)?, - row.get::<_, i32>(3)?, + row.get::<_, Chattype>(2)?, + row.get::<_, MessageState>(3)?, )) }, ) { @@ -1203,7 +1308,7 @@ pub unsafe fn dc_mdn_from_ext( /* if already marked as MDNS_RCVD msgstate_can_fail() returns false. however, it is important, that ret_msg_id is set above as this will allow the caller eg. to move the message away */ - if msgstate_can_fail(msg_state) { + if msg_state.can_fail() { let mdn_already_in_table = context .sql .exists( @@ -1220,8 +1325,8 @@ pub unsafe fn dc_mdn_from_ext( } // Normal chat? that's quite easy. - if chat_type == 100 { - dc_update_msg_state(context, *ret_msg_id, DC_STATE_OUT_MDN_RCVD); + if chat_type == Chattype::Single { + dc_update_msg_state(context, *ret_msg_id, MessageState::OutMdnRcvd); read_by_all = 1; } else { /* send event about new state */ @@ -1249,7 +1354,7 @@ pub unsafe fn dc_mdn_from_ext( // for rounding, SELF is already included! let soll_cnt = (chat::get_chat_contact_cnt(context, *ret_chat_id) + 1) / 2; if ist_cnt >= soll_cnt { - dc_update_msg_state(context, *ret_msg_id, DC_STATE_OUT_MDN_RCVD); + dc_update_msg_state(context, *ret_msg_id, MessageState::OutMdnRcvd); read_by_all = 1; } /* else wait for more receipts */ } diff --git a/src/dc_qr.rs b/src/dc_qr.rs index 71cb8574d..755960bec 100644 --- a/src/dc_qr.rs +++ b/src/dc_qr.rs @@ -1,302 +1,469 @@ +use lazy_static::lazy_static; use percent_encoding::percent_decode_str; use crate::chat; use crate::constants::Blocked; use crate::contact::*; use crate::context::Context; -use crate::dc_strencode::*; -use crate::dc_tools::*; +use crate::error::Error; +use crate::key::dc_format_fingerprint; use crate::key::*; -use crate::lot::Lot; +use crate::lot::{Lot, LotState}; use crate::param::*; use crate::peerstate::*; -use crate::types::*; -use crate::x::*; -// out-of-band verification -// id=contact -// text1=groupname -// id=contact -// id=contact -// test1=formatted fingerprint -// id=contact -// text1=text -// text1=URL -// text1=error string -pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> Lot { - let mut ok_to_continue = true; - let mut payload: *mut libc::c_char = 0 as *mut libc::c_char; - // must be normalized, if set - let mut addr: *mut libc::c_char = 0 as *mut libc::c_char; - // must be normalized, if set - let mut fingerprint: *mut libc::c_char = 0 as *mut libc::c_char; - let mut name: *mut libc::c_char = 0 as *mut libc::c_char; - let mut invitenumber: *mut libc::c_char = 0 as *mut libc::c_char; - let mut auth: *mut libc::c_char = 0 as *mut libc::c_char; - let mut qr_parsed = Lot::new(); - let mut chat_id: uint32_t = 0i32 as uint32_t; - let mut device_msg = "".to_string(); - let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char; - let mut grpname: *mut libc::c_char = 0 as *mut libc::c_char; - qr_parsed.state = 0i32; - if !qr.is_null() { - info!(context, 0, "Scanned QR code: {}", as_str(qr),); - /* split parameters from the qr code - ------------------------------------ */ - if strncasecmp( - qr, - b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char, - strlen(b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char), - ) == 0i32 - { - payload = - dc_strdup(&*qr.offset(strlen( - b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char, - ) as isize)); - let mut fragment: *mut libc::c_char = strchr(payload, '#' as i32); - if !fragment.is_null() { - *fragment = 0i32 as libc::c_char; - fragment = fragment.offset(1isize); - let param: Params = as_str(fragment).parse().expect("invalid params"); - addr = param - .get(Param::Forwarded) - .map(|s| s.strdup()) - .unwrap_or_else(|| std::ptr::null_mut()); - if !addr.is_null() { - if let Some(ref name_enc) = param.get(Param::SetLongitude) { - let name_r = percent_decode_str(name_enc) - .decode_utf8() - .expect("invalid name"); - name = normalize_name(name_r).strdup(); - } - invitenumber = param - .get(Param::ProfileImage) - .map(|s| s.strdup()) - .unwrap_or_else(|| std::ptr::null_mut()); - auth = param - .get(Param::Auth) - .map(|s| s.strdup()) - .unwrap_or_else(|| std::ptr::null_mut()); - grpid = param - .get(Param::GroupId) - .map(|s| s.strdup()) - .unwrap_or_else(|| std::ptr::null_mut()); - if !grpid.is_null() { - if let Some(grpname_enc) = param.get(Param::GroupName) { - let grpname_r = percent_decode_str(grpname_enc) - .decode_utf8() - .expect("invalid groupname"); - grpname = grpname_r.strdup(); - } - } - } - } - fingerprint = dc_normalize_fingerprint_c(payload); - } else if strncasecmp( - qr, - b"mailto:\x00" as *const u8 as *const libc::c_char, - strlen(b"mailto:\x00" as *const u8 as *const libc::c_char), - ) == 0i32 - { - payload = dc_strdup( - &*qr.offset(strlen(b"mailto:\x00" as *const u8 as *const libc::c_char) as isize), - ); - let query: *mut libc::c_char = strchr(payload, '?' as i32); - if !query.is_null() { - *query = 0i32 as libc::c_char - } - addr = dc_strdup(payload); - } else if strncasecmp( - qr, - b"SMTP:\x00" as *const u8 as *const libc::c_char, - strlen(b"SMTP:\x00" as *const u8 as *const libc::c_char), - ) == 0i32 - { - payload = dc_strdup( - &*qr.offset(strlen(b"SMTP:\x00" as *const u8 as *const libc::c_char) as isize), - ); - let colon: *mut libc::c_char = strchr(payload, ':' as i32); - if !colon.is_null() { - *colon = 0i32 as libc::c_char - } - addr = dc_strdup(payload); - } else if strncasecmp( - qr, - b"MATMSG:\x00" as *const u8 as *const libc::c_char, - strlen(b"MATMSG:\x00" as *const u8 as *const libc::c_char), - ) == 0i32 - { - /* scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;` - there may or may not be linebreaks after the fields */ - /* does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field. we ignore this case. */ - let to: *mut libc::c_char = strstr(qr, b"TO:\x00" as *const u8 as *const libc::c_char); - if !to.is_null() { - addr = dc_strdup(&mut *to.offset(3isize)); - let semicolon: *mut libc::c_char = strchr(addr, ';' as i32); - if !semicolon.is_null() { - *semicolon = 0i32 as libc::c_char - } - } else { - qr_parsed.state = 400i32; - qr_parsed.text1 = - dc_strdup(b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char); - ok_to_continue = false; +const OPENPGP4FPR_SCHEME: &str = "OPENPGP4FPR:"; // yes: uppercase +const MAILTO_SCHEME: &str = "mailto:"; +const MATMSG_SCHEME: &str = "MATMSG:"; +const VCARD_SCHEME: &str = "BEGIN:VCARD"; +const SMTP_SCHEME: &str = "SMTP:"; +const HTTP_SCHEME: &str = "http://"; +const HTTPS_SCHEME: &str = "https://"; + +// Make it easy to convert errors into the final `Lot`. +impl Into for Error { + fn into(self) -> Lot { + let mut l = Lot::new(); + l.state = LotState::QrError; + l.text1 = Some(self.to_string()); + + l + } +} + +/// Check a scanned QR code. +/// The function should be called after a QR code is scanned. +/// The function takes the raw text scanned and checks what can be done with it. +pub fn dc_check_qr(context: &Context, qr: impl AsRef) -> Lot { + let qr = qr.as_ref(); + + info!(context, 0, "Scanned QR code: {}", qr); + + if qr.starts_with(OPENPGP4FPR_SCHEME) { + decode_openpgp(context, qr) + } else if qr.starts_with(MAILTO_SCHEME) { + decode_mailto(context, qr) + } else if qr.starts_with(SMTP_SCHEME) { + decode_smtp(context, qr) + } else if qr.starts_with(MATMSG_SCHEME) { + decode_matmsg(context, qr) + } else if qr.starts_with(VCARD_SCHEME) { + decode_vcard(context, qr) + } else if qr.starts_with(HTTP_SCHEME) || qr.starts_with(HTTPS_SCHEME) { + Lot::from_url(qr) + } else { + Lot::from_text(qr) + } +} + +/// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH` +/// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH` +fn decode_openpgp(context: &Context, qr: &str) -> Lot { + let payload = &qr[OPENPGP4FPR_SCHEME.len()..]; + + let (fingerprint, fragment) = match payload.find('#').map(|offset| { + let (fp, rest) = payload.split_at(offset); + // need to remove the # from the fragment + (fp, &rest[1..]) + }) { + Some(pair) => pair, + None => return format_err!("Invalid OPENPGP4FPR found").into(), + }; + + dbg!(fingerprint); + dbg!(fragment); + + // replace & with \n to match expected param format + let fragment = fragment.replace('&', "\n"); + dbg!(&fragment); + + // Then parse the parameters + let param: Params = match fragment.parse() { + Ok(params) => params, + Err(err) => return err.into(), + }; + dbg!(¶m); + + let addr = if let Some(addr) = param.get(Param::Forwarded) { + match normalize_address(addr) { + Ok(addr) => addr, + Err(err) => return err.into(), + } + } else { + return format_err!("Missing address").into(); + }; + + // what is up with that param name? + let name = if let Some(encoded_name) = param.get(Param::SetLongitude) { + match percent_decode_str(encoded_name).decode_utf8() { + Ok(name) => name.to_string(), + Err(err) => return format_err!("Invalid name: {}", err).into(), + } + } else { + "".to_string() + }; + + let invitenumber = param.get(Param::ProfileImage).map(|s| s.to_string()); + let auth = param.get(Param::Auth).map(|s| s.to_string()); + let grpid = param.get(Param::GroupId).map(|s| s.to_string()); + + let grpname = if grpid.is_some() { + if let Some(encoded_name) = param.get(Param::GroupName) { + match percent_decode_str(encoded_name).decode_utf8() { + Ok(name) => Some(name.to_string()), + Err(err) => return format_err!("Invalid group name: {}", err).into(), } } else { - if strncasecmp( - qr, - b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char, - strlen(b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char), - ) == 0i32 - { - let lines = dc_split_into_lines(qr); - for &key in &lines { - dc_trim(key); - let mut value: *mut libc::c_char = strchr(key, ':' as i32); - if !value.is_null() { - *value = 0i32 as libc::c_char; - value = value.offset(1isize); - let mut semicolon_0: *mut libc::c_char = strchr(key, ';' as i32); - if !semicolon_0.is_null() { - *semicolon_0 = 0i32 as libc::c_char - } - if strcasecmp(key, b"EMAIL\x00" as *const u8 as *const libc::c_char) == 0i32 - { - semicolon_0 = strchr(value, ';' as i32); - if !semicolon_0.is_null() { - *semicolon_0 = 0i32 as libc::c_char - } - addr = dc_strdup(value) - } else if strcasecmp(key, b"N\x00" as *const u8 as *const libc::c_char) - == 0i32 - { - semicolon_0 = strchr(value, ';' as i32); - if !semicolon_0.is_null() { - semicolon_0 = strchr(semicolon_0.offset(1isize), ';' as i32); - if !semicolon_0.is_null() { - *semicolon_0 = 0i32 as libc::c_char - } - } - name = dc_strdup(value); - dc_str_replace( - &mut name, - b";\x00" as *const u8 as *const libc::c_char, - b",\x00" as *const u8 as *const libc::c_char, - ); - name = normalize_name(as_str(name)).strdup(); - } - } - } - dc_free_splitted_lines(lines); - } + None } - if ok_to_continue { - /* check the parameters - ---------------------- */ - if !addr.is_null() { - /* urldecoding is needed at least for OPENPGP4FPR but should not hurt in the other cases */ - let mut temp: *mut libc::c_char = dc_urldecode(addr); - free(addr as *mut libc::c_void); - addr = temp; - temp = addr_normalize(as_str(addr)).strdup(); - free(addr as *mut libc::c_void); - addr = temp; - if !may_be_valid_addr(as_str(addr)) { - qr_parsed.state = 400i32; - qr_parsed.text1 = - dc_strdup(b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char); - ok_to_continue = false; - } - } - } - if ok_to_continue && !fingerprint.is_null() && strlen(fingerprint) != 40 { - qr_parsed.state = 400i32; - qr_parsed.text1 = dc_strdup( - b"Bad fingerprint length in QR code.\x00" as *const u8 as *const libc::c_char, - ); - ok_to_continue = false; - } - if ok_to_continue { - if !fingerprint.is_null() { - let peerstate = - Peerstate::from_fingerprint(context, &context.sql, as_str(fingerprint)); - if addr.is_null() || invitenumber.is_null() || auth.is_null() { - if let Some(peerstate) = peerstate { - qr_parsed.state = 210i32; - let addr = peerstate - .addr - .as_ref() - .map(|s| s.as_str()) - .unwrap_or_else(|| ""); - qr_parsed.id = - Contact::add_or_lookup(context, "", addr, Origin::UnhandledQrScan) - .map(|(id, _)| id) - .unwrap_or_default(); - let (id, _) = chat::create_or_lookup_by_contact_id( - context, - qr_parsed.id, - Blocked::Deaddrop, - ) - .unwrap_or_default(); - chat_id = id; - device_msg = format!("{} verified.", peerstate.addr.unwrap_or_default()); - } else { - qr_parsed.text1 = dc_format_fingerprint_c(fingerprint); - qr_parsed.state = 230i32; - } - } else { - if !grpid.is_null() && !grpname.is_null() { - qr_parsed.state = 202i32; - qr_parsed.text1 = dc_strdup(grpname); - qr_parsed.text2 = dc_strdup(grpid) - } else { - qr_parsed.state = 200i32 - } - qr_parsed.id = Contact::add_or_lookup( - context, - as_str(name), - as_str(addr), - Origin::UnhandledQrScan, - ) - .map(|(id, _)| id) - .unwrap_or_default(); - qr_parsed.fingerprint = dc_strdup(fingerprint); - qr_parsed.invitenumber = dc_strdup(invitenumber); - qr_parsed.auth = dc_strdup(auth) - } - } else if !addr.is_null() { - qr_parsed.state = 320i32; - qr_parsed.id = Contact::add_or_lookup( - context, - as_str(name), - as_str(addr), - Origin::UnhandledQrScan, - ) + } else { + None + }; + + let fingerprint = dc_normalize_fingerprint(fingerprint); + + // ensure valid fingerprint + if fingerprint.len() != 40 { + return format_err!("Bad fingerprint length in QR code").into(); + } + + println!( + "{:?} {:?} {:?} {:?} {:?} {:?} {:?}", + addr, name, invitenumber, auth, grpid, grpname, fingerprint + ); + + let mut lot = Lot::new(); + + // retrieve known state for this fingerprint + let peerstate = Peerstate::from_fingerprint(context, &context.sql, &fingerprint); + + if invitenumber.is_none() || auth.is_none() { + if let Some(peerstate) = peerstate { + lot.state = LotState::QrFprOk; + let addr = peerstate + .addr + .as_ref() + .map(|s| s.as_str()) + .unwrap_or_else(|| ""); + + lot.id = Contact::add_or_lookup(context, name, addr, Origin::UnhandledQrScan) .map(|(id, _)| id) .unwrap_or_default(); - } else if strstr(qr, b"http://\x00" as *const u8 as *const libc::c_char) - == qr as *mut libc::c_char - || strstr(qr, b"https://\x00" as *const u8 as *const libc::c_char) - == qr as *mut libc::c_char - { - qr_parsed.state = 332i32; - qr_parsed.text1 = dc_strdup(qr) - } else { - qr_parsed.state = 330i32; - qr_parsed.text1 = dc_strdup(qr) - } - if !device_msg.is_empty() { - chat::add_device_msg(context, chat_id, device_msg); - } - } - } - free(addr as *mut libc::c_void); - free(fingerprint as *mut libc::c_void); - free(payload as *mut libc::c_void); - free(name as *mut libc::c_void); - free(invitenumber as *mut libc::c_void); - free(auth as *mut libc::c_void); - free(grpname as *mut libc::c_void); - free(grpid as *mut libc::c_void); - qr_parsed + let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop) + .unwrap_or_default(); + + chat::add_device_msg( + context, + id, + format!("{} verified.", peerstate.addr.unwrap_or_default()), + ); + } else { + lot.state = LotState::QrFprWithoutAddr; + lot.text1 = Some(dc_format_fingerprint(&fingerprint)); + } + } else { + if grpid.is_some() && grpname.is_some() { + lot.state = LotState::QrAskVerifyGroup; + lot.text1 = grpname; + lot.text2 = grpid + } else { + lot.state = LotState::QrAskVerifyContact; + } + lot.id = Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledQrScan) + .map(|(id, _)| id) + .unwrap_or_default(); + + lot.fingerprint = Some(fingerprint); + lot.invitenumber = invitenumber; + lot.auth = auth; + } + + lot +} + +/// Extract address for the mailto scheme. +/// +/// Scheme: `mailto:addr...?subject=...&body=..` +fn decode_mailto(context: &Context, qr: &str) -> Lot { + let payload = &qr[MAILTO_SCHEME.len()..]; + + let addr = if let Some(query_index) = payload.find('?') { + &payload[..query_index] + } else { + return format_err!("Invalid mailto found").into(); + }; + + let addr = match normalize_address(addr) { + Ok(addr) => addr, + Err(err) => return err.into(), + }; + + let name = "".to_string(); + Lot::from_address(context, name, addr) +} + +/// Extract address for the smtp scheme. +/// +/// Scheme: `SMTP:addr...:subject...:body...` +fn decode_smtp(context: &Context, qr: &str) -> Lot { + let payload = &qr[SMTP_SCHEME.len()..]; + + let addr = if let Some(query_index) = payload.find(':') { + &payload[..query_index] + } else { + return format_err!("Invalid SMTP found").into(); + }; + + let addr = match normalize_address(addr) { + Ok(addr) => addr, + Err(err) => return err.into(), + }; + + let name = "".to_string(); + Lot::from_address(context, name, addr) +} + +/// Extract address for the matmsg scheme. +/// +/// Scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;` +/// +/// There may or may not be linebreaks after the fields. +fn decode_matmsg(context: &Context, qr: &str) -> Lot { + // Does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field. + // we ignore this case. + let addr = if let Some(to_index) = qr.find("TO:") { + let addr = qr[to_index + 3..].trim(); + if let Some(semi_index) = addr.find(';') { + addr[..semi_index].trim() + } else { + addr + } + } else { + return format_err!("Invalid MATMSG found").into(); + }; + + let addr = match normalize_address(addr) { + Ok(addr) => addr, + Err(err) => return err.into(), + }; + + let name = "".to_string(); + Lot::from_address(context, name, addr) +} + +lazy_static! { + static ref VCARD_NAME_RE: regex::Regex = + regex::Regex::new(r"(?m)^N:([^;]*);([^;\n]*)").unwrap(); + static ref VCARD_EMAIL_RE: regex::Regex = + regex::Regex::new(r"(?m)^EMAIL([^:\n]*):([^;\n]*)").unwrap(); +} + +/// Extract address for the matmsg scheme. +/// +/// Scheme: `VCARD:BEGIN\nN:last name;first name;...;\nEMAIL;:addr...; +fn decode_vcard(context: &Context, qr: &str) -> Lot { + let name = VCARD_NAME_RE + .captures(qr) + .map(|caps| { + let last_name = &caps[1]; + let first_name = &caps[2]; + + format!("{} {}", first_name.trim(), last_name.trim()) + }) + .unwrap_or_default(); + + let addr = if let Some(caps) = VCARD_EMAIL_RE.captures(qr) { + match normalize_address(caps[2].trim()) { + Ok(addr) => addr, + Err(err) => return err.into(), + } + } else { + return format_err!("Bad e-mail address").into(); + }; + + Lot::from_address(context, name, addr) +} + +impl Lot { + pub fn from_text(text: impl AsRef) -> Self { + let mut l = Lot::new(); + l.state = LotState::QrText; + l.text1 = Some(text.as_ref().to_string()); + + l + } + + pub fn from_url(url: impl AsRef) -> Self { + let mut l = Lot::new(); + l.state = LotState::QrUrl; + l.text1 = Some(url.as_ref().to_string()); + + l + } + + pub fn from_address(context: &Context, name: String, addr: String) -> Self { + let mut l = Lot::new(); + l.state = LotState::QrAddr; + l.id = match Contact::add_or_lookup(context, name, addr, Origin::UnhandledQrScan) { + Ok((id, _)) => id, + Err(err) => return err.into(), + }; + + l + } +} + +/// URL decodes a given address, does basic email validation on the result. +fn normalize_address(addr: &str) -> Result { + // urldecoding is needed at least for OPENPGP4FPR but should not hurt in the other cases + let new_addr = percent_decode_str(addr).decode_utf8()?; + let new_addr = addr_normalize(&new_addr); + + ensure!(may_be_valid_addr(&new_addr), "Bad e-mail address"); + + Ok(new_addr.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::test_utils::dummy_context; + + #[test] + fn test_decode_http() { + let ctx = dummy_context(); + + let res = dc_check_qr(&ctx.ctx, "http://www.hello.com"); + + assert_eq!(res.get_state(), LotState::QrUrl); + assert_eq!(res.get_id(), 0); + assert_eq!(res.get_text1().unwrap(), "http://www.hello.com"); + assert!(res.get_text2().is_none()); + } + + #[test] + fn test_decode_https() { + let ctx = dummy_context(); + + let res = dc_check_qr(&ctx.ctx, "https://www.hello.com"); + + assert_eq!(res.get_state(), LotState::QrUrl); + assert_eq!(res.get_id(), 0); + assert_eq!(res.get_text1().unwrap(), "https://www.hello.com"); + assert!(res.get_text2().is_none()); + } + + #[test] + fn test_decode_text() { + let ctx = dummy_context(); + + let res = dc_check_qr(&ctx.ctx, "I am so cool"); + + assert_eq!(res.get_state(), LotState::QrText); + assert_eq!(res.get_id(), 0); + assert_eq!(res.get_text1().unwrap(), "I am so cool"); + assert!(res.get_text2().is_none()); + } + + #[test] + fn test_decode_vcard() { + let ctx = dummy_context(); + + let res = dc_check_qr( + &ctx.ctx, + "BEGIN:VCARD\nVERSION:3.0\nN:Last;First\nEMAIL;TYPE=INTERNET:stress@test.local\nEND:VCARD" + ); + + println!("{:?}", res); + assert_eq!(res.get_state(), LotState::QrAddr); + assert_ne!(res.get_id(), 0); + + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + assert_eq!(contact.get_addr(), "stress@test.local"); + assert_eq!(contact.get_name(), "First Last"); + } + + #[test] + fn test_decode_matmsg() { + let ctx = dummy_context(); + + let res = dc_check_qr( + &ctx.ctx, + "MATMSG:TO:\n\nstress@test.local ; \n\nSUB:\n\nSubject here\n\nBODY:\n\nhelloworld\n;;", + ); + + println!("{:?}", res); + assert_eq!(res.get_state(), LotState::QrAddr); + assert_ne!(res.get_id(), 0); + + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + assert_eq!(contact.get_addr(), "stress@test.local"); + } + + #[test] + fn test_decode_mailto() { + let ctx = dummy_context(); + + let res = dc_check_qr( + &ctx.ctx, + "mailto:stress@test.local?subject=hello&body=world", + ); + + println!("{:?}", res); + assert_eq!(res.get_state(), LotState::QrAddr); + assert_ne!(res.get_id(), 0); + + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + assert_eq!(contact.get_addr(), "stress@test.local"); + } + + #[test] + fn test_decode_smtp() { + let ctx = dummy_context(); + + let res = dc_check_qr(&ctx.ctx, "SMTP:stress@test.local:subjecthello:bodyworld"); + + println!("{:?}", res); + assert_eq!(res.get_state(), LotState::QrAddr); + assert_ne!(res.get_id(), 0); + + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + assert_eq!(contact.get_addr(), "stress@test.local"); + } + + #[test] + fn test_decode_openpgp_group() { + let ctx = dummy_context(); + + let res = dc_check_qr( + &ctx.ctx, + "OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=testtesttest&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL9cxRL" + ); + + println!("{:?}", res); + assert_eq!(res.get_state(), LotState::QrAskVerifyGroup); + assert_ne!(res.get_id(), 0); + + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + assert_eq!(contact.get_addr(), "cli@deltachat.de"); + } + + #[test] + fn test_decode_openpgp_secure_join() { + let ctx = dummy_context(); + + let res = dc_check_qr( + &ctx.ctx, + "OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&n=&i=TbnwJ6lSvD5&s=0ejvbdFSQxB" + ); + + println!("{:?}", res); + assert_eq!(res.get_state(), LotState::QrAskVerifyContact); + assert_ne!(res.get_id(), 0); + + let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap(); + assert_eq!(contact.get_addr(), "cli@deltachat.de"); + } } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index bce036626..95dc879bb 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -282,7 +282,7 @@ unsafe fn add_parts( created_db_entries: &mut Vec<(usize, usize)>, create_event_to_send: &mut Option, ) -> Result<()> { - let mut state: libc::c_int; + let mut state: MessageState; let mut msgrmsg: libc::c_int; let mut chat_id_blocked = Blocked::Not; let mut sort_timestamp = 0; @@ -389,9 +389,9 @@ unsafe fn add_parts( // (of course, the user can add other chats manually later) if 0 != incoming { state = if 0 != flags & DC_IMAP_SEEN { - DC_STATE_IN_SEEN + MessageState::InSeen } else { - DC_STATE_IN_FRESH + MessageState::InFresh }; *to_id = 1; // handshake messages must be processed _before_ chats are created @@ -405,7 +405,7 @@ unsafe fn add_parts( if 0 != handshake & DC_HANDSHAKE_STOP_NORMAL_PROCESSING { *hidden = 1; *add_delete_job = handshake & DC_HANDSHAKE_ADD_DELETE_JOB; - state = DC_STATE_IN_SEEN; + state = MessageState::InSeen; } } @@ -498,9 +498,9 @@ unsafe fn add_parts( // if the chat_id is blocked, // for unknown senders and non-delta messages set the state to NOTICED // to not result in a contact request (this would require the state FRESH) - if Blocked::Not != chat_id_blocked && state == DC_STATE_IN_FRESH { + if Blocked::Not != chat_id_blocked && state == MessageState::InFresh { if !incoming_origin.is_verified() && msgrmsg == 0 { - state = DC_STATE_IN_NOTICED; + state = MessageState::InNoticed; } } } else { @@ -508,7 +508,7 @@ unsafe fn add_parts( // the mail is on the IMAP server, probably it is also delivered. // We cannot recreate other states (read, error). - state = DC_STATE_OUT_DELIVERED; + state = MessageState::OutDelivered; *from_id = DC_CONTACT_ID_SELF as u32; if !to_ids.is_empty() { *to_id = to_ids[0]; @@ -640,8 +640,8 @@ unsafe fn add_parts( || *part.msg.offset(0) as libc::c_int == 0) { *hidden = 1; - if state == DC_STATE_IN_FRESH { - state = DC_STATE_IN_NOTICED; + if state == MessageState::InFresh { + state = MessageState::InNoticed; } } if part.type_0 == Viewtype::Text as i32 { @@ -732,7 +732,7 @@ unsafe fn add_parts( // check event to send if *chat_id == DC_CHAT_ID_TRASH as u32 { *create_event_to_send = None; - } else if 0 != incoming && state == DC_STATE_IN_FRESH { + } else if 0 != incoming && state == MessageState::InFresh { if 0 != from_id_blocked { *create_event_to_send = None; } else if Blocked::Not != chat_id_blocked { diff --git a/src/dc_securejoin.rs b/src/dc_securejoin.rs index fd83d0ad7..8dcc311df 100644 --- a/src/dc_securejoin.rs +++ b/src/dc_securejoin.rs @@ -14,6 +14,7 @@ use crate::dc_qr::*; use crate::dc_token::*; use crate::dc_tools::*; use crate::key::*; +use crate::lot::LotState; use crate::param::*; use crate::peerstate::*; use crate::stock::StockMessage; @@ -142,8 +143,10 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> ongoing_allocated = dc_alloc_ongoing(context); if !(ongoing_allocated == 0i32) { - let qr_scan = dc_check_qr(context, qr); - if qr_scan.state != 200i32 && qr_scan.state != 202i32 { + let qr_scan = dc_check_qr(context, as_str(qr)); + if qr_scan.state != LotState::QrAskVerifyContact + && qr_scan.state != LotState::QrAskVerifyGroup + { error!(context, 0, "Unknown QR code.",); } else { contact_chat_id = chat::create_by_contact_id(context, qr_scan.id).unwrap_or_default(); @@ -156,7 +159,7 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> .unwrap() .shall_stop_ongoing) { - join_vg = (qr_scan.state == 202i32) as libc::c_int; + join_vg = (qr_scan.get_state() == LotState::QrAskVerifyGroup) as libc::c_int; { let mut bob = context.bob.write().unwrap(); bob.status = 0; @@ -171,7 +174,9 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> .qr_scan .as_ref() .unwrap() - .fingerprint, + .fingerprint + .as_ref() + .unwrap(), contact_chat_id, ) { info!(context, 0, "Taking protocol shortcut."); @@ -190,12 +195,32 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> } else { b"vc-request-with-auth\x00" as *const u8 as *const libc::c_char }, - context.bob.read().unwrap().qr_scan.as_ref().unwrap().auth, + context + .bob + .read() + .unwrap() + .qr_scan + .as_ref() + .unwrap() + .auth + .as_ref() + .unwrap() + .to_string(), own_fingerprint, if 0 != join_vg { - as_str(context.bob.read().unwrap().qr_scan.as_ref().unwrap().text2) + context + .bob + .read() + .unwrap() + .qr_scan + .as_ref() + .unwrap() + .text2 + .as_ref() + .unwrap() + .to_string() } else { - "" + "".to_string() }, ); free(own_fingerprint as *mut libc::c_void); @@ -216,7 +241,9 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> .qr_scan .as_ref() .unwrap() - .invitenumber, + .invitenumber + .as_ref() + .unwrap(), 0 as *const libc::c_char, "", ); @@ -241,7 +268,7 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) -> if 0 != join_vg { ret_chat_id = chat::get_chat_id_by_grpid( context, - to_string(bob.qr_scan.as_ref().unwrap().text2), + bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(), None, 0 as *mut libc::c_int, ) as libc::c_int @@ -262,7 +289,7 @@ unsafe fn send_handshake_msg( context: &Context, contact_chat_id: uint32_t, step: *const libc::c_char, - param2: *const libc::c_char, + param2: impl AsRef, fingerprint: *const libc::c_char, grpid: impl AsRef, ) { @@ -276,8 +303,8 @@ unsafe fn send_handshake_msg( } else { (*msg).param.set(Param::Arg, as_str(step)); } - if !param2.is_null() { - (*msg).param.set(Param::Arg2, as_str(param2)); + if !param2.as_ref().is_empty() { + (*msg).param.set(Param::Arg2, param2); } if !fingerprint.is_null() { (*msg).param.set(Param::Arg3, as_str(fingerprint)); @@ -311,20 +338,17 @@ unsafe fn chat_id_2_contact_id(context: &Context, contact_chat_id: uint32_t) -> unsafe fn fingerprint_equals_sender( context: &Context, - fingerprint: *const libc::c_char, - contact_chat_id: uint32_t, + fingerprint: impl AsRef, + contact_chat_id: u32, ) -> libc::c_int { - if fingerprint.is_null() { - return 0; - } - let mut fingerprint_equal: libc::c_int = 0i32; + let mut fingerprint_equal = 0; let contacts = chat::get_chat_contacts(context, contact_chat_id); if contacts.len() == 1 { if let Ok(contact) = Contact::load_from_db(context, contacts[0]) { if let Some(peerstate) = Peerstate::from_addr(context, &context.sql, contact.get_addr()) { - let fingerprint_normalized = dc_normalize_fingerprint(as_str(fingerprint)); + let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref()); if peerstate.public_key_fingerprint.is_some() && &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap() { @@ -348,8 +372,6 @@ pub unsafe fn dc_handle_securejoin_handshake( let mut current_block: u64; let step: *const libc::c_char; let join_vg: libc::c_int; - let mut scanned_fingerprint_of_alice: *mut libc::c_char = 0 as *mut libc::c_char; - let mut auth: *mut libc::c_char = 0 as *mut libc::c_char; let mut own_fingerprint: *mut libc::c_char = 0 as *mut libc::c_char; let contact_chat_id: u32; let contact_chat_id_blocked: Blocked; @@ -411,7 +433,7 @@ pub unsafe fn dc_handle_securejoin_handshake( } else { b"vc-auth-required\x00" as *const u8 as *const libc::c_char }, - 0 as *const libc::c_char, + "", 0 as *const libc::c_char, "", ); @@ -429,7 +451,9 @@ pub unsafe fn dc_handle_securejoin_handshake( let cond = { let bob = context.bob.read().unwrap(); let scan = bob.qr_scan.as_ref(); - scan.is_none() || bob.expects != 2 || 0 != join_vg && scan.unwrap().state != 202 + scan.is_none() + || bob.expects != 2 + || 0 != join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup }; if cond { @@ -437,26 +461,44 @@ pub unsafe fn dc_handle_securejoin_handshake( // no error, just aborted somehow or a mail from another handshake current_block = 4378276786830486580; } else { - { - scanned_fingerprint_of_alice = dc_strdup( - context - .bob - .read() - .unwrap() - .qr_scan - .as_ref() - .unwrap() - .fingerprint, - ); - auth = - dc_strdup(context.bob.read().unwrap().qr_scan.as_ref().unwrap().auth); - if 0 != join_vg { - grpid = to_string( - context.bob.read().unwrap().qr_scan.as_ref().unwrap().text2, - ); - } + let scanned_fingerprint_of_alice = context + .bob + .read() + .unwrap() + .qr_scan + .as_ref() + .unwrap() + .fingerprint + .as_ref() + .unwrap() + .to_string(); + + let auth = context + .bob + .read() + .unwrap() + .qr_scan + .as_ref() + .unwrap() + .auth + .as_ref() + .unwrap() + .to_string(); + if 0 != join_vg { + grpid = context + .bob + .read() + .unwrap() + .qr_scan + .as_ref() + .unwrap() + .text2 + .as_ref() + .unwrap() + .to_string(); } - if !encrypted_and_signed(mimeparser, scanned_fingerprint_of_alice) { + + if !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice) { could_not_establish_secure_connection( context, contact_chat_id, @@ -471,7 +513,7 @@ pub unsafe fn dc_handle_securejoin_handshake( } else if 0 == fingerprint_equals_sender( context, - scanned_fingerprint_of_alice, + &scanned_fingerprint_of_alice, contact_chat_id, ) { @@ -523,8 +565,7 @@ pub unsafe fn dc_handle_securejoin_handshake( ==== Step 6 in "Out-of-band verified groups" protocol ==== ============================================================ */ // verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob - let fingerprint: *const libc::c_char; - fingerprint = lookup_field(mimeparser, "Secure-Join-Fingerprint"); + let fingerprint = lookup_field(mimeparser, "Secure-Join-Fingerprint"); if fingerprint.is_null() { could_not_establish_secure_connection( context, @@ -532,14 +573,16 @@ pub unsafe fn dc_handle_securejoin_handshake( b"Fingerprint not provided.\x00" as *const u8 as *const libc::c_char, ); current_block = 4378276786830486580; - } else if !encrypted_and_signed(mimeparser, fingerprint) { + } else if !encrypted_and_signed(mimeparser, as_str(fingerprint)) { could_not_establish_secure_connection( context, contact_chat_id, b"Auth not encrypted.\x00" as *const u8 as *const libc::c_char, ); current_block = 4378276786830486580; - } else if 0 == fingerprint_equals_sender(context, fingerprint, contact_chat_id) { + } else if 0 + == fingerprint_equals_sender(context, as_str(fingerprint), contact_chat_id) + { could_not_establish_secure_connection( context, contact_chat_id, @@ -566,7 +609,7 @@ pub unsafe fn dc_handle_securejoin_handshake( b"Auth invalid.\x00" as *const u8 as *const libc::c_char, ); current_block = 4378276786830486580; - } else if 0 == mark_peer_as_verified(context, fingerprint) { + } else if 0 == mark_peer_as_verified(context, as_str(fingerprint)) { could_not_establish_secure_connection( context, contact_chat_id, @@ -617,7 +660,7 @@ pub unsafe fn dc_handle_securejoin_handshake( context, contact_chat_id, b"vc-contact-confirm\x00" as *const u8 as *const libc::c_char, - 0 as *const libc::c_char, + "", 0 as *const libc::c_char, "", ); @@ -649,7 +692,8 @@ pub unsafe fn dc_handle_securejoin_handshake( let cond = { let bob = context.bob.read().unwrap(); let scan = bob.qr_scan.as_ref(); - scan.is_none() || 0 != join_vg && scan.unwrap().state != 202 + scan.is_none() + || 0 != join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup }; if cond { warn!( @@ -658,23 +702,32 @@ pub unsafe fn dc_handle_securejoin_handshake( ); current_block = 4378276786830486580; } else { - { - scanned_fingerprint_of_alice = dc_strdup( - context - .bob - .read() - .unwrap() - .qr_scan - .as_ref() - .unwrap() - .fingerprint, - ); - if 0 != join_vg { - grpid = to_string( - context.bob.read().unwrap().qr_scan.as_ref().unwrap().text2, - ); - } + let scanned_fingerprint_of_alice = context + .bob + .read() + .unwrap() + .qr_scan + .as_ref() + .unwrap() + .fingerprint + .as_ref() + .unwrap() + .to_string(); + + if 0 != join_vg { + grpid = context + .bob + .read() + .unwrap() + .qr_scan + .as_ref() + .unwrap() + .text2 + .as_ref() + .unwrap() + .to_string(); } + let mut vg_expect_encrypted: libc::c_int = 1i32; if 0 != join_vg { let mut is_verified_group: libc::c_int = 0i32; @@ -689,7 +742,7 @@ pub unsafe fn dc_handle_securejoin_handshake( } } if 0 != vg_expect_encrypted { - if !encrypted_and_signed(mimeparser, scanned_fingerprint_of_alice) { + if !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice) { could_not_establish_secure_connection( context, contact_chat_id, @@ -707,8 +760,10 @@ pub unsafe fn dc_handle_securejoin_handshake( match current_block { 4378276786830486580 => {} _ => { - if 0 == mark_peer_as_verified(context, scanned_fingerprint_of_alice) - { + if 0 == mark_peer_as_verified( + context, + &scanned_fingerprint_of_alice, + ) { could_not_establish_secure_connection( context, contact_chat_id, @@ -758,7 +813,7 @@ pub unsafe fn dc_handle_securejoin_handshake( contact_chat_id, b"vg-member-added-received\x00" as *const u8 as *const libc::c_char, - 0 as *const libc::c_char, + "", 0 as *const libc::c_char, "", ); @@ -816,8 +871,6 @@ pub unsafe fn dc_handle_securejoin_handshake( } } - free(scanned_fingerprint_of_alice as *mut libc::c_void); - free(auth as *mut libc::c_void); free(own_fingerprint as *mut libc::c_void); ret @@ -882,16 +935,13 @@ unsafe fn could_not_establish_secure_connection( error!(context, 0, "{} ({})", &msg, as_str(details)); } -unsafe fn mark_peer_as_verified( - context: &Context, - fingerprint: *const libc::c_char, -) -> libc::c_int { +unsafe fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef) -> libc::c_int { let mut success = 0; if let Some(ref mut peerstate) = - Peerstate::from_fingerprint(context, &context.sql, as_str(fingerprint)) + Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref()) { - if peerstate.set_verified(1, as_str(fingerprint), 2) { + if peerstate.set_verified(1, fingerprint.as_ref(), 2) { peerstate.prefer_encrypt = EncryptPreference::Mutual; peerstate.to_save = Some(ToSave::All); peerstate.save_to_db(&context.sql, false); @@ -908,7 +958,7 @@ unsafe fn mark_peer_as_verified( unsafe fn encrypted_and_signed( mimeparser: &dc_mimeparser_t, - expected_fingerprint: *const libc::c_char, + expected_fingerprint: impl AsRef, ) -> bool { if 0 == mimeparser.e2ee_helper.encrypted { warn!(mimeparser.context, 0, "Message not encrypted.",); @@ -918,20 +968,20 @@ unsafe fn encrypted_and_signed( warn!(mimeparser.context, 0, "Message not signed.",); return false; } - if expected_fingerprint.is_null() { + if expected_fingerprint.as_ref().is_empty() { warn!(mimeparser.context, 0, "Fingerprint for comparison missing.",); return false; } if !mimeparser .e2ee_helper .signatures - .contains(as_str(expected_fingerprint)) + .contains(expected_fingerprint.as_ref()) { warn!( mimeparser.context, 0, "Message does not match expected fingerprint {}.", - as_str(expected_fingerprint), + expected_fingerprint.as_ref(), ); return false; } diff --git a/src/error.rs b/src/error.rs index 26f4700fd..8f3bc7838 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,8 @@ pub enum Error { Message(String), #[fail(display = "{:?}", _0)] Image(image_meta::ImageError), + #[fail(display = "{:?}", _0)] + Utf8(std::str::Utf8Error), } pub type Result = std::result::Result; @@ -48,6 +50,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: std::str::Utf8Error) -> Error { + Error::Utf8(err) + } +} + impl From for Error { fn from(err: image_meta::ImageError) -> Error { Error::Image(err) diff --git a/src/key.rs b/src/key.rs index 84138adc5..bb7ac3115 100644 --- a/src/key.rs +++ b/src/key.rs @@ -306,7 +306,7 @@ pub fn dc_key_save_self_keypair( } /// Make a fingerprint human-readable, in hex format. -fn dc_format_fingerprint(fingerprint: &str) -> String { +pub fn dc_format_fingerprint(fingerprint: &str) -> String { // split key into chunks of 4 with space, and 20 newline let mut res = String::new(); diff --git a/src/lot.rs b/src/lot.rs index 15a5976ce..85a11f4f1 100644 --- a/src/lot.rs +++ b/src/lot.rs @@ -1,11 +1,4 @@ -use crate::chat::*; -use crate::constants::Chattype; -use crate::contact::*; -use crate::context::Context; -use crate::dc_msg::*; -use crate::dc_tools::*; -use crate::stock::StockMessage; -use crate::x::*; +use deltachat_derive::{FromSql, ToSql}; /// An object containing a set of values. /// The meaning of the values is defined by the function returning the object. @@ -13,46 +6,52 @@ use crate::x::*; /// eg. by chatlist.get_summary() or dc_msg_get_summary(). /// /// _Lot_ is used in the meaning _heap_ here. -#[derive(Clone)] +#[derive(Default, Debug, Clone)] pub struct Lot { - pub(crate) text1_meaning: i32, - pub(crate) text1: *mut libc::c_char, - pub(crate) text2: *mut libc::c_char, + pub(crate) text1_meaning: Meaning, + pub(crate) text1: Option, + pub(crate) text2: Option, pub(crate) timestamp: i64, - pub(crate) state: i32, + pub(crate) state: LotState, pub(crate) id: u32, - pub(crate) fingerprint: *mut libc::c_char, - pub(crate) invitenumber: *mut libc::c_char, - pub(crate) auth: *mut libc::c_char, + pub(crate) fingerprint: Option, + pub(crate) invitenumber: Option, + pub(crate) auth: Option, +} + +#[repr(u8)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] +pub enum Meaning { + None = 0, + Text1Draft = 1, + Text1Username = 2, + Text1Self = 3, +} + +impl Default for Meaning { + fn default() -> Self { + Meaning::None + } } impl Lot { pub fn new() -> Self { - Lot { - text1_meaning: 0, - text1: std::ptr::null_mut(), - text2: std::ptr::null_mut(), - timestamp: 0, - state: 0, - id: 0, - fingerprint: std::ptr::null_mut(), - invitenumber: std::ptr::null_mut(), - auth: std::ptr::null_mut(), - } - } - pub unsafe fn get_text1(&self) -> *mut libc::c_char { - dc_strdup_keep_null(self.text1) + Default::default() } - pub unsafe fn get_text2(&self) -> *mut libc::c_char { - dc_strdup_keep_null(self.text2) + pub fn get_text1(&self) -> Option<&str> { + self.text1.as_ref().map(|s| s.as_str()) } - pub fn get_text1_meaning(&self) -> i32 { + pub fn get_text2(&self) -> Option<&str> { + self.text2.as_ref().map(|s| s.as_str()) + } + + pub fn get_text1_meaning(&self) -> Meaning { self.text1_meaning } - pub fn get_state(&self) -> i32 { + pub fn get_state(&self) -> LotState { self.state } @@ -63,74 +62,48 @@ impl Lot { pub fn get_timestamp(&self) -> i64 { self.timestamp } - - /* library-internal */ - /* in practice, the user additionally cuts the string himself pixel-accurate */ - pub unsafe fn fill( - &mut self, - msg: *mut dc_msg_t, - chat: &Chat, - contact: Option<&Contact>, - context: &Context, - ) { - if msg.is_null() { - return; - } - if (*msg).state == 19i32 { - self.text1 = context.stock_str(StockMessage::Draft).strdup(); - self.text1_meaning = 1i32 - } else if (*msg).from_id == 1i32 as libc::c_uint { - if 0 != dc_msg_is_info(msg) || chat.is_self_talk() { - self.text1 = 0 as *mut libc::c_char; - self.text1_meaning = 0i32 - } else { - self.text1 = context.stock_str(StockMessage::SelfMsg).strdup(); - self.text1_meaning = 3i32 - } - } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { - if 0 != dc_msg_is_info(msg) || contact.is_none() { - self.text1 = 0 as *mut libc::c_char; - self.text1_meaning = 0i32 - } else { - if chat.id == 1 { - if let Some(contact) = contact { - self.text1 = contact.get_display_name().strdup(); - } else { - self.text1 = std::ptr::null_mut(); - } - } else { - if let Some(contact) = contact { - self.text1 = contact.get_first_name().strdup(); - } else { - self.text1 = std::ptr::null_mut(); - } - } - self.text1_meaning = 2i32; - } - } - - self.text2 = dc_msg_get_summarytext_by_raw( - (*msg).type_0, - (*msg).text.as_ref(), - &mut (*msg).param, - 160, - context, - ) - .strdup(); - - self.timestamp = dc_msg_get_timestamp(msg); - self.state = (*msg).state; - } } -impl Drop for Lot { - fn drop(&mut self) { - unsafe { - free(self.text1.cast()); - free(self.text2.cast()); - free(self.fingerprint.cast()); - free(self.invitenumber.cast()); - free(self.auth.cast()); - } +#[repr(i32)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] +pub enum LotState { + // Default + Undefined = 0, + + // Qr States + /// id=contact + QrAskVerifyContact = 200, + /// text1=groupname + QrAskVerifyGroup = 202, + /// id=contact + QrFprOk = 210, + /// id=contact + QrFprMissmatch = 220, + /// test1=formatted fingerprint + QrFprWithoutAddr = 230, + /// id=contact + QrAddr = 320, + /// text1=text + QrText = 330, + /// text1=URL + QrUrl = 332, + /// text1=error string + QrError = 400, + + // Message States + MsgInFresh = 10, + MsgInNoticed = 13, + MsgInSeen = 16, + MsgOutPreparing = 18, + MsgOutDraft = 19, + MsgOutPending = 20, + MsgOutFailed = 24, + MsgOutDelivered = 26, + MsgOutMdnRcvd = 28, +} + +impl Default for LotState { + fn default() -> Self { + LotState::Undefined } } diff --git a/src/param.rs b/src/param.rs index 2919d6e47..f18f3d04b 100644 --- a/src/param.rs +++ b/src/param.rs @@ -113,7 +113,7 @@ impl str::FromStr for Params { continue; } // TODO: probably nicer using a regex - ensure!(pair.len() > 2, "Invalid key pair: '{}'", pair); + ensure!(pair.len() > 1, "Invalid key pair: '{}'", pair); let mut split = pair.splitn(2, '='); let key = split.next(); let value = split.next(); @@ -235,4 +235,12 @@ mod tests { assert!(p1.is_empty()); assert_eq!(p1.len(), 0) } + + #[test] + fn test_regression() { + let p1: Params = "a=cli%40deltachat.de\nn=\ni=TbnwJ6lSvD5\ns=0ejvbdFSQxB" + .parse() + .unwrap(); + assert_eq!(p1.get(Param::Forwarded).unwrap(), "cli%40deltachat.de"); + } } diff --git a/src/x.rs b/src/x.rs index f1bb01e37..d617617ad 100644 --- a/src/x.rs +++ b/src/x.rs @@ -54,27 +54,6 @@ pub(crate) unsafe fn strcasecmp(s1: *const libc::c_char, s2: *const libc::c_char } } -pub(crate) unsafe fn strncasecmp( - s1: *const libc::c_char, - s2: *const libc::c_char, - n: libc::size_t, -) -> libc::c_int { - let s1 = std::ffi::CStr::from_ptr(s1) - .to_string_lossy() - .to_lowercase(); - let s2 = std::ffi::CStr::from_ptr(s2) - .to_string_lossy() - .to_lowercase(); - let m1 = std::cmp::min(n, s1.len()); - let m2 = std::cmp::min(n, s2.len()); - - if s1[..m1] == s2[..m2] { - 0 - } else { - 1 - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/tests/stress.rs b/tests/stress.rs index 22607bd1a..018f98155 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -11,11 +11,8 @@ use deltachat::config; use deltachat::constants::*; use deltachat::contact::*; use deltachat::context::*; -use deltachat::dc_configure::*; use deltachat::dc_imex::*; use deltachat::dc_mimeparser::*; -use deltachat::dc_qr::*; -use deltachat::dc_securejoin::*; use deltachat::dc_tools::*; use deltachat::key::*; use deltachat::keyring::*; @@ -412,61 +409,55 @@ unsafe fn stress_functions(context: &Context) { 0 ); free(buf_1 as *mut libc::c_void); - if 0 != dc_is_configured(context) { - let setupcode = dc_create_setup_code(context); - let setupcode_c = CString::yolo(setupcode.clone()); - let setupfile = dc_render_setup_file(context, &setupcode).unwrap(); - let setupfile_c = CString::yolo(setupfile); - let payload: *mut libc::c_char; - let mut headerline_2: *const libc::c_char = 0 as *const libc::c_char; - payload = dc_decrypt_setup_file(context, setupcode_c.as_ptr(), setupfile_c.as_ptr()); - assert!(payload.is_null()); - assert!(!dc_split_armored_data( - payload, - &mut headerline_2, - 0 as *mut *const libc::c_char, - 0 as *mut *const libc::c_char, - 0 as *mut *const libc::c_char, - )); - assert!(!headerline_2.is_null()); - assert_eq!( - strcmp( - headerline_2, - b"-----BEGIN PGP PRIVATE KEY BLOCK-----\x00" as *const u8 as *const libc::c_char, - ), - 0 - ); - free(payload as *mut libc::c_void); - } - if 0 != dc_is_configured(context) { - let qr: *mut libc::c_char = dc_get_securejoin_qr(context, 0i32 as uint32_t); - assert!( - !(strlen(qr) > 55 - && strncmp( - qr, - b"OPENPGP4FPR:\x00" as *const u8 as *const libc::c_char, - 12, - ) == 0i32 - && strncmp( - &mut *qr.offset(52isize), - b"#a=\x00" as *const u8 as *const libc::c_char, - 3, - ) == 0i32) - ); - let mut res = dc_check_qr(context, qr); - assert!( - !(res.get_state() == 200i32 || res.get_state() == 220i32 || res.get_state() == 230i32) - ); + // Cant check, no configured context + // assert!(dc_is_configured(context) != 0, "Missing configured context"); - free(qr as *mut libc::c_void); - res = - dc_check_qr(context, - b"BEGIN:VCARD\nVERSION:3.0\nN:Last;First\nEMAIL;TYPE=INTERNET:stress@test.local\nEND:VCARD\x00" - as *const u8 as *const libc::c_char); - assert!(!(res.get_state() == 320i32)); - assert!(!(res.get_id() != 0i32 as libc::c_uint)); - }; + // let setupcode = dc_create_setup_code(context); + // let setupcode_c = CString::yolo(setupcode.clone()); + // let setupfile = dc_render_setup_file(context, &setupcode).unwrap(); + // let setupfile_c = CString::yolo(setupfile); + // let mut headerline_2: *const libc::c_char = 0 as *const libc::c_char; + // let payload = dc_decrypt_setup_file(context, setupcode_c.as_ptr(), setupfile_c.as_ptr()); + + // assert!(payload.is_null()); + // assert!(!dc_split_armored_data( + // payload, + // &mut headerline_2, + // 0 as *mut *const libc::c_char, + // 0 as *mut *const libc::c_char, + // 0 as *mut *const libc::c_char, + // )); + // assert!(!headerline_2.is_null()); + // assert_eq!( + // strcmp( + // headerline_2, + // b"-----BEGIN PGP PRIVATE KEY BLOCK-----\x00" as *const u8 as *const libc::c_char, + // ), + // 0 + // ); + // free(payload as *mut libc::c_void); + + // Cant check, no configured context + // assert!(dc_is_configured(context) != 0, "missing configured context"); + + // let qr = dc_get_securejoin_qr(context, 0); + // assert!(!qr.is_null(), "Invalid qr code generated"); + // let qr_r = as_str(qr); + + // assert!(qr_r.len() > 55); + // assert!(qr_r.starts_with("OPENPGP4FPR:")); + + // let res = dc_check_qr(context, qr); + // let s = res.get_state(); + + // assert!( + // s == QrState::AskVerifyContact + // || s == QrState::FprMissmatch + // || s == QrState::FprWithoutAddr + // ); + + // free(qr.cast()); } #[test]