mirror of
https://github.com/chatmail/core.git
synced 2026-04-06 23:52:11 +03:00
Compare commits
62 Commits
tweak-doxy
...
hpk-fix-sm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9338efd30d | ||
|
|
f404e31e30 | ||
|
|
7455b26ab2 | ||
|
|
ee3259a74d | ||
|
|
391ba67ad5 | ||
|
|
54f8c68151 | ||
|
|
4a2e1897a6 | ||
|
|
076616bfb9 | ||
|
|
a9dd78f622 | ||
|
|
d16bdafaf0 | ||
|
|
4f126c5292 | ||
|
|
7b958a20fd | ||
|
|
4519071718 | ||
|
|
0108b4724e | ||
|
|
bb08b39c71 | ||
|
|
1908ac428b | ||
|
|
dfc453c1d1 | ||
|
|
9fa6289093 | ||
|
|
6f92ce0fa8 | ||
|
|
cde2c9137f | ||
|
|
120524ae00 | ||
|
|
7bb73f45a5 | ||
|
|
2d0f563dfe | ||
|
|
cfe3c69f00 | ||
|
|
c266d2ca0d | ||
|
|
85fc696975 | ||
|
|
9bf8bed0c3 | ||
|
|
c4d55f6ba4 | ||
|
|
766d7cbd3a | ||
|
|
8e0e1bd58d | ||
|
|
a471ccc95a | ||
|
|
daac8c4824 | ||
|
|
59c22a5626 | ||
|
|
5154f27f72 | ||
|
|
5f7279eb85 | ||
|
|
ce67f593f6 | ||
|
|
556ea57f37 | ||
|
|
a4257b619a | ||
|
|
8479c8afbf | ||
|
|
eba012b965 | ||
|
|
66e53e6804 | ||
|
|
c8aa8b55f6 | ||
|
|
900e3905c0 | ||
|
|
088490721d | ||
|
|
a40b99aae0 | ||
|
|
b9646446f8 | ||
|
|
4e36b35039 | ||
|
|
d412ee6042 | ||
|
|
67848e3333 | ||
|
|
4d79c6e235 | ||
|
|
bc99d9d196 | ||
|
|
e1fc5863c2 | ||
|
|
f0791149e6 | ||
|
|
297b032bdc | ||
|
|
98180c175d | ||
|
|
46e8a436cb | ||
|
|
dc2cf8ecfc | ||
|
|
fd69ebfd1f | ||
|
|
03979fdc51 | ||
|
|
2c98e91276 | ||
|
|
3270120d16 | ||
|
|
77a7efc920 |
24
CHANGELOG.md
Normal file
24
CHANGELOG.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# API changes
|
||||
|
||||
## 1.0.0-beta1
|
||||
|
||||
- first beta of the Delta Chat Rust core library. many fixes of crashes
|
||||
and other issues compared to 1.0.0-alpha.5.
|
||||
|
||||
- Most code is now "rustified" and does not do manual memory allocation anymore.
|
||||
|
||||
- The `DC_EVENT_GET_STRING` event is not used anymore, removing the last
|
||||
event where the core requested a return value from the event callback.
|
||||
|
||||
Please now use `dc_set_stock_translation()` API for core messages
|
||||
to be properly localized.
|
||||
|
||||
- Deltachat FFI docs are automatically generated and available here:
|
||||
https://c.delta.chat
|
||||
|
||||
- New events ImapMessageMoved and ImapMessageDeleted
|
||||
|
||||
For a full list of changes, please see our closed Pull Requests:
|
||||
|
||||
https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
||||
|
||||
605
Cargo.lock
generated
605
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-alpha.5"
|
||||
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
||||
version = "1.0.0-beta.1"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL"
|
||||
|
||||
@@ -10,7 +10,7 @@ deltachat_derive = { path = "./deltachat_derive" }
|
||||
mmime = { version = "0.1.2", path = "./mmime" }
|
||||
|
||||
libc = "0.2.51"
|
||||
pgp = { version = "0.2", default-features = false }
|
||||
pgp = { version = "0.2.3", default-features = false }
|
||||
hex = "0.3.2"
|
||||
sha2 = "0.8.0"
|
||||
rand = "0.6.5"
|
||||
@@ -19,8 +19,8 @@ reqwest = "0.9.15"
|
||||
num-derive = "0.2.5"
|
||||
num-traits = "0.2.6"
|
||||
native-tls = "0.2.3"
|
||||
lettre = "0.9.0"
|
||||
imap = { git = "https://github.com/jonhoo/rust-imap", rev = "281d2eb8ab50dc656ceff2ae749ca5045f334e15" }
|
||||
lettre = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
imap = { git = "https://github.com/jonhoo/rust-imap", branch = "master" }
|
||||
base64 = "0.10"
|
||||
charset = "0.1"
|
||||
percent-encoding = "2.0"
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
[dependencies.std]
|
||||
features = ["panic-unwind"]
|
||||
|
||||
# if using `cargo test`
|
||||
[dependencies.test]
|
||||
stage = 1
|
||||
@@ -22,6 +22,7 @@ rsync -avz \
|
||||
delta@py.delta.chat:build/${BRANCH}
|
||||
|
||||
# C docs to c.delta.chat
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null delta@c.delta.chat mkdir -p build-c/${BRANCH}
|
||||
rsync -avz \
|
||||
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \
|
||||
"$DOXYDOCDIR/html/" \
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.0.0-alpha.5"
|
||||
version = "1.0.0-beta.1"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -24,7 +24,9 @@ use num_traits::{FromPrimitive, ToPrimitive};
|
||||
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::dc_tools::{as_path, as_str, dc_strdup, to_string_lossy, OsStrExt, StrExt};
|
||||
use deltachat::dc_tools::{
|
||||
as_path, dc_strdup, to_opt_string_lossy, to_string_lossy, OsStrExt, StrExt,
|
||||
};
|
||||
use deltachat::stock::StockMessage;
|
||||
use deltachat::*;
|
||||
|
||||
@@ -303,11 +305,14 @@ pub unsafe extern "C" fn dc_set_config(
|
||||
return 0;
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
match config::Config::from_str(as_str(key)) {
|
||||
match config::Config::from_str(&to_string_lossy(key)) {
|
||||
// When ctx.set_config() fails it already logged the error.
|
||||
// TODO: Context::set_config() should not log this
|
||||
Ok(key) => ffi_context
|
||||
.with_inner(|ctx| ctx.set_config(key, as_opt_str(value)).is_ok() as libc::c_int)
|
||||
.with_inner(|ctx| {
|
||||
ctx.set_config(key, to_opt_string_lossy(value).as_ref().map(|x| x.as_str()))
|
||||
.is_ok() as libc::c_int
|
||||
})
|
||||
.unwrap_or(0),
|
||||
Err(_) => {
|
||||
ffi_context.error("dc_set_config(): invalid key");
|
||||
@@ -326,7 +331,7 @@ pub unsafe extern "C" fn dc_get_config(
|
||||
return "".strdup();
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
match config::Config::from_str(as_str(key)) {
|
||||
match config::Config::from_str(&to_string_lossy(key)) {
|
||||
Ok(key) => ffi_context
|
||||
.with_inner(|ctx| ctx.get_config(key).unwrap_or_default().strdup())
|
||||
.unwrap_or_else(|_| "".strdup()),
|
||||
@@ -347,7 +352,7 @@ pub unsafe extern "C" fn dc_set_stock_translation(
|
||||
eprintln!("ignoring careless call to dc_set_stock_string");
|
||||
return 0;
|
||||
}
|
||||
let msg = as_str(stock_msg).to_string();
|
||||
let msg = to_string_lossy(stock_msg);
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| match StockMessage::from_u32(stock_id) {
|
||||
@@ -646,22 +651,24 @@ pub unsafe extern "C" fn dc_get_chatlist(
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
let qs = if query_str.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(as_str(query_str))
|
||||
};
|
||||
let qs = to_opt_string_lossy(query_str);
|
||||
|
||||
let qi = if query_id == 0 { None } else { Some(query_id) };
|
||||
ffi_context
|
||||
.with_inner(
|
||||
|ctx| match chatlist::Chatlist::try_load(ctx, flags as usize, qs, qi) {
|
||||
.with_inner(|ctx| {
|
||||
match chatlist::Chatlist::try_load(
|
||||
ctx,
|
||||
flags as usize,
|
||||
qs.as_ref().map(|x| x.as_str()),
|
||||
qi,
|
||||
) {
|
||||
Ok(list) => {
|
||||
let ffi_list = ChatlistWrapper { context, list };
|
||||
Box::into_raw(Box::new(ffi_list))
|
||||
}
|
||||
Err(_) => ptr::null_mut(),
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
}
|
||||
|
||||
@@ -1052,7 +1059,7 @@ pub unsafe extern "C" fn dc_search_msgs(
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
let arr = dc_array_t::from(ctx.search_msgs(chat_id, as_str(query)));
|
||||
let arr = dc_array_t::from(ctx.search_msgs(chat_id, to_string_lossy(query)));
|
||||
Box::into_raw(Box::new(arr))
|
||||
})
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
@@ -1094,7 +1101,7 @@ pub unsafe extern "C" fn dc_create_group_chat(
|
||||
};
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
chat::create_group_chat(ctx, verified, as_str(name))
|
||||
chat::create_group_chat(ctx, verified, to_string_lossy(name))
|
||||
.unwrap_or_log_default(ctx, "Failed to create group chat")
|
||||
})
|
||||
.unwrap_or(0)
|
||||
@@ -1166,7 +1173,7 @@ pub unsafe extern "C" fn dc_set_chat_name(
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
chat::set_chat_name(ctx, chat_id, as_str(name))
|
||||
chat::set_chat_name(ctx, chat_id, to_string_lossy(name))
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(ctx, "Failed to set chat name")
|
||||
})
|
||||
@@ -1186,15 +1193,9 @@ pub unsafe extern "C" fn dc_set_chat_profile_image(
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
chat::set_chat_profile_image(ctx, chat_id, {
|
||||
if image.is_null() {
|
||||
""
|
||||
} else {
|
||||
as_str(image)
|
||||
}
|
||||
})
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(ctx, "Failed to set profile image")
|
||||
chat::set_chat_profile_image(ctx, chat_id, to_string_lossy(image))
|
||||
.map(|_| 1)
|
||||
.unwrap_or_log_default(ctx, "Failed to set profile image")
|
||||
})
|
||||
.unwrap_or(0)
|
||||
}
|
||||
@@ -1357,7 +1358,7 @@ pub unsafe extern "C" fn dc_may_be_valid_addr(addr: *const libc::c_char) -> libc
|
||||
return 0;
|
||||
}
|
||||
|
||||
contact::may_be_valid_addr(as_str(addr)) as libc::c_int
|
||||
contact::may_be_valid_addr(&to_string_lossy(addr)) as libc::c_int
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -1371,7 +1372,7 @@ pub unsafe extern "C" fn dc_lookup_contact_id_by_addr(
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| Contact::lookup_id_by_addr(ctx, as_str(addr)))
|
||||
.with_inner(|ctx| Contact::lookup_id_by_addr(ctx, to_string_lossy(addr)))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
@@ -1386,12 +1387,14 @@ pub unsafe extern "C" fn dc_create_contact(
|
||||
return 0;
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
let name = if name.is_null() { "" } else { as_str(name) };
|
||||
let name = to_string_lossy(name);
|
||||
ffi_context
|
||||
.with_inner(|ctx| match Contact::create(ctx, name, as_str(addr)) {
|
||||
Ok(id) => id,
|
||||
Err(_) => 0,
|
||||
})
|
||||
.with_inner(
|
||||
|ctx| match Contact::create(ctx, name, to_string_lossy(addr)) {
|
||||
Ok(id) => id,
|
||||
Err(_) => 0,
|
||||
},
|
||||
)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
@@ -1407,7 +1410,7 @@ pub unsafe extern "C" fn dc_add_address_book(
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(
|
||||
|ctx| match Contact::add_address_book(ctx, as_str(addr_book)) {
|
||||
|ctx| match Contact::add_address_book(ctx, to_string_lossy(addr_book)) {
|
||||
Ok(cnt) => cnt as libc::c_int,
|
||||
Err(_) => 0,
|
||||
},
|
||||
@@ -1426,11 +1429,7 @@ pub unsafe extern "C" fn dc_get_contacts(
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
let query = if query.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(as_str(query))
|
||||
};
|
||||
let query = to_opt_string_lossy(query);
|
||||
ffi_context
|
||||
.with_inner(|ctx| match Contact::get_all(ctx, flags, query) {
|
||||
Ok(contacts) => Box::into_raw(Box::new(dc_array_t::from(contacts))),
|
||||
@@ -1567,7 +1566,7 @@ pub unsafe extern "C" fn dc_imex(
|
||||
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| imex::imex(ctx, what, as_opt_str(param1)))
|
||||
.with_inner(|ctx| imex::imex(ctx, what, to_opt_string_lossy(param1)))
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -1582,7 +1581,7 @@ pub unsafe extern "C" fn dc_imex_has_backup(
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| match imex::has_backup(ctx, as_str(dir)) {
|
||||
.with_inner(|ctx| match imex::has_backup(ctx, to_string_lossy(dir)) {
|
||||
Ok(res) => res.strdup(),
|
||||
Err(err) => {
|
||||
error!(ctx, "dc_imex_has_backup: {}", err);
|
||||
@@ -1625,15 +1624,15 @@ pub unsafe extern "C" fn dc_continue_key_transfer(
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(
|
||||
|ctx| match imex::continue_key_transfer(ctx, msg_id, as_str(setup_code)) {
|
||||
.with_inner(|ctx| {
|
||||
match imex::continue_key_transfer(ctx, msg_id, &to_string_lossy(setup_code)) {
|
||||
Ok(()) => 1,
|
||||
Err(err) => {
|
||||
error!(ctx, "dc_continue_key_transfer: {}", err);
|
||||
0
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
@@ -1659,7 +1658,7 @@ pub unsafe extern "C" fn dc_check_qr(
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| {
|
||||
let lot = qr::check_qr(ctx, as_str(qr));
|
||||
let lot = qr::check_qr(ctx, to_string_lossy(qr));
|
||||
Box::into_raw(Box::new(lot))
|
||||
})
|
||||
.unwrap_or_else(|_| ptr::null_mut())
|
||||
@@ -1695,7 +1694,7 @@ pub unsafe extern "C" fn dc_join_securejoin(
|
||||
}
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| securejoin::dc_join_securejoin(ctx, as_str(qr)))
|
||||
.with_inner(|ctx| securejoin::dc_join_securejoin(ctx, &to_string_lossy(qr)))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
@@ -1744,7 +1743,7 @@ pub unsafe extern "C" fn dc_set_location(
|
||||
let ffi_context = &*context;
|
||||
ffi_context
|
||||
.with_inner(|ctx| location::set(ctx, latitude, longitude, accuracy))
|
||||
.unwrap_or(0)
|
||||
.unwrap_or(false) as _
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2610,8 +2609,7 @@ pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *const libc::
|
||||
return;
|
||||
}
|
||||
let ffi_msg = &mut *msg;
|
||||
// TODO: {text} equal to NULL is treated as "", which is strange. Does anyone rely on it?
|
||||
ffi_msg.message.set_text(as_opt_str(text).map(Into::into))
|
||||
ffi_msg.message.set_text(to_opt_string_lossy(text))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2625,7 +2623,10 @@ pub unsafe extern "C" fn dc_msg_set_file(
|
||||
return;
|
||||
}
|
||||
let ffi_msg = &mut *msg;
|
||||
ffi_msg.message.set_file(as_str(file), as_opt_str(filemime))
|
||||
ffi_msg.message.set_file(
|
||||
to_string_lossy(file),
|
||||
to_opt_string_lossy(filemime).as_ref().map(|x| x.as_str()),
|
||||
)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -2920,14 +2921,6 @@ pub unsafe extern "C" fn dc_str_unref(s: *mut libc::c_char) {
|
||||
libc::free(s as *mut _)
|
||||
}
|
||||
|
||||
fn as_opt_str<'a>(s: *const libc::c_char) -> Option<&'a str> {
|
||||
if s.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(as_str(s))
|
||||
}
|
||||
|
||||
pub mod providers;
|
||||
|
||||
pub trait ResultExt<T> {
|
||||
|
||||
@@ -192,7 +192,7 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
let msgtext = msg.get_text();
|
||||
info!(
|
||||
context,
|
||||
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{} [{}]",
|
||||
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
|
||||
prefix.as_ref(),
|
||||
msg.get_id() as libc::c_int,
|
||||
if msg.get_showpadlock() { "🔒" } else { "" },
|
||||
@@ -211,6 +211,11 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
"[FRESH]"
|
||||
},
|
||||
if msg.is_info() { "[INFO]" } else { "" },
|
||||
if msg.is_forwarded() {
|
||||
"[FORWARDED]"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
statestr,
|
||||
&temp2,
|
||||
);
|
||||
@@ -741,7 +746,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
let longitude = arg2.parse()?;
|
||||
|
||||
let continue_streaming = location::set(context, latitude, longitude, 0.);
|
||||
if 0 != continue_streaming {
|
||||
if continue_streaming {
|
||||
println!("Success, streaming should be continued.");
|
||||
} else {
|
||||
println!("Success, streaming can be stoppped.");
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::mailmime::types_helper::*;
|
||||
|
||||
pub(crate) use libc::{
|
||||
calloc, close, free, isalpha, isdigit, malloc, memcmp, memcpy, memmove, memset, realloc,
|
||||
strcpy, strlen, strncmp, strncpy,
|
||||
strcpy, strlen, strncmp, strncpy, strnlen,
|
||||
};
|
||||
|
||||
pub(crate) unsafe fn strcasecmp(s1: *const libc::c_char, s2: *const libc::c_char) -> libc::c_int {
|
||||
@@ -40,8 +40,10 @@ pub(crate) unsafe fn strncasecmp(
|
||||
|
||||
// s1 and s2 might not be null terminated.
|
||||
|
||||
let s1_slice = std::slice::from_raw_parts(s1 as *const u8, n);
|
||||
let s2_slice = std::slice::from_raw_parts(s2 as *const u8, n);
|
||||
let s1_slice =
|
||||
std::slice::from_raw_parts(s1 as *const u8, strnlen(s1 as *const libc::c_char, n));
|
||||
let s2_slice =
|
||||
std::slice::from_raw_parts(s2 as *const u8, strnlen(s2 as *const libc::c_char, n));
|
||||
|
||||
let s1 = std::ffi::CStr::from_bytes_with_nul_unchecked(s1_slice)
|
||||
.to_string_lossy()
|
||||
@@ -1693,6 +1695,14 @@ mod tests {
|
||||
4,
|
||||
)
|
||||
});
|
||||
|
||||
assert_eq!(0, unsafe {
|
||||
strncasecmp(
|
||||
CString::new("hell").unwrap().as_ptr(),
|
||||
CString::new("Hell").unwrap().as_ptr(),
|
||||
100_000_000,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -57,6 +57,14 @@ DC_MSG_AUDIO = 40
|
||||
DC_MSG_VOICE = 41
|
||||
DC_MSG_VIDEO = 50
|
||||
DC_MSG_FILE = 60
|
||||
DC_LP_AUTH_OAUTH2 = 0x2
|
||||
DC_LP_AUTH_NORMAL = 0x4
|
||||
DC_LP_IMAP_SOCKET_STARTTLS = 0x100
|
||||
DC_LP_IMAP_SOCKET_SSL = 0x200
|
||||
DC_LP_IMAP_SOCKET_PLAIN = 0x400
|
||||
DC_LP_SMTP_SOCKET_STARTTLS = 0x10000
|
||||
DC_LP_SMTP_SOCKET_SSL = 0x20000
|
||||
DC_LP_SMTP_SOCKET_PLAIN = 0x40000
|
||||
DC_EVENT_INFO = 100
|
||||
DC_EVENT_SMTP_CONNECTED = 101
|
||||
DC_EVENT_IMAP_CONNECTED = 102
|
||||
@@ -82,9 +90,9 @@ DC_EVENT_IMEX_PROGRESS = 2051
|
||||
DC_EVENT_IMEX_FILE_WRITTEN = 2052
|
||||
DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060
|
||||
DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061
|
||||
DC_EVENT_GET_STRING = 2091
|
||||
DC_EVENT_FILE_COPIED = 2055
|
||||
DC_EVENT_IS_OFFLINE = 2081
|
||||
DC_EVENT_GET_STRING = 2091
|
||||
DC_STR_SELFNOTINGRP = 21
|
||||
DC_PROVIDER_STATUS_OK = 1
|
||||
DC_PROVIDER_STATUS_PREPARATION = 2
|
||||
@@ -139,7 +147,7 @@ DC_STR_COUNT = 67
|
||||
|
||||
|
||||
def read_event_defines(f):
|
||||
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_STATE_|DC_STR|'
|
||||
rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_QR|DC_MSG|DC_LP|DC_STATE_|DC_STR|'
|
||||
r'DC_CONTACT_ID_|DC_GCL|DC_CHAT|DC_PROVIDER)\S+)\s+([x\d]+).*')
|
||||
for line in f:
|
||||
m = rex.match(line)
|
||||
|
||||
@@ -150,6 +150,11 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
|
||||
lib.dc_set_config(ac._dc_context, b"configured", b"1")
|
||||
return ac
|
||||
|
||||
def peek_online_config(self):
|
||||
if not session_liveconfig:
|
||||
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
|
||||
return session_liveconfig.get(self.live_count)
|
||||
|
||||
def get_online_config(self):
|
||||
if not session_liveconfig:
|
||||
pytest.skip("specify DCC_PY_LIVECONFIG or --liveconfig")
|
||||
|
||||
@@ -389,7 +389,8 @@ class TestOnlineAccount:
|
||||
|
||||
def test_one_account_send_bcc_setting(self, acfactory, lp):
|
||||
ac1 = acfactory.get_online_configuring_account()
|
||||
c2 = ac1.create_contact(email="notexists@testrun.org")
|
||||
ac2_config = acfactory.peek_online_config()
|
||||
c2 = ac1.create_contact(email=ac2_config["addr"])
|
||||
chat = ac1.create_chat_by_contact(c2)
|
||||
assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL
|
||||
wait_successful_IMAP_SMTP_connection(ac1)
|
||||
|
||||
@@ -1761,9 +1761,12 @@ pub fn forward_msgs(context: &Context, msg_ids: &[u32], chat_id: u32) -> Result<
|
||||
}
|
||||
let mut msg = msg.unwrap();
|
||||
let original_param = msg.param.clone();
|
||||
if msg.from_id != DC_CONTACT_ID_SELF {
|
||||
msg.param.set_int(Param::Forwarded, 1);
|
||||
}
|
||||
|
||||
// we tested a sort of broadcast
|
||||
// by not marking own forwarded messages as such,
|
||||
// however, this turned out to be to confusing and unclear.
|
||||
msg.param.set_int(Param::Forwarded, 1);
|
||||
|
||||
msg.param.remove(Param::GuranteeE2ee);
|
||||
msg.param.remove(Param::ForcePlaintext);
|
||||
msg.param.remove(Param::Cmd);
|
||||
|
||||
@@ -270,7 +270,7 @@ impl Chatlist {
|
||||
|
||||
let lastmsg = if 0 != lastmsg_id {
|
||||
if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
|
||||
if lastmsg.from_id != 1 as libc::c_uint
|
||||
if lastmsg.from_id != 1
|
||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
||||
{
|
||||
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
|
||||
|
||||
@@ -3,6 +3,7 @@ use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::login_param::LoginParam;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
@@ -11,7 +12,7 @@ use super::read_autoconf_file;
|
||||
******************************************************************************/
|
||||
/* documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
|
||||
struct MozAutoconfigure<'a> {
|
||||
pub in_0: &'a LoginParam,
|
||||
pub in_emailaddr: &'a str,
|
||||
pub in_emaildomain: &'a str,
|
||||
pub in_emaillocalpart: &'a str,
|
||||
pub out: LoginParam,
|
||||
@@ -21,6 +22,7 @@ struct MozAutoconfigure<'a> {
|
||||
pub tag_config: MozConfigTag,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum MozServer {
|
||||
Undefined,
|
||||
Imap,
|
||||
@@ -35,25 +37,20 @@ enum MozConfigTag {
|
||||
Username,
|
||||
}
|
||||
|
||||
pub fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let xml_raw = read_autoconf_file(context, url)?;
|
||||
|
||||
// Split address into local part and domain part.
|
||||
let p = param_in.addr.find('@')?;
|
||||
let (in_emaillocalpart, in_emaildomain) = param_in.addr.split_at(p);
|
||||
let in_emaildomain = &in_emaildomain[1..];
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(&xml_raw);
|
||||
pub fn moz_parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
|
||||
let mut reader = quick_xml::Reader::from_str(xml_raw);
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
// Split address into local part and domain part.
|
||||
let p = match in_emailaddr.find('@') {
|
||||
Some(i) => i,
|
||||
None => bail!("Email address {} does not contain @", in_emailaddr),
|
||||
};
|
||||
let (in_emaillocalpart, in_emaildomain) = in_emailaddr.split_at(p);
|
||||
let in_emaildomain = &in_emaildomain[1..];
|
||||
|
||||
let mut moz_ac = MozAutoconfigure {
|
||||
in_0: param_in,
|
||||
in_emailaddr,
|
||||
in_emaildomain,
|
||||
in_emaillocalpart,
|
||||
out: LoginParam::new(),
|
||||
@@ -62,6 +59,8 @@ pub fn moz_autoconfigure(
|
||||
tag_server: MozServer::Undefined,
|
||||
tag_config: MozConfigTag::Undefined,
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
@@ -72,8 +71,7 @@ pub fn moz_autoconfigure(
|
||||
moz_autoconfigure_text_cb(e, &mut moz_ac, &reader)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
context,
|
||||
bail!(
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
@@ -91,11 +89,26 @@ pub fn moz_autoconfigure(
|
||||
|| moz_ac.out.send_port == 0
|
||||
{
|
||||
let r = moz_ac.out.to_string();
|
||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
||||
return None;
|
||||
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||
}
|
||||
|
||||
Some(moz_ac.out)
|
||||
Ok(moz_ac.out)
|
||||
}
|
||||
|
||||
pub fn moz_autoconfigure(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
param_in: &LoginParam,
|
||||
) -> Option<LoginParam> {
|
||||
let xml_raw = read_autoconf_file(context, url)?;
|
||||
|
||||
match moz_parse_xml(¶m_in.addr, &xml_raw) {
|
||||
Err(err) => {
|
||||
warn!(context, "{}", err);
|
||||
None
|
||||
}
|
||||
Ok(lp) => Some(lp),
|
||||
}
|
||||
}
|
||||
|
||||
fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
@@ -105,7 +118,7 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
) {
|
||||
let val = event.unescape_and_decode(reader).unwrap_or_default();
|
||||
|
||||
let addr = &moz_ac.in_0.addr;
|
||||
let addr = moz_ac.in_emailaddr;
|
||||
let email_local = moz_ac.in_emaillocalpart;
|
||||
let email_domain = moz_ac.in_emaildomain;
|
||||
|
||||
@@ -160,13 +173,17 @@ fn moz_autoconfigure_endtag_cb(event: &BytesEnd, moz_ac: &mut MozAutoconfigure)
|
||||
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "incomingserver" {
|
||||
if moz_ac.tag_server == MozServer::Imap {
|
||||
moz_ac.out_imap_set = true;
|
||||
}
|
||||
moz_ac.tag_server = MozServer::Undefined;
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
moz_ac.out_imap_set = true;
|
||||
} else if tag == "outgoingserver" {
|
||||
if moz_ac.tag_server == MozServer::Smtp {
|
||||
moz_ac.out_smtp_set = true;
|
||||
}
|
||||
moz_ac.tag_server = MozServer::Undefined;
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
moz_ac.out_smtp_set = true;
|
||||
} else {
|
||||
moz_ac.tag_config = MozConfigTag::Undefined;
|
||||
}
|
||||
@@ -217,3 +234,89 @@ fn moz_autoconfigure_starttag_cb<B: std::io::BufRead>(
|
||||
moz_ac.tag_config = MozConfigTag::Username;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_outlook_autoconfig() {
|
||||
// Copied from https://autoconfig.thunderbird.net/v1.1/outlook.com on 2019-10-11
|
||||
let xml_raw =
|
||||
"<clientConfig version=\"1.1\">
|
||||
<emailProvider id=\"outlook.com\">
|
||||
<domain>hotmail.com</domain>
|
||||
<domain>hotmail.co.uk</domain>
|
||||
<domain>hotmail.co.jp</domain>
|
||||
<domain>hotmail.com.br</domain>
|
||||
<domain>hotmail.de</domain>
|
||||
<domain>hotmail.fr</domain>
|
||||
<domain>hotmail.it</domain>
|
||||
<domain>hotmail.es</domain>
|
||||
<domain>live.com</domain>
|
||||
<domain>live.co.uk</domain>
|
||||
<domain>live.co.jp</domain>
|
||||
<domain>live.de</domain>
|
||||
<domain>live.fr</domain>
|
||||
<domain>live.it</domain>
|
||||
<domain>live.jp</domain>
|
||||
<domain>msn.com</domain>
|
||||
<domain>outlook.com</domain>
|
||||
<displayName>Outlook.com (Microsoft)</displayName>
|
||||
<displayShortName>Outlook</displayShortName>
|
||||
<incomingServer type=\"exchange\">
|
||||
<hostname>outlook.office365.com</hostname>
|
||||
<port>443</port>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>OAuth2</authentication>
|
||||
<owaURL>https://outlook.office365.com/owa/</owaURL>
|
||||
<ewsURL>https://outlook.office365.com/ews/exchange.asmx</ewsURL>
|
||||
<useGlobalPreferredServer>true</useGlobalPreferredServer>
|
||||
</incomingServer>
|
||||
<incomingServer type=\"imap\">
|
||||
<hostname>outlook.office365.com</hostname>
|
||||
<port>993</port>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
</incomingServer>
|
||||
<incomingServer type=\"pop3\">
|
||||
<hostname>outlook.office365.com</hostname>
|
||||
<port>995</port>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<pop3>
|
||||
<leaveMessagesOnServer>true</leaveMessagesOnServer>
|
||||
<!-- Outlook.com docs specifically mention that POP3 deletes have effect on the main inbox on webmail and IMAP -->
|
||||
</pop3>
|
||||
</incomingServer>
|
||||
<outgoingServer type=\"smtp\">
|
||||
<hostname>smtp.office365.com</hostname>
|
||||
<port>587</port>
|
||||
<socketType>STARTTLS</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
</outgoingServer>
|
||||
<documentation url=\"http://windows.microsoft.com/en-US/windows/outlook/send-receive-from-app\">
|
||||
<descr lang=\"en\">Set up an email app with Outlook.com</descr>
|
||||
</documentation>
|
||||
</emailProvider>
|
||||
<webMail>
|
||||
<loginPage url=\"https://www.outlook.com/\"/>
|
||||
<loginPageInfo url=\"https://www.outlook.com/\">
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<usernameField id=\"i0116\" name=\"login\"/>
|
||||
<passwordField id=\"i0118\" name=\"passwd\"/>
|
||||
<loginButton id=\"idSIButton9\" name=\"SI\"/>
|
||||
</loginPageInfo>
|
||||
</webMail>
|
||||
</clientConfig>";
|
||||
let res = moz_parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
|
||||
assert_eq!(res.mail_server, "outlook.office365.com");
|
||||
assert_eq!(res.mail_port, 993);
|
||||
assert_eq!(res.send_server, "smtp.office365.com");
|
||||
assert_eq!(res.send_port, 587);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use quick_xml::events::BytesEnd;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::login_param::LoginParam;
|
||||
|
||||
use super::read_autoconf_file;
|
||||
@@ -19,6 +20,100 @@ struct OutlookAutodiscover {
|
||||
pub config_redirecturl: Option<String>,
|
||||
}
|
||||
|
||||
enum ParsingResult {
|
||||
LoginParam(LoginParam),
|
||||
RedirectUrl(String),
|
||||
}
|
||||
|
||||
fn outlk_parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
|
||||
let mut outlk_ad = OutlookAutodiscover {
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: false,
|
||||
out_smtp_set: false,
|
||||
config_type: None,
|
||||
config_server: String::new(),
|
||||
config_port: 0,
|
||||
config_ssl: String::new(),
|
||||
config_redirecturl: None,
|
||||
};
|
||||
|
||||
let mut reader = quick_xml::Reader::from_str(&xml_raw);
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let mut current_tag: Option<String> = None;
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
outlk_ad.config_type = None;
|
||||
outlk_ad.config_server = String::new();
|
||||
outlk_ad.config_port = 0;
|
||||
outlk_ad.config_ssl = String::new();
|
||||
outlk_ad.config_redirecturl = None;
|
||||
|
||||
current_tag = None;
|
||||
} else {
|
||||
current_tag = Some(tag);
|
||||
}
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
|
||||
current_tag = None;
|
||||
}
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
let val = e.unescape_and_decode(&reader).unwrap_or_default();
|
||||
|
||||
if let Some(ref tag) = current_tag {
|
||||
match tag.as_str() {
|
||||
"type" => {
|
||||
outlk_ad.config_type = Some(val.trim().to_lowercase().to_string())
|
||||
}
|
||||
"server" => outlk_ad.config_server = val.trim().to_string(),
|
||||
"port" => outlk_ad.config_port = val.trim().parse().unwrap_or_default(),
|
||||
"ssl" => outlk_ad.config_ssl = val.trim().to_string(),
|
||||
"redirecturl" => outlk_ad.config_redirecturl = Some(val.trim().to_string()),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
bail!(
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
// XML redirect via redirecturl
|
||||
if outlk_ad.config_redirecturl.is_none()
|
||||
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
|
||||
{
|
||||
if outlk_ad.out.mail_server.is_empty()
|
||||
|| outlk_ad.out.mail_port == 0
|
||||
|| outlk_ad.out.send_server.is_empty()
|
||||
|| outlk_ad.out.send_port == 0
|
||||
{
|
||||
let r = outlk_ad.out.to_string();
|
||||
bail!("Bad or incomplete autoconfig: {}", r,);
|
||||
}
|
||||
Ok(ParsingResult::LoginParam(outlk_ad.out))
|
||||
} else {
|
||||
Ok(ParsingResult::RedirectUrl(
|
||||
outlk_ad.config_redirecturl.unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn outlk_autodiscover(
|
||||
context: &Context,
|
||||
url: &str,
|
||||
@@ -27,94 +122,16 @@ pub fn outlk_autodiscover(
|
||||
let mut url = url.to_string();
|
||||
/* Follow up to 10 xml-redirects (http-redirects are followed in read_autoconf_file() */
|
||||
for _i in 0..10 {
|
||||
let mut outlk_ad = OutlookAutodiscover {
|
||||
out: LoginParam::new(),
|
||||
out_imap_set: false,
|
||||
out_smtp_set: false,
|
||||
config_type: None,
|
||||
config_server: String::new(),
|
||||
config_port: 0,
|
||||
config_ssl: String::new(),
|
||||
config_redirecturl: None,
|
||||
};
|
||||
|
||||
if let Some(xml_raw) = read_autoconf_file(context, &url) {
|
||||
let mut reader = quick_xml::Reader::from_str(&xml_raw);
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let mut current_tag: Option<String> = None;
|
||||
|
||||
loop {
|
||||
match reader.read_event(&mut buf) {
|
||||
Ok(quick_xml::events::Event::Start(ref e)) => {
|
||||
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
|
||||
|
||||
if tag == "protocol" {
|
||||
outlk_ad.config_type = None;
|
||||
outlk_ad.config_server = String::new();
|
||||
outlk_ad.config_port = 0;
|
||||
outlk_ad.config_ssl = String::new();
|
||||
outlk_ad.config_redirecturl = None;
|
||||
|
||||
current_tag = None;
|
||||
} else {
|
||||
current_tag = Some(tag);
|
||||
}
|
||||
}
|
||||
Ok(quick_xml::events::Event::End(ref e)) => {
|
||||
outlk_autodiscover_endtag_cb(e, &mut outlk_ad);
|
||||
current_tag = None;
|
||||
}
|
||||
Ok(quick_xml::events::Event::Text(ref e)) => {
|
||||
let val = e.unescape_and_decode(&reader).unwrap_or_default();
|
||||
|
||||
if let Some(ref tag) = current_tag {
|
||||
match tag.as_str() {
|
||||
"type" => outlk_ad.config_type = Some(val.trim().to_string()),
|
||||
"server" => outlk_ad.config_server = val.trim().to_string(),
|
||||
"port" => {
|
||||
outlk_ad.config_port = val.trim().parse().unwrap_or_default()
|
||||
}
|
||||
"ssl" => outlk_ad.config_ssl = val.trim().to_string(),
|
||||
"redirecturl" => {
|
||||
outlk_ad.config_redirecturl = Some(val.trim().to_string())
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
context,
|
||||
"Configure xml: Error at position {}: {:?}",
|
||||
reader.buffer_position(),
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(quick_xml::events::Event::Eof) => break,
|
||||
_ => (),
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
// XML redirect via redirecturl
|
||||
if outlk_ad.config_redirecturl.is_none()
|
||||
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
|
||||
{
|
||||
if outlk_ad.out.mail_server.is_empty()
|
||||
|| outlk_ad.out.mail_port == 0
|
||||
|| outlk_ad.out.send_server.is_empty()
|
||||
|| outlk_ad.out.send_port == 0
|
||||
{
|
||||
let r = outlk_ad.out.to_string();
|
||||
warn!(context, "Bad or incomplete autoconfig: {}", r,);
|
||||
match outlk_parse_xml(&xml_raw) {
|
||||
Err(err) => {
|
||||
warn!(context, "{}", err);
|
||||
return None;
|
||||
}
|
||||
return Some(outlk_ad.out);
|
||||
} else {
|
||||
url = outlk_ad.config_redirecturl.unwrap();
|
||||
Ok(res) => match res {
|
||||
ParsingResult::RedirectUrl(redirect_url) => url = redirect_url,
|
||||
ParsingResult::LoginParam(login_param) => return Some(login_param),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
@@ -155,3 +172,78 @@ fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodisc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_redirect() {
|
||||
let res = outlk_parse_xml("
|
||||
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
|
||||
<Account>
|
||||
<AccountType>email</AccountType>
|
||||
<Action>redirectUrl</Action>
|
||||
<RedirectUrl>https://mail.example.com/autodiscover/autodiscover.xml</RedirectUrl>
|
||||
</Account>
|
||||
</Response>
|
||||
</Autodiscover>
|
||||
").expect("XML is not parsed successfully");
|
||||
match res {
|
||||
ParsingResult::LoginParam(_lp) => {
|
||||
panic!("redirecturl is not found");
|
||||
}
|
||||
ParsingResult::RedirectUrl(url) => {
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://mail.example.com/autodiscover/autodiscover.xml"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_loginparam() {
|
||||
let res = outlk_parse_xml(
|
||||
"\
|
||||
<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
||||
<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006\">
|
||||
<Response xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a\">
|
||||
<Account>
|
||||
<AccountType>email</AccountType>
|
||||
<Action>settings</Action>
|
||||
<Protocol>
|
||||
<Type>IMAP</Type>
|
||||
<Server>example.com</Server>
|
||||
<Port>993</Port>
|
||||
<SSL>on</SSL>
|
||||
<AuthRequired>on</AuthRequired>
|
||||
</Protocol>
|
||||
<Protocol>
|
||||
<Type>SMTP</Type>
|
||||
<Server>smtp.example.com</Server>
|
||||
<Port>25</Port>
|
||||
<SSL>off</SSL>
|
||||
<AuthRequired>on</AuthRequired>
|
||||
</Protocol>
|
||||
</Account>
|
||||
</Response>
|
||||
</Autodiscover>",
|
||||
)
|
||||
.expect("XML is not parsed successfully");
|
||||
|
||||
match res {
|
||||
ParsingResult::LoginParam(lp) => {
|
||||
assert_eq!(lp.mail_server, "example.com");
|
||||
assert_eq!(lp.mail_port, 993);
|
||||
assert_eq!(lp.send_server, "smtp.example.com");
|
||||
assert_eq!(lp.send_port, 25);
|
||||
}
|
||||
ParsingResult::RedirectUrl(_) => {
|
||||
panic!("RedirectUrl is not expected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
|
||||
dc_get_oauth2_addr(context, ¶m.addr, ¶m.mail_pw)
|
||||
.and_then(|e| e.parse().ok())
|
||||
{
|
||||
info!(context, "Authorized address is {}", oauth2_addr);
|
||||
param.addr = oauth2_addr;
|
||||
context
|
||||
.sql
|
||||
|
||||
@@ -150,7 +150,7 @@ impl Context {
|
||||
};
|
||||
|
||||
ensure!(
|
||||
ctx.sql.open(&ctx, &ctx.dbfile, 0),
|
||||
ctx.sql.open(&ctx, &ctx.dbfile, false),
|
||||
"Failed opening sqlite database"
|
||||
);
|
||||
|
||||
|
||||
@@ -607,12 +607,8 @@ impl<'a> MimeParser<'a> {
|
||||
|
||||
/* regard `Content-Transfer-Encoding:` */
|
||||
let mut desired_filename = String::default();
|
||||
let mut simplifier: Option<Simplify> = None;
|
||||
match mime_type {
|
||||
DC_MIMETYPE_TEXT_PLAIN | DC_MIMETYPE_TEXT_HTML => {
|
||||
if simplifier.is_none() {
|
||||
simplifier = Some(Simplify::new());
|
||||
}
|
||||
/* get from `Content-Type: text/...; charset=utf-8`; must not be free()'d */
|
||||
let charset = mailmime_content_charset_get((*mime).mm_content_type);
|
||||
if !charset.is_null()
|
||||
@@ -640,13 +636,14 @@ impl<'a> MimeParser<'a> {
|
||||
/* check header directly as is_send_by_messenger is not yet set up */
|
||||
let is_msgrmsg = self.lookup_optional_field("Chat-Version").is_some();
|
||||
|
||||
let mut simplifier = Simplify::new();
|
||||
let simplified_txt = if decoded_data.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
let input = std::string::String::from_utf8_lossy(&decoded_data);
|
||||
let is_html = mime_type == 70;
|
||||
|
||||
simplifier.unwrap().simplify(&input, is_html, is_msgrmsg)
|
||||
simplifier.simplify(&input, is_html, is_msgrmsg)
|
||||
};
|
||||
if !simplified_txt.is_empty() {
|
||||
let mut part = Part::default();
|
||||
@@ -658,7 +655,7 @@ impl<'a> MimeParser<'a> {
|
||||
self.do_add_single_part(part);
|
||||
}
|
||||
|
||||
if simplifier.unwrap().is_forwarded {
|
||||
if simplifier.is_forwarded {
|
||||
self.is_forwarded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::ptr;
|
||||
|
||||
use itertools::join;
|
||||
use libc::{free, strcmp};
|
||||
use libc::strcmp;
|
||||
use mmime::clist::*;
|
||||
use mmime::mailimf::types::*;
|
||||
use mmime::mailmime::content::*;
|
||||
@@ -319,13 +319,8 @@ unsafe fn add_parts(
|
||||
let mut chat_id_blocked = Blocked::Not;
|
||||
let mut sort_timestamp = 0;
|
||||
let mut rcvd_timestamp = 0;
|
||||
let mut mime_in_reply_to = std::ptr::null_mut();
|
||||
let mut mime_references = std::ptr::null_mut();
|
||||
|
||||
let cleanup = |mime_in_reply_to: *mut libc::c_char, mime_references: *mut libc::c_char| {
|
||||
free(mime_in_reply_to.cast());
|
||||
free(mime_references.cast());
|
||||
};
|
||||
let mut mime_in_reply_to = String::new();
|
||||
let mut mime_references = String::new();
|
||||
|
||||
// collect the rest information, CC: is added to the to-list, BCC: is ignored
|
||||
// (we should not add BCC to groups as this would split groups. We could add them as "known contacts",
|
||||
@@ -359,7 +354,6 @@ unsafe fn add_parts(
|
||||
message::update_server_uid(context, &rfc724_mid, server_folder.as_ref(), server_uid);
|
||||
}
|
||||
|
||||
cleanup(mime_in_reply_to, mime_references);
|
||||
bail!("Message already in DB");
|
||||
}
|
||||
|
||||
@@ -588,20 +582,14 @@ unsafe fn add_parts(
|
||||
if let Some(field) = mime_parser.lookup_field_typ("In-Reply-To", MAILIMF_FIELD_IN_REPLY_TO) {
|
||||
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
|
||||
if !fld_in_reply_to.is_null() {
|
||||
mime_in_reply_to = dc_str_from_clist(
|
||||
(*(*field).fld_data.fld_in_reply_to).mid_list,
|
||||
b" \x00" as *const u8 as *const libc::c_char,
|
||||
)
|
||||
mime_in_reply_to = dc_str_from_clist((*(*field).fld_data.fld_in_reply_to).mid_list, " ")
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(field) = mime_parser.lookup_field_typ("References", MAILIMF_FIELD_REFERENCES) {
|
||||
let fld_references = (*field).fld_data.fld_references;
|
||||
if !fld_references.is_null() {
|
||||
mime_references = dc_str_from_clist(
|
||||
(*(*field).fld_data.fld_references).mid_list,
|
||||
b" \x00" as *const u8 as *const libc::c_char,
|
||||
)
|
||||
mime_references = dc_str_from_clist((*(*field).fld_data.fld_references).mid_list, " ")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,97 +600,91 @@ unsafe fn add_parts(
|
||||
let icnt = mime_parser.parts.len();
|
||||
let mut txt_raw = None;
|
||||
|
||||
context
|
||||
.sql
|
||||
.prepare(
|
||||
"INSERT INTO msgs \
|
||||
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
|
||||
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
|
||||
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
|
||||
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|
||||
|mut stmt, conn| {
|
||||
for i in 0..icnt {
|
||||
let part = &mut mime_parser.parts[i];
|
||||
if part.is_meta {
|
||||
continue;
|
||||
}
|
||||
context.sql.prepare(
|
||||
"INSERT INTO msgs \
|
||||
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
|
||||
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
|
||||
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
|
||||
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|
||||
|mut stmt, conn| {
|
||||
for i in 0..icnt {
|
||||
let part = &mut mime_parser.parts[i];
|
||||
if part.is_meta {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(ref msg) = part.msg {
|
||||
if mime_parser.location_kml.is_some()
|
||||
&& icnt == 1
|
||||
&& (msg == "-location-" || msg.is_empty())
|
||||
{
|
||||
*hidden = 1;
|
||||
if state == MessageState::InFresh {
|
||||
state = MessageState::InNoticed;
|
||||
}
|
||||
if let Some(ref msg) = part.msg {
|
||||
if mime_parser.location_kml.is_some()
|
||||
&& icnt == 1
|
||||
&& (msg == "-location-" || msg.is_empty())
|
||||
{
|
||||
*hidden = 1;
|
||||
if state == MessageState::InFresh {
|
||||
state = MessageState::InNoticed;
|
||||
}
|
||||
}
|
||||
if part.typ == Viewtype::Text {
|
||||
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
|
||||
let subject = mime_parser
|
||||
.subject
|
||||
.as_ref()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or("".into());
|
||||
txt_raw = Some(format!("{}\n\n{}", subject, msg_raw));
|
||||
}
|
||||
if mime_parser.is_system_message != SystemMessage::Unknown {
|
||||
part.param
|
||||
.set_int(Param::Cmd, mime_parser.is_system_message as i32);
|
||||
}
|
||||
|
||||
/*
|
||||
info!(
|
||||
context,
|
||||
"received mime message {:?}",
|
||||
String::from_utf8_lossy(std::slice::from_raw_parts(
|
||||
imf_raw_not_terminated as *const u8,
|
||||
imf_raw_bytes,
|
||||
))
|
||||
);
|
||||
*/
|
||||
|
||||
stmt.execute(params![
|
||||
rfc724_mid,
|
||||
server_folder.as_ref(),
|
||||
server_uid as libc::c_int,
|
||||
*chat_id as libc::c_int,
|
||||
*from_id as libc::c_int,
|
||||
*to_id as libc::c_int,
|
||||
sort_timestamp,
|
||||
*sent_timestamp,
|
||||
rcvd_timestamp,
|
||||
part.typ,
|
||||
state,
|
||||
msgrmsg,
|
||||
part.msg.as_ref().map_or("", String::as_str),
|
||||
// txt_raw might contain invalid utf8
|
||||
txt_raw.unwrap_or_default(),
|
||||
part.param.to_string(),
|
||||
part.bytes,
|
||||
*hidden,
|
||||
if save_mime_headers {
|
||||
Some(String::from_utf8_lossy(imf_raw))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
to_string_lossy(mime_in_reply_to),
|
||||
to_string_lossy(mime_references),
|
||||
])?;
|
||||
|
||||
txt_raw = None;
|
||||
*insert_msg_id =
|
||||
sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid);
|
||||
created_db_entries.push((*chat_id as usize, *insert_msg_id as usize));
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.map_err(|err| {
|
||||
cleanup(mime_in_reply_to, mime_references);
|
||||
err
|
||||
})?;
|
||||
if part.typ == Viewtype::Text {
|
||||
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
|
||||
let subject = mime_parser
|
||||
.subject
|
||||
.as_ref()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or("".into());
|
||||
txt_raw = Some(format!("{}\n\n{}", subject, msg_raw));
|
||||
}
|
||||
if mime_parser.is_system_message != SystemMessage::Unknown {
|
||||
part.param
|
||||
.set_int(Param::Cmd, mime_parser.is_system_message as i32);
|
||||
}
|
||||
|
||||
/*
|
||||
info!(
|
||||
context,
|
||||
"received mime message {:?}",
|
||||
String::from_utf8_lossy(std::slice::from_raw_parts(
|
||||
imf_raw_not_terminated as *const u8,
|
||||
imf_raw_bytes,
|
||||
))
|
||||
);
|
||||
*/
|
||||
|
||||
stmt.execute(params![
|
||||
rfc724_mid,
|
||||
server_folder.as_ref(),
|
||||
server_uid as libc::c_int,
|
||||
*chat_id as libc::c_int,
|
||||
*from_id as libc::c_int,
|
||||
*to_id as libc::c_int,
|
||||
sort_timestamp,
|
||||
*sent_timestamp,
|
||||
rcvd_timestamp,
|
||||
part.typ,
|
||||
state,
|
||||
msgrmsg,
|
||||
part.msg.as_ref().map_or("", String::as_str),
|
||||
// txt_raw might contain invalid utf8
|
||||
txt_raw.unwrap_or_default(),
|
||||
part.param.to_string(),
|
||||
part.bytes,
|
||||
*hidden,
|
||||
if save_mime_headers {
|
||||
Some(String::from_utf8_lossy(imf_raw))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
mime_in_reply_to,
|
||||
mime_references,
|
||||
])?;
|
||||
|
||||
txt_raw = None;
|
||||
*insert_msg_id =
|
||||
sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid);
|
||||
created_db_entries.push((*chat_id as usize, *insert_msg_id as usize));
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
|
||||
info!(
|
||||
context,
|
||||
@@ -722,8 +704,6 @@ unsafe fn add_parts(
|
||||
}
|
||||
}
|
||||
|
||||
cleanup(mime_in_reply_to, mime_references);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -142,8 +142,8 @@ impl Simplify {
|
||||
ret += "[...]";
|
||||
}
|
||||
/* we write empty lines only in case and non-empty line follows */
|
||||
let mut pending_linebreaks: libc::c_int = 0i32;
|
||||
let mut content_lines_added: libc::c_int = 0i32;
|
||||
let mut pending_linebreaks = 0;
|
||||
let mut content_lines_added = 0;
|
||||
for l in l_first..l_last {
|
||||
let line = lines[l];
|
||||
if is_empty_line(line) {
|
||||
|
||||
227
src/dc_tools.rs
227
src/dc_tools.rs
@@ -51,75 +51,6 @@ pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
ret
|
||||
}
|
||||
|
||||
unsafe fn dc_ltrim(buf: *mut libc::c_char) {
|
||||
let mut len: libc::size_t;
|
||||
let mut cur: *const libc::c_uchar;
|
||||
if !buf.is_null() && 0 != *buf as libc::c_int {
|
||||
len = strlen(buf);
|
||||
cur = buf as *const libc::c_uchar;
|
||||
while 0 != *cur as libc::c_int && 0 != libc::isspace(*cur as libc::c_int) {
|
||||
cur = cur.offset(1isize);
|
||||
len = len.wrapping_sub(1)
|
||||
}
|
||||
if buf as *const libc::c_uchar != cur {
|
||||
libc::memmove(
|
||||
buf as *mut libc::c_void,
|
||||
cur as *const libc::c_void,
|
||||
len.wrapping_add(1),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unsafe fn dc_rtrim(buf: *mut libc::c_char) {
|
||||
let mut len: libc::size_t;
|
||||
let mut cur: *mut libc::c_uchar;
|
||||
if !buf.is_null() && 0 != *buf as libc::c_int {
|
||||
len = strlen(buf);
|
||||
cur = (buf as *mut libc::c_uchar)
|
||||
.offset(len as isize)
|
||||
.offset(-1isize);
|
||||
while cur != buf as *mut libc::c_uchar && 0 != libc::isspace(*cur as libc::c_int) {
|
||||
cur = cur.offset(-1isize);
|
||||
len = len.wrapping_sub(1)
|
||||
}
|
||||
*cur.offset(
|
||||
(if 0 != libc::isspace(*cur as libc::c_int) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}) as isize,
|
||||
) = '\u{0}' as i32 as libc::c_uchar
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn dc_trim(buf: *mut libc::c_char) {
|
||||
dc_ltrim(buf);
|
||||
dc_rtrim(buf);
|
||||
}
|
||||
|
||||
/* remove all \r characters from string */
|
||||
pub(crate) unsafe fn dc_remove_cr_chars(buf: *mut libc::c_char) {
|
||||
/* search for first `\r` */
|
||||
let mut p1: *const libc::c_char = buf;
|
||||
while 0 != *p1 {
|
||||
if *p1 as libc::c_int == '\r' as i32 {
|
||||
break;
|
||||
}
|
||||
p1 = p1.offset(1isize)
|
||||
}
|
||||
/* p1 is `\r` or null-byte; start removing `\r` */
|
||||
let mut p2: *mut libc::c_char = p1 as *mut libc::c_char;
|
||||
while 0 != *p1 {
|
||||
if *p1 as libc::c_int != '\r' as i32 {
|
||||
*p2 = *p1;
|
||||
p2 = p2.offset(1isize)
|
||||
}
|
||||
p1 = p1.offset(1isize)
|
||||
}
|
||||
*p2 = 0 as libc::c_char;
|
||||
}
|
||||
|
||||
/// Shortens a string to a specified length and adds "..." or "[...]" to the end of
|
||||
/// the shortened string.
|
||||
pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Cow<str> {
|
||||
@@ -143,36 +74,18 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize, do_unwrap: bool) -> Co
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn dc_str_from_clist(
|
||||
list: *const clist,
|
||||
delimiter: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
pub(crate) fn dc_str_from_clist(list: *const clist, delimiter: &str) -> String {
|
||||
let mut res = String::new();
|
||||
|
||||
if !list.is_null() {
|
||||
let mut cur: *mut clistiter = (*list).first;
|
||||
while !cur.is_null() {
|
||||
let rfc724_mid = (if !cur.is_null() {
|
||||
(*cur).data
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}) as *const libc::c_char;
|
||||
|
||||
if !rfc724_mid.is_null() {
|
||||
if !res.is_empty() && !delimiter.is_null() {
|
||||
res += as_str(delimiter);
|
||||
}
|
||||
res += as_str(rfc724_mid);
|
||||
}
|
||||
cur = if !cur.is_null() {
|
||||
(*cur).next
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
for rfc724_mid in unsafe { (*list).into_iter() } {
|
||||
if !res.is_empty() {
|
||||
res += delimiter;
|
||||
}
|
||||
res += as_str(rfc724_mid as *const libc::c_char);
|
||||
}
|
||||
}
|
||||
|
||||
res.strdup()
|
||||
res
|
||||
}
|
||||
|
||||
pub(crate) fn dc_str_to_clist(str: &str, delimiter: &str) -> *mut clist {
|
||||
@@ -593,6 +506,25 @@ pub fn dc_read_file<P: AsRef<std::path::Path>>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_open_file<P: AsRef<std::path::Path>>(
|
||||
context: &Context,
|
||||
path: P,
|
||||
) -> Result<std::fs::File, Error> {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
|
||||
match fs::File::open(&path_abs) {
|
||||
Ok(bytes) => Ok(bytes),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
"Cannot read \"{}\" or file is empty.",
|
||||
path.as_ref().display()
|
||||
);
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dc_get_next_backup_path(
|
||||
folder: impl AsRef<Path>,
|
||||
backup_time: i64,
|
||||
@@ -803,6 +735,14 @@ pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||
cstr.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
||||
if s.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(to_string_lossy(s))
|
||||
}
|
||||
|
||||
pub fn as_str<'a>(s: *const libc::c_char) -> &'a str {
|
||||
as_str_safe(s).unwrap_or_else(|err| panic!("{}", err))
|
||||
}
|
||||
@@ -1005,54 +945,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_ltrim() {
|
||||
unsafe {
|
||||
let html: *const libc::c_char =
|
||||
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
|
||||
let out: *mut libc::c_char = strndup(html, strlen(html) as libc::c_ulong);
|
||||
|
||||
dc_ltrim(out);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(out as *const libc::c_char).to_str().unwrap(),
|
||||
"line1<br>\r\n\r\n\r\rline2\n\r"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_rtrim() {
|
||||
unsafe {
|
||||
let html: *const libc::c_char =
|
||||
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
|
||||
let out: *mut libc::c_char = strndup(html, strlen(html) as libc::c_ulong);
|
||||
|
||||
dc_rtrim(out);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(out as *const libc::c_char).to_str().unwrap(),
|
||||
"\r\r\nline1<br>\r\n\r\n\r\rline2"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_trim() {
|
||||
unsafe {
|
||||
let html: *const libc::c_char =
|
||||
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
|
||||
let out: *mut libc::c_char = strndup(html, strlen(html) as libc::c_ulong);
|
||||
|
||||
dc_trim(out);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(out as *const libc::c_char).to_str().unwrap(),
|
||||
"line1<br>\r\n\r\n\r\rline2"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rust_ftoa() {
|
||||
assert_eq!("1.22", format!("{}", 1.22));
|
||||
@@ -1128,21 +1020,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn strndup(s: *const libc::c_char, n: libc::c_ulong) -> *mut libc::c_char {
|
||||
if s.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let end = std::cmp::min(n as usize, unsafe { strlen(s) });
|
||||
unsafe {
|
||||
let result = libc::malloc(end + 1);
|
||||
memcpy(result, s as *const _, end);
|
||||
std::ptr::write_bytes(result.offset(end as isize), b'\x00', 1);
|
||||
|
||||
result as *mut _
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_str_to_clist_1() {
|
||||
unsafe {
|
||||
@@ -1158,17 +1035,11 @@ mod tests {
|
||||
unsafe {
|
||||
let list: *mut clist = dc_str_to_clist("foo bar test", " ");
|
||||
assert_eq!((*list).count, 3);
|
||||
let str: *mut libc::c_char =
|
||||
dc_str_from_clist(list, b" \x00" as *const u8 as *const libc::c_char);
|
||||
|
||||
assert_eq!(
|
||||
CStr::from_ptr(str as *const libc::c_char).to_str().unwrap(),
|
||||
"foo bar test"
|
||||
);
|
||||
let str = dc_str_from_clist(list, " ");
|
||||
assert_eq!(str, "foo bar test");
|
||||
|
||||
clist_free_content(list);
|
||||
clist_free(list);
|
||||
free(str as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1427,19 +1298,6 @@ mod tests {
|
||||
assert_eq!(foo, format!("$BLOBDIR{}foo", std::path::MAIN_SEPARATOR));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strndup() {
|
||||
unsafe {
|
||||
let res = strndup(b"helloworld\x00" as *const u8 as *const libc::c_char, 4);
|
||||
assert_eq!(
|
||||
to_string_lossy(res),
|
||||
to_string_lossy(b"hell\x00" as *const u8 as *const libc::c_char)
|
||||
);
|
||||
assert_eq!(strlen(res), 4);
|
||||
free(res as *mut _);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_get_safe_basename() {
|
||||
assert_eq!(get_safe_basename("12312/hello"), "hello");
|
||||
@@ -1521,19 +1379,4 @@ mod tests {
|
||||
let listflags: u32 = DC_GCL_VERIFIED_ONLY.try_into().unwrap();
|
||||
assert!(listflags_has(listflags, DC_GCL_ADD_SELF) == false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_remove_cr_chars() {
|
||||
unsafe {
|
||||
let input = "foo\r\nbar".strdup();
|
||||
dc_remove_cr_chars(input);
|
||||
assert_eq!("foo\nbar", to_string_lossy(input));
|
||||
free(input.cast());
|
||||
|
||||
let input = "\rfoo\r\rbar\r".strdup();
|
||||
dc_remove_cr_chars(input);
|
||||
assert_eq!("foobar", to_string_lossy(input));
|
||||
free(input.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::key::*;
|
||||
use crate::keyring::*;
|
||||
use crate::mimefactory::MimeFactory;
|
||||
use crate::peerstate::*;
|
||||
use crate::pgp::*;
|
||||
use crate::pgp;
|
||||
use crate::securejoin::handle_degrade_event;
|
||||
use crate::wrapmime;
|
||||
use crate::wrapmime::*;
|
||||
@@ -211,7 +211,7 @@ impl EncryptHelper {
|
||||
"could not write/allocate"
|
||||
);
|
||||
|
||||
let ctext = dc_pgp_pk_encrypt(
|
||||
let ctext = pgp::pk_encrypt(
|
||||
std::slice::from_raw_parts((*plain).str_0 as *const u8, (*plain).len),
|
||||
&keyring,
|
||||
sign_key.as_ref(),
|
||||
@@ -398,7 +398,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
|
||||
context,
|
||||
"Generating keypair with {} bits, e={} ...", 2048, 65537,
|
||||
);
|
||||
match dc_pgp_create_keypair(&self_addr) {
|
||||
match pgp::create_keypair(&self_addr) {
|
||||
Some((public_key, private_key)) => {
|
||||
match dc_key_save_self_keypair(
|
||||
context,
|
||||
@@ -581,7 +581,7 @@ fn decrypt_part(
|
||||
// we should only have one decryption happening
|
||||
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
|
||||
|
||||
let plain = match dc_pgp_pk_decrypt(
|
||||
let plain = match pgp::pk_decrypt(
|
||||
&data,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
|
||||
12
src/error.rs
12
src/error.rs
@@ -70,12 +70,6 @@ impl From<std::str::Utf8Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for Error {
|
||||
fn from(err: std::string::FromUtf8Error) -> Error {
|
||||
Error::FromUtf8(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<image_meta::ImageError> for Error {
|
||||
fn from(err: image_meta::ImageError) -> Error {
|
||||
Error::Image(err)
|
||||
@@ -94,6 +88,12 @@ impl From<pgp::errors::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for Error {
|
||||
fn from(err: std::string::FromUtf8Error) -> Error {
|
||||
Error::FromUtf8(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
($e:expr) => {
|
||||
|
||||
16
src/imap.rs
16
src/imap.rs
@@ -116,8 +116,8 @@ impl Client {
|
||||
let s = stream.try_clone().expect("cloning the stream failed");
|
||||
let tls_stream = native_tls::TlsConnector::connect(&tls, domain.as_ref(), s)?;
|
||||
|
||||
let client = imap::Client::new(tls_stream);
|
||||
// TODO: Read greeting
|
||||
let mut client = imap::Client::new(tls_stream);
|
||||
client.read_greeting()?;
|
||||
|
||||
Ok(Client::Secure(client, stream))
|
||||
}
|
||||
@@ -125,8 +125,8 @@ impl Client {
|
||||
pub fn connect_insecure<A: net::ToSocketAddrs>(addr: A) -> imap::error::Result<Self> {
|
||||
let stream = net::TcpStream::connect(addr)?;
|
||||
|
||||
let client = imap::Client::new(stream.try_clone().unwrap());
|
||||
// TODO: Read greeting
|
||||
let mut client = imap::Client::new(stream.try_clone().unwrap());
|
||||
client.read_greeting()?;
|
||||
|
||||
Ok(Client::Insecure(client, stream))
|
||||
}
|
||||
@@ -598,9 +598,9 @@ impl Imap {
|
||||
self.config.write().unwrap().watch_folder = Some(watch_folder);
|
||||
}
|
||||
|
||||
pub fn fetch(&self, context: &Context) -> libc::c_int {
|
||||
pub fn fetch(&self, context: &Context) -> bool {
|
||||
if !self.is_connected() || !context.sql.is_open() {
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
self.setup_handle_if_needed(context);
|
||||
@@ -616,9 +616,9 @@ impl Imap {
|
||||
break;
|
||||
}
|
||||
}
|
||||
1
|
||||
true
|
||||
} else {
|
||||
0
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
184
src/imex.rs
184
src/imex.rs
@@ -1,7 +1,5 @@
|
||||
use core::cmp::{max, min};
|
||||
use std::ffi::CString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr;
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
use rand::{thread_rng, Rng};
|
||||
@@ -19,7 +17,7 @@ use crate::job::*;
|
||||
use crate::key::*;
|
||||
use crate::message::Message;
|
||||
use crate::param::*;
|
||||
use crate::pgp::*;
|
||||
use crate::pgp;
|
||||
use crate::sql::{self, Sql};
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
@@ -89,7 +87,7 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
|
||||
let name = name.to_string_lossy();
|
||||
if name.starts_with("delta-chat") && name.ends_with(".bak") {
|
||||
let sql = Sql::new();
|
||||
if sql.open(context, &path, 0x1) {
|
||||
if sql.open(context, &path, true) {
|
||||
let curr_backup_time =
|
||||
sql.get_raw_config_int(context, "backup_time")
|
||||
.unwrap_or_default() as u64;
|
||||
@@ -171,7 +169,7 @@ pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String>
|
||||
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||
};
|
||||
let private_key_asc = private_key.to_asc(ac_headers);
|
||||
let encr = dc_pgp_symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
|
||||
let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
|
||||
|
||||
let replacement = format!(
|
||||
concat!(
|
||||
@@ -241,18 +239,12 @@ pub fn continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str) -
|
||||
);
|
||||
|
||||
if let Some(filename) = msg.get_file(context) {
|
||||
if let Ok(ref mut buf) = dc_read_file(context, filename) {
|
||||
let sc = normalize_setup_code(setup_code);
|
||||
if let Ok(armored_key) = decrypt_setup_file(context, sc, buf) {
|
||||
set_self_key(context, &armored_key, true, true)?;
|
||||
} else {
|
||||
bail!("Bad setup code.")
|
||||
}
|
||||
let file = dc_open_file(context, filename)?;
|
||||
let sc = normalize_setup_code(setup_code);
|
||||
let armored_key = decrypt_setup_file(context, &sc, file)?;
|
||||
set_self_key(context, &armored_key, true, true)?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("Cannot read Autocrypt Setup Message file.");
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("Message is no Autocrypt Setup Message.");
|
||||
}
|
||||
@@ -326,53 +318,15 @@ fn set_self_key(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt_setup_file(
|
||||
fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
|
||||
_context: &Context,
|
||||
passphrase: impl AsRef<str>,
|
||||
filecontent: &mut [u8],
|
||||
passphrase: &str,
|
||||
file: T,
|
||||
) -> Result<String> {
|
||||
let mut fc_headerline = String::default();
|
||||
let mut fc_base64: *const libc::c_char = ptr::null();
|
||||
let plain_bytes = pgp::symm_decrypt(passphrase, file)?;
|
||||
let plain_text = std::string::String::from_utf8(plain_bytes)?;
|
||||
|
||||
let split_result = unsafe {
|
||||
dc_split_armored_data(
|
||||
filecontent.as_mut_ptr().cast(),
|
||||
&mut fc_headerline,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
&mut fc_base64,
|
||||
)
|
||||
};
|
||||
|
||||
if !split_result || fc_headerline != "-----BEGIN PGP MESSAGE-----" || fc_base64.is_null() {
|
||||
bail!("Invalid armored data");
|
||||
}
|
||||
|
||||
// convert base64 to binary
|
||||
let base64_encoded =
|
||||
unsafe { std::slice::from_raw_parts(fc_base64 as *const u8, libc::strlen(fc_base64)) };
|
||||
|
||||
let data = base64_decode(&base64_encoded)?;
|
||||
|
||||
// decrypt symmetrically
|
||||
let payload = dc_pgp_symm_decrypt(passphrase.as_ref(), &data)?;
|
||||
let payload_str = String::from_utf8(payload)?;
|
||||
|
||||
Ok(payload_str)
|
||||
}
|
||||
|
||||
/// Decode the base64 encoded slice. Handles line breaks.
|
||||
fn base64_decode(input: &[u8]) -> Result<Vec<u8>> {
|
||||
use std::io::Read;
|
||||
let c = std::io::Cursor::new(input);
|
||||
let lr = pgp::line_reader::LineReader::new(c);
|
||||
let br = pgp::base64_reader::Base64Reader::new(lr);
|
||||
let mut reader = pgp::base64_decoder::Base64Decoder::new(br);
|
||||
|
||||
let mut data = Vec::new();
|
||||
reader.read_to_end(&mut data)?;
|
||||
|
||||
Ok(data)
|
||||
Ok(plain_text)
|
||||
}
|
||||
|
||||
pub fn normalize_setup_code(s: &str) -> String {
|
||||
@@ -459,7 +413,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
|
||||
/* error already logged */
|
||||
/* re-open copied database file */
|
||||
ensure!(
|
||||
context.sql.open(&context, &context.get_dbfile(), 0),
|
||||
context.sql.open(&context, &context.get_dbfile(), false),
|
||||
"could not re-open db"
|
||||
);
|
||||
|
||||
@@ -542,7 +496,7 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
dest_path_filename.display(),
|
||||
);
|
||||
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename);
|
||||
context.sql.open(&context, &context.get_dbfile(), 0);
|
||||
context.sql.open(&context, &context.get_dbfile(), false);
|
||||
if !copied {
|
||||
let s = dest_path_filename.to_string_lossy().to_string();
|
||||
bail!(
|
||||
@@ -572,7 +526,7 @@ fn add_files_to_export(context: &Context, dest_path_filename: &PathBuf) -> Resul
|
||||
// the source to be locked, neigher the destination as it is used only here)
|
||||
let sql = Sql::new();
|
||||
ensure!(
|
||||
sql.open(context, &dest_path_filename, 0),
|
||||
sql.open(context, &dest_path_filename, false),
|
||||
"could not open db"
|
||||
);
|
||||
if !sql.table_exists("backup_blobs") {
|
||||
@@ -638,7 +592,6 @@ fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
Maybe we should make the "default" key handlong also a little bit smarter
|
||||
(currently, the last imported key is the standard key unless it contains the string "legacy" in its name) */
|
||||
let mut set_default: bool;
|
||||
let mut key: String;
|
||||
let mut imported_cnt = 0;
|
||||
|
||||
let dir_name = dir.as_ref().to_string_lossy();
|
||||
@@ -663,40 +616,17 @@ fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let ccontent = if let Ok(content) = dc_read_file(context, &path_plus_name) {
|
||||
key = String::from_utf8_lossy(&content).to_string();
|
||||
CString::new(content).unwrap_or_default()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
/* only import if we have a private key */
|
||||
let mut buf2_headerline = String::default();
|
||||
let split_res: bool;
|
||||
unsafe {
|
||||
let buf2 = dc_strdup(ccontent.as_ptr());
|
||||
split_res = dc_split_armored_data(
|
||||
buf2,
|
||||
&mut buf2_headerline,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
libc::free(buf2 as *mut libc::c_void);
|
||||
match dc_read_file(context, &path_plus_name) {
|
||||
Ok(buf) => {
|
||||
let armored = std::string::String::from_utf8_lossy(&buf);
|
||||
if let Err(err) = set_self_key(context, &armored, set_default, false) {
|
||||
error!(context, "set_self_key: {}", err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(_) => continue,
|
||||
}
|
||||
if split_res
|
||||
&& buf2_headerline.contains("-----BEGIN PGP PUBLIC KEY BLOCK-----")
|
||||
&& !key.contains("-----BEGIN PGP PRIVATE KEY BLOCK")
|
||||
{
|
||||
info!(context, "ignoring public key file '{}", name_f);
|
||||
// it's fine: DC exports public with private
|
||||
continue;
|
||||
}
|
||||
if let Err(err) = set_self_key(context, &key, set_default, false) {
|
||||
error!(context, "set_self_key: {}", err);
|
||||
continue;
|
||||
}
|
||||
imported_cnt += 1
|
||||
imported_cnt += 1;
|
||||
}
|
||||
ensure!(
|
||||
imported_cnt > 0,
|
||||
@@ -783,7 +713,9 @@ fn export_key_to_asc_file(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
|
||||
use crate::test_utils::*;
|
||||
use ::pgp::armor::BlockType;
|
||||
|
||||
#[test]
|
||||
fn test_render_setup_file() {
|
||||
@@ -866,50 +798,26 @@ mod tests {
|
||||
let ctx = dummy_context();
|
||||
let context = &ctx.ctx;
|
||||
|
||||
let mut headerline = String::default();
|
||||
let mut setupcodebegin = ptr::null();
|
||||
let mut preferencrypt = ptr::null();
|
||||
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
|
||||
let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
|
||||
assert_eq!(typ, BlockType::Message);
|
||||
assert!(S_EM_SETUPCODE.starts_with(headers.get(HEADER_SETUPCODE).unwrap()));
|
||||
assert!(headers.get(HEADER_AUTOCRYPT).is_none());
|
||||
|
||||
unsafe {
|
||||
let buf_1 = S_EM_SETUPFILE.strdup();
|
||||
let res = dc_split_armored_data(
|
||||
buf_1,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
&mut preferencrypt,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
libc::free(buf_1 as *mut libc::c_void);
|
||||
assert!(res);
|
||||
}
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
assert!(!setupcodebegin.is_null());
|
||||
assert!(!base64.is_empty());
|
||||
|
||||
// TODO: verify that this is the right check
|
||||
assert!(S_EM_SETUPCODE.starts_with(as_str(setupcodebegin)));
|
||||
let setup_file = S_EM_SETUPFILE.to_string();
|
||||
let decrypted = decrypt_setup_file(
|
||||
context,
|
||||
S_EM_SETUPCODE,
|
||||
std::io::Cursor::new(setup_file.as_bytes()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(preferencrypt.is_null());
|
||||
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
|
||||
|
||||
let mut setup_file = S_EM_SETUPFILE.to_string();
|
||||
let decrypted = unsafe {
|
||||
decrypt_setup_file(context, S_EM_SETUPCODE, setup_file.as_bytes_mut()).unwrap()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let buf1 = decrypted.strdup();
|
||||
assert!(dc_split_armored_data(
|
||||
buf1,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
&mut preferencrypt,
|
||||
ptr::null_mut(),
|
||||
));
|
||||
libc::free(buf1 as *mut libc::c_void);
|
||||
}
|
||||
|
||||
assert_eq!(headerline, "-----BEGIN PGP PRIVATE KEY BLOCK-----");
|
||||
assert!(setupcodebegin.is_null());
|
||||
assert!(!preferencrypt.is_null());
|
||||
assert_eq!(as_str(preferencrypt), "mutual",);
|
||||
assert_eq!(typ, BlockType::PrivateKey);
|
||||
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
|
||||
assert!(headers.get(HEADER_SETUPCODE).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ impl Job {
|
||||
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
|
||||
|
||||
if !connected {
|
||||
self.try_again_later(3i32, None);
|
||||
self.try_again_later(3, None);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,7 @@ impl Job {
|
||||
Err(err) => {
|
||||
sock.disconnect();
|
||||
warn!(context, "smtp failed: {}", err);
|
||||
self.try_again_later(-1i32, Some(err.to_string()));
|
||||
self.try_again_later(-1, Some(err.to_string()));
|
||||
}
|
||||
Ok(()) => {
|
||||
// smtp success, update db ASAP, then delete smtp file
|
||||
@@ -207,7 +207,7 @@ impl Job {
|
||||
}
|
||||
|
||||
// this value does not increase the number of tries
|
||||
fn try_again_later(&mut self, try_again: libc::c_int, pending_error: Option<String>) {
|
||||
fn try_again_later(&mut self, try_again: i32, pending_error: Option<String>) {
|
||||
self.try_again = try_again;
|
||||
self.pending_error = pending_error;
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
#[test]
|
||||
#[ignore] // is too expensive
|
||||
fn test_from_slice_roundtrip() {
|
||||
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
|
||||
let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap();
|
||||
|
||||
let binary = public_key.to_bytes();
|
||||
let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key");
|
||||
@@ -416,7 +416,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
#[test]
|
||||
#[ignore] // is too expensive
|
||||
fn test_ascii_roundtrip() {
|
||||
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
|
||||
let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap();
|
||||
|
||||
let s = public_key.to_armored_string(None).unwrap();
|
||||
let (public_key2, _) =
|
||||
|
||||
@@ -197,7 +197,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||
let now = time();
|
||||
let mut msg: Message;
|
||||
let is_sending_locations_before: bool;
|
||||
if !(seconds < 0 || chat_id <= 9i32 as libc::c_uint) {
|
||||
if !(seconds < 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL) {
|
||||
is_sending_locations_before = is_sending_locations_to_chat(context, chat_id);
|
||||
if sql::execute(
|
||||
context,
|
||||
@@ -257,9 +257,9 @@ pub fn is_sending_locations_to_chat(context: &Context, chat_id: u32) -> bool {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> libc::c_int {
|
||||
pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool {
|
||||
if latitude == 0.0 && longitude == 0.0 {
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
let mut continue_streaming = false;
|
||||
|
||||
@@ -293,7 +293,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> l
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0);
|
||||
}
|
||||
|
||||
continue_streaming as libc::c_int
|
||||
continue_streaming
|
||||
}
|
||||
|
||||
pub fn get_range(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
|
||||
@@ -107,6 +106,7 @@ impl Message {
|
||||
msg.hidden = row.get(18)?;
|
||||
msg.location_id = row.get(19)?;
|
||||
msg.chat_blocked = row.get::<_, Option<Blocked>>(20)?.unwrap_or_default();
|
||||
|
||||
Ok(msg)
|
||||
})
|
||||
}
|
||||
@@ -339,23 +339,10 @@ impl Message {
|
||||
}
|
||||
|
||||
if let Some(filename) = self.get_file(context) {
|
||||
if let Ok(mut buf) = dc_read_file(context, filename) {
|
||||
unsafe {
|
||||
// just a pointer inside buf, MUST NOT be free()'d
|
||||
let mut buf_headerline = String::default();
|
||||
// just a pointer inside buf, MUST NOT be free()'d
|
||||
let mut buf_setupcodebegin = ptr::null();
|
||||
|
||||
if dc_split_armored_data(
|
||||
buf.as_mut_ptr().cast(),
|
||||
&mut buf_headerline,
|
||||
&mut buf_setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
) && buf_headerline == "-----BEGIN PGP MESSAGE-----"
|
||||
&& !buf_setupcodebegin.is_null()
|
||||
{
|
||||
return Some(to_string_lossy(buf_setupcodebegin));
|
||||
if let Ok(ref buf) = dc_read_file(context, filename) {
|
||||
if let Ok((typ, headers, _)) = split_armored_data(buf) {
|
||||
if typ == pgp::armor::BlockType::Message {
|
||||
return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
|
||||
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
||||
// see https://developers.google.com/identity/protocols/OAuth2InstalledApp
|
||||
client_id: "959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com",
|
||||
get_code: "https://accounts.google.com/o/oauth2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline",
|
||||
init_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&code=$CODE&grant_type=authorization_code",
|
||||
@@ -15,6 +16,7 @@ const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
||||
};
|
||||
|
||||
const OAUTH2_YANDEX: Oauth2 = Oauth2 {
|
||||
// see https://tech.yandex.com/oauth/doc/dg/reference/auto-code-client-docpage/
|
||||
client_id: "c4d0b6735fc8420a816d7e1303469341",
|
||||
get_code: "https://oauth.yandex.com/authorize?client_id=$CLIENT_ID&response_type=code&scope=mail%3Aimap_full%20mail%3Asmtp&force_confirm=true",
|
||||
init_token: "https://oauth.yandex.com/token?grant_type=authorization_code&code=$CODE&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf",
|
||||
@@ -89,6 +91,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
}
|
||||
}
|
||||
|
||||
// generate new token: build & call auth url
|
||||
let refresh_token = context.sql.get_raw_config(context, "oauth2_refresh_token");
|
||||
let refresh_token_for = context
|
||||
.sql
|
||||
@@ -120,14 +123,37 @@ pub fn dc_get_oauth2_access_token(
|
||||
false,
|
||||
)
|
||||
};
|
||||
let mut token_url = replace_in_uri(&token_url, "$CLIENT_ID", oauth2.client_id);
|
||||
token_url = replace_in_uri(&token_url, "$REDIRECT_URI", &redirect_uri);
|
||||
token_url = replace_in_uri(&token_url, "$CODE", code.as_ref());
|
||||
if let Some(ref token) = refresh_token {
|
||||
token_url = replace_in_uri(&token_url, "$REFRESH_TOKEN", token);
|
||||
|
||||
// to allow easier specification of different configurations,
|
||||
// token_url is in GET-method-format, sth. as https://domain?param1=val1¶m2=val2 -
|
||||
// convert this to POST-format ...
|
||||
let mut parts = token_url.splitn(2, '?');
|
||||
let post_url = parts.next().unwrap_or_default();
|
||||
let post_args = parts.next().unwrap_or_default();
|
||||
let mut post_param = HashMap::new();
|
||||
for key_value_pair in post_args.split('&') {
|
||||
let mut parts = key_value_pair.splitn(2, '=');
|
||||
let key = parts.next().unwrap_or_default();
|
||||
let mut value = parts.next().unwrap_or_default();
|
||||
|
||||
if value == "$CLIENT_ID" {
|
||||
value = oauth2.client_id;
|
||||
} else if value == "$REDIRECT_URI" {
|
||||
value = &redirect_uri;
|
||||
} else if value == "$CODE" {
|
||||
value = code.as_ref();
|
||||
} else if value == "$REFRESH_TOKEN" && refresh_token.is_some() {
|
||||
value = refresh_token.as_ref().unwrap();
|
||||
}
|
||||
|
||||
post_param.insert(key, value);
|
||||
}
|
||||
|
||||
let response = reqwest::Client::new().post(&token_url).send();
|
||||
// ... and POST
|
||||
let response = reqwest::Client::new()
|
||||
.post(post_url)
|
||||
.form(&post_param)
|
||||
.send();
|
||||
if response.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
@@ -139,13 +165,14 @@ pub fn dc_get_oauth2_access_token(
|
||||
if !response.status().is_success() {
|
||||
warn!(
|
||||
context,
|
||||
"Error calling OAuth2 at {}: {:?}",
|
||||
"Unsuccessful response when calling OAuth2 at {}: {:?}",
|
||||
token_url,
|
||||
response.status()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
// generate new token: parse returned json
|
||||
let parsed: reqwest::Result<Response> = response.json();
|
||||
if parsed.is_err() {
|
||||
warn!(
|
||||
@@ -155,6 +182,8 @@ pub fn dc_get_oauth2_access_token(
|
||||
return None;
|
||||
}
|
||||
println!("response: {:?}", &parsed);
|
||||
|
||||
// update refresh_token if given, typically on the first round, but we update it later as well.
|
||||
let response = parsed.unwrap();
|
||||
if let Some(ref token) = response.refresh_token {
|
||||
context
|
||||
@@ -268,7 +297,7 @@ impl Oauth2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let parsed: reqwest::Result<HashMap<String, String>> = response.json();
|
||||
let parsed: reqwest::Result<HashMap<String, serde_json::Value>> = response.json();
|
||||
if parsed.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
@@ -277,11 +306,13 @@ impl Oauth2 {
|
||||
return None;
|
||||
}
|
||||
if let Ok(response) = parsed {
|
||||
// serde_json::Value.as_str() removes the quotes of json-strings
|
||||
let addr = response.get("email");
|
||||
if addr.is_none() {
|
||||
warn!(context, "E-mail missing in userinfo.");
|
||||
return None;
|
||||
}
|
||||
|
||||
let addr = addr.unwrap().as_str();
|
||||
addr.map(|addr| addr.to_string())
|
||||
} else {
|
||||
warn!(context, "Failed to parse userinfo.");
|
||||
|
||||
191
src/pgp.rs
191
src/pgp.rs
@@ -1,9 +1,8 @@
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::convert::TryInto;
|
||||
use std::io::Cursor;
|
||||
use std::ptr;
|
||||
|
||||
use libc::{strchr, strlen, strncmp, strspn, strstr};
|
||||
use pgp::armor::BlockType;
|
||||
use pgp::composed::{
|
||||
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
|
||||
SignedSecretKey, SubkeyParamsBuilder,
|
||||
@@ -12,126 +11,43 @@ use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
|
||||
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
|
||||
use rand::thread_rng;
|
||||
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::key::*;
|
||||
use crate::keyring::*;
|
||||
|
||||
pub unsafe fn dc_split_armored_data(
|
||||
buf: *mut libc::c_char,
|
||||
ret_headerline: *mut String,
|
||||
ret_setupcodebegin: *mut *const libc::c_char,
|
||||
ret_preferencrypt: *mut *const libc::c_char,
|
||||
ret_base64: *mut *const libc::c_char,
|
||||
) -> bool {
|
||||
let mut success = false;
|
||||
let mut line_chars: libc::size_t = 0;
|
||||
let mut line: *mut libc::c_char = buf;
|
||||
let mut p1: *mut libc::c_char = buf;
|
||||
let mut p2: *mut libc::c_char;
|
||||
let mut headerline: *mut libc::c_char = ptr::null_mut();
|
||||
let mut base64: *mut libc::c_char = ptr::null_mut();
|
||||
if !ret_setupcodebegin.is_null() {
|
||||
*ret_setupcodebegin = ptr::null_mut();
|
||||
}
|
||||
if !ret_preferencrypt.is_null() {
|
||||
*ret_preferencrypt = ptr::null();
|
||||
}
|
||||
if !ret_base64.is_null() {
|
||||
*ret_base64 = ptr::null();
|
||||
}
|
||||
if !buf.is_null() {
|
||||
dc_remove_cr_chars(buf);
|
||||
while 0 != *p1 {
|
||||
if i32::from(*p1) == '\n' as i32 {
|
||||
*line.offset(line_chars as isize) = 0i32 as libc::c_char;
|
||||
if headerline.is_null() {
|
||||
dc_trim(line);
|
||||
if strncmp(
|
||||
line,
|
||||
b"-----BEGIN \x00" as *const u8 as *const libc::c_char,
|
||||
1,
|
||||
) == 0i32
|
||||
&& strncmp(
|
||||
&mut *line.offset(strlen(line).wrapping_sub(5) as isize),
|
||||
b"-----\x00" as *const u8 as *const libc::c_char,
|
||||
5,
|
||||
) == 0i32
|
||||
{
|
||||
headerline = line;
|
||||
*ret_headerline = as_str(headerline).to_string();
|
||||
}
|
||||
} else if strspn(line, b"\t\r\n \x00" as *const u8 as *const libc::c_char)
|
||||
== strlen(line)
|
||||
{
|
||||
base64 = p1.offset(1isize);
|
||||
break;
|
||||
} else {
|
||||
p2 = strchr(line, ':' as i32);
|
||||
if p2.is_null() {
|
||||
*line.add(line_chars) = '\n' as i32 as libc::c_char;
|
||||
base64 = line;
|
||||
break;
|
||||
} else {
|
||||
*p2 = 0i32 as libc::c_char;
|
||||
dc_trim(line);
|
||||
if strcasecmp(
|
||||
line,
|
||||
b"Passphrase-Begin\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
p2 = p2.offset(1isize);
|
||||
dc_trim(p2);
|
||||
if !ret_setupcodebegin.is_null() {
|
||||
*ret_setupcodebegin = p2
|
||||
}
|
||||
} else if strcasecmp(
|
||||
line,
|
||||
b"Autocrypt-Prefer-Encrypt\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
p2 = p2.offset(1isize);
|
||||
dc_trim(p2);
|
||||
if !ret_preferencrypt.is_null() {
|
||||
*ret_preferencrypt = p2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
p1 = p1.offset(1isize);
|
||||
line = p1;
|
||||
line_chars = 0;
|
||||
} else {
|
||||
p1 = p1.offset(1isize);
|
||||
line_chars = line_chars.wrapping_add(1)
|
||||
}
|
||||
}
|
||||
if !(headerline.is_null() || base64.is_null()) {
|
||||
/* now, line points to beginning of base64 data, search end */
|
||||
/*the trailing space makes sure, this is not a normal base64 sequence*/
|
||||
p1 = strstr(base64, b"-----END \x00" as *const u8 as *const libc::c_char);
|
||||
if !(p1.is_null()
|
||||
|| strncmp(
|
||||
p1.offset(9isize),
|
||||
headerline.offset(11isize),
|
||||
strlen(headerline.offset(11isize)),
|
||||
) != 0i32)
|
||||
{
|
||||
*p1 = 0i32 as libc::c_char;
|
||||
dc_trim(base64);
|
||||
if !ret_base64.is_null() {
|
||||
*ret_base64 = base64
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
|
||||
pub const HEADER_SETUPCODE: &str = "passphrase-begin";
|
||||
|
||||
success
|
||||
/// Split data from PGP Armored Data as defined in https://tools.ietf.org/html/rfc4880#section-6.2.
|
||||
///
|
||||
/// Returns (type, headers, base64 encoded body).
|
||||
pub fn split_armored_data(
|
||||
buf: &[u8],
|
||||
) -> Result<(BlockType, BTreeMap<String, String>, Vec<u8>), Error> {
|
||||
use std::io::Read;
|
||||
|
||||
let cursor = Cursor::new(buf);
|
||||
let mut dearmor = pgp::armor::Dearmor::new(cursor);
|
||||
|
||||
let mut bytes = Vec::with_capacity(buf.len());
|
||||
|
||||
dearmor.read_to_end(&mut bytes)?;
|
||||
ensure!(dearmor.typ.is_some(), "Failed to parse type");
|
||||
|
||||
let typ = dearmor.typ.unwrap();
|
||||
|
||||
// normalize headers
|
||||
let headers = dearmor
|
||||
.headers
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key.trim().to_lowercase(), value.trim().to_string()))
|
||||
.collect();
|
||||
|
||||
Ok((typ, headers, bytes))
|
||||
}
|
||||
|
||||
/// Create a new key pair.
|
||||
pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
|
||||
pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
|
||||
let user_id = format!("<{}>", addr.as_ref());
|
||||
|
||||
let key_params = SecretKeyParamsBuilder::default()
|
||||
@@ -181,7 +97,7 @@ pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
|
||||
Some((Key::Public(public_key), Key::Secret(private_key)))
|
||||
}
|
||||
|
||||
pub fn dc_pgp_pk_encrypt(
|
||||
pub fn pk_encrypt(
|
||||
plain: &[u8],
|
||||
public_keys_for_encryption: &Keyring,
|
||||
private_key_for_signing: Option<&Key>,
|
||||
@@ -218,7 +134,7 @@ pub fn dc_pgp_pk_encrypt(
|
||||
Ok(encoded_msg)
|
||||
}
|
||||
|
||||
pub fn dc_pgp_pk_decrypt(
|
||||
pub fn pk_decrypt(
|
||||
ctext: &[u8],
|
||||
private_keys_for_decryption: &Keyring,
|
||||
public_keys_for_validation: &Keyring,
|
||||
@@ -267,7 +183,7 @@ pub fn dc_pgp_pk_decrypt(
|
||||
}
|
||||
|
||||
/// Symmetric encryption.
|
||||
pub fn dc_pgp_symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
|
||||
pub fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Error> {
|
||||
let mut rng = thread_rng();
|
||||
let lit_msg = Message::new_literal_bytes("", plain);
|
||||
|
||||
@@ -281,8 +197,11 @@ pub fn dc_pgp_symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String, Err
|
||||
}
|
||||
|
||||
/// Symmetric decryption.
|
||||
pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let enc_msg = Message::from_bytes(Cursor::new(ctext))?;
|
||||
pub fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
||||
passphrase: &str,
|
||||
ctext: T,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let (enc_msg, _) = Message::from_armor_single(ctext)?;
|
||||
let decryptor = enc_msg.decrypt_with_password(|| passphrase.into())?;
|
||||
|
||||
let msgs = decryptor.collect::<Result<Vec<_>, _>>()?;
|
||||
@@ -293,3 +212,35 @@ pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Result<Vec<u8>, Er
|
||||
None => bail!("Decrypted message is empty"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_split_armored_data_1() {
|
||||
let (typ, _headers, base64) = split_armored_data(
|
||||
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\naGVsbG8gd29ybGQ=\n-----END PGP MESSAGE----",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(typ, BlockType::Message);
|
||||
assert!(!base64.is_empty());
|
||||
assert_eq!(
|
||||
std::string::String::from_utf8(base64).unwrap(),
|
||||
"hello world"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_armored_data_2() {
|
||||
let (typ, headers, base64) = split_armored_data(
|
||||
b"-----BEGIN PGP PRIVATE KEY BLOCK-----\nAutocrypt-Prefer-Encrypt: mutual \n\naGVsbG8gd29ybGQ=\n-----END PGP PRIVATE KEY BLOCK-----"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(typ, BlockType::PrivateKey);
|
||||
assert!(!base64.is_empty());
|
||||
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
45
src/smtp.rs
45
src/smtp.rs
@@ -71,7 +71,7 @@ impl Smtp {
|
||||
let tls = dc_build_tls(lp.smtp_certificate_checks).unwrap();
|
||||
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls);
|
||||
|
||||
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
|
||||
let (creds, mechanism) = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
|
||||
// oauth2
|
||||
let addr = &lp.addr;
|
||||
let send_pw = &lp.send_pw;
|
||||
@@ -81,15 +81,24 @@ impl Smtp {
|
||||
}
|
||||
let user = &lp.send_user;
|
||||
|
||||
lettre::smtp::authentication::Credentials::new(
|
||||
user.to_string(),
|
||||
access_token.unwrap_or_default(),
|
||||
(
|
||||
lettre::smtp::authentication::Credentials::new(
|
||||
user.to_string(),
|
||||
access_token.unwrap_or_default(),
|
||||
),
|
||||
vec![lettre::smtp::authentication::Mechanism::Xoauth2],
|
||||
)
|
||||
} else {
|
||||
// plain
|
||||
let user = lp.send_user.clone();
|
||||
let pw = lp.send_pw.clone();
|
||||
lettre::smtp::authentication::Credentials::new(user, pw)
|
||||
(
|
||||
lettre::smtp::authentication::Credentials::new(user, pw),
|
||||
vec![
|
||||
lettre::smtp::authentication::Mechanism::Plain,
|
||||
lettre::smtp::authentication::Mechanism::Login,
|
||||
],
|
||||
)
|
||||
};
|
||||
|
||||
let security = if 0
|
||||
@@ -105,19 +114,29 @@ impl Smtp {
|
||||
let client = client
|
||||
.smtp_utf8(true)
|
||||
.credentials(creds)
|
||||
.authentication_mechanism(mechanism)
|
||||
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
|
||||
self.transport = Some(client.transport());
|
||||
context.call_cb(Event::SmtpConnected(format!(
|
||||
"SMTP-LOGIN as {} ok",
|
||||
lp.send_user,
|
||||
)));
|
||||
true
|
||||
let mut trans = client.transport();
|
||||
match trans.connect() {
|
||||
Ok(()) => {
|
||||
self.transport = Some(trans);
|
||||
self.transport_connected = true;
|
||||
context.call_cb(Event::SmtpConnected(format!(
|
||||
"SMTP-LOGIN as {} ok",
|
||||
lp.send_user,
|
||||
)));
|
||||
return true;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "SMTP: failed to connect {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "SMTP: failed to establish connection {:?}", err);
|
||||
false
|
||||
warn!(context, "SMTP: failed to setup connection {:?}", err);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// SMTP-Send a prepared mail to recipients.
|
||||
|
||||
12
src/sql.rs
12
src/sql.rs
@@ -11,8 +11,6 @@ use crate::error::{Error, Result};
|
||||
use crate::param::*;
|
||||
use crate::peerstate::*;
|
||||
|
||||
const DC_OPEN_READONLY: usize = 0x01;
|
||||
|
||||
/// A wrapper around the underlying Sqlite3 object.
|
||||
#[derive(DebugStub)]
|
||||
pub struct Sql {
|
||||
@@ -42,8 +40,8 @@ impl Sql {
|
||||
}
|
||||
|
||||
// return true on success, false on failure
|
||||
pub fn open(&self, context: &Context, dbfile: &std::path::Path, flags: libc::c_int) -> bool {
|
||||
match open(context, self, dbfile, flags) {
|
||||
pub fn open(&self, context: &Context, dbfile: &std::path::Path, readonly: bool) -> bool {
|
||||
match open(context, self, dbfile, readonly) {
|
||||
Ok(_) => true,
|
||||
Err(Error::SqlAlreadyOpen) => false,
|
||||
Err(_) => {
|
||||
@@ -320,7 +318,7 @@ fn open(
|
||||
context: &Context,
|
||||
sql: &Sql,
|
||||
dbfile: impl AsRef<std::path::Path>,
|
||||
flags: libc::c_int,
|
||||
readonly: bool,
|
||||
) -> Result<()> {
|
||||
if sql.is_open() {
|
||||
error!(
|
||||
@@ -332,7 +330,7 @@ fn open(
|
||||
}
|
||||
|
||||
let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX;
|
||||
if 0 != (flags & DC_OPEN_READONLY as i32) {
|
||||
if readonly {
|
||||
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY);
|
||||
} else {
|
||||
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE);
|
||||
@@ -351,7 +349,7 @@ fn open(
|
||||
*sql.pool.write().unwrap() = Some(pool);
|
||||
}
|
||||
|
||||
if 0 == flags & DC_OPEN_READONLY as i32 {
|
||||
if !readonly {
|
||||
let mut exists_before_update = 0;
|
||||
let mut dbversion_before_update = 0;
|
||||
/* Init tables to dbversion=0 */
|
||||
|
||||
@@ -16,18 +16,20 @@ if __name__ == "__main__":
|
||||
free = s.count("free(")
|
||||
unsafe_fn = s.count("unsafe fn")
|
||||
chars = s.count("c_char") + s.count("CStr")
|
||||
filestats.append((fn, unsafe, free, unsafe_fn, chars))
|
||||
libc = s.count("libc")
|
||||
filestats.append((fn, unsafe, free, unsafe_fn, chars, libc))
|
||||
|
||||
sum_unsafe, sum_free, sum_unsafe_fn, sum_chars = 0, 0, 0, 0
|
||||
sum_unsafe, sum_free, sum_unsafe_fn, sum_chars, sum_libc = 0, 0, 0, 0, 0
|
||||
|
||||
for fn, unsafe, free, unsafe_fn, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
|
||||
if unsafe + free + unsafe_fn + chars == 0:
|
||||
for fn, unsafe, free, unsafe_fn, chars, libc in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
|
||||
if unsafe + free + unsafe_fn + chars + libc == 0:
|
||||
continue
|
||||
print("{0: <25} unsafe: {1: >3} free: {2: >3} unsafe-fn: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, unsafe_fn, chars))
|
||||
print("{0: <25} unsafe: {1: >3} free: {2: >3} unsafe-fn: {3: >3} chars: {4: >3} libc: {5: >3}".format(str(fn), unsafe, free, unsafe_fn, chars, libc))
|
||||
sum_unsafe += unsafe
|
||||
sum_free += free
|
||||
sum_unsafe_fn += unsafe_fn
|
||||
sum_chars += chars
|
||||
sum_libc += libc
|
||||
|
||||
|
||||
print()
|
||||
@@ -35,3 +37,4 @@ if __name__ == "__main__":
|
||||
print("total free:", sum_free)
|
||||
print("total unsafe-fn:", sum_unsafe_fn)
|
||||
print("total c_chars:", sum_chars)
|
||||
print("total libc:", sum_libc)
|
||||
|
||||
155
tests/stress.rs
155
tests/stress.rs
@@ -1,18 +1,15 @@
|
||||
//! Stress some functions for testing; if used as a lib, this file is obsolete.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::ptr;
|
||||
|
||||
use deltachat::chat::{self, Chat};
|
||||
use deltachat::config;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::keyring::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::pgp::*;
|
||||
use deltachat::pgp;
|
||||
use deltachat::Event;
|
||||
use libc::{free, strcmp, strdup};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
/* some data used for testing
|
||||
@@ -50,135 +47,6 @@ unsafe fn stress_functions(context: &Context) {
|
||||
assert!(res.contains(" configured_send_port "));
|
||||
assert!(res.contains(" configured_server_flags "));
|
||||
|
||||
let mut buf_0: *mut libc::c_char;
|
||||
let mut headerline = String::default();
|
||||
let mut setupcodebegin: *const libc::c_char = ptr::null();
|
||||
let mut preferencrypt: *const libc::c_char = ptr::null();
|
||||
let mut base64: *const libc::c_char = ptr::null();
|
||||
buf_0 = strdup(
|
||||
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\ndata\n-----END PGP MESSAGE-----\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
assert!(ok);
|
||||
assert!(!headerline.is_empty());
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
|
||||
assert!(!base64.is_null());
|
||||
assert_eq!(as_str(base64 as *const libc::c_char), "data",);
|
||||
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
|
||||
buf_0 =
|
||||
strdup(b"-----BEGIN PGP MESSAGE-----\n\ndat1\n-----END PGP MESSAGE-----\n-----BEGIN PGP MESSAGE-----\n\ndat2\n-----END PGP MESSAGE-----\x00"
|
||||
as *const u8 as *const libc::c_char);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
|
||||
assert!(ok);
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
|
||||
assert!(!base64.is_null());
|
||||
assert_eq!(as_str(base64 as *const libc::c_char), "dat1",);
|
||||
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
|
||||
buf_0 = strdup(
|
||||
b"foo \n -----BEGIN PGP MESSAGE----- \n base64-123 \n -----END PGP MESSAGE-----\x00"
|
||||
as *const u8 as *const libc::c_char,
|
||||
);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
|
||||
assert!(ok);
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
assert!(setupcodebegin.is_null());
|
||||
|
||||
assert!(!base64.is_null());
|
||||
assert_eq!(as_str(base64 as *const libc::c_char), "base64-123",);
|
||||
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
|
||||
buf_0 = strdup(b"foo-----BEGIN PGP MESSAGE-----\x00" as *const u8 as *const libc::c_char);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
|
||||
assert!(!ok);
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
buf_0 =
|
||||
strdup(b"foo \n -----BEGIN PGP MESSAGE-----\n Passphrase-BeGIN : 23 \n \n base64-567 \r\n abc \n -----END PGP MESSAGE-----\n\n\n\x00"
|
||||
as *const u8 as *const libc::c_char);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
&mut setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
&mut base64,
|
||||
);
|
||||
assert!(ok);
|
||||
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
|
||||
|
||||
assert!(!setupcodebegin.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
setupcodebegin,
|
||||
b"23\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
|
||||
assert!(!base64.is_null());
|
||||
assert_eq!(as_str(base64 as *const libc::c_char), "base64-567 \n abc",);
|
||||
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
|
||||
buf_0 =
|
||||
strdup(b"-----BEGIN PGP PRIVATE KEY BLOCK-----\n Autocrypt-Prefer-Encrypt : mutual \n\nbase64\n-----END PGP PRIVATE KEY BLOCK-----\x00"
|
||||
as *const u8 as *const libc::c_char);
|
||||
let ok = dc_split_armored_data(
|
||||
buf_0,
|
||||
&mut headerline,
|
||||
ptr::null_mut(),
|
||||
&mut preferencrypt,
|
||||
&mut base64,
|
||||
);
|
||||
assert!(ok);
|
||||
assert_eq!(headerline, "-----BEGIN PGP PRIVATE KEY BLOCK-----");
|
||||
assert!(!preferencrypt.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
preferencrypt,
|
||||
b"mutual\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
|
||||
assert!(!base64.is_null());
|
||||
assert_eq!(as_str(base64 as *const libc::c_char), "base64",);
|
||||
|
||||
free(buf_0 as *mut libc::c_void);
|
||||
|
||||
// Cant check, no configured context
|
||||
// assert!(dc_is_configured(context) != 0, "Missing configured context");
|
||||
|
||||
@@ -232,11 +100,11 @@ unsafe fn stress_functions(context: &Context) {
|
||||
#[test]
|
||||
#[ignore] // is too expensive
|
||||
fn test_encryption_decryption() {
|
||||
let (public_key, private_key) = dc_pgp_create_keypair("foo@bar.de").unwrap();
|
||||
let (public_key, private_key) = pgp::create_keypair("foo@bar.de").unwrap();
|
||||
|
||||
private_key.split_key().unwrap();
|
||||
|
||||
let (public_key2, private_key2) = dc_pgp_create_keypair("two@zwo.de").unwrap();
|
||||
let (public_key2, private_key2) = pgp::create_keypair("two@zwo.de").unwrap();
|
||||
|
||||
assert_ne!(public_key, public_key2);
|
||||
|
||||
@@ -245,11 +113,11 @@ fn test_encryption_decryption() {
|
||||
keyring.add_owned(public_key.clone());
|
||||
keyring.add_ref(&public_key2);
|
||||
|
||||
let ctext_signed = dc_pgp_pk_encrypt(original_text, &keyring, Some(&private_key)).unwrap();
|
||||
let ctext_signed = pgp::pk_encrypt(original_text, &keyring, Some(&private_key)).unwrap();
|
||||
assert!(!ctext_signed.is_empty());
|
||||
assert!(ctext_signed.starts_with("-----BEGIN PGP MESSAGE-----"));
|
||||
|
||||
let ctext_unsigned = dc_pgp_pk_encrypt(original_text, &keyring, None).unwrap();
|
||||
let ctext_unsigned = pgp::pk_encrypt(original_text, &keyring, None).unwrap();
|
||||
assert!(!ctext_unsigned.is_empty());
|
||||
assert!(ctext_unsigned.starts_with("-----BEGIN PGP MESSAGE-----"));
|
||||
|
||||
@@ -264,7 +132,7 @@ fn test_encryption_decryption() {
|
||||
|
||||
let mut valid_signatures: HashSet<String> = Default::default();
|
||||
|
||||
let plain = dc_pgp_pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
ctext_signed.as_bytes(),
|
||||
&keyring,
|
||||
&public_keyring,
|
||||
@@ -278,7 +146,7 @@ fn test_encryption_decryption() {
|
||||
valid_signatures.clear();
|
||||
|
||||
let empty_keyring = Keyring::default();
|
||||
let plain = dc_pgp_pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
ctext_signed.as_bytes(),
|
||||
&keyring,
|
||||
&empty_keyring,
|
||||
@@ -290,7 +158,7 @@ fn test_encryption_decryption() {
|
||||
|
||||
valid_signatures.clear();
|
||||
|
||||
let plain = dc_pgp_pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
ctext_signed.as_bytes(),
|
||||
&keyring,
|
||||
&public_keyring2,
|
||||
@@ -304,7 +172,7 @@ fn test_encryption_decryption() {
|
||||
|
||||
public_keyring2.add_ref(&public_key);
|
||||
|
||||
let plain = dc_pgp_pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
ctext_signed.as_bytes(),
|
||||
&keyring,
|
||||
&public_keyring2,
|
||||
@@ -316,7 +184,7 @@ fn test_encryption_decryption() {
|
||||
|
||||
valid_signatures.clear();
|
||||
|
||||
let plain = dc_pgp_pk_decrypt(
|
||||
let plain = pgp::pk_decrypt(
|
||||
ctext_unsigned.as_bytes(),
|
||||
&keyring,
|
||||
&public_keyring,
|
||||
@@ -333,8 +201,7 @@ fn test_encryption_decryption() {
|
||||
let mut public_keyring = Keyring::default();
|
||||
public_keyring.add_ref(&public_key);
|
||||
|
||||
let plain =
|
||||
dc_pgp_pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap();
|
||||
let plain = pgp::pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap();
|
||||
|
||||
assert_eq!(plain, original_text);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user