refactor: save lot implementation and follow up refactors

rewrote qr code to match the now safe lot
This commit is contained in:
dignifiedquire
2019-08-18 00:37:25 +02:00
parent 05ec266d9b
commit 8a49ae2361
20 changed files with 944 additions and 703 deletions

6
Xargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[dependencies.std]
features = ["panic-unwind"]
# if using `cargo test`
[dependencies.test]
stage = 1

View File

@@ -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*);

View File

@@ -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<T: Default, E: std::fmt::Display> ResultExt<T> for Result<T, E> {
}
}
}
unsafe fn strdup_opt(s: Option<impl AsRef<str>>) -> *mut libc::c_char {
match s {
Some(s) => s.as_ref().strdup(),
None => ptr::null_mut(),
}
}

View File

@@ -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<str>, 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<str>, 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,
&timestr,
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 <qr-content> 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" => {

View File

@@ -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);

View File

@@ -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<u32, Error> {
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<str>) {
2,
dc_create_smeared_timestamp(context),
Viewtype::Text,
DC_STATE_IN_NOTICED,
MessageState::InNoticed,
text.as_ref(),
as_str(rfc724_mid),
]

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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> {
"<unset>"
},
);
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::<EmailAddress>().is_ok()
let res = addr.parse::<EmailAddress>();
println!("{:?}", res);
res.is_ok()
}
pub fn addr_normalize(addr: &str) -> &str {

View File

@@ -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(

View File

@@ -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<MessageState> 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<i32>>(1)?.unwrap_or_default()))
Ok((row.get::<_, MessageState>(0)?, row.get::<_, Option<Blocked>>(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 */
}

View File

@@ -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<Lot> 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<str>) -> 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!(&param);
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;<type>: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<str>) -> 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<str>) -> 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<String, Error> {
// 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");
}
}

View File

@@ -282,7 +282,7 @@ unsafe fn add_parts(
created_db_entries: &mut Vec<(usize, usize)>,
create_event_to_send: &mut Option<Event>,
) -> 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 {

View File

@@ -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<str>,
fingerprint: *const libc::c_char,
grpid: impl AsRef<str>,
) {
@@ -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<str>,
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<str>) -> 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<str>,
) -> 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;
}

View File

@@ -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<T> = std::result::Result<T, Error>;
@@ -48,6 +50,12 @@ impl From<std::io::Error> for Error {
}
}
impl From<std::str::Utf8Error> for Error {
fn from(err: std::str::Utf8Error) -> Error {
Error::Utf8(err)
}
}
impl From<image_meta::ImageError> for Error {
fn from(err: image_meta::ImageError) -> Error {
Error::Image(err)

View File

@@ -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();

View File

@@ -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<String>,
pub(crate) text2: Option<String>,
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<String>,
pub(crate) invitenumber: Option<String>,
pub(crate) auth: Option<String>,
}
#[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
}
}

View File

@@ -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");
}
}

View File

@@ -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::*;

View File

@@ -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]