Merge branch 'master' into fix3782

This commit is contained in:
Sebastian Klähn
2023-01-05 18:55:47 +01:00
committed by GitHub
49 changed files with 4199 additions and 307 deletions

View File

@@ -782,17 +782,35 @@ impl ChatId {
// the times are average, no matter if there are fresh messages or not -
// and have to be multiplied by the number of items shown at once on the chatlist,
// so savings up to 2 seconds are possible on older devices - newer ones will feel "snappier" :)
let count = context
.sql
.count(
"SELECT COUNT(*)
let count = if self.is_archived_link() {
context
.sql
.count(
"SELECT COUNT(DISTINCT(m.chat_id))
FROM msgs m
LEFT JOIN chats c ON m.chat_id=c.id
WHERE m.state=10
and m.hidden=0
AND m.chat_id>9
AND c.blocked=0
AND c.archived=1
",
paramsv![],
)
.await?
} else {
context
.sql
.count(
"SELECT COUNT(*)
FROM msgs
WHERE state=?
AND hidden=0
AND chat_id=?;",
paramsv![MessageState::InFresh, self],
)
.await?;
paramsv![MessageState::InFresh, self],
)
.await?
};
Ok(count)
}
@@ -1216,6 +1234,10 @@ impl Chat {
if !image_rel.is_empty() {
return Ok(Some(get_abs_path(context, image_rel)));
}
} else if self.id.is_archived_link() {
if let Ok(image_rel) = get_archive_icon(context).await {
return Ok(Some(get_abs_path(context, image_rel)));
}
} else if self.typ == Chattype::Single {
let contacts = get_chat_contacts(context, self.id).await?;
if let Some(contact_id) = contacts.first() {
@@ -1710,6 +1732,21 @@ pub(crate) async fn get_broadcast_icon(context: &Context) -> Result<String> {
Ok(icon)
}
pub(crate) async fn get_archive_icon(context: &Context) -> Result<String> {
if let Some(icon) = context.sql.get_raw_config("icon-archive").await? {
return Ok(icon);
}
let icon = include_bytes!("../assets/icon-archive.png");
let blob = BlobObject::create(context, "icon-archive.png", icon).await?;
let icon = blob.as_name().to_string();
context
.sql
.set_raw_config("icon-archive", Some(&icon))
.await?;
Ok(icon)
}
async fn update_special_chat_name(
context: &Context,
contact_id: ContactId,

View File

@@ -92,8 +92,6 @@ impl Chatlist {
let flag_no_specials = 0 != listflags & DC_GCL_NO_SPECIALS;
let flag_add_alldone_hint = 0 != listflags & DC_GCL_ADD_ALLDONE_HINT;
let mut add_archived_link_item = false;
let process_row = |row: &rusqlite::Row| {
let chat_id: ChatId = row.get(0)?;
let msg_id: Option<MsgId> = row.get(1)?;
@@ -123,7 +121,7 @@ impl Chatlist {
//
// The query shows messages from blocked contacts in
// groups. Otherwise it would be hard to follow conversations.
let mut ids = if let Some(query_contact_id) = query_contact_id {
let ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_map(
"SELECT c.id, m.id
@@ -216,7 +214,7 @@ impl Chatlist {
} else {
ChatId::new(0)
};
let ids = context.sql.query_map(
let mut ids = context.sql.query_map(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
@@ -236,19 +234,15 @@ impl Chatlist {
process_row,
process_rows,
).await?;
if !flag_no_specials {
add_archived_link_item = true;
if !flag_no_specials && get_archived_cnt(context).await? > 0 {
if ids.is_empty() && flag_add_alldone_hint {
ids.push((DC_CHAT_ID_ALLDONE_HINT, None));
}
ids.insert(0, (DC_CHAT_ID_ARCHIVED_LINK, None));
}
ids
};
if add_archived_link_item && get_archived_cnt(context).await? > 0 {
if ids.is_empty() && flag_add_alldone_hint {
ids.push((DC_CHAT_ID_ALLDONE_HINT, None));
}
ids.push((DC_CHAT_ID_ARCHIVED_LINK, None));
}
Ok(Chatlist { ids })
}

View File

@@ -579,10 +579,10 @@ async fn try_imap_one_param(
let mut imap = match Imap::new(param, socks5_config.clone(), addr, provider_strict_tls, r) {
Err(err) => {
info!(context, "failure: {}", err);
info!(context, "failure: {:#}", err);
return Err(ConfigurationError {
config: inf,
msg: err.to_string(),
msg: format!("{:#}", err),
});
}
Ok(imap) => imap,
@@ -590,10 +590,10 @@ async fn try_imap_one_param(
match imap.connect(context).await {
Err(err) => {
info!(context, "failure: {}", err);
info!(context, "failure: {:#}", err);
Err(ConfigurationError {
config: inf,
msg: err.to_string(),
msg: format!("{:#}", err),
})
}
Ok(()) => {
@@ -634,7 +634,7 @@ async fn try_smtp_one_param(
info!(context, "failure: {}", err);
Err(ConfigurationError {
config: inf,
msg: err.to_string(),
msg: format!("{:#}", err),
})
} else {
info!(context, "success: {}", inf);

View File

@@ -62,7 +62,7 @@ fn parse_server<B: BufRead>(
reader: &mut quick_xml::Reader<B>,
server_event: &BytesStart,
) -> Result<Option<Server>, quick_xml::Error> {
let end_tag = String::from_utf8_lossy(server_event.name())
let end_tag = String::from_utf8_lossy(server_event.name().as_ref())
.trim()
.to_lowercase();
@@ -70,12 +70,17 @@ fn parse_server<B: BufRead>(
.attributes()
.find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "type")
.map(|a| {
String::from_utf8_lossy(a.key.as_ref())
.trim()
.to_lowercase()
== "type"
})
.unwrap_or_default()
})
.map(|typ| {
typ.unwrap()
.unescape_and_decode_value(reader)
.decode_and_unescape_value(reader)
.unwrap_or_default()
.to_lowercase()
})
@@ -89,25 +94,23 @@ fn parse_server<B: BufRead>(
let mut tag_config = MozConfigTag::Undefined;
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf)? {
match reader.read_event_into(&mut buf)? {
Event::Start(ref event) => {
tag_config = String::from_utf8_lossy(event.name())
tag_config = String::from_utf8_lossy(event.name().as_ref())
.parse()
.unwrap_or_default();
}
Event::End(ref event) => {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
if tag == end_tag {
break;
}
}
Event::Text(ref event) => {
let val = event
.unescape_and_decode(reader)
.unwrap_or_default()
.trim()
.to_owned();
let val = event.unescape().unwrap_or_default().trim().to_owned();
match tag_config {
MozConfigTag::Hostname => hostname = Some(val),
@@ -150,9 +153,11 @@ fn parse_xml_reader<B: BufRead>(
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf)? {
match reader.read_event_into(&mut buf)? {
Event::Start(ref event) => {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
if tag == "incomingserver" {
if let Some(incoming_server) = parse_server(reader, event)? {

View File

@@ -59,12 +59,18 @@ fn parse_protocol<B: BufRead>(
let mut current_tag: Option<String> = None;
loop {
match reader.read_event(&mut buf)? {
match reader.read_event_into(&mut buf)? {
Event::Start(ref event) => {
current_tag = Some(String::from_utf8_lossy(event.name()).trim().to_lowercase());
current_tag = Some(
String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase(),
);
}
Event::End(ref event) => {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
if tag == "protocol" {
break;
}
@@ -73,7 +79,7 @@ fn parse_protocol<B: BufRead>(
}
}
Event::Text(ref e) => {
let val = e.unescape_and_decode(reader).unwrap_or_default();
let val = e.unescape().unwrap_or_default();
if let Some(ref tag) = current_tag {
match tag.as_str() {
@@ -115,9 +121,9 @@ fn parse_redirecturl<B: BufRead>(
reader: &mut quick_xml::Reader<B>,
) -> Result<String, quick_xml::Error> {
let mut buf = Vec::new();
match reader.read_event(&mut buf)? {
match reader.read_event_into(&mut buf)? {
Event::Text(ref e) => {
let val = e.unescape_and_decode(reader).unwrap_or_default();
let val = e.unescape().unwrap_or_default();
Ok(val.trim().to_string())
}
_ => Ok("".to_string()),
@@ -131,9 +137,11 @@ fn parse_xml_reader<B: BufRead>(
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf)? {
match reader.read_event_into(&mut buf)? {
Event::Start(ref e) => {
let tag = String::from_utf8_lossy(e.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(e.name().as_ref())
.trim()
.to_lowercase();
if tag == "protocol" {
if let Some(protocol) = parse_protocol(reader)? {

View File

@@ -88,18 +88,30 @@ fn dehtml_quick_xml(buf: &str) -> String {
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
match reader.read_event_into(&mut buf) {
Ok(quick_xml::events::Event::Start(ref e)) => {
dehtml_starttag_cb(e, &mut dehtml, &reader)
}
Ok(quick_xml::events::Event::End(ref e)) => dehtml_endtag_cb(e, &mut dehtml),
Ok(quick_xml::events::Event::Text(ref e)) => dehtml_text_cb(e, &mut dehtml),
Ok(quick_xml::events::Event::CData(e)) => dehtml_text_cb(&e.escape(), &mut dehtml),
Ok(quick_xml::events::Event::CData(e)) => match e.escape() {
Ok(e) => dehtml_text_cb(&e, &mut dehtml),
Err(e) => {
eprintln!(
"CDATA escape error at position {}: {:?}",
reader.buffer_position(),
e,
);
}
},
Ok(quick_xml::events::Event::Empty(ref e)) => {
// Handle empty tags as a start tag immediately followed by end tag.
// For example, `<p/>` is treated as `<p></p>`.
dehtml_starttag_cb(e, &mut dehtml, &reader);
dehtml_endtag_cb(&BytesEnd::borrowed(e.name()), &mut dehtml);
dehtml_endtag_cb(
&BytesEnd::new(String::from_utf8_lossy(e.name().as_ref())),
&mut dehtml,
);
}
Err(e) => {
eprintln!(
@@ -121,7 +133,7 @@ fn dehtml_text_cb(event: &BytesText, dehtml: &mut Dehtml) {
if dehtml.get_add_text() == AddText::YesPreserveLineEnds
|| dehtml.get_add_text() == AddText::YesRemoveLineEnds
{
let last_added = escaper::decode_html_buf_sloppy(event.escaped()).unwrap_or_default();
let last_added = escaper::decode_html_buf_sloppy(event as &[_]).unwrap_or_default();
if dehtml.get_add_text() == AddText::YesRemoveLineEnds {
dehtml.strbuilder += LINE_RE.replace_all(&last_added, "\r").as_ref();
@@ -135,7 +147,9 @@ fn dehtml_text_cb(event: &BytesText, dehtml: &mut Dehtml) {
}
fn dehtml_endtag_cb(event: &BytesEnd, dehtml: &mut Dehtml) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
match tag.as_str() {
"p" | "table" | "td" | "style" | "script" | "title" | "pre" => {
@@ -176,7 +190,9 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
dehtml: &mut Dehtml,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
match tag.as_str() {
"p" | "table" | "td" => {
@@ -206,10 +222,15 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
if let Some(href) = event
.html_attributes()
.filter_map(|attr| attr.ok())
.find(|attr| String::from_utf8_lossy(attr.key).trim().to_lowercase() == "href")
.find(|attr| {
String::from_utf8_lossy(attr.key.as_ref())
.trim()
.to_lowercase()
== "href"
})
{
let href = href
.unescape_and_decode_value(reader)
.decode_and_unescape_value(reader)
.unwrap_or_default()
.to_lowercase();
@@ -258,7 +279,7 @@ fn maybe_push_tag(
fn tag_contains_attr(event: &BytesStart, reader: &Reader<impl BufRead>, name: &str) -> bool {
event.attributes().any(|r| {
r.map(|a| {
a.unescape_and_decode_value(reader)
a.decode_and_unescape_value(reader)
.map(|v| v == name)
.unwrap_or(false)
})

View File

@@ -1,7 +1,5 @@
//! # Download large messages manually.
#![allow(missing_docs)]
use anyhow::{anyhow, Result};
use deltachat_derive::{FromSql, ToSql};
use serde::{Deserialize, Serialize};
@@ -33,6 +31,7 @@ const MIN_DOWNLOAD_LIMIT: u32 = 32768;
/// `MIN_DELETE_SERVER_AFTER` increases the timeout in this case.
pub(crate) const MIN_DELETE_SERVER_AFTER: i64 = 48 * 60 * 60;
/// Download state of the message.
#[derive(
Debug,
Display,
@@ -49,9 +48,16 @@ pub(crate) const MIN_DELETE_SERVER_AFTER: i64 = 48 * 60 * 60;
)]
#[repr(u32)]
pub enum DownloadState {
/// Message is fully downloaded.
Done = 0,
/// Message is partially downloaded and can be fully downloaded at request.
Available = 10,
/// Failed to fully download the message.
Failure = 20,
/// Full download of the message is in progress.
InProgress = 1000,
}

12
src/fuzzing.rs Normal file
View File

@@ -0,0 +1,12 @@
//! # Fuzzing module.
//!
//! This module exposes private APIs for fuzzing.
/// Fuzzing target for simplify().
///
/// Calls simplify() and panics if simplify() panics.
/// Does not return any vaule to avoid exposing internal crate types.
#[cfg(fuzzing)]
pub fn simplify(input: String, is_chat_message: bool) {
crate::simplify::simplify(input, is_chat_message);
}

View File

@@ -297,6 +297,7 @@ impl Imap {
let oauth2 = self.config.lp.oauth2;
info!(context, "Connecting to IMAP server");
let connection_res: Result<Client> = if self.config.lp.security == Socket::Starttls
|| self.config.lp.security == Socket::Plain
{
@@ -304,22 +305,23 @@ impl Imap {
let imap_server: &str = config.lp.server.as_ref();
let imap_port = config.lp.port;
let connection = if let Some(socks5_config) = &config.socks5_config {
Client::connect_insecure_socks5((imap_server, imap_port), socks5_config.clone())
if let Some(socks5_config) = &config.socks5_config {
if config.lp.security == Socket::Starttls {
Client::connect_starttls_socks5(
imap_server,
imap_port,
socks5_config.clone(),
config.strict_tls,
)
.await
} else {
Client::connect_insecure_socks5((imap_server, imap_port), socks5_config.clone())
.await
}
} else if config.lp.security == Socket::Starttls {
Client::connect_starttls(imap_server, imap_port, config.strict_tls).await
} else {
Client::connect_insecure((imap_server, imap_port)).await
};
match connection {
Ok(client) => {
if config.lp.security == Socket::Starttls {
client.secure(imap_server, config.strict_tls).await
} else {
Ok(client)
}
}
Err(err) => Err(err),
}
} else {
let config = &self.config;
@@ -328,8 +330,8 @@ impl Imap {
if let Some(socks5_config) = &config.socks5_config {
Client::connect_secure_socks5(
(imap_server, imap_port),
imap_server,
imap_port,
config.strict_tls,
socks5_config.clone(),
)
@@ -345,6 +347,7 @@ impl Imap {
let imap_pw: &str = config.lp.password.as_ref();
let login_res = if oauth2 {
info!(context, "Logging into IMAP server with OAuth 2");
let addr: &str = config.addr.as_ref();
let token = get_oauth2_access_token(context, addr, imap_pw, true)
@@ -356,6 +359,7 @@ impl Imap {
};
client.authenticate("XOAUTH2", auth).await
} else {
info!(context, "Logging into IMAP server with LOGIN");
client.login(imap_user, imap_pw).await
};
@@ -371,6 +375,7 @@ impl Imap {
"IMAP-LOGIN as {}",
self.config.lp.user
)));
info!(context, "Successfully logged into IMAP server");
Ok(())
}
@@ -378,7 +383,7 @@ impl Imap {
let imap_user = self.config.lp.user.to_owned();
let message = stock_str::cannot_login(context, &imap_user).await;
warn!(context, "{} ({})", message, err);
warn!(context, "{} ({:#})", message, err);
let lock = context.wrong_pw_warning_mutex.lock().await;
if self.login_failed_once
@@ -386,7 +391,7 @@ impl Imap {
&& context.get_config_bool(Config::NotifyAboutWrongPw).await?
{
if let Err(e) = context.set_config(Config::NotifyAboutWrongPw, None).await {
warn!(context, "{}", e);
warn!(context, "{:#}", e);
}
drop(lock);
@@ -396,13 +401,13 @@ impl Imap {
chat::add_device_msg_with_importance(context, None, Some(&mut msg), true)
.await
{
warn!(context, "{}", e);
warn!(context, "{:#}", e);
}
} else {
self.login_failed_once = true;
}
Err(format_err!("{}\n\n{}", message, err))
Err(format_err!("{}\n\n{:#}", message, err))
}
}
}
@@ -672,7 +677,10 @@ impl Imap {
return Ok(false);
}
let new_emails = self.select_with_uidvalidity(context, folder).await?;
let new_emails = self
.select_with_uidvalidity(context, folder)
.await
.with_context(|| format!("failed to select folder {}", folder))?;
if !new_emails && !fetch_existing_msgs {
info!(context, "No new emails in folder {}", folder);
@@ -832,9 +840,15 @@ impl Imap {
}
self.prepare(context).await.context("could not connect")?;
add_all_recipients_as_contacts(context, self, Config::ConfiguredSentboxFolder).await;
add_all_recipients_as_contacts(context, self, Config::ConfiguredMvboxFolder).await;
add_all_recipients_as_contacts(context, self, Config::ConfiguredInboxFolder).await;
add_all_recipients_as_contacts(context, self, Config::ConfiguredSentboxFolder)
.await
.context("failed to get recipients from the sentbox")?;
add_all_recipients_as_contacts(context, self, Config::ConfiguredMvboxFolder)
.await
.context("failed to ge recipients from the movebox")?;
add_all_recipients_as_contacts(context, self, Config::ConfiguredInboxFolder)
.await
.context("failed to get recipients from the inbox")?;
if context.get_config_bool(Config::FetchExistingMsgs).await? {
for config in &[
@@ -843,17 +857,18 @@ impl Imap {
Config::ConfiguredSentboxFolder,
] {
if let Some(folder) = context.get_config(*config).await? {
info!(
context,
"Fetching existing messages from folder \"{}\"", folder
);
self.fetch_new_messages(context, &folder, false, true)
.await
.context("could not fetch messages")?;
.context("could not fetch existing messages")?;
}
}
}
info!(context, "Done fetching existing messages.");
context
.set_config_bool(Config::FetchedExistingMsgs, true)
.await?;
Ok(())
}
}
@@ -1202,8 +1217,10 @@ impl Imap {
/// Prefetch all messages greater than or equal to `uid_next`. Returns a list of fetch results
/// in the order of ascending delivery time to the server (INTERNALDATE).
async fn prefetch(&mut self, uid_next: u32) -> Result<Vec<(u32, async_imap::types::Fetch)>> {
let session = self.session.as_mut();
let session = session.context("fetch_after(): IMAP No Connection established")?;
let session = self
.session
.as_mut()
.context("no IMAP connection established")?;
// fetch messages with larger UID than the last one seen
let set = format!("{}:*", uid_next);
@@ -1228,7 +1245,6 @@ impl Imap {
}
}
}
drop(list);
Ok(msgs.into_iter().map(|((_, uid), msg)| (uid, msg)).collect())
}
@@ -2301,49 +2317,58 @@ impl std::fmt::Display for UidRange {
}
}
}
async fn add_all_recipients_as_contacts(context: &Context, imap: &mut Imap, folder: Config) {
let mailbox = if let Ok(Some(m)) = context.get_config(folder).await {
async fn add_all_recipients_as_contacts(
context: &Context,
imap: &mut Imap,
folder: Config,
) -> Result<()> {
let mailbox = if let Some(m) = context.get_config(folder).await? {
m
} else {
return;
info!(
context,
"Folder {} is not configured, skipping fetching contacts from it.", folder
);
return Ok(());
};
if let Err(e) = imap.select_with_uidvalidity(context, &mailbox).await {
// We are using Anyhow's .context() and to show the inner error, too, we need the {:#}:
warn!(context, "Could not select {}: {:#}", mailbox, e);
return;
}
match imap.get_all_recipients(context).await {
Ok(contacts) => {
let mut any_modified = false;
for contact in contacts {
let display_name_normalized = contact
.display_name
.as_ref()
.map(|s| normalize_name(s))
.unwrap_or_default();
imap.select_with_uidvalidity(context, &mailbox)
.await
.with_context(|| format!("could not select {}", mailbox))?;
match Contact::add_or_lookup(
context,
&display_name_normalized,
&contact.addr,
Origin::OutgoingTo,
)
.await
{
Ok((_, modified)) => {
if modified != Modifier::None {
any_modified = true;
}
}
Err(e) => warn!(context, "Could not add recipient: {}", e),
let contacts = imap
.get_all_recipients(context)
.await
.context("could not get recipients")?;
let mut any_modified = false;
for contact in contacts {
let display_name_normalized = contact
.display_name
.as_ref()
.map(|s| normalize_name(s))
.unwrap_or_default();
match Contact::add_or_lookup(
context,
&display_name_normalized,
&contact.addr,
Origin::OutgoingTo,
)
.await
{
Ok((_, modified)) => {
if modified != Modifier::None {
any_modified = true;
}
}
if any_modified {
context.emit_event(EventType::ContactsChanged(None));
}
Err(e) => warn!(context, "Could not add recipient: {}", e),
}
Err(e) => warn!(context, "Could not add recipients: {}", e),
};
}
if any_modified {
context.emit_event(EventType::ContactsChanged(None));
}
Ok(())
}
#[cfg(test)]

View File

@@ -8,13 +8,13 @@ use anyhow::{Context as _, Result};
use async_imap::Client as ImapClient;
use async_imap::Session as ImapSession;
use tokio::net::{self, TcpStream};
use tokio::time::timeout;
use tokio_io_timeout::TimeoutStream;
use tokio::io::BufWriter;
use tokio::net::ToSocketAddrs;
use super::capabilities::Capabilities;
use super::session::Session;
use crate::login_param::build_tls;
use crate::net::connect_tcp;
use crate::socks::Socks5Config;
use super::session::SessionStream;
@@ -24,7 +24,6 @@ pub(crate) const IMAP_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug)]
pub(crate) struct Client {
is_secure: bool,
inner: ImapClient<Box<dyn SessionStream>>,
}
@@ -93,108 +92,131 @@ impl Client {
}
pub async fn connect_secure(hostname: &str, port: u16, strict_tls: bool) -> Result<Self> {
let tcp_stream = timeout(IMAP_TIMEOUT, TcpStream::connect((hostname, port))).await??;
let mut timeout_stream = TimeoutStream::new(tcp_stream);
timeout_stream.set_write_timeout(Some(IMAP_TIMEOUT));
timeout_stream.set_read_timeout(Some(IMAP_TIMEOUT));
let timeout_stream = Box::pin(timeout_stream);
let tcp_stream = connect_tcp((hostname, port), IMAP_TIMEOUT).await?;
let tls = build_tls(strict_tls);
let tls_stream: Box<dyn SessionStream> =
Box::new(tls.connect(hostname, timeout_stream).await?);
let mut client = ImapClient::new(tls_stream);
let tls_stream = tls.connect(hostname, tcp_stream).await?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")?;
.context("failed to read greeting")??;
Ok(Client {
is_secure: true,
inner: client,
})
Ok(Client { inner: client })
}
pub async fn connect_insecure(addr: impl net::ToSocketAddrs) -> Result<Self> {
let tcp_stream = timeout(IMAP_TIMEOUT, TcpStream::connect(addr)).await??;
let mut timeout_stream = TimeoutStream::new(tcp_stream);
timeout_stream.set_write_timeout(Some(IMAP_TIMEOUT));
timeout_stream.set_read_timeout(Some(IMAP_TIMEOUT));
let timeout_stream = Box::pin(timeout_stream);
let stream: Box<dyn SessionStream> = Box::new(timeout_stream);
let mut client = ImapClient::new(stream);
pub async fn connect_insecure(addr: impl ToSocketAddrs) -> Result<Self> {
let tcp_stream = connect_tcp(addr, IMAP_TIMEOUT).await?;
let buffered_stream = BufWriter::new(tcp_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")?;
.context("failed to read greeting")??;
Ok(Client {
is_secure: false,
inner: client,
})
Ok(Client { inner: client })
}
pub async fn connect_starttls(hostname: &str, port: u16, strict_tls: bool) -> Result<Self> {
let tcp_stream = connect_tcp((hostname, port), IMAP_TIMEOUT).await?;
// Run STARTTLS command and convert the client back into a stream.
let session_stream: Box<dyn SessionStream> = Box::new(tcp_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")??;
client
.run_command_and_check_ok("STARTTLS", None)
.await
.context("STARTTLS command failed")?;
let tcp_stream = client.into_inner();
let tls = build_tls(strict_tls);
let tls_stream = tls
.connect(hostname, tcp_stream)
.await
.context("STARTTLS upgrade failed")?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let client = ImapClient::new(session_stream);
Ok(Client { inner: client })
}
pub async fn connect_secure_socks5(
target_addr: impl net::ToSocketAddrs,
domain: &str,
port: u16,
strict_tls: bool,
socks5_config: Socks5Config,
) -> Result<Self> {
let socks5_stream: Box<dyn SessionStream> =
Box::new(socks5_config.connect(target_addr, IMAP_TIMEOUT).await?);
let socks5_stream = socks5_config.connect((domain, port), IMAP_TIMEOUT).await?;
let tls = build_tls(strict_tls);
let tls_stream: Box<dyn SessionStream> =
Box::new(tls.connect(domain, socks5_stream).await?);
let mut client = ImapClient::new(tls_stream);
let tls_stream = tls.connect(domain, socks5_stream).await?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")?;
.context("failed to read greeting")??;
Ok(Client {
is_secure: true,
inner: client,
})
Ok(Client { inner: client })
}
pub async fn connect_insecure_socks5(
target_addr: impl net::ToSocketAddrs,
target_addr: impl ToSocketAddrs,
socks5_config: Socks5Config,
) -> Result<Self> {
let socks5_stream: Box<dyn SessionStream> =
Box::new(socks5_config.connect(target_addr, IMAP_TIMEOUT).await?);
let mut client = ImapClient::new(socks5_stream);
let socks5_stream = socks5_config.connect(target_addr, IMAP_TIMEOUT).await?;
let buffered_stream = BufWriter::new(socks5_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")?;
.context("failed to read greeting")??;
Ok(Client {
is_secure: false,
inner: client,
})
Ok(Client { inner: client })
}
pub async fn secure(self, domain: &str, strict_tls: bool) -> Result<Self> {
if self.is_secure {
Ok(self)
} else {
let Client { mut inner, .. } = self;
let tls = build_tls(strict_tls);
inner.run_command_and_check_ok("STARTTLS", None).await?;
pub async fn connect_starttls_socks5(
hostname: &str,
port: u16,
socks5_config: Socks5Config,
strict_tls: bool,
) -> Result<Self> {
let socks5_stream = socks5_config
.connect((hostname, port), IMAP_TIMEOUT)
.await?;
let stream = inner.into_inner();
let ssl_stream = tls.connect(domain, stream).await?;
let boxed: Box<dyn SessionStream> = Box::new(ssl_stream);
// Run STARTTLS command and convert the client back into a stream.
let session_stream: Box<dyn SessionStream> = Box::new(socks5_stream);
let mut client = ImapClient::new(session_stream);
let _greeting = client
.read_response()
.await
.context("failed to read greeting")??;
client
.run_command_and_check_ok("STARTTLS", None)
.await
.context("STARTTLS command failed")?;
let socks5_stream = client.into_inner();
Ok(Client {
is_secure: true,
inner: ImapClient::new(boxed),
})
}
let tls = build_tls(strict_tls);
let tls_stream = tls
.connect(hostname, socks5_stream)
.await
.context("STARTTLS upgrade failed")?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let client = ImapClient::new(session_stream);
Ok(Client { inner: client })
}
}

View File

@@ -6,6 +6,7 @@ use async_imap::types::Mailbox;
use async_imap::Session as ImapSession;
use async_native_tls::TlsStream;
use fast_socks5::client::Socks5Stream;
use tokio::io::BufWriter;
use tokio::net::TcpStream;
use tokio_io_timeout::TimeoutStream;
@@ -33,12 +34,17 @@ pub(crate) trait SessionStream:
fn set_read_timeout(&mut self, timeout: Option<Duration>);
}
impl SessionStream for TlsStream<Box<dyn SessionStream>> {
impl SessionStream for Box<dyn SessionStream> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.as_mut().set_read_timeout(timeout);
}
}
impl<T: SessionStream> SessionStream for TlsStream<T> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.get_mut().set_read_timeout(timeout);
}
}
impl SessionStream for TlsStream<Pin<Box<TimeoutStream<TcpStream>>>> {
impl<T: SessionStream> SessionStream for BufWriter<T> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.get_mut().set_read_timeout(timeout);
}
@@ -48,7 +54,7 @@ impl SessionStream for Pin<Box<TimeoutStream<TcpStream>>> {
self.as_mut().set_read_timeout_pinned(timeout);
}
}
impl SessionStream for Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>> {
impl<T: SessionStream> SessionStream for Socks5Stream<T> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.get_socket_mut().set_read_timeout(timeout)
}

View File

@@ -100,6 +100,7 @@ mod dehtml;
mod authres;
mod color;
pub mod html;
mod net;
pub mod plaintext;
mod ratelimit;
pub mod summary;
@@ -117,3 +118,6 @@ pub const DCC_MIME_DEBUG: &str = "DCC_MIME_DEBUG";
mod test_utils;
#[cfg(test)]
mod tests;
#[cfg(fuzzing)]
pub mod fuzzing;

View File

@@ -1,7 +1,5 @@
//! Location handling.
#![allow(missing_docs)]
use std::convert::TryFrom;
use std::time::Duration;
@@ -20,32 +18,63 @@ use crate::mimeparser::SystemMessage;
use crate::stock_str;
use crate::tools::{duration_to_str, time};
/// Location record
/// Location record.
#[derive(Debug, Clone, Default)]
pub struct Location {
/// Row ID of the location.
pub location_id: u32,
/// Location latitude.
pub latitude: f64,
/// Location longitude.
pub longitude: f64,
/// Nonstandard `accuracy` attribute of the `coordinates` tag.
pub accuracy: f64,
/// Location timestamp in seconds.
pub timestamp: i64,
/// Contact ID.
pub contact_id: ContactId,
/// Message ID.
pub msg_id: u32,
/// Chat ID.
pub chat_id: ChatId,
/// A marker string, such as an emoji, to be displayed on top of the location.
pub marker: Option<String>,
/// Whether location is independent, i.e. not part of the path.
pub independent: u32,
}
impl Location {
/// Creates a new empty location.
pub fn new() -> Self {
Default::default()
}
}
/// KML document.
///
/// See <https://www.ogc.org/standards/kml/> for the standard and
/// <https://developers.google.com/kml> for documentation.
#[derive(Debug, Clone, Default)]
pub struct Kml {
/// Nonstandard `addr` attribute of the `Document` tag storing the user email address.
pub addr: Option<String>,
/// Placemarks.
pub locations: Vec<Location>,
/// Currently parsed XML tag.
tag: KmlTag,
/// Currently parsed placemark.
pub curr: Location,
}
@@ -62,10 +91,12 @@ bitflags! {
}
impl Kml {
/// Creates a new empty KML document.
pub fn new() -> Self {
Default::default()
}
/// Parses a KML document.
pub fn parse(to_parse: &[u8]) -> Result<Self> {
ensure!(to_parse.len() <= 1024 * 1024, "kml-file is too large");
@@ -78,7 +109,7 @@ impl Kml {
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf).with_context(|| {
match reader.read_event_into(&mut buf).with_context(|| {
format!(
"location parsing error at position {}",
reader.buffer_position()
@@ -86,7 +117,7 @@ impl Kml {
})? {
quick_xml::events::Event::Start(ref e) => kml.starttag_cb(e, &reader),
quick_xml::events::Event::End(ref e) => kml.endtag_cb(e),
quick_xml::events::Event::Text(ref e) => kml.text_cb(e, &reader),
quick_xml::events::Event::Text(ref e) => kml.text_cb(e),
quick_xml::events::Event::Eof => break,
_ => (),
}
@@ -96,9 +127,9 @@ impl Kml {
Ok(kml)
}
fn text_cb<B: std::io::BufRead>(&mut self, event: &BytesText, reader: &quick_xml::Reader<B>) {
fn text_cb(&mut self, event: &BytesText) {
if self.tag.contains(KmlTag::WHEN) || self.tag.contains(KmlTag::COORDINATES) {
let val = event.unescape_and_decode(reader).unwrap_or_default();
let val = event.unescape().unwrap_or_default();
let val = val.replace(['\n', '\r', '\t', ' '], "");
@@ -127,7 +158,9 @@ impl Kml {
}
fn endtag_cb(&mut self, event: &BytesEnd) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
if tag == "placemark" {
if self.tag.contains(KmlTag::PLACEMARK)
@@ -147,14 +180,20 @@ impl Kml {
event: &BytesStart,
reader: &quick_xml::Reader<B>,
) {
let tag = String::from_utf8_lossy(event.name()).trim().to_lowercase();
let tag = String::from_utf8_lossy(event.name().as_ref())
.trim()
.to_lowercase();
if tag == "document" {
if let Some(addr) = event
.attributes()
.filter_map(|a| a.ok())
.find(|attr| String::from_utf8_lossy(attr.key).trim().to_lowercase() == "addr")
{
self.addr = addr.unescape_and_decode_value(reader).ok();
if let Some(addr) = event.attributes().filter_map(|a| a.ok()).find(|attr| {
String::from_utf8_lossy(attr.key.as_ref())
.trim()
.to_lowercase()
== "addr"
}) {
self.addr = addr
.decode_and_unescape_value(reader)
.ok()
.map(|a| a.into_owned());
}
} else if tag == "placemark" {
self.tag = KmlTag::PLACEMARK;
@@ -172,12 +211,17 @@ impl Kml {
self.tag = KmlTag::PLACEMARK | KmlTag::POINT | KmlTag::COORDINATES;
if let Some(acc) = event.attributes().find(|attr| {
attr.as_ref()
.map(|a| String::from_utf8_lossy(a.key).trim().to_lowercase() == "accuracy")
.map(|a| {
String::from_utf8_lossy(a.key.as_ref())
.trim()
.to_lowercase()
== "accuracy"
})
.unwrap_or_default()
}) {
let v = acc
.unwrap()
.unescape_and_decode_value(reader)
.decode_and_unescape_value(reader)
.unwrap_or_default();
self.curr.accuracy = v.trim().parse().unwrap_or_default();
@@ -259,6 +303,7 @@ pub async fn is_sending_locations_to_chat(
Ok(exists)
}
/// Sets current location of the user device.
pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool {
if latitude == 0.0 && longitude == 0.0 {
return true;
@@ -306,6 +351,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
continue_streaming
}
/// Searches for locations in the given time range, optionally filtering by chat and contact IDs.
pub async fn get_range(
context: &Context,
chat_id: Option<ChatId>,
@@ -396,6 +442,7 @@ pub async fn delete_all(context: &Context) -> Result<()> {
Ok(())
}
/// Returns `location.kml` contents.
pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)> {
let mut last_added_location_id = 0;
@@ -481,6 +528,7 @@ fn get_kml_timestamp(utc: i64) -> String {
.to_string()
}
/// Returns a KML document containing a single location with the given timestamp and coordinates.
pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String {
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
@@ -498,6 +546,7 @@ pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String
)
}
/// Sets the timestamp of the last time location was sent in the chat.
pub async fn set_kml_sent_timestamp(
context: &Context,
chat_id: ChatId,
@@ -513,6 +562,7 @@ pub async fn set_kml_sent_timestamp(
Ok(())
}
/// Sets the location of the message.
pub async fn set_msg_location_id(context: &Context, msg_id: MsgId, location_id: u32) -> Result<()> {
context
.sql

View File

@@ -1168,6 +1168,7 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
"3gp" => (Viewtype::Video, "video/3gpp"),
"aac" => (Viewtype::Audio, "audio/aac"),
"avi" => (Viewtype::Video, "video/x-msvideo"),
"avif" => (Viewtype::File, "image/avif"), // supported since Android 12 / iOS 16
"doc" => (Viewtype::File, "application/msword"),
"docx" => (
Viewtype::File,
@@ -1176,6 +1177,8 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
"epub" => (Viewtype::File, "application/epub+zip"),
"flac" => (Viewtype::Audio, "audio/flac"),
"gif" => (Viewtype::Gif, "image/gif"),
"heic" => (Viewtype::File, "image/heic"), // supported since Android 10 / iOS 11
"heif" => (Viewtype::File, "image/heif"), // supported since Android 10 / iOS 11
"html" => (Viewtype::File, "text/html"),
"htm" => (Viewtype::File, "text/html"),
"ico" => (Viewtype::File, "image/vnd.microsoft.icon"),
@@ -1200,10 +1203,15 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
"oga" => (Viewtype::Audio, "audio/ogg"),
"ogg" => (Viewtype::Audio, "audio/ogg"),
"ogv" => (Viewtype::File, "video/ogg"),
"opus" => (Viewtype::File, "audio/ogg"), // not supported eg. on Android 4
"opus" => (Viewtype::File, "audio/ogg"), // supported since Android 10
"otf" => (Viewtype::File, "font/otf"),
"pdf" => (Viewtype::File, "application/pdf"),
"png" => (Viewtype::Image, "image/png"),
"ppt" => (Viewtype::File, "application/vnd.ms-powerpoint"),
"pptx" => (
Viewtype::File,
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
),
"rar" => (Viewtype::File, "application/vnd.rar"),
"rtf" => (Viewtype::File, "application/rtf"),
"spx" => (Viewtype::File, "audio/ogg"), // Ogg Speex Profile
@@ -1212,6 +1220,7 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
"tiff" => (Viewtype::File, "image/tiff"),
"tif" => (Viewtype::File, "image/tiff"),
"ttf" => (Viewtype::File, "font/ttf"),
"txt" => (Viewtype::File, "text/plain"),
"vcard" => (Viewtype::File, "text/vcard"),
"vcf" => (Viewtype::File, "text/vcard"),
"wav" => (Viewtype::File, "audio/wav"),
@@ -1221,11 +1230,12 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
"wmv" => (Viewtype::Video, "video/x-ms-wmv"),
"xdc" => (Viewtype::Webxdc, "application/webxdc+zip"),
"xhtml" => (Viewtype::File, "application/xhtml+xml"),
"xls" => (Viewtype::File, "application/vnd.ms-excel"),
"xlsx" => (
Viewtype::File,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
),
"xml" => (Viewtype::File, "application/vnd.ms-excel"),
"xml" => (Viewtype::File, "application/xml"),
"zip" => (Viewtype::File, "application/zip"),
_ => {
return None;

View File

@@ -799,6 +799,7 @@ impl<'a> MimeFactory<'a> {
})
}
/// Returns MIME part with a `message.kml` attachment.
fn get_message_kml_part(&self) -> Option<PartBuilder> {
let latitude = self.msg.param.get_float(Param::SetLatitude)?;
let longitude = self.msg.param.get_float(Param::SetLongitude)?;
@@ -818,6 +819,7 @@ impl<'a> MimeFactory<'a> {
Some(part)
}
/// Returns MIME part with a `location.kml` attachment.
async fn get_location_kml_part(&mut self, context: &Context) -> Result<PartBuilder> {
let (kml_content, last_added_location_id) =
location::get_kml(context, self.msg.chat_id).await?;

33
src/net.rs Normal file
View File

@@ -0,0 +1,33 @@
///! # Common network utilities.
use std::pin::Pin;
use std::time::Duration;
use anyhow::{Context as _, Result};
use tokio::net::{TcpStream, ToSocketAddrs};
use tokio::time::timeout;
use tokio_io_timeout::TimeoutStream;
/// Returns a TCP connection stream with read/write timeouts set
/// and Nagle's algorithm disabled with `TCP_NODELAY`.
///
/// `TCP_NODELAY` ensures writing to the stream always results in immediate sending of the packet
/// to the network, which is important to reduce the latency of interactive protocols such as IMAP.
pub(crate) async fn connect_tcp(
addr: impl ToSocketAddrs,
timeout_val: Duration,
) -> Result<Pin<Box<TimeoutStream<TcpStream>>>> {
let tcp_stream = timeout(timeout_val, TcpStream::connect(addr))
.await
.context("connection timeout")?
.context("connection failure")?;
// Disable Nagle's algorithm.
tcp_stream.set_nodelay(true)?;
let mut timeout_stream = TimeoutStream::new(tcp_stream);
timeout_stream.set_write_timeout(Some(timeout_val));
timeout_stream.set_read_timeout(Some(timeout_val));
let pinned_stream = Box::pin(timeout_stream);
Ok(pinned_stream)
}

View File

@@ -141,8 +141,19 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
match ctx.get_config_bool(Config::FetchedExistingMsgs).await {
Ok(fetched_existing_msgs) => {
if !fetched_existing_msgs {
// Consider it done even if we fail.
//
// This operation is not critical enough to retry,
// especially if the error is persistent.
if let Err(err) =
ctx.set_config_bool(Config::FetchedExistingMsgs, true).await
{
warn!(ctx, "Can't set Config::FetchedExistingMsgs: {:#}", err);
}
if let Err(err) = connection.fetch_existing_msgs(&ctx).await {
warn!(ctx, "Failed to fetch existing messages: {:#}", err);
connection.trigger_reconnect(&ctx);
}
}
}
@@ -198,8 +209,8 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder_config: Config)
.await
.context("prepare IMAP connection")
{
connection.trigger_reconnect(ctx);
warn!(ctx, "{:#}", err);
connection.trigger_reconnect(ctx);
return connection.fake_idle(ctx, Some(watch_folder)).await;
}

View File

@@ -1,13 +1,13 @@
//! # Simplify incoming plaintext.
// protect lines starting with `--` against being treated as a footer.
// for that, we insert a ZERO WIDTH SPACE (ZWSP, 0x200B);
// this should be invisible on most systems and there is no need to unescape it again
// (which won't be done by non-deltas anyway)
//
// this escapes a bit more than actually needed by delta (eg. also lines as "-- footer"),
// but for non-delta-compatibility, that seems to be better.
// (to be only compatible with delta, only "[\r\n|\n]-- {0,2}[\r\n|\n]" needs to be replaced)
/// Protects lines starting with `--` against being treated as a footer.
/// for that, we insert a ZERO WIDTH SPACE (ZWSP, 0x200B);
/// this should be invisible on most systems and there is no need to unescape it again
/// (which won't be done by non-deltas anyway).
///
/// This escapes a bit more than actually needed by delta (e.g. also lines as "-- footer"),
/// but for non-delta-compatibility, that seems to be better.
/// (to be only compatible with delta, only "[\r\n|\n]-- {0,2}[\r\n|\n]" needs to be replaced)
pub fn escape_message_footer_marks(text: &str) -> String {
if let Some(text) = text.strip_prefix("--") {
"-\u{200B}-".to_string() + &text.replace("\n--", "\n-\u{200B}-")
@@ -74,6 +74,7 @@ pub(crate) fn split_lines(buf: &str) -> Vec<&str> {
/// Simplified text and some additional information gained from the input.
#[derive(Debug, Default)]
pub(crate) struct SimplifiedText {
/// The text itself.
pub text: String,
/// True if the message is forwarded.

View File

@@ -4,10 +4,10 @@ use std::fmt;
use std::pin::Pin;
use std::time::Duration;
use anyhow::{Context as _, Result};
use crate::net::connect_tcp;
use anyhow::Result;
pub use async_smtp::ServerAddress;
use tokio::net::{self, TcpStream};
use tokio::time::timeout;
use tokio_io_timeout::TimeoutStream;
use crate::context::Context;
@@ -59,14 +59,7 @@ impl Socks5Config {
target_addr: impl net::ToSocketAddrs,
timeout_val: Duration,
) -> Result<Socks5Stream<Pin<Box<TimeoutStream<TcpStream>>>>> {
let tcp_stream = timeout(timeout_val, TcpStream::connect(target_addr))
.await
.context("connection timeout")?
.context("connection failure")?;
let mut timeout_stream = TimeoutStream::new(tcp_stream);
timeout_stream.set_write_timeout(Some(timeout_val));
timeout_stream.set_read_timeout(Some(timeout_val));
let timeout_stream = Box::pin(timeout_stream);
let tcp_stream = connect_tcp(target_addr, timeout_val).await?;
let authentication_method = if let Some((username, password)) = self.user_password.as_ref()
{
@@ -78,8 +71,7 @@ impl Socks5Config {
None
};
let socks_stream =
Socks5Stream::use_stream(timeout_stream, authentication_method, Config::default())
.await?;
Socks5Stream::use_stream(tcp_stream, authentication_method, Config::default()).await?;
Ok(socks_stream)
}

View File

@@ -1,7 +1,5 @@
//! # Message summary for chatlist.
#![allow(missing_docs)]
use crate::chat::Chat;
use crate::constants::Chattype;
use crate::contact::{Contact, ContactId};
@@ -54,6 +52,8 @@ pub struct Summary {
}
impl Summary {
/// Constucts chatlist summary
/// from the provided message, chat and message author contact snapshots.
pub async fn new(
context: &Context,
msg: &Message,