Compare commits

..

12 Commits

Author SHA1 Message Date
holger krekel
e0932ba0cc rework set_mdnsent and set_seen 2019-09-30 23:26:21 +02:00
holger krekel
ab2ef1e1e4 shift most mmime functions to wrapmime 2019-09-30 00:52:14 +02:00
holger krekel
18030fa61e remove duplicate code and possibly a leak 2019-09-30 00:52:14 +02:00
holger krekel
064337b5d3 refactor one occassion of determinig transfer encoding 2019-09-30 00:52:14 +02:00
Dmitry Bogatov
a6a6fc48c1 Remove unused outlk_autodiscover_t.redirect field 2019-09-29 23:35:01 +02:00
holger krekel
d72e9bb05b remove dc_get_fine_* method and validate_filename 2019-09-29 22:49:01 +02:00
holger krekel
7a9fdb4acd add a new tested context.new_blob_dir method to simplify writing out blob files 2019-09-29 22:49:01 +02:00
Alexander Krotov
a6d0464735 Merge pull request #643 from deltachat/top_evil_rs-skip-safe
top_evil_rs.py: do not list safe files
2019-09-29 19:07:56 +00:00
Alexander Krotov
52f69cc7dc top_evil_rs.py: do not list safe files 2019-09-29 18:51:48 +03:00
Dmitry Bogatov
0beadde758 Remove _safe suffix from dc_decode_header function
There is no longer unsafe version of this function, so suffix is
useless now.
2019-09-28 23:18:41 +02:00
Dmitry Bogatov
618abd63cf Drop unsafe version of dc_decode_header_words
Replace all call sites with safe version and adjust tests as apporiate.
2019-09-28 23:18:41 +02:00
Friedel Ziegelmayer
34b3ddf63b refactor(e2ee): reduce unsafe spread 2019-09-28 13:20:33 -06:00
14 changed files with 717 additions and 709 deletions

View File

@@ -123,7 +123,7 @@ unsafe fn display_mime_discrete_type(mut discrete_type: *mut mailmime_discrete_t
_ => {}
};
}
unsafe fn display_mime_data(mut data: *mut mailmime_data) {
pub unsafe fn display_mime_data(mut data: *mut mailmime_data) {
match (*data).dt_type {
0 => {
println!(

View File

@@ -21,7 +21,6 @@ struct outlk_autodiscover_t<'a> {
pub out_smtp_set: libc::c_int,
pub tag_config: libc::c_int,
pub config: [*mut libc::c_char; 6],
pub redirect: *mut libc::c_char,
}
pub unsafe fn outlk_autodiscover(
@@ -38,7 +37,6 @@ pub unsafe fn outlk_autodiscover(
out_smtp_set: 0,
tag_config: 0,
config: [ptr::null_mut(); 6],
redirect: ptr::null_mut(),
};
let ok_to_continue;
let mut i = 0;

View File

@@ -1,5 +1,7 @@
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Condvar, Mutex, RwLock};
@@ -8,6 +10,7 @@ use libc::uintptr_t;
use crate::chat::*;
use crate::constants::*;
use crate::contact::*;
use crate::dc_tools::{dc_copy_file, dc_derive_safe_stem_ext};
use crate::error::*;
use crate::events::Event;
use crate::imap::*;
@@ -20,6 +23,7 @@ use crate::message::{self, Message};
use crate::param::Params;
use crate::smtp::*;
use crate::sql::Sql;
use rand::{thread_rng, Rng};
/// Callback function type for [Context]
///
@@ -158,6 +162,57 @@ impl Context {
self.blobdir.as_path()
}
pub fn copy_to_blobdir(&self, orig_filename: impl AsRef<str>) -> Result<String> {
// return a $BLOBDIR/<filename> with the content of orig_filename
// copied into it. The <filename> will be safely derived from
// orig_filename, and will not clash with existing filenames.
let dest = self.new_blob_file(&orig_filename, b"")?;
if dc_copy_file(
&self,
PathBuf::from(orig_filename.as_ref()),
PathBuf::from(&dest),
) {
Ok(dest)
} else {
bail!("could not copy {} to {}", orig_filename.as_ref(), dest);
}
}
pub fn new_blob_file(&self, orig_filename: impl AsRef<str>, data: &[u8]) -> Result<String> {
// return a $BLOBDIR/<FILENAME> string which corresponds to the
// respective file in the blobdir, and which contains the data.
// FILENAME is computed by looking and possibly mangling the
// basename of orig_filename. The resulting filenames are meant
// to be human-readable.
let (stem, ext) = dc_derive_safe_stem_ext(orig_filename.as_ref());
// ext starts with "." or is empty string, so we can always resconstruct
for i in 0..3 {
let candidate_basename = match i {
// first a try to just use the (possibly mangled) original basename
0 => format!("{}{}", stem, ext),
// otherwise extend stem with random numbers
_ => {
let mut rng = thread_rng();
let random_id: u32 = rng.gen();
format!("{}-{}{}", stem, random_id, ext)
}
};
let path = self.get_blobdir().join(&candidate_basename);
if let Ok(mut file) = fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&path)
{
file.write_all(data)?;
return Ok(format!("$BLOBDIR/{}", candidate_basename));
}
}
bail!("out of luck to create new blob file");
}
pub fn call_cb(&self, event: Event) -> uintptr_t {
(*self.cb)(self, event)
}
@@ -438,6 +493,7 @@ pub fn get_version_str() -> &'static str {
mod tests {
use super::*;
use crate::dc_tools::*;
use crate::test_utils::*;
#[test]
@@ -475,6 +531,51 @@ mod tests {
assert!(res.is_err());
}
#[test]
fn test_new_blob_file() {
let t = dummy_context();
let context = t.ctx;
let x = &context.new_blob_file("hello", b"data").unwrap();
assert!(dc_file_exist(&context, x));
assert!(x.starts_with("$BLOBDIR"));
assert!(dc_read_file(&context, x).unwrap() == b"data");
let y = &context.new_blob_file("hello", b"data").unwrap();
assert!(dc_file_exist(&context, y));
assert!(y.starts_with("$BLOBDIR/hello-"));
let x = &context.new_blob_file("xyz/hello.png", b"data").unwrap();
assert!(dc_file_exist(&context, x));
assert_eq!(x, "$BLOBDIR/hello.png");
let y = &context.new_blob_file("hello\\world.png", b"data").unwrap();
assert!(dc_file_exist(&context, y));
assert_eq!(y, "$BLOBDIR/world.png");
}
#[test]
fn test_new_blob_file_long_names() {
let t = dummy_context();
let context = t.ctx;
let s = "12312312039182039182039812039810293810293810293810293801293801293123123";
let x = &context.new_blob_file(s, b"data").unwrap();
println!("blobfilename '{}'", x);
println!("xxxxfilename '{}'", s);
assert!(x.len() < s.len());
assert!(dc_file_exist(&context, x));
assert!(x.starts_with("$BLOBDIR"));
}
#[test]
fn test_new_blob_file_unicode() {
let t = dummy_context();
let context = t.ctx;
let s = "helloäworld.qwe";
let x = &context.new_blob_file(s, b"data").unwrap();
assert_eq!(x, "$BLOBDIR/hello-world.qwe");
assert_eq!(dc_read_file(&context, x).unwrap(), b"data");
}
#[test]
fn test_sqlite_parent_not_exists() {
let tmp = tempfile::tempdir().unwrap();

View File

@@ -11,7 +11,6 @@ use mmime::mailmime::content::*;
use mmime::mailmime::disposition::*;
use mmime::mailmime::types::*;
use mmime::mailmime::*;
use mmime::mmapstring::*;
use mmime::other::*;
use crate::constants::Viewtype;
@@ -25,6 +24,7 @@ use crate::error::Error;
use crate::location;
use crate::param::*;
use crate::stock::StockMessage;
use crate::wrapmime;
#[derive(Debug)]
pub struct MimeParser<'a> {
@@ -127,7 +127,7 @@ impl<'a> MimeParser<'a> {
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
let subj = (*(*field).fld_data.fld_subject).sbj_value;
self.subject = as_opt_str(subj).map(dc_decode_header_words_safe);
self.subject = as_opt_str(subj).map(dc_decode_header_words);
}
}
@@ -287,12 +287,12 @@ impl<'a> MimeParser<'a> {
) == MAILIMF_NO_ERROR as libc::c_int
&& !mb_list.is_null()
{
if let Some(dn_to_addr) = mailimf_find_first_addr(mb_list) {
if let Some(dn_to_addr) = wrapmime::mailimf_find_first_addr(mb_list) {
if let Some(from_field) = self.lookup_field("From") {
if (*from_field).fld_type == MAILIMF_FIELD_FROM as libc::c_int
&& !(*from_field).fld_data.fld_from.is_null()
{
let from_addr = mailimf_find_first_addr(
let from_addr = wrapmime::mailimf_find_first_addr(
(*(*from_field).fld_data.fld_from).frm_mb_list,
);
if let Some(from_addr) = from_addr {
@@ -590,7 +590,7 @@ impl<'a> MimeParser<'a> {
return false;
}
let mut decoded_data = match mailmime_transfer_decode(mime) {
let mut decoded_data = match wrapmime::mailmime_transfer_decode(mime) {
Ok(decoded_data) => decoded_data,
Err(_) => {
// Note that it's now always an error - might be no data
@@ -704,7 +704,7 @@ impl<'a> MimeParser<'a> {
// might be a wrongly encoded filename
let s = to_string_lossy((*dsp_param).pa_data.pa_filename);
// this is used only if the parts buffer stays empty
desired_filename = dc_decode_header_words_safe(&s)
desired_filename = dc_decode_header_words(&s)
}
}
}
@@ -776,28 +776,34 @@ impl<'a> MimeParser<'a> {
decoded_data: &[u8],
desired_filename: &str,
) {
/* create a free file name to use */
let path_filename = dc_get_fine_path_filename(self.context, "$BLOBDIR", desired_filename);
/* copy data to file */
if dc_write_file(self.context, &path_filename, decoded_data) {
let mut part = Part::default();
part.typ = msg_type;
part.mimetype = mime_type;
part.bytes = decoded_data.len() as libc::c_int;
part.param.set(Param::File, path_filename.to_string_lossy());
if let Some(raw_mime) = raw_mime {
part.param.set(Param::MimeType, raw_mime);
/* write decoded data to new blob file */
let bpath = match self.context.new_blob_file(desired_filename, decoded_data) {
Ok(path) => path,
Err(err) => {
error!(
self.context,
"Could not add blob for mime part {}, error {}", desired_filename, err
);
return;
}
};
if mime_type == DC_MIMETYPE_IMAGE {
if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
part.param.set_int(Param::Width, width as i32);
part.param.set_int(Param::Height, height as i32);
}
}
self.do_add_single_part(part);
let mut part = Part::default();
part.typ = msg_type;
part.mimetype = mime_type;
part.bytes = decoded_data.len() as libc::c_int;
part.param.set(Param::File, bpath);
if let Some(raw_mime) = raw_mime {
part.param.set(Param::MimeType, raw_mime);
}
if mime_type == DC_MIMETYPE_IMAGE {
if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
part.param.set_int(Param::Width, width as i32);
part.param.set_int(Param::Height, height as i32);
}
}
self.do_add_single_part(part);
}
fn do_add_single_part(&mut self, mut part: Part) {
@@ -832,7 +838,7 @@ impl<'a> MimeParser<'a> {
let mut fld_from: *const mailimf_from = ptr::null();
/* get From: and check there is exactly one sender */
let fld = mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
let fld = wrapmime::mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
if !(fld.is_null()
|| {
fld_from = (*fld).fld_data.fld_from;
@@ -850,7 +856,7 @@ impl<'a> MimeParser<'a> {
if !mb.is_null() {
let from_addr_norm = addr_normalize(as_str((*mb).mb_addr_spec));
let recipients = mailimf_get_recipients(self.header_root);
let recipients = wrapmime::mailimf_get_recipients(self.header_root);
if recipients.len() == 1 {
if recipients.contains(from_addr_norm) {
sender_equals_recipient = true;
@@ -911,22 +917,6 @@ pub struct Part {
pub param: Params,
}
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
if mb_list.is_null() {
return None;
}
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
let mb = cur as *mut mailimf_mailbox;
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
let addr = unsafe { as_str((*mb).mb_addr_spec) };
return Some(addr_normalize(addr).to_string());
}
}
None
}
unsafe fn hash_header(out: &mut HashMap<String, *mut mailimf_field>, in_0: *const mailimf_fields) {
if in_0.is_null() {
return;
@@ -1148,214 +1138,6 @@ pub unsafe fn mailmime_find_ct_parameter(
ptr::null_mut()
}
pub unsafe fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
ensure!(!mime.is_null(), "invalid inputs");
let mut mime_transfer_encoding = MAILMIME_MECHANISM_BINARY as libc::c_int;
let mime_data = (*mime).mm_data.mm_single;
if !(*mime).mm_mime_fields.is_null() {
for cur in (*(*(*mime).mm_mime_fields).fld_list).into_iter() {
let field = cur as *mut mailmime_field;
if !field.is_null()
&& (*field).fld_type == MAILMIME_FIELD_TRANSFER_ENCODING as libc::c_int
&& !(*field).fld_data.fld_encoding.is_null()
{
mime_transfer_encoding = (*(*field).fld_data.fld_encoding).enc_type;
break;
}
}
}
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
{
let decoded_data = (*mime_data).dt_data.dt_text.dt_data;
let decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
if decoded_data.is_null() || decoded_data_bytes <= 0 {
bail!("No data to decode found");
} else {
let result = std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes);
return Ok(result.to_vec());
}
}
let mut current_index = 0;
let mut transfer_decoding_buffer = ptr::null_mut();
let mut decoded_data_bytes = 0;
let r = mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
);
if r == MAILIMF_NO_ERROR as libc::c_int
&& !transfer_decoding_buffer.is_null()
&& decoded_data_bytes > 0
{
let result =
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
.to_vec();
mmap_string_unref(transfer_decoding_buffer);
return Ok(result);
}
Err(format_err!("Failed to to decode"))
}
pub fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
/* returned addresses are normalized. */
let mut recipients: HashSet<String> = Default::default();
for cur in unsafe { (*(*imffields).fld_list).into_iter() } {
let fld = cur as *mut mailimf_field;
let fld_to: *mut mailimf_to;
let fld_cc: *mut mailimf_cc;
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
if fld.is_null() {
continue;
}
let fld = unsafe { *fld };
// TODO match on enums /rtn
match fld.fld_type {
13 => {
fld_to = unsafe { fld.fld_data.fld_to };
if !fld_to.is_null() {
addr_list = unsafe { (*fld_to).to_addr_list };
}
}
14 => {
fld_cc = unsafe { fld.fld_data.fld_cc };
if !fld_cc.is_null() {
addr_list = unsafe { (*fld_cc).cc_addr_list };
}
}
_ => {}
}
if !addr_list.is_null() {
for cur2 in unsafe { &(*(*addr_list).ad_list) } {
let adr = cur2 as *mut mailimf_address;
if adr.is_null() {
continue;
}
let adr = unsafe { *adr };
if adr.ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
mailimf_get_recipients_add_addr(&mut recipients, unsafe {
adr.ad_data.ad_mailbox
});
} else if adr.ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
let group = unsafe { adr.ad_data.ad_group };
if !group.is_null() && unsafe { !(*group).grp_mb_list.is_null() } {
for cur3 in unsafe { &(*(*(*group).grp_mb_list).mb_list) } {
mailimf_get_recipients_add_addr(
&mut recipients,
cur3 as *mut mailimf_mailbox,
);
}
}
}
}
}
}
recipients
}
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
if !mb.is_null() {
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
recipients.insert(addr_norm.into());
}
}
/*the result is a pointer to mime, must not be freed*/
pub fn mailimf_find_field(
header: *mut mailimf_fields,
wanted_fld_type: libc::c_int,
) -> *mut mailimf_field {
if header.is_null() {
return ptr::null_mut();
}
let header = unsafe { (*header) };
if header.fld_list.is_null() {
return ptr::null_mut();
}
for cur in unsafe { &(*header.fld_list) } {
let field = cur as *mut mailimf_field;
if !field.is_null() {
if unsafe { (*field).fld_type } == wanted_fld_type {
return field;
}
}
}
ptr::null_mut()
}
/*the result is a pointer to mime, must not be freed*/
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
if mime.is_null() {
return ptr::null_mut();
}
match (*mime).mm_type as _ {
MAILMIME_MULTIPLE => {
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
if !header.is_null() {
return header;
}
}
}
MAILMIME_MESSAGE => return (*mime).mm_data.mm_message.mm_fields,
_ => {}
}
ptr::null_mut()
}
pub unsafe fn mailimf_find_optional_field(
header: *mut mailimf_fields,
wanted_fld_name: *const libc::c_char,
) -> *mut mailimf_optional_field {
if header.is_null() || (*header).fld_list.is_null() {
return ptr::null_mut();
}
for cur_data in (*(*header).fld_list).into_iter() {
let field: *mut mailimf_field = cur_data as *mut _;
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
if !optional_field.is_null()
&& !(*optional_field).fld_name.is_null()
&& !(*optional_field).fld_value.is_null()
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
{
return optional_field;
}
}
}
ptr::null_mut()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1371,14 +1153,13 @@ mod tests {
let mut mime: *mut Mailmime = ptr::null_mut();
let mut dummy = 0;
let res = mailmime_parse(txt, strlen(txt), &mut dummy, &mut mime);
assert_eq!(res, MAIL_NO_ERROR as libc::c_int);
assert!(!mime.is_null());
let fields: *mut mailimf_fields = mailmime_find_mailimf_fields(mime);
let fields: *mut mailimf_fields = wrapmime::mailmime_find_mailimf_fields(mime);
assert!(!fields.is_null());
let mut of_a: *mut mailimf_optional_field = mailimf_find_optional_field(
let mut of_a: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
fields,
b"fielda\x00" as *const u8 as *const libc::c_char,
);
@@ -1398,7 +1179,7 @@ mod tests {
"ValueA",
);
of_a = mailimf_find_optional_field(
of_a = wrapmime::mailimf_find_optional_field(
fields,
b"FIELDA\x00" as *const u8 as *const libc::c_char,
);
@@ -1418,7 +1199,7 @@ mod tests {
"ValueA",
);
let of_b: *mut mailimf_optional_field = mailimf_find_optional_field(
let of_b: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
fields,
b"FieldB\x00" as *const u8 as *const libc::c_char,
);
@@ -1441,7 +1222,9 @@ mod tests {
let context = dummy_context();
let raw = include_bytes!("../test-data/message/issue_523.txt");
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe { mimeparser.parse(&raw[..]).unwrap() };
unsafe {
mimeparser.parse(&raw[..]).unwrap();
};
assert_eq!(mimeparser.subject, None);
assert_eq!(mimeparser.parts.len(), 1);
}
@@ -1449,9 +1232,13 @@ mod tests {
proptest! {
#[test]
fn test_dc_mailmime_parse_crash_fuzzy(data in "[!-~\t ]{2000,}") {
// this test doesn't exercise much of dc_mimeparser anymore
// because a missing From-field early aborts parsing
let context = dummy_context();
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe { mimeparser.parse(data.as_bytes()).unwrap() };
unsafe {
assert!(mimeparser.parse(data.as_bytes()).is_err());
}
}
}
@@ -1480,7 +1267,7 @@ mod tests {
fn test_mimeparser_with_context() {
unsafe {
let context = dummy_context();
let raw = b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
let raw = b"From: hello\nContent-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
let mut mimeparser = MimeParser::new(&context.ctx);
mimeparser.parse(&raw[..]).unwrap();

View File

@@ -29,6 +29,7 @@ use crate::peerstate::*;
use crate::securejoin::handle_securejoin_handshake;
use crate::sql;
use crate::stock::StockMessage;
use crate::wrapmime;
#[derive(Debug, PartialEq, Eq)]
enum CreateEvent {
@@ -795,7 +796,7 @@ unsafe fn handle_reports(
b"disposition-notification\x00" as *const u8 as *const libc::c_char,
) == 0
{
if let Ok(report_body) = mailmime_transfer_decode(report_data) {
if let Ok(report_body) = wrapmime::mailmime_transfer_decode(report_data) {
let mut report_parsed = std::ptr::null_mut();
let mut dummy = 0;
@@ -807,13 +808,14 @@ unsafe fn handle_reports(
) == MAIL_NO_ERROR as libc::c_int
&& !report_parsed.is_null()
{
let report_fields = mailmime_find_mailimf_fields(report_parsed);
let report_fields =
wrapmime::mailmime_find_mailimf_fields(report_parsed);
if !report_fields.is_null() {
let of_disposition = mailimf_find_optional_field(
let of_disposition = wrapmime::mailimf_find_optional_field(
report_fields,
b"Disposition\x00" as *const u8 as *const libc::c_char,
);
let of_org_msgid = mailimf_find_optional_field(
let of_org_msgid = wrapmime::mailimf_find_optional_field(
report_fields,
b"Original-Message-ID\x00" as *const u8 as *const libc::c_char,
);
@@ -1076,7 +1078,7 @@ unsafe fn create_or_lookup_group(
}
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Name") {
grpname = Some(dc_decode_header_words_safe(&optional_field));
grpname = Some(dc_decode_header_words(&optional_field));
}
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Member-Removed") {
X_MrRemoveFromGrp = Some(optional_field);
@@ -1965,7 +1967,7 @@ unsafe fn add_or_lookup_contact_by_addr(
/* add addr_spec if missing, update otherwise */
let mut display_name_dec = "".to_string();
if !display_name_enc.is_null() {
let tmp = as_str(dc_decode_header_words(display_name_enc));
let tmp = dc_decode_header_words(as_str(display_name_enc));
display_name_dec = normalize_name(&tmp);
}
/*can be NULL*/

View File

@@ -4,7 +4,7 @@ use std::ffi::CString;
use std::ptr;
use charset::Charset;
use libc::{free, strlen};
use libc::free;
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
@@ -68,28 +68,7 @@ fn quote_word(word: &[u8]) -> String {
* Encode/decode header words, RFC 2047
******************************************************************************/
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
if in_0.is_null() {
return ptr::null_mut();
}
let mut out: *mut libc::c_char = ptr::null_mut();
let mut cur_token = 0;
let r: libc::c_int = mailmime_encoded_phrase_parse(
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
in_0,
strlen(in_0),
&mut cur_token,
b"utf-8\x00" as *const u8 as *const libc::c_char,
&mut out,
);
if r != MAILIMF_NO_ERROR as libc::c_int || out.is_null() {
out = dc_strdup(in_0)
}
out
}
pub fn dc_decode_header_words_safe(input: &str) -> String {
pub(crate) fn dc_decode_header_words(input: &str) -> String {
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
static TO_ENCODING: &[u8] = b"utf-8\x00";
let mut out = ptr::null_mut();
@@ -177,60 +156,31 @@ pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
mod tests {
use super::*;
use libc::strcmp;
use std::ffi::CStr;
#[test]
fn test_dc_decode_header_words() {
unsafe {
let mut buf1: *mut libc::c_char = dc_decode_header_words(
b"=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?=\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(
strcmp(
buf1,
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf1 as *mut libc::c_void);
assert_eq!(
dc_decode_header_words("=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?="),
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
buf1 =
dc_decode_header_words(b"just ascii test\x00" as *const u8 as *const libc::c_char);
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
free(buf1 as *mut libc::c_void);
assert_eq!(dc_decode_header_words("just ascii test"), "just ascii test");
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
let r = dc_encode_header_words(
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
.unwrap(),
let r = dc_encode_header_words(
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert!(r.starts_with("=?utf-8"));
assert_eq!(
dc_decode_header_words(&r),
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert_eq!(
dc_decode_header_words("=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?="),
std::string::String::from_utf8(b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39".to_vec()).unwrap(),
);
assert!(r.starts_with("=?utf-8"));
buf1 = r.strdup();
let buf2: *mut libc::c_char = dc_decode_header_words(buf1);
assert_eq!(
strcmp(
buf2,
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf2 as *mut libc::c_void);
buf1 = dc_decode_header_words(
b"=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?=\x00" as *const u8 as *const libc::c_char
);
assert_eq!(
strcmp(
buf1,
b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39\x00" as *const u8 as *const libc::c_char,
),
0
);
}
}
#[test]
@@ -293,7 +243,7 @@ mod tests {
#[test]
fn test_dc_header_roundtrip(input: String) {
let encoded = dc_encode_header_words(&input);
let decoded = dc_decode_header_words_safe(&encoded);
let decoded = dc_decode_header_words(&encoded);
assert_eq!(input, decoded);
}

View File

@@ -1,8 +1,9 @@
//! Some tools and enhancements to the used libraries, there should be
//! no references to Context and other "larger" entities here.
use core::cmp::max;
use std::borrow::Cow;
use std::ffi::{CStr, CString, OsString};
use std::ffi::{CStr, CString};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::SystemTime;
@@ -415,12 +416,41 @@ pub(crate) fn dc_ensure_no_slash_safe(path: &str) -> &str {
path
}
/// Function modifies the given buffer and replaces all characters not valid in filenames by a "-".
fn validate_filename(filename: &str) -> String {
filename
.replace('/', "-")
.replace('\\', "-")
.replace(':', "-")
// Function returns a sanitized basename that does not contain
// win/linux path separators and also not any non-ascii chars
fn get_safe_basename(filename: &str) -> String {
// return the (potentially mangled) basename of the input filename
// this might be a path that comes in from another operating system
let mut index: usize = 0;
if let Some(unix_index) = filename.rfind("/") {
index = unix_index + 1;
}
if let Some(win_index) = filename.rfind("\\") {
index = max(index, win_index + 1);
}
if index >= filename.len() {
"nobasename".to_string()
} else {
// we don't allow any non-ascii to be super-safe
filename[index..].replace(|c: char| !c.is_ascii() || c == ':', "-")
}
}
pub fn dc_derive_safe_stem_ext(filename: &str) -> (String, String) {
let basename = get_safe_basename(&filename);
let (mut stem, mut ext) = if let Some(index) = basename.rfind(".") {
(
basename[0..index].to_string(),
basename[index..].to_string(),
)
} else {
(basename, "".to_string())
};
// limit length of stem and ext
stem.truncate(32);
ext.truncate(32);
(stem, ext)
}
// the returned suffix is lower-case
@@ -566,49 +596,24 @@ pub fn dc_read_file<P: AsRef<std::path::Path>>(
}
}
pub(crate) fn dc_get_fine_path_filename(
context: &Context,
pub(crate) fn dc_get_next_backup_path(
folder: impl AsRef<Path>,
desired_filename_suffix: impl AsRef<str>,
) -> PathBuf {
let now = time();
backup_time: i64,
) -> Result<PathBuf, Error> {
let folder = PathBuf::from(folder.as_ref());
// XXX sanitize desired_filename eg using
// https://github.com/kardeiz/sanitize-filename/blob/master/src/lib.rs
let suffix = validate_filename(desired_filename_suffix.as_ref());
let file_name = PathBuf::from(suffix);
let extension = file_name.extension().map(|c| c.clone());
let stem = chrono::NaiveDateTime::from_timestamp(backup_time, 0)
.format("delta-chat-%Y-%m-%d")
.to_string();
for i in 0..100_000 {
let ret = if i == 0 {
let mut folder = folder.clone();
folder.push(&file_name);
folder
} else {
let idx = if i < 100 { i } else { now + i };
let file_name = if let Some(stem) = file_name.file_stem() {
let mut stem = stem.to_os_string();
stem.push(format!("-{}", idx));
stem
} else {
OsString::from(idx.to_string())
};
let mut folder = folder.clone();
folder.push(file_name);
if let Some(ext) = extension {
folder.set_extension(&ext);
}
folder
};
if !dc_file_exist(context, &ret) {
// fine filename found
return ret;
// 64 backup files per day should be enough for everyone
for i in 0..64 {
let mut path = folder.clone();
path.push(format!("{}-{}.bak", stem, i));
if !path.exists() {
return Ok(path);
}
}
panic!("Something is really wrong, you need to clean up your disk");
bail!("could not create backup file, disk full?");
}
pub(crate) fn dc_is_blobdir_path(context: &Context, path: impl AsRef<str>) -> bool {
@@ -636,13 +641,10 @@ pub(crate) fn dc_make_rel_and_copy(context: &Context, path: &mut String) -> bool
dc_make_rel_path(context, path);
return true;
}
let blobdir_path = dc_get_fine_path_filename(context, "$BLOBDIR", &path);
if dc_copy_file(context, &path, &blobdir_path) {
*path = blobdir_path.to_string_lossy().to_string();
dc_make_rel_path(context, path);
if let Ok(blobdir_path) = context.copy_to_blobdir(&path) {
*path = blobdir_path;
return true;
}
false
}
@@ -1437,6 +1439,19 @@ mod tests {
}
}
#[test]
fn test_file_get_safe_basename() {
assert_eq!(get_safe_basename("12312/hello"), "hello");
assert_eq!(get_safe_basename("12312\\hello"), "hello");
assert_eq!(get_safe_basename("//12312\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12/\\\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12//hello"), "hello");
assert_eq!(get_safe_basename("//123:12//"), "nobasename");
assert_eq!(get_safe_basename("//123:12/"), "nobasename");
assert!(get_safe_basename("123\x012.hello").ends_with(".hello"));
}
#[test]
fn test_file_handling() {
let t = dummy_context();
@@ -1471,6 +1486,7 @@ mod tests {
assert!(dc_file_exist(context, &abs_path));
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let buf = dc_read_file(context, "$BLOBDIR/dada").unwrap();
@@ -1483,14 +1499,12 @@ mod tests {
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn0, PathBuf::from("$BLOBDIR/foobar.dadada"));
let fn0 = "$BLOBDIR/data.data";
assert!(dc_write_file(context, &fn0, b"content"));
let fn1 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn1, PathBuf::from("$BLOBDIR/foobar-1.dadada"));
assert!(dc_delete_file(context, &fn0));
assert!(!dc_file_exist(context, &fn0));
}
#[test]

View File

@@ -22,7 +22,6 @@ use num_traits::FromPrimitive;
use crate::aheader::*;
use crate::config::Config;
use crate::context::Context;
use crate::dc_mimeparser::*;
use crate::dc_tools::*;
use crate::error::*;
use crate::key::*;
@@ -266,103 +265,80 @@ pub fn try_decrypt(
context: &Context,
in_out_message: *mut Mailmime,
) -> Result<(bool, HashSet<String>, HashSet<String>)> {
// just a pointer into mailmime structure, must not be freed
let imffields = unsafe { mailmime_find_mailimf_fields(in_out_message) };
ensure!(
!in_out_message.is_null() && !imffields.is_null(),
"corrupt invalid mime inputs"
);
let from = wrapmime::get_field_from(imffields)?;
let message_time = wrapmime::get_field_date(imffields)?;
let mut peerstate = None;
let autocryptheader = Aheader::from_imffields(&from, imffields);
if message_time > 0 {
peerstate = Peerstate::from_addr(context, &context.sql, &from);
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false).unwrap();
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false).unwrap();
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
p.save_to_db(&context.sql, true).unwrap();
peerstate = Some(p);
}
}
/* possibly perform decryption */
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut encrypted = false;
let mut signatures = HashSet::default();
let mut gossipped_addr = HashSet::default();
// just a pointer into mailmime structure, must not be freed
let imffields = unsafe { mailmime_find_mailimf_fields(in_out_message) };
let mut message_time = 0;
let mut from = None;
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut gossip_headers = ptr::null_mut();
let self_addr = context.get_config(Config::ConfiguredAddr);
// XXX do wrapmime:: helper for the next block
if !(in_out_message.is_null() || imffields.is_null()) {
let mut field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
from = mailimf_find_first_addr(mb_list);
}
field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
if !orig_date.is_null() {
let dt = unsafe { (*orig_date).dt_date_time };
message_time = dc_timestamp_from_date(dt);
if message_time != 0 && message_time > time() {
message_time = time()
if let Some(self_addr) = self_addr {
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate = Peerstate::from_addr(&context, &context.sql, &from);
}
if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate)?;
}
if let Some(ref key) = peerstate.gossip_key {
public_keyring_for_validate.add_ref(key);
}
if let Some(ref key) = peerstate.public_key {
public_keyring_for_validate.add_ref(key);
}
}
}
let mut peerstate = None;
let autocryptheader = from
.as_ref()
.and_then(|from| Aheader::from_imffields(from, imffields));
if message_time > 0 {
if let Some(ref from) = from {
peerstate = Peerstate::from_addr(context, &context.sql, from);
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false).unwrap();
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false).unwrap();
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
p.save_to_db(&context.sql, true).unwrap();
peerstate = Some(p);
}
}
}
/* load private key for decryption */
let self_addr = context.get_config(Config::ConfiguredAddr);
if let Some(self_addr) = self_addr {
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate =
Peerstate::from_addr(&context, &context.sql, &from.unwrap_or_default());
}
if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate)?;
}
if let Some(ref key) = peerstate.gossip_key {
public_keyring_for_validate.add_ref(key);
}
if let Some(ref key) = peerstate.public_key {
public_keyring_for_validate.add_ref(key);
}
}
encrypted = decrypt_if_autocrypt_message(
context,
in_out_message,
&private_keyring,
&public_keyring_for_validate,
&mut signatures,
&mut gossip_headers,
)?;
if !gossip_headers.is_null() {
gossipped_addr =
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
}
let mut gossip_headers = ptr::null_mut();
encrypted = decrypt_if_autocrypt_message(
context,
in_out_message,
&private_keyring,
&public_keyring_for_validate,
&mut signatures,
&mut gossip_headers,
)?;
if !gossip_headers.is_null() {
gossipped_addr =
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
unsafe { mailimf_fields_free(gossip_headers) };
}
}
}
if !gossip_headers.is_null() {
unsafe { mailimf_fields_free(gossip_headers) };
}
Ok((encrypted, signatures, gossipped_addr))
}
@@ -600,22 +576,16 @@ fn decrypt_part(
mime_transfer_encoding = enc;
}
let (decoded_data, decoded_data_bytes) =
wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
let data: Vec<u8> = wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
// encrypted, non-NULL decoded data in decoded_data now ...
// Note that we need to take care of freeing decoded_data ourself,
// after encryption has been attempted.
let mut ret_decrypted_mime = ptr::null_mut();
ensure!(!decoded_data.is_null(), "Missing data");
let data = unsafe { std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes) };
if has_decrypted_pgp_armor(data) {
if has_decrypted_pgp_armor(&data) {
// we should only have one decryption happening
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
let plain = match dc_pgp_pk_decrypt(
data,
&data,
&private_keyring,
&public_keyring_for_validate,
Some(ret_valid_signatures),
@@ -624,10 +594,7 @@ fn decrypt_part(
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
plain
}
Err(err) => {
unsafe { mmap_string_unref(decoded_data) };
bail!("could not decrypt: {}", err)
}
Err(err) => bail!("could not decrypt: {}", err),
};
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
@@ -655,7 +622,6 @@ fn decrypt_part(
ret_decrypted_mime = decrypted_mime;
}
}
unsafe { mmap_string_unref(decoded_data) };
Ok(ret_decrypted_mime)
}

View File

@@ -1197,8 +1197,8 @@ impl Imap {
};
if copied {
if self.add_flag(context, uid, "\\Deleted") == 0 {
warn!(context, "Cannot mark message as \"Deleted\".",);
if !self.add_flag_finalized(context, uid, "\\Deleted") {
warn!(context, "Giving up: cannot mark {} as \"Deleted\".", uid);
}
self.config.write().unwrap().selected_folder_needs_expunge = true;
res = ImapResult::Success;
@@ -1223,13 +1223,21 @@ impl Imap {
}
}
fn add_flag<S: AsRef<str>>(&self, context: &Context, server_uid: u32, flag: S) -> usize {
fn add_flag_finalized(&self, context: &Context, server_uid: u32, flag: &str) -> bool {
// return true if we successfully set the flag or we otherwise
// think add_flag should not be retried: Disconnection during setting
// the flag, or other imap-errors, returns true as well.
//
// returning false means that the operation can be retried.
if server_uid == 0 {
return 0;
return true; // might be moved but we don't want to have a stuck job
}
if self.should_reconnect() {
return false;
}
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
let set = format!("{}", server_uid);
let query = format!("+FLAGS ({})", flag.as_ref());
let query = format!("+FLAGS ({})", flag);
match session.uid_store(&set, &query) {
Ok(_) => {}
Err(err) => {
@@ -1239,161 +1247,126 @@ impl Imap {
);
}
}
}
// All non-connection states are treated as success - the mail may
// already be deleted or moved away on the server.
if self.should_reconnect() {
0
return true; // we tried once, that's probably enough for setting flag
} else {
1
unreachable!();
}
}
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
let mut res = ImapResult::RetryLater;
pub fn prepare_imap_operation_on_msg(
&self,
context: &Context,
folder: &str,
uid: u32,
) -> Option<ImapResult> {
if uid == 0 {
res = ImapResult::Failed
} else if self.is_connected() {
info!(
Some(ImapResult::Failed)
} else if !self.is_connected() {
Some(ImapResult::RetryLater)
} else if self.select_folder(context, Some(&folder)) == 0 {
warn!(
context,
"Marking message {}/{} as seen...",
folder.as_ref(),
uid,
"Cannot select folder {} for preparing IMAP operation", folder
);
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
"Cannot select folder {} for setting SEEN flag.",
folder.as_ref(),
);
} else if self.add_flag(context, uid, "\\Seen") == 0 {
warn!(context, "Cannot mark message as seen.",);
} else {
res = ImapResult::Success
}
}
if res == ImapResult::RetryLater {
if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
}
Some(ImapResult::RetryLater)
} else {
res
None
}
}
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
// returns 0=job should be retried later, 1=job done, 2=job done and flag just set
let mut res = ImapResult::RetryLater;
pub fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapResult {
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
return imapresult;
}
// we are connected, and the folder is selected
info!(context, "Marking message {}/{} as seen...", folder, uid,);
if self.add_flag_finalized(context, uid, "\\Seen") {
ImapResult::Success
} else {
warn!(
context,
"Cannot mark message {} in folder {} as seen, ignoring.", uid, folder
);
ImapResult::Failed
}
}
pub fn set_mdnsent(&self, context: &Context, folder: &str, uid: u32) -> ImapResult {
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
return imapresult;
}
// we are connected, and the folder is selected
let set = format!("{}", uid);
info!(context, "Marking message {}/{} as $MDNSent...", folder, uid,);
if uid == 0 {
res = ImapResult::Failed;
} else if self.is_connected() {
info!(
context,
"Marking message {}/{} as $MDNSent...",
folder.as_ref(),
uid,
);
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
// set: set the flags and return this information.
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
if self.select_folder(context, Some(folder.as_ref())) == 0 {
let can_create_flag = self
.config
.read()
.unwrap()
.selected_mailbox
.as_ref()
.map(|mbox| {
// empty means, everything can be stored
mbox.permanent_flags.is_empty()
|| mbox
.permanent_flags
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
});
match can_create_flag {
None | Some(false) => {
warn!(
context,
"Cannot select folder {} for setting $MDNSent flag.",
folder.as_ref()
"can't store $MDNSent flags in folder {}, ignoring.", folder
);
} else {
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
// set: set the flags and return this information.
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
// return ImapResult::Failed;
}
Some(true) => {}
}
let can_create_flag = self
.config
.read()
.unwrap()
.selected_mailbox
.as_ref()
.map(|mbox| {
// empty means, everything can be stored
mbox.permanent_flags.is_empty()
|| mbox
.permanent_flags
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.expect("just selected folder");
if can_create_flag {
let fetched_msgs =
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, FETCH_FLAGS) {
Ok(res) => Some(res),
Err(err) => {
eprintln!("fetch error: {:?}", err);
None
}
}
} else {
unreachable!();
};
if let Some(msgs) = fetched_msgs {
let flag_set = msgs
.first()
.map(|msg| {
msg.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.unwrap_or_else(|| false);
res = if flag_set {
ImapResult::AlreadyDone
} else if self.add_flag(context, uid, "$MDNSent") != 0 {
ImapResult::Success
} else {
res
};
if res == ImapResult::Success {
info!(context, "$MDNSent just set and MDN will be sent.");
} else {
info!(context, "$MDNSent already set and MDN already sent.");
}
}
} else {
res = ImapResult::Success;
info!(
context,
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
);
let msgs = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(&set, FETCH_FLAGS) {
Ok(res) => res,
Err(err) => {
warn!(context, "IMAP uid_fetch {:?} error: {}", set, err);
return ImapResult::Failed;
}
}
}
if res == ImapResult::RetryLater {
if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
}
} else {
res
unreachable!();
};
let flag_set = msgs
.first()
.map(|msg| {
msg.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.unwrap_or_else(|| false);
if flag_set {
info!(context, "$MDNSent already set and MDN already sent.");
ImapResult::AlreadyDone
} else if self.add_flag_finalized(context, uid, "$MDNSent") {
info!(context, "$MDNSent just set and MDN will be sent.");
ImapResult::Success
} else {
info!(context, "$MDNSent could not be set, ignoring");
ImapResult::Failed
}
}
@@ -1463,7 +1436,7 @@ impl Imap {
}
// mark the message for deletion
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
if !self.add_flag_finalized(context, *server_uid, "\\Deleted") {
warn!(context, "Cannot mark message as \"Deleted\".");
} else {
self.config.write().unwrap().selected_folder_needs_expunge = true;

View File

@@ -129,14 +129,15 @@ pub fn initiate_key_transfer(context: &Context) -> Result<String> {
.unwrap()
.shall_stop_ongoing
{
let setup_file_name =
dc_get_fine_path_filename(context, "$BLOBDIR", "autocrypt-setup-message.html");
if dc_write_file(context, &setup_file_name, setup_file_content.as_bytes()) {
let setup_file_name = context.new_blob_file(
"autocrypt-setup-message.html",
setup_file_content.as_bytes(),
)?;
{
if let Ok(chat_id) = chat::create_by_contact_id(context, 1) {
msg = Message::default();
msg.type_0 = Viewtype::File;
msg.param
.set(Param::File, setup_file_name.to_string_lossy());
msg.param.set(Param::File, setup_file_name);
msg.param
.set(Param::MimeType, "application/autocrypt-setup");
@@ -573,13 +574,10 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
let mut delete_dest_file: libc::c_int = 0;
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete. however, currently it is not clear it the import exists in the long run (may be replaced by a restore-from-imap)
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete.
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
let now = time();
let res = chrono::NaiveDateTime::from_timestamp(now as i64, 0)
.format("delta-chat-%Y-%m-%d.bak")
.to_string();
let dest_path_filename = dc_get_fine_path_filename(context, dir, res);
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
sql::housekeeping(context);

View File

@@ -317,7 +317,7 @@ impl Job {
{
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_mdnsent(context, folder, msg.server_uid) {
match inbox.set_mdnsent(&context, folder, msg.server_uid) {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
@@ -965,41 +965,39 @@ fn send_mdn(context: &Context, msg_id: u32) {
#[allow(non_snake_case)]
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let mut param = Params::new();
let path_filename = dc_get_fine_path_filename(context, "$BLOBDIR", &mimefactory.rfc724_mid);
let bytes = unsafe {
std::slice::from_raw_parts(
(*mimefactory.out).str_0 as *const u8,
(*mimefactory.out).len,
)
};
if !dc_write_file(context, &path_filename, bytes) {
error!(
context,
"Could not write message <{}> to \"{}\".",
mimefactory.rfc724_mid,
path_filename.display(),
);
} else {
info!(context, "add_smtp_job file written: {:?}", path_filename);
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, path_filename.to_string_lossy());
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id
} else {
0
}) as libc::c_int,
param,
0,
);
success = 1;
}
success
let bpath = match context.new_blob_file(&mimefactory.rfc724_mid, bytes) {
Ok(path) => path,
Err(err) => {
error!(
context,
"Could not write {} smtp-message, error {}", mimefactory.rfc724_mid, err
);
return 0;
}
};
info!(context, "add_smtp_job file written: {:?}", bpath);
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, &bpath);
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id
} else {
0
}) as libc::c_int,
param,
0,
);
1
}
pub fn job_add(

View File

@@ -16,7 +16,7 @@ use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::{get_version_str, Context};
use crate::dc_mimeparser::{mailmime_find_mailimf_fields, SystemMessage};
use crate::dc_mimeparser::SystemMessage;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
@@ -645,7 +645,7 @@ impl<'a> MimeFactory<'a> {
);
/*just a pointer into mailmime structure, must not be freed*/
let imffields_unprotected = mailmime_find_mailimf_fields(message);
let imffields_unprotected = wrapmime::mailmime_find_mailimf_fields(message);
ensure!(
!imffields_unprotected.is_null(),
"could not find mime fields"

View File

@@ -21,6 +21,8 @@ if __name__ == "__main__":
sum_unsafe, sum_free, sum_gotoblocks, sum_chars = 0, 0, 0, 0
for fn, unsafe, free, gotoblocks, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
if unsafe + free + gotoblocks + chars == 0:
continue
print("{0: <25} unsafe: {1: >3} free: {2: >3} ok_to_cont: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, gotoblocks, chars))
sum_unsafe += unsafe
sum_free += free

View File

@@ -1,9 +1,12 @@
use std::collections::HashSet;
use std::ffi::CString;
use std::ptr;
use crate::contact::addr_normalize;
use crate::dc_tools::*;
use crate::error::Error;
use mmime::clist::*;
// use mmime::display::*;
use mmime::mailimf::types::*;
use mmime::mailimf::types_helper::*;
use mmime::mailmime::content::*;
@@ -11,6 +14,7 @@ use mmime::mailmime::disposition::*;
use mmime::mailmime::types::*;
use mmime::mailmime::types_helper::*;
use mmime::mailmime::*;
use mmime::mmapstring::*;
use mmime::other::*;
#[macro_export]
@@ -88,6 +92,192 @@ pub fn has_decryptable_data(mime_data: *mut mailmime_data) -> bool {
}
}
pub fn get_field_from(imffields: *mut mailimf_fields) -> Result<String, Error> {
let field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
if let Some(addr) = mailimf_find_first_addr(mb_list) {
return Ok(addr);
}
}
bail!("not From field found");
}
pub fn get_field_date(imffields: *mut mailimf_fields) -> Result<i64, Error> {
let field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
let mut message_time = 0;
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
if !orig_date.is_null() {
let dt = unsafe { (*orig_date).dt_date_time };
message_time = dc_timestamp_from_date(dt);
if message_time != 0 && message_time > time() {
message_time = time()
}
}
}
Ok(message_time)
}
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
if !mb.is_null() {
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
recipients.insert(addr_norm.into());
}
}
/*the result is a pointer to mime, must not be freed*/
pub fn mailimf_find_field(
header: *mut mailimf_fields,
wanted_fld_type: libc::c_int,
) -> *mut mailimf_field {
if header.is_null() {
return ptr::null_mut();
}
let header = unsafe { (*header) };
if header.fld_list.is_null() {
return ptr::null_mut();
}
for cur in unsafe { &(*header.fld_list) } {
let field = cur as *mut mailimf_field;
if !field.is_null() {
if unsafe { (*field).fld_type } == wanted_fld_type {
return field;
}
}
}
ptr::null_mut()
}
/*the result is a pointer to mime, must not be freed*/
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
if mime.is_null() {
return ptr::null_mut();
}
match (*mime).mm_type as _ {
MAILMIME_MULTIPLE => {
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
if !header.is_null() {
return header;
}
}
}
MAILMIME_MESSAGE => return (*mime).mm_data.mm_message.mm_fields,
_ => {}
}
ptr::null_mut()
}
pub unsafe fn mailimf_find_optional_field(
header: *mut mailimf_fields,
wanted_fld_name: *const libc::c_char,
) -> *mut mailimf_optional_field {
if header.is_null() || (*header).fld_list.is_null() {
return ptr::null_mut();
}
for cur_data in (*(*header).fld_list).into_iter() {
let field: *mut mailimf_field = cur_data as *mut _;
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
if !optional_field.is_null()
&& !(*optional_field).fld_name.is_null()
&& !(*optional_field).fld_value.is_null()
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
{
return optional_field;
}
}
}
ptr::null_mut()
}
pub fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
/* returned addresses are normalized. */
let mut recipients: HashSet<String> = Default::default();
for cur in unsafe { (*(*imffields).fld_list).into_iter() } {
let fld = cur as *mut mailimf_field;
let fld_to: *mut mailimf_to;
let fld_cc: *mut mailimf_cc;
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
if fld.is_null() {
continue;
}
let fld = unsafe { *fld };
// TODO match on enums /rtn
match fld.fld_type {
13 => {
fld_to = unsafe { fld.fld_data.fld_to };
if !fld_to.is_null() {
addr_list = unsafe { (*fld_to).to_addr_list };
}
}
14 => {
fld_cc = unsafe { fld.fld_data.fld_cc };
if !fld_cc.is_null() {
addr_list = unsafe { (*fld_cc).cc_addr_list };
}
}
_ => {}
}
if !addr_list.is_null() {
for cur2 in unsafe { &(*(*addr_list).ad_list) } {
let adr = cur2 as *mut mailimf_address;
if adr.is_null() {
continue;
}
let adr = unsafe { *adr };
if adr.ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
mailimf_get_recipients_add_addr(&mut recipients, unsafe {
adr.ad_data.ad_mailbox
});
} else if adr.ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
let group = unsafe { adr.ad_data.ad_group };
if !group.is_null() && unsafe { !(*group).grp_mb_list.is_null() } {
for cur3 in unsafe { &(*(*(*group).grp_mb_list).mb_list) } {
mailimf_get_recipients_add_addr(
&mut recipients,
cur3 as *mut mailimf_mailbox,
);
}
}
}
}
}
}
recipients
}
pub fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
ensure!(!mime.is_null(), "invalid inputs");
let mime_transfer_encoding =
get_mime_transfer_encoding(mime).unwrap_or(MAILMIME_MECHANISM_BINARY as i32);
let mime_data = unsafe { (*mime).mm_data.mm_single };
decode_dt_data(mime_data, mime_transfer_encoding)
}
pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
unsafe {
let mm_mime_fields = (*mime).mm_mime_fields;
@@ -108,48 +298,77 @@ pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
pub fn decode_dt_data(
mime_data: *mut mailmime_data,
mime_transfer_encoding: libc::c_int,
) -> Result<(*mut libc::c_char, libc::size_t), Error> {
) -> Result<Vec<u8>, Error> {
// Decode data according to mime_transfer_encoding
// returns Ok with a (decoded_data,decoded_data_bytes) pointer
// where the caller must make sure to free it.
// It may return Ok(ptr::null_mut(), 0)
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
let decoded_data: *mut libc::c_char;
let mut decoded_data_bytes: libc::size_t = 0;
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
{
unsafe {
decoded_data = (*mime_data).dt_data.dt_text.dt_data as *mut _;
decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
}
ensure!(
!decoded_data.is_null() && decoded_data_bytes > 0,
"could not decode mime message"
);
} else {
let mut current_index: libc::size_t = 0;
unsafe {
let r = mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
);
if r != MAILIMF_NO_ERROR as libc::c_int
|| transfer_decoding_buffer.is_null()
|| decoded_data_bytes <= 0
{
bail!("mailmime_part_parse returned error or invalid data");
}
decoded_data = transfer_decoding_buffer;
let decoded_data = unsafe { (*mime_data).dt_data.dt_text.dt_data };
let decoded_data_bytes = unsafe { (*mime_data).dt_data.dt_text.dt_length };
if decoded_data.is_null() || decoded_data_bytes <= 0 {
bail!("No data to decode found");
} else {
let result = unsafe {
std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes)
};
return Ok(result.to_vec());
}
}
Ok((decoded_data, decoded_data_bytes))
// unsafe { display_mime_data(mime_data) };
let mut current_index = 0;
let mut transfer_decoding_buffer = ptr::null_mut();
let mut decoded_data_bytes = 0;
let r = unsafe {
mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
)
};
if r == MAILIMF_NO_ERROR as libc::c_int
&& !transfer_decoding_buffer.is_null()
&& decoded_data_bytes > 0
{
let result = unsafe {
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
}
.to_vec();
// we return a fresh vec and transfer_decoding_buffer is not used or passed anywhere
// so it's safe to free it right away, as mailman_part_parse has
// allocated it fresh.
unsafe { mmap_string_unref(transfer_decoding_buffer) };
return Ok(result);
}
Err(format_err!("Failed to to decode"))
}
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
if mb_list.is_null() {
return None;
}
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
let mb = cur as *mut mailimf_mailbox;
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
let addr = unsafe { as_str((*mb).mb_addr_spec) };
return Some(addr_normalize(addr).to_string());
}
}
None
}
/**************************************