mirror of
https://github.com/chatmail/core.git
synced 2026-04-21 15:36:30 +03:00
refactor: save lot implementation and follow up refactors
rewrote qr code to match the now safe lot
This commit is contained in:
6
Xargo.toml
Normal file
6
Xargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[dependencies.std]
|
||||
features = ["panic-unwind"]
|
||||
|
||||
# if using `cargo test`
|
||||
[dependencies.test]
|
||||
stage = 1
|
||||
@@ -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*);
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
×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 <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" => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
30
src/chat.rs
30
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<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),
|
||||
]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
177
src/dc_msg.rs
177
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<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 */
|
||||
}
|
||||
|
||||
737
src/dc_qr.rs
737
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<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!(¶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;<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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
175
src/lot.rs
175
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<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
|
||||
}
|
||||
}
|
||||
|
||||
10
src/param.rs
10
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");
|
||||
}
|
||||
}
|
||||
|
||||
21
src/x.rs
21
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::*;
|
||||
|
||||
103
tests/stress.rs
103
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]
|
||||
|
||||
Reference in New Issue
Block a user