mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2024-11-28 09:07:32 +00:00
Bump to mail-parser 0.9
This commit is contained in:
parent
6939603932
commit
833db92ded
34 changed files with 513 additions and 580 deletions
101
Cargo.lock
generated
101
Cargo.lock
generated
|
@ -1297,7 +1297,7 @@ dependencies = [
|
||||||
"smtp-proto",
|
"smtp-proto",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
"tracing",
|
"tracing",
|
||||||
"utils",
|
"utils",
|
||||||
]
|
]
|
||||||
|
@ -1480,14 +1480,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enum-as-inner"
|
name = "enum-as-inner"
|
||||||
version = "0.5.1"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116"
|
checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 2.0.29",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2143,7 +2143,7 @@ dependencies = [
|
||||||
"hyper 0.14.27",
|
"hyper 0.14.27",
|
||||||
"rustls 0.21.7",
|
"rustls 0.21.7",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2214,17 +2214,6 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "idna"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
|
||||||
dependencies = [
|
|
||||||
"matches",
|
|
||||||
"unicode-bidi",
|
|
||||||
"unicode-normalization",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -2253,7 +2242,7 @@ dependencies = [
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
"store",
|
"store",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
"tracing",
|
"tracing",
|
||||||
"utils",
|
"utils",
|
||||||
]
|
]
|
||||||
|
@ -2578,7 +2567,7 @@ dependencies = [
|
||||||
"rustls-native-certs",
|
"rustls-native-certs",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"url",
|
"url",
|
||||||
|
@ -2694,8 +2683,8 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mail-auth"
|
name = "mail-auth"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
source = "git+https://github.com/stalwartlabs/mail-auth#a6cd1d6cc0a79943903e8154eecc29f2de003e2a"
|
source = "git+https://github.com/stalwartlabs/mail-auth#949b8fcd91f329b424e22a3f2bbc3040869f3490"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.3",
|
"ahash 0.8.3",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
@ -2703,7 +2692,7 @@ dependencies = [
|
||||||
"mail-builder",
|
"mail-builder",
|
||||||
"mail-parser",
|
"mail-parser",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"quick-xml 0.28.2",
|
"quick-xml 0.30.0",
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2722,8 +2711,8 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mail-parser"
|
name = "mail-parser"
|
||||||
version = "0.8.2"
|
version = "0.9.0"
|
||||||
source = "git+https://github.com/stalwartlabs/mail-parser#7c08078617ef9b355da445dbf88df64879eb8059"
|
source = "git+https://github.com/stalwartlabs/mail-parser#e5a4e65112fd8aa4c527d37b87413d939f1259a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2740,7 +2729,7 @@ dependencies = [
|
||||||
"rustls 0.21.7",
|
"rustls 0.21.7",
|
||||||
"smtp-proto",
|
"smtp-proto",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
"webpki-roots 0.25.2",
|
"webpki-roots 0.25.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2781,7 +2770,7 @@ dependencies = [
|
||||||
"sieve-rs",
|
"sieve-rs",
|
||||||
"store",
|
"store",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
"tracing",
|
"tracing",
|
||||||
"utils",
|
"utils",
|
||||||
]
|
]
|
||||||
|
@ -2807,12 +2796,6 @@ dependencies = [
|
||||||
"regex-automata 0.1.10",
|
"regex-automata 0.1.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "matches"
|
|
||||||
version = "0.1.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchit"
|
name = "matchit"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -3733,9 +3716,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.28.2"
|
version = "0.30.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1"
|
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -3977,7 +3960,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"url",
|
"url",
|
||||||
|
@ -4552,7 +4535,7 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sieve-rs"
|
name = "sieve-rs"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
source = "git+https://github.com/stalwartlabs/sieve#53ff94606cf4a3e03b8820280ef8fae0bfa500ec"
|
source = "git+https://github.com/stalwartlabs/sieve#f17c085cbefd2c422d49924795d68255a6c7658c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.3",
|
"ahash 0.8.3",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
@ -4637,7 +4620,7 @@ dependencies = [
|
||||||
"smtp-proto",
|
"smtp-proto",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
"tracing",
|
"tracing",
|
||||||
"utils",
|
"utils",
|
||||||
"webpki-roots 0.25.2",
|
"webpki-roots 0.25.2",
|
||||||
|
@ -5154,7 +5137,7 @@ dependencies = [
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"store",
|
"store",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"utils",
|
"utils",
|
||||||
|
@ -5294,17 +5277,6 @@ dependencies = [
|
||||||
"syn 2.0.29",
|
"syn 2.0.29",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-rustls"
|
|
||||||
version = "0.23.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
|
|
||||||
dependencies = [
|
|
||||||
"rustls 0.20.9",
|
|
||||||
"tokio",
|
|
||||||
"webpki",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.24.1"
|
version = "0.24.1"
|
||||||
|
@ -5336,7 +5308,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"rustls 0.21.7",
|
"rustls 0.21.7",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
"tungstenite 0.19.0",
|
"tungstenite 0.19.0",
|
||||||
"webpki-roots 0.23.1",
|
"webpki-roots 0.23.1",
|
||||||
]
|
]
|
||||||
|
@ -5544,9 +5516,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "trust-dns-proto"
|
name = "trust-dns-proto"
|
||||||
version = "0.22.0"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26"
|
checksum = "0dc775440033cb114085f6f2437682b194fa7546466024b1037e82a48a052a69"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
@ -5555,44 +5527,45 @@ dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"idna 0.2.3",
|
"idna",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"lazy_static",
|
"once_cell",
|
||||||
"rand",
|
"rand",
|
||||||
"ring",
|
"ring",
|
||||||
"rustls 0.20.9",
|
"rustls 0.21.7",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile",
|
||||||
|
"rustls-webpki 0.101.4",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.23.4",
|
"tokio-rustls",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
"webpki",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "trust-dns-resolver"
|
name = "trust-dns-resolver"
|
||||||
version = "0.22.0"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe"
|
checksum = "2dff7aed33ef3e8bf2c9966fccdfed93f93d46f432282ea875cd66faabc6ef2f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"ipconfig",
|
"ipconfig",
|
||||||
"lazy_static",
|
|
||||||
"lru-cache",
|
"lru-cache",
|
||||||
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"rand",
|
||||||
"resolv-conf",
|
"resolv-conf",
|
||||||
"rustls 0.20.9",
|
"rustls 0.21.7",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.23.4",
|
"tokio-rustls",
|
||||||
"tracing",
|
"tracing",
|
||||||
"trust-dns-proto",
|
"trust-dns-proto",
|
||||||
"webpki-roots 0.22.6",
|
"webpki-roots 0.25.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -5745,7 +5718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna 0.4.0",
|
"idna",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -5778,7 +5751,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"smtp-proto",
|
"smtp-proto",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-appender",
|
"tracing-appender",
|
||||||
"tracing-opentelemetry",
|
"tracing-opentelemetry",
|
||||||
|
|
|
@ -29,7 +29,7 @@ use imap_proto::{
|
||||||
|
|
||||||
use jmap::email::ingest::IngestEmail;
|
use jmap::email::ingest::IngestEmail;
|
||||||
use jmap_proto::types::{acl::Acl, keyword::Keyword, state::StateChange, type_state::TypeState};
|
use jmap_proto::types::{acl::Acl, keyword::Keyword, state::StateChange, type_state::TypeState};
|
||||||
use mail_parser::Message;
|
use mail_parser::MessageParser;
|
||||||
use tokio::io::AsyncRead;
|
use tokio::io::AsyncRead;
|
||||||
|
|
||||||
use crate::core::{MailboxId, SelectedMailbox, Session, SessionData};
|
use crate::core::{MailboxId, SelectedMailbox, Session, SessionData};
|
||||||
|
@ -135,7 +135,7 @@ impl SessionData {
|
||||||
.jmap
|
.jmap
|
||||||
.email_ingest(IngestEmail {
|
.email_ingest(IngestEmail {
|
||||||
raw_message: &message.message,
|
raw_message: &message.message,
|
||||||
message: Message::parse(&message.message),
|
message: MessageParser::new().parse(&message.message),
|
||||||
account_id,
|
account_id,
|
||||||
account_quota,
|
account_quota,
|
||||||
mailbox_ids: vec![mailbox_id],
|
mailbox_ids: vec![mailbox_id],
|
||||||
|
|
|
@ -45,7 +45,7 @@ use jmap_proto::{
|
||||||
property::Property, state::StateChange, type_state::TypeState, value::Value,
|
property::Property, state::StateChange, type_state::TypeState, value::Value,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use mail_parser::{GetHeader, Message, PartType, RfcHeader};
|
use mail_parser::{Address, GetHeader, HeaderName, Message, MessageParser, PartType};
|
||||||
use store::{
|
use store::{
|
||||||
query::log::{Change, Query},
|
query::log::{Change, Query},
|
||||||
write::{assert::HashedValue, BatchBuilder, F_BITMAP, F_VALUE},
|
write::{assert::HashedValue, BatchBuilder, F_BITMAP, F_VALUE},
|
||||||
|
@ -319,7 +319,7 @@ impl SessionData {
|
||||||
};
|
};
|
||||||
|
|
||||||
let message = if let Some(raw_message) = &raw_message {
|
let message = if let Some(raw_message) = &raw_message {
|
||||||
if let Some(message) = Message::parse(raw_message) {
|
if let Some(message) = MessageParser::new().parse(raw_message) {
|
||||||
message.into()
|
message.into()
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
|
@ -671,8 +671,8 @@ impl<'x> AsImapDataItem<'x> for Message<'x> {
|
||||||
};
|
};
|
||||||
let content_type = part
|
let content_type = part
|
||||||
.headers
|
.headers
|
||||||
.rfc(&RfcHeader::ContentType)
|
.header_value(&HeaderName::ContentType)
|
||||||
.and_then(|ct| ct.as_content_type_ref());
|
.and_then(|ct| ct.as_content_type());
|
||||||
|
|
||||||
let mut body_md5 = None;
|
let mut body_md5 = None;
|
||||||
let mut extension = BodyPartExtension::default();
|
let mut extension = BodyPartExtension::default();
|
||||||
|
@ -695,18 +695,18 @@ impl<'x> AsImapDataItem<'x> for Message<'x> {
|
||||||
|
|
||||||
fields.body_id = part
|
fields.body_id = part
|
||||||
.headers
|
.headers
|
||||||
.rfc(&RfcHeader::ContentId)
|
.header_value(&HeaderName::ContentId)
|
||||||
.and_then(|id| id.as_text_ref().map(|id| format!("<{}>", id).into()));
|
.and_then(|id| id.as_text().map(|id| format!("<{}>", id).into()));
|
||||||
|
|
||||||
fields.body_description = part
|
fields.body_description = part
|
||||||
.headers
|
.headers
|
||||||
.rfc(&RfcHeader::ContentDescription)
|
.header_value(&HeaderName::ContentDescription)
|
||||||
.and_then(|ct| ct.as_text_ref().map(|ct| ct.into()));
|
.and_then(|ct| ct.as_text().map(|ct| ct.into()));
|
||||||
|
|
||||||
fields.body_encoding = part
|
fields.body_encoding = part
|
||||||
.headers
|
.headers
|
||||||
.rfc(&RfcHeader::ContentTransferEncoding)
|
.header_value(&HeaderName::ContentTransferEncoding)
|
||||||
.and_then(|ct| ct.as_text_ref().map(|ct| ct.into()));
|
.and_then(|ct| ct.as_text().map(|ct| ct.into()));
|
||||||
|
|
||||||
fields.body_size_octets = body.as_ref().map(|b| b.len()).unwrap_or(0);
|
fields.body_size_octets = body.as_ref().map(|b| b.len()).unwrap_or(0);
|
||||||
|
|
||||||
|
@ -730,8 +730,10 @@ impl<'x> AsImapDataItem<'x> for Message<'x> {
|
||||||
.map(|b| format!("{:x}", md5::compute(b)).into());
|
.map(|b| format!("{:x}", md5::compute(b)).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
extension.body_disposition =
|
extension.body_disposition = part
|
||||||
part.headers.rfc(&RfcHeader::ContentDisposition).map(|cd| {
|
.headers
|
||||||
|
.header_value(&HeaderName::ContentDisposition)
|
||||||
|
.map(|cd| {
|
||||||
let cd = cd.content_type();
|
let cd = cd.content_type();
|
||||||
|
|
||||||
(
|
(
|
||||||
|
@ -747,18 +749,18 @@ impl<'x> AsImapDataItem<'x> for Message<'x> {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
extension.body_language =
|
extension.body_language = part
|
||||||
part.headers
|
.headers
|
||||||
.rfc(&RfcHeader::ContentLanguage)
|
.header_value(&HeaderName::ContentLanguage)
|
||||||
.and_then(|hv| {
|
.and_then(|hv| {
|
||||||
hv.as_text_list()
|
hv.as_text_list()
|
||||||
.map(|list| list.into_iter().map(|item| item.into()).collect())
|
.map(|list| list.into_iter().map(|item| item.into()).collect())
|
||||||
});
|
});
|
||||||
|
|
||||||
extension.body_location = part
|
extension.body_location = part
|
||||||
.headers
|
.headers
|
||||||
.rfc(&RfcHeader::ContentLocation)
|
.header_value(&HeaderName::ContentLocation)
|
||||||
.and_then(|ct| ct.as_text_ref().map(|ct| ct.into()));
|
.and_then(|ct| ct.as_text().map(|ct| ct.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
match &part.body {
|
match &part.body {
|
||||||
|
@ -1045,27 +1047,27 @@ impl<'x> AsImapDataItem<'x> for Message<'x> {
|
||||||
date: self.date().cloned(),
|
date: self.date().cloned(),
|
||||||
subject: self.subject().map(|s| s.into()),
|
subject: self.subject().map(|s| s.into()),
|
||||||
from: self
|
from: self
|
||||||
.header_values(RfcHeader::From)
|
.header_values(HeaderName::From)
|
||||||
.flat_map(|a| a.as_imap_address())
|
.flat_map(|a| a.as_imap_address())
|
||||||
.collect(),
|
.collect(),
|
||||||
sender: self
|
sender: self
|
||||||
.header_values(RfcHeader::Sender)
|
.header_values(HeaderName::Sender)
|
||||||
.flat_map(|a| a.as_imap_address())
|
.flat_map(|a| a.as_imap_address())
|
||||||
.collect(),
|
.collect(),
|
||||||
reply_to: self
|
reply_to: self
|
||||||
.header_values(RfcHeader::ReplyTo)
|
.header_values(HeaderName::ReplyTo)
|
||||||
.flat_map(|a| a.as_imap_address())
|
.flat_map(|a| a.as_imap_address())
|
||||||
.collect(),
|
.collect(),
|
||||||
to: self
|
to: self
|
||||||
.header_values(RfcHeader::To)
|
.header_values(HeaderName::To)
|
||||||
.flat_map(|a| a.as_imap_address())
|
.flat_map(|a| a.as_imap_address())
|
||||||
.collect(),
|
.collect(),
|
||||||
cc: self
|
cc: self
|
||||||
.header_values(RfcHeader::Cc)
|
.header_values(HeaderName::Cc)
|
||||||
.flat_map(|a| a.as_imap_address())
|
.flat_map(|a| a.as_imap_address())
|
||||||
.collect(),
|
.collect(),
|
||||||
bcc: self
|
bcc: self
|
||||||
.header_values(RfcHeader::Bcc)
|
.header_values(HeaderName::Bcc)
|
||||||
.flat_map(|a| a.as_imap_address())
|
.flat_map(|a| a.as_imap_address())
|
||||||
.collect(),
|
.collect(),
|
||||||
in_reply_to: self.in_reply_to().as_text_list().map(|list| {
|
in_reply_to: self.in_reply_to().as_text_list().map(|list| {
|
||||||
|
@ -1109,15 +1111,7 @@ impl AsImapAddress for mail_parser::HeaderValue<'_> {
|
||||||
let mut addresses = Vec::new();
|
let mut addresses = Vec::new();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
mail_parser::HeaderValue::Address(addr) => {
|
mail_parser::HeaderValue::Address(Address::List(list)) => {
|
||||||
if let Some(email) = &addr.address {
|
|
||||||
addresses.push(fetch::Address::Single(fetch::EmailAddress {
|
|
||||||
name: addr.name.as_ref().map(|n| n.as_ref().into()),
|
|
||||||
address: email.as_ref().into(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mail_parser::HeaderValue::AddressList(list) => {
|
|
||||||
for addr in list {
|
for addr in list {
|
||||||
if let Some(email) = &addr.address {
|
if let Some(email) = &addr.address {
|
||||||
addresses.push(fetch::Address::Single(fetch::EmailAddress {
|
addresses.push(fetch::Address::Single(fetch::EmailAddress {
|
||||||
|
@ -1127,23 +1121,7 @@ impl AsImapAddress for mail_parser::HeaderValue<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mail_parser::HeaderValue::Group(group) => {
|
mail_parser::HeaderValue::Address(Address::Group(list)) => {
|
||||||
addresses.push(fetch::Address::Group(fetch::AddressGroup {
|
|
||||||
name: group.name.as_ref().map(|n| n.as_ref().into()),
|
|
||||||
addresses: group
|
|
||||||
.addresses
|
|
||||||
.iter()
|
|
||||||
.filter_map(|addr| {
|
|
||||||
fetch::EmailAddress {
|
|
||||||
name: addr.name.as_ref().map(|n| n.as_ref().into()),
|
|
||||||
address: addr.address.as_ref()?.as_ref().into(),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
mail_parser::HeaderValue::GroupList(list) => {
|
|
||||||
for group in list {
|
for group in list {
|
||||||
addresses.push(fetch::Address::Group(fetch::AddressGroup {
|
addresses.push(fetch::Address::Group(fetch::AddressGroup {
|
||||||
name: group.name.as_ref().map(|n| n.as_ref().into()),
|
name: group.name.as_ref().map(|n| n.as_ref().into()),
|
||||||
|
|
|
@ -33,7 +33,7 @@ use imap_proto::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use jmap_proto::types::{collection::Collection, id::Id, keyword::Keyword, property::Property};
|
use jmap_proto::types::{collection::Collection, id::Id, keyword::Keyword, property::Property};
|
||||||
use mail_parser::{HeaderName, RfcHeader};
|
use mail_parser::HeaderName;
|
||||||
use store::{
|
use store::{
|
||||||
fts::{builder::MAX_TOKEN_LENGTH, Language},
|
fts::{builder::MAX_TOKEN_LENGTH, Language},
|
||||||
query::{self, log::Query, sort::Pagination, ResultSet},
|
query::{self, log::Query, sort::Pagination, ResultSet},
|
||||||
|
@ -353,17 +353,22 @@ impl SessionData {
|
||||||
Language::None,
|
Language::None,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
search::Filter::Header(header, value) => {
|
search::Filter::Header(header, value) => match HeaderName::parse(&header) {
|
||||||
if let Some(HeaderName::Rfc(header_name)) = HeaderName::parse(&header) {
|
Some(HeaderName::Other(_)) | None => {
|
||||||
|
return Err(StatusResponse::no(format!(
|
||||||
|
"Querying non-RFC header '{header}' is not allowed.",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Some(header_name) => {
|
||||||
let is_id = matches!(
|
let is_id = matches!(
|
||||||
header_name,
|
header_name,
|
||||||
RfcHeader::MessageId
|
HeaderName::MessageId
|
||||||
| RfcHeader::InReplyTo
|
| HeaderName::InReplyTo
|
||||||
| RfcHeader::References
|
| HeaderName::References
|
||||||
| RfcHeader::ResentMessageId
|
| HeaderName::ResentMessageId
|
||||||
);
|
);
|
||||||
let tokens = if !value.is_empty() {
|
let tokens = if !value.is_empty() {
|
||||||
let header_num = u8::from(header_name).to_string();
|
let header_num = header_name.id().to_string();
|
||||||
value
|
value
|
||||||
.split_ascii_whitespace()
|
.split_ascii_whitespace()
|
||||||
.filter_map(|token| {
|
.filter_map(|token| {
|
||||||
|
@ -386,7 +391,7 @@ impl SessionData {
|
||||||
0 => {
|
0 => {
|
||||||
filters.push(query::Filter::has_raw_text(
|
filters.push(query::Filter::has_raw_text(
|
||||||
Property::Headers,
|
Property::Headers,
|
||||||
u8::from(header_name).to_string(),
|
header_name.id().to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
|
@ -406,12 +411,8 @@ impl SessionData {
|
||||||
filters.push(query::Filter::End);
|
filters.push(query::Filter::End);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
return Err(StatusResponse::no(format!(
|
},
|
||||||
"Querying non-RFC header '{header}' is not allowed.",
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
search::Filter::Keyword(keyword) => {
|
search::Filter::Keyword(keyword) => {
|
||||||
filters.push(query::Filter::is_in_bitmap(
|
filters.push(query::Filter::is_in_bitmap(
|
||||||
Property::Keywords,
|
Property::Keywords,
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use mail_parser::RfcHeader;
|
use mail_parser::HeaderName;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use store::write::{DeserializeFrom, SerializeInto};
|
use store::write::{DeserializeFrom, SerializeInto};
|
||||||
|
|
||||||
|
@ -692,19 +692,19 @@ impl Property {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_rfc_header(&self) -> RfcHeader {
|
pub fn as_rfc_header(&self) -> HeaderName<'static> {
|
||||||
match self {
|
match self {
|
||||||
Property::MessageId => RfcHeader::MessageId,
|
Property::MessageId => HeaderName::MessageId,
|
||||||
Property::InReplyTo => RfcHeader::InReplyTo,
|
Property::InReplyTo => HeaderName::InReplyTo,
|
||||||
Property::References => RfcHeader::References,
|
Property::References => HeaderName::References,
|
||||||
Property::Sender => RfcHeader::Sender,
|
Property::Sender => HeaderName::Sender,
|
||||||
Property::From => RfcHeader::From,
|
Property::From => HeaderName::From,
|
||||||
Property::To => RfcHeader::To,
|
Property::To => HeaderName::To,
|
||||||
Property::Cc => RfcHeader::Cc,
|
Property::Cc => HeaderName::Cc,
|
||||||
Property::Bcc => RfcHeader::Bcc,
|
Property::Bcc => HeaderName::Bcc,
|
||||||
Property::ReplyTo => RfcHeader::ReplyTo,
|
Property::ReplyTo => HeaderName::ReplyTo,
|
||||||
Property::Subject => RfcHeader::Subject,
|
Property::Subject => HeaderName::Subject,
|
||||||
Property::SentAt => RfcHeader::Date,
|
Property::SentAt => HeaderName::Date,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -994,21 +994,21 @@ impl From<Property> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RfcHeader> for Property {
|
impl Property {
|
||||||
fn from(value: RfcHeader) -> Self {
|
pub fn from_header(header: &HeaderName) -> Self {
|
||||||
match value {
|
match header {
|
||||||
RfcHeader::Subject => Property::Subject,
|
HeaderName::Subject => Property::Subject,
|
||||||
RfcHeader::From => Property::From,
|
HeaderName::From => Property::From,
|
||||||
RfcHeader::To => Property::To,
|
HeaderName::To => Property::To,
|
||||||
RfcHeader::Cc => Property::Cc,
|
HeaderName::Cc => Property::Cc,
|
||||||
RfcHeader::Date => Property::SentAt,
|
HeaderName::Date => Property::SentAt,
|
||||||
RfcHeader::Bcc => Property::Bcc,
|
HeaderName::Bcc => Property::Bcc,
|
||||||
RfcHeader::ReplyTo => Property::ReplyTo,
|
HeaderName::ReplyTo => Property::ReplyTo,
|
||||||
RfcHeader::Sender => Property::Sender,
|
HeaderName::Sender => Property::Sender,
|
||||||
RfcHeader::InReplyTo => Property::InReplyTo,
|
HeaderName::InReplyTo => Property::InReplyTo,
|
||||||
RfcHeader::MessageId => Property::MessageId,
|
HeaderName::MessageId => Property::MessageId,
|
||||||
RfcHeader::References => Property::References,
|
HeaderName::References => Property::References,
|
||||||
RfcHeader::ResentMessageId => Property::EmailIds,
|
HeaderName::ResentMessageId => Property::EmailIds,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -496,13 +496,7 @@ impl From<Group<'_>> for Value {
|
||||||
group
|
group
|
||||||
.addresses
|
.addresses
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|addr| {
|
.map(Value::from)
|
||||||
if addr.address.as_ref()?.contains('@') {
|
|
||||||
Some(addr.into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<Value>>(),
|
.collect::<Vec<Value>>(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -14,7 +14,7 @@ smtp-proto = { git = "https://github.com/stalwartlabs/smtp-proto" }
|
||||||
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", features = ["full_encoding", "serde_support", "ludicrous_mode"] }
|
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", features = ["full_encoding", "serde_support", "ludicrous_mode"] }
|
||||||
mail-builder = { git = "https://github.com/stalwartlabs/mail-builder", features = ["ludicrous_mode"] }
|
mail-builder = { git = "https://github.com/stalwartlabs/mail-builder", features = ["ludicrous_mode"] }
|
||||||
mail-send = { git = "https://github.com/stalwartlabs/mail-send", default-features = false, features = ["cram-md5", "skip-ehlo"] }
|
mail-send = { git = "https://github.com/stalwartlabs/mail-send", default-features = false, features = ["cram-md5", "skip-ehlo"] }
|
||||||
sieve-rs = { git = "https://github.com/stalwartlabs/sieve" }
|
sieve-rs = { git = "https://github.com/stalwartlabs/sieve" }
|
||||||
serde = { version = "1.0", features = ["derive"]}
|
serde = { version = "1.0", features = ["derive"]}
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
hyper = { version = "1.0.0-rc.4", features = ["server", "http1", "http2"] }
|
hyper = { version = "1.0.0-rc.4", features = ["server", "http1", "http2"] }
|
||||||
|
|
|
@ -26,7 +26,7 @@ use std::{borrow::Cow, collections::BTreeSet, fmt::Display};
|
||||||
use aes::cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit};
|
use aes::cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit};
|
||||||
use jmap_proto::types::{collection::Collection, property::Property};
|
use jmap_proto::types::{collection::Collection, property::Property};
|
||||||
use mail_builder::{encoders::base64::base64_encode_mime, mime::make_boundary};
|
use mail_builder::{encoders::base64::base64_encode_mime, mime::make_boundary};
|
||||||
use mail_parser::{decoders::base64::base64_decode, Message, MimeHeaders};
|
use mail_parser::{decoders::base64::base64_decode, Message, MessageParser, MimeHeaders};
|
||||||
use pgp::{composed, crypto::sym::SymmetricKeyAlgorithm, Deserializable, SignedPublicKey};
|
use pgp::{composed, crypto::sym::SymmetricKeyAlgorithm, Deserializable, SignedPublicKey};
|
||||||
use rand::{rngs::StdRng, RngCore, SeedableRng};
|
use rand::{rngs::StdRng, RngCore, SeedableRng};
|
||||||
use rasn::types::{ObjectIdentifier, OctetString};
|
use rasn::types::{ObjectIdentifier, OctetString};
|
||||||
|
@ -632,11 +632,11 @@ impl JMAP {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try a test encryption
|
// Try a test encryption
|
||||||
if let Err(EncryptMessageError::Error(message)) =
|
if let Err(EncryptMessageError::Error(message)) = MessageParser::new()
|
||||||
Message::parse("Subject: test\r\ntest\r\n".as_bytes())
|
.parse("Subject: test\r\ntest\r\n".as_bytes())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.encrypt(¶ms)
|
.encrypt(¶ms)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Err(Cow::from(message));
|
return Err(Cow::from(message));
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ use jmap_proto::{
|
||||||
property::Property, value::Value,
|
property::Property, value::Value,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use mail_parser::Message;
|
use mail_parser::MessageParser;
|
||||||
|
|
||||||
use crate::{auth::AccessToken, email::headers::HeaderToValue, JMAP};
|
use crate::{auth::AccessToken, email::headers::HeaderToValue, JMAP};
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ impl JMAP {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
let message = if !raw_message.is_empty() {
|
let message = if !raw_message.is_empty() {
|
||||||
let message = Message::parse(&raw_message);
|
let message = MessageParser::new().parse(&raw_message);
|
||||||
if message.is_none() {
|
if message.is_none() {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
event = "parse-error",
|
event = "parse-error",
|
||||||
|
|
|
@ -41,7 +41,7 @@ use mail_builder::{
|
||||||
},
|
},
|
||||||
MessageBuilder,
|
MessageBuilder,
|
||||||
};
|
};
|
||||||
use mail_parser::{parsers::MessageStream, Addr, HeaderName, HeaderValue, MessagePart, RfcHeader};
|
use mail_parser::{parsers::MessageStream, Addr, HeaderName, HeaderValue, MessagePart};
|
||||||
|
|
||||||
pub trait IntoForm {
|
pub trait IntoForm {
|
||||||
fn into_form(self, form: &HeaderForm) -> Value;
|
fn into_form(self, form: &HeaderForm) -> Value;
|
||||||
|
@ -71,52 +71,24 @@ impl HeaderToValue for MessagePart<'_> {
|
||||||
header.form,
|
header.form,
|
||||||
header.all,
|
header.all,
|
||||||
),
|
),
|
||||||
Property::Sender => (
|
Property::Sender => (HeaderName::Sender, HeaderForm::Addresses, false),
|
||||||
HeaderName::Rfc(RfcHeader::Sender),
|
Property::From => (HeaderName::From, HeaderForm::Addresses, false),
|
||||||
HeaderForm::Addresses,
|
Property::To => (HeaderName::To, HeaderForm::Addresses, false),
|
||||||
false,
|
Property::Cc => (HeaderName::Cc, HeaderForm::Addresses, false),
|
||||||
),
|
Property::Bcc => (HeaderName::Bcc, HeaderForm::Addresses, false),
|
||||||
Property::From => (
|
Property::ReplyTo => (HeaderName::ReplyTo, HeaderForm::Addresses, false),
|
||||||
HeaderName::Rfc(RfcHeader::From),
|
Property::Subject => (HeaderName::Subject, HeaderForm::Text, false),
|
||||||
HeaderForm::Addresses,
|
Property::MessageId => (HeaderName::MessageId, HeaderForm::MessageIds, false),
|
||||||
false,
|
Property::InReplyTo => (HeaderName::InReplyTo, HeaderForm::MessageIds, false),
|
||||||
),
|
Property::References => (HeaderName::References, HeaderForm::MessageIds, false),
|
||||||
Property::To => (HeaderName::Rfc(RfcHeader::To), HeaderForm::Addresses, false),
|
Property::SentAt => (HeaderName::Date, HeaderForm::Date, false),
|
||||||
Property::Cc => (HeaderName::Rfc(RfcHeader::Cc), HeaderForm::Addresses, false),
|
|
||||||
Property::Bcc => (
|
|
||||||
HeaderName::Rfc(RfcHeader::Bcc),
|
|
||||||
HeaderForm::Addresses,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
Property::ReplyTo => (
|
|
||||||
HeaderName::Rfc(RfcHeader::ReplyTo),
|
|
||||||
HeaderForm::Addresses,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
Property::Subject => (HeaderName::Rfc(RfcHeader::Subject), HeaderForm::Text, false),
|
|
||||||
Property::MessageId => (
|
|
||||||
HeaderName::Rfc(RfcHeader::MessageId),
|
|
||||||
HeaderForm::MessageIds,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
Property::InReplyTo => (
|
|
||||||
HeaderName::Rfc(RfcHeader::InReplyTo),
|
|
||||||
HeaderForm::MessageIds,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
Property::References => (
|
|
||||||
HeaderName::Rfc(RfcHeader::References),
|
|
||||||
HeaderForm::MessageIds,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
Property::SentAt => (HeaderName::Rfc(RfcHeader::Date), HeaderForm::Date, false),
|
|
||||||
_ => return Value::Null,
|
_ => return Value::Null,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut headers = Vec::new();
|
let mut headers = Vec::new();
|
||||||
|
|
||||||
match (&header_name, &form) {
|
match (&header_name, &form) {
|
||||||
(HeaderName::Other(_), _) | (HeaderName::Rfc(_), HeaderForm::Raw) => {
|
(HeaderName::Other(_), _) | (_, HeaderForm::Raw) => {
|
||||||
let header_name = header_name.as_str();
|
let header_name = header_name.as_str();
|
||||||
for header in self.headers().iter().rev() {
|
for header in self.headers().iter().rev() {
|
||||||
if header.name.as_str().eq_ignore_ascii_case(header_name) {
|
if header.name.as_str().eq_ignore_ascii_case(header_name) {
|
||||||
|
@ -142,7 +114,7 @@ impl HeaderToValue for MessagePart<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(HeaderName::Rfc(header_name), _) => {
|
(header_name, _) => {
|
||||||
let header_name = header_name.as_str();
|
let header_name = header_name.as_str();
|
||||||
for header in self.headers().iter().rev() {
|
for header in self.headers().iter().rev() {
|
||||||
if header.name.as_str().eq_ignore_ascii_case(header_name) {
|
if header.name.as_str().eq_ignore_ascii_case(header_name) {
|
||||||
|
@ -197,58 +169,44 @@ impl IntoForm for HeaderValue<'_> {
|
||||||
(HeaderValue::Text(text), HeaderForm::MessageIds) => Value::List(vec![text.into()]),
|
(HeaderValue::Text(text), HeaderForm::MessageIds) => Value::List(vec![text.into()]),
|
||||||
(HeaderValue::TextList(texts), HeaderForm::MessageIds) => texts.into(),
|
(HeaderValue::TextList(texts), HeaderForm::MessageIds) => texts.into(),
|
||||||
(HeaderValue::DateTime(datetime), HeaderForm::Date) => datetime.into(),
|
(HeaderValue::DateTime(datetime), HeaderForm::Date) => datetime.into(),
|
||||||
|
(HeaderValue::Address(mail_parser::Address::List(addrlist)), HeaderForm::URLs) => {
|
||||||
|
Value::List(
|
||||||
|
addrlist
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|addr| match addr {
|
||||||
|
Addr {
|
||||||
|
address: Some(addr),
|
||||||
|
..
|
||||||
|
} if addr.contains(':') => Some(addr.into()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(HeaderValue::Address(mail_parser::Address::List(addrlist)), HeaderForm::Addresses) => {
|
||||||
|
addrlist.into()
|
||||||
|
}
|
||||||
(
|
(
|
||||||
HeaderValue::Address(Addr {
|
HeaderValue::Address(mail_parser::Address::Group(grouplist)),
|
||||||
address: Some(addr),
|
HeaderForm::Addresses,
|
||||||
..
|
) => Value::List(
|
||||||
}),
|
|
||||||
HeaderForm::URLs,
|
|
||||||
) if addr.contains(':') => Value::List(vec![addr.into()]),
|
|
||||||
(HeaderValue::AddressList(addrlist), HeaderForm::URLs) => Value::List(
|
|
||||||
addrlist
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|addr| match addr {
|
|
||||||
Addr {
|
|
||||||
address: Some(addr),
|
|
||||||
..
|
|
||||||
} if addr.contains(':') => Some(addr.into()),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
(HeaderValue::Address(addr), HeaderForm::Addresses) => Value::List(vec![addr.into()]),
|
|
||||||
(HeaderValue::AddressList(addrlist), HeaderForm::Addresses) => addrlist.into(),
|
|
||||||
(HeaderValue::Group(group), HeaderForm::Addresses) => group.addresses.into(),
|
|
||||||
(HeaderValue::GroupList(grouplist), HeaderForm::Addresses) => Value::List(
|
|
||||||
grouplist
|
grouplist
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|group| group.addresses)
|
.flat_map(|group| group.addresses)
|
||||||
.filter_map(|addr| {
|
.map(Value::from)
|
||||||
if addr.address.as_ref()?.contains('@') {
|
|
||||||
Some(addr.into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
(HeaderValue::Address(addr), HeaderForm::GroupedAddresses) => {
|
(
|
||||||
Value::List(vec![Object::with_capacity(2)
|
HeaderValue::Address(mail_parser::Address::List(addrlist)),
|
||||||
.with_property(Property::Name, Value::Null)
|
HeaderForm::GroupedAddresses,
|
||||||
.with_property(Property::Addresses, Value::List(vec![addr.into()]))
|
) => Value::List(vec![Object::with_capacity(2)
|
||||||
.into()])
|
.with_property(Property::Name, Value::Null)
|
||||||
}
|
.with_property(Property::Addresses, addrlist)
|
||||||
|
.into()]),
|
||||||
(HeaderValue::AddressList(addrlist), HeaderForm::GroupedAddresses) => {
|
(
|
||||||
Value::List(vec![Object::with_capacity(2)
|
HeaderValue::Address(mail_parser::Address::Group(grouplist)),
|
||||||
.with_property(Property::Name, Value::Null)
|
HeaderForm::GroupedAddresses,
|
||||||
.with_property(Property::Addresses, addrlist)
|
) => grouplist.into(),
|
||||||
.into()])
|
|
||||||
}
|
|
||||||
(HeaderValue::Group(group), HeaderForm::GroupedAddresses) => {
|
|
||||||
Value::List(vec![group.into()])
|
|
||||||
}
|
|
||||||
(HeaderValue::GroupList(grouplist), HeaderForm::GroupedAddresses) => grouplist.into(),
|
|
||||||
|
|
||||||
_ => Value::Null,
|
_ => Value::Null,
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ use jmap_proto::{
|
||||||
type_state::TypeState,
|
type_state::TypeState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use mail_parser::Message;
|
use mail_parser::MessageParser;
|
||||||
use utils::map::vec_map::VecMap;
|
use utils::map::vec_map::VecMap;
|
||||||
|
|
||||||
use crate::{auth::AccessToken, IngestError, JMAP};
|
use crate::{auth::AccessToken, IngestError, JMAP};
|
||||||
|
@ -134,7 +134,7 @@ impl JMAP {
|
||||||
match self
|
match self
|
||||||
.email_ingest(IngestEmail {
|
.email_ingest(IngestEmail {
|
||||||
raw_message: &raw_message,
|
raw_message: &raw_message,
|
||||||
message: Message::parse(&raw_message),
|
message: MessageParser::new().parse(&raw_message),
|
||||||
account_id,
|
account_id,
|
||||||
account_quota,
|
account_quota,
|
||||||
mailbox_ids,
|
mailbox_ids,
|
||||||
|
|
|
@ -35,7 +35,7 @@ use jmap_proto::{
|
||||||
use mail_parser::{
|
use mail_parser::{
|
||||||
decoders::html::html_to_text,
|
decoders::html::html_to_text,
|
||||||
parsers::{fields::thread::thread_name, preview::preview_text},
|
parsers::{fields::thread::thread_name, preview::preview_text},
|
||||||
Addr, GetHeader, Group, HeaderName, HeaderValue, Message, MessagePart, PartType, RfcHeader,
|
Addr, Address, GetHeader, Group, HeaderName, HeaderValue, Message, MessagePart, PartType,
|
||||||
};
|
};
|
||||||
use store::{
|
use store::{
|
||||||
fts::{
|
fts::{
|
||||||
|
@ -53,6 +53,7 @@ pub const MAX_SORT_FIELD_LENGTH: usize = 255;
|
||||||
pub const MAX_STORED_FIELD_LENGTH: usize = 512;
|
pub const MAX_STORED_FIELD_LENGTH: usize = 512;
|
||||||
pub const PREVIEW_LENGTH: usize = 256;
|
pub const PREVIEW_LENGTH: usize = 256;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct SortedAddressBuilder {
|
pub struct SortedAddressBuilder {
|
||||||
last_is_space: bool,
|
last_is_space: bool,
|
||||||
pub buf: String,
|
pub buf: String,
|
||||||
|
@ -120,176 +121,177 @@ impl IndexMessage for BatchBuilder {
|
||||||
language = part_language;
|
language = part_language;
|
||||||
let mut extra_ids = Vec::new();
|
let mut extra_ids = Vec::new();
|
||||||
for header in part.headers.into_iter().rev() {
|
for header in part.headers.into_iter().rev() {
|
||||||
if let HeaderName::Rfc(rfc_header) = header.name {
|
if matches!(header.name, HeaderName::Other(_)) {
|
||||||
// Index hasHeader property
|
continue;
|
||||||
let header_num = (rfc_header as u8).to_string();
|
}
|
||||||
fts.index_raw_token(Property::Headers, &header_num);
|
// Index hasHeader property
|
||||||
|
let header_num = header.name.id().to_string();
|
||||||
|
fts.index_raw_token(Property::Headers, &header_num);
|
||||||
|
|
||||||
match rfc_header {
|
match header.name {
|
||||||
RfcHeader::MessageId
|
HeaderName::MessageId
|
||||||
| RfcHeader::InReplyTo
|
| HeaderName::InReplyTo
|
||||||
| RfcHeader::References
|
| HeaderName::References
|
||||||
| RfcHeader::ResentMessageId => {
|
| HeaderName::ResentMessageId => {
|
||||||
header.value.visit_text(|id| {
|
header.value.visit_text(|id| {
|
||||||
// Add ids to inverted index
|
// Add ids to inverted index
|
||||||
if id.len() < MAX_ID_LENGTH {
|
if id.len() < MAX_ID_LENGTH {
|
||||||
self.value(Property::MessageId, id, F_INDEX);
|
self.value(Property::MessageId, id, F_INDEX);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index ids without stemming
|
// Index ids without stemming
|
||||||
if id.len() < MAX_TOKEN_LENGTH {
|
if id.len() < MAX_TOKEN_LENGTH {
|
||||||
fts.index_raw_token(
|
fts.index_raw_token(
|
||||||
Property::Headers,
|
Property::Headers,
|
||||||
format!("{header_num}{id}"),
|
format!("{header_num}{id}"),
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if matches!(
|
|
||||||
rfc_header,
|
|
||||||
RfcHeader::MessageId
|
|
||||||
| RfcHeader::InReplyTo
|
|
||||||
| RfcHeader::References
|
|
||||||
) && !seen_headers[rfc_header as usize]
|
|
||||||
{
|
|
||||||
metadata.append(
|
|
||||||
rfc_header.into(),
|
|
||||||
header
|
|
||||||
.value
|
|
||||||
.trim_text(MAX_STORED_FIELD_LENGTH)
|
|
||||||
.into_form(&HeaderForm::MessageIds),
|
|
||||||
);
|
);
|
||||||
seen_headers[rfc_header as usize] = true;
|
|
||||||
} else {
|
|
||||||
header.value.into_visit_text(|id| {
|
|
||||||
extra_ids.push(Value::Text(id));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
header.name,
|
||||||
|
HeaderName::MessageId
|
||||||
|
| HeaderName::InReplyTo
|
||||||
|
| HeaderName::References
|
||||||
|
) && !seen_headers[header.name.id() as usize]
|
||||||
|
{
|
||||||
|
metadata.append(
|
||||||
|
Property::from_header(&header.name),
|
||||||
|
header
|
||||||
|
.value
|
||||||
|
.trim_text(MAX_STORED_FIELD_LENGTH)
|
||||||
|
.into_form(&HeaderForm::MessageIds),
|
||||||
|
);
|
||||||
|
seen_headers[header.name.id() as usize] = true;
|
||||||
|
} else {
|
||||||
|
header.value.into_visit_text(|id| {
|
||||||
|
extra_ids.push(Value::Text(id));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
RfcHeader::From
|
}
|
||||||
| RfcHeader::To
|
HeaderName::From
|
||||||
| RfcHeader::Cc
|
| HeaderName::To
|
||||||
| RfcHeader::Bcc
|
| HeaderName::Cc
|
||||||
| RfcHeader::ReplyTo
|
| HeaderName::Bcc
|
||||||
| RfcHeader::Sender => {
|
| HeaderName::ReplyTo
|
||||||
let property = Property::from(rfc_header);
|
| HeaderName::Sender => {
|
||||||
let seen_header = seen_headers[rfc_header as usize];
|
let property = Property::from_header(&header.name);
|
||||||
if matches!(
|
let seen_header = seen_headers[header.name.id() as usize];
|
||||||
rfc_header,
|
if matches!(
|
||||||
RfcHeader::From
|
header.name,
|
||||||
| RfcHeader::To
|
HeaderName::From
|
||||||
| RfcHeader::Cc
|
| HeaderName::To
|
||||||
| RfcHeader::Bcc
|
| HeaderName::Cc
|
||||||
) {
|
| HeaderName::Bcc
|
||||||
let mut sort_text = SortedAddressBuilder::new();
|
) {
|
||||||
let mut found_addr = seen_header;
|
let mut sort_text = SortedAddressBuilder::new();
|
||||||
|
let mut found_addr = seen_header;
|
||||||
|
|
||||||
header.value.visit_addresses(|element, value| {
|
header.value.visit_addresses(|element, value| {
|
||||||
if !found_addr {
|
if !found_addr {
|
||||||
match element {
|
match element {
|
||||||
AddressElement::Name => {
|
AddressElement::Name => {
|
||||||
found_addr = !sort_text.push(value);
|
found_addr = !sort_text.push(value);
|
||||||
}
|
|
||||||
AddressElement::Address => {
|
|
||||||
sort_text.push(value);
|
|
||||||
found_addr = true;
|
|
||||||
}
|
|
||||||
AddressElement::GroupName => (),
|
|
||||||
}
|
}
|
||||||
|
AddressElement::Address => {
|
||||||
|
sort_text.push(value);
|
||||||
|
found_addr = true;
|
||||||
|
}
|
||||||
|
AddressElement::GroupName => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index an address name or email without stemming
|
|
||||||
fts.index_raw(u8::from(&property), value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if !seen_header {
|
|
||||||
// Add address to inverted index
|
|
||||||
self.value(u8::from(&property), sort_text.build(), F_INDEX);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Index an address name or email without stemming
|
||||||
|
fts.index_raw(u8::from(&property), value);
|
||||||
|
});
|
||||||
|
|
||||||
if !seen_header {
|
if !seen_header {
|
||||||
// Add address to metadata
|
// Add address to inverted index
|
||||||
metadata.append(
|
self.value(u8::from(&property), sort_text.build(), F_INDEX);
|
||||||
property,
|
|
||||||
header
|
|
||||||
.value
|
|
||||||
.trim_text(MAX_STORED_FIELD_LENGTH)
|
|
||||||
.into_form(&HeaderForm::Addresses),
|
|
||||||
);
|
|
||||||
seen_headers[rfc_header as usize] = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RfcHeader::Date => {
|
|
||||||
if !seen_headers[rfc_header as usize] {
|
if !seen_header {
|
||||||
if let HeaderValue::DateTime(datetime) = &header.value {
|
// Add address to metadata
|
||||||
self.value(
|
metadata.append(
|
||||||
Property::SentAt,
|
property,
|
||||||
datetime.to_timestamp() as u64,
|
header
|
||||||
F_INDEX,
|
.value
|
||||||
);
|
.trim_text(MAX_STORED_FIELD_LENGTH)
|
||||||
}
|
.into_form(&HeaderForm::Addresses),
|
||||||
metadata.append(
|
);
|
||||||
Property::SentAt,
|
seen_headers[header.name.id() as usize] = true;
|
||||||
header.value.into_form(&HeaderForm::Date),
|
|
||||||
);
|
|
||||||
seen_headers[rfc_header as usize] = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
RfcHeader::Subject => {
|
}
|
||||||
// Index subject
|
HeaderName::Date => {
|
||||||
let subject = match &header.value {
|
if !seen_headers[header.name.id() as usize] {
|
||||||
HeaderValue::Text(text) => text.clone(),
|
if let HeaderValue::DateTime(datetime) = &header.value {
|
||||||
HeaderValue::TextList(list) if !list.is_empty() => {
|
|
||||||
list.first().unwrap().clone()
|
|
||||||
}
|
|
||||||
_ => "".into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !seen_headers[rfc_header as usize] {
|
|
||||||
// Add to metadata
|
|
||||||
metadata.append(
|
|
||||||
Property::Subject,
|
|
||||||
header
|
|
||||||
.value
|
|
||||||
.trim_text(MAX_STORED_FIELD_LENGTH)
|
|
||||||
.into_form(&HeaderForm::Text),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Index thread name
|
|
||||||
let thread_name = thread_name(&subject);
|
|
||||||
self.value(
|
self.value(
|
||||||
Property::Subject,
|
Property::SentAt,
|
||||||
if !thread_name.is_empty() {
|
datetime.to_timestamp() as u64,
|
||||||
thread_name.trim_text(MAX_SORT_FIELD_LENGTH)
|
|
||||||
} else {
|
|
||||||
"!"
|
|
||||||
},
|
|
||||||
F_INDEX,
|
F_INDEX,
|
||||||
);
|
);
|
||||||
|
|
||||||
seen_headers[rfc_header as usize] = true;
|
|
||||||
}
|
}
|
||||||
|
metadata.append(
|
||||||
// Index subject for FTS
|
Property::SentAt,
|
||||||
fts.index(Property::Subject, subject, language);
|
header.value.into_form(&HeaderForm::Date),
|
||||||
|
);
|
||||||
|
seen_headers[header.name.id() as usize] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
RfcHeader::Comments | RfcHeader::Keywords | RfcHeader::ListId => {
|
|
||||||
// Index headers
|
|
||||||
header.value.visit_text(|text| {
|
|
||||||
for token in text.split_ascii_whitespace() {
|
|
||||||
if token.len() < MAX_TOKEN_LENGTH {
|
|
||||||
fts.index_raw_token(
|
|
||||||
Property::Headers,
|
|
||||||
format!("{header_num}{}", token.to_lowercase()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
HeaderName::Subject => {
|
||||||
|
// Index subject
|
||||||
|
let subject = match &header.value {
|
||||||
|
HeaderValue::Text(text) => text.clone(),
|
||||||
|
HeaderValue::TextList(list) if !list.is_empty() => {
|
||||||
|
list.first().unwrap().clone()
|
||||||
|
}
|
||||||
|
_ => "".into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !seen_headers[header.name.id() as usize] {
|
||||||
|
// Add to metadata
|
||||||
|
metadata.append(
|
||||||
|
Property::Subject,
|
||||||
|
header
|
||||||
|
.value
|
||||||
|
.trim_text(MAX_STORED_FIELD_LENGTH)
|
||||||
|
.into_form(&HeaderForm::Text),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Index thread name
|
||||||
|
let thread_name = thread_name(&subject);
|
||||||
|
self.value(
|
||||||
|
Property::Subject,
|
||||||
|
if !thread_name.is_empty() {
|
||||||
|
thread_name.trim_text(MAX_SORT_FIELD_LENGTH)
|
||||||
|
} else {
|
||||||
|
"!"
|
||||||
|
},
|
||||||
|
F_INDEX,
|
||||||
|
);
|
||||||
|
|
||||||
|
seen_headers[header.name.id() as usize] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index subject for FTS
|
||||||
|
fts.index(Property::Subject, subject, language);
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderName::Comments | HeaderName::Keywords | HeaderName::ListId => {
|
||||||
|
// Index headers
|
||||||
|
header.value.visit_text(|text| {
|
||||||
|
for token in text.split_ascii_whitespace() {
|
||||||
|
if token.len() < MAX_TOKEN_LENGTH {
|
||||||
|
fts.index_raw_token(
|
||||||
|
Property::Headers,
|
||||||
|
format!("{header_num}{}", token.to_lowercase()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,7 +302,7 @@ impl IndexMessage for BatchBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add subject to index if missing
|
// Add subject to index if missing
|
||||||
if !seen_headers[RfcHeader::Subject as usize] {
|
if !seen_headers[HeaderName::Subject.id() as usize] {
|
||||||
self.value(Property::Subject, "!", F_INDEX);
|
self.value(Property::Subject, "!", F_INDEX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +349,7 @@ impl IndexMessage for BatchBuilder {
|
||||||
.language()
|
.language()
|
||||||
.unwrap_or(Language::Unknown);
|
.unwrap_or(Language::Unknown);
|
||||||
if let Some(HeaderValue::Text(subject)) =
|
if let Some(HeaderValue::Text(subject)) =
|
||||||
nested_message.remove_header_rfc(RfcHeader::Subject)
|
nested_message.remove_header(HeaderName::Subject)
|
||||||
{
|
{
|
||||||
fts.index(
|
fts.index(
|
||||||
Property::Attachments,
|
Property::Attachments,
|
||||||
|
@ -549,17 +551,19 @@ trait GetContentLanguage {
|
||||||
|
|
||||||
impl GetContentLanguage for MessagePart<'_> {
|
impl GetContentLanguage for MessagePart<'_> {
|
||||||
fn language(&self) -> Option<Language> {
|
fn language(&self) -> Option<Language> {
|
||||||
self.headers.rfc(&RfcHeader::ContentLanguage).and_then(|v| {
|
self.headers
|
||||||
Language::from_iso_639(match v {
|
.header_value(&HeaderName::ContentLanguage)
|
||||||
HeaderValue::Text(v) => v.as_ref(),
|
.and_then(|v| {
|
||||||
HeaderValue::TextList(v) => v.first()?,
|
Language::from_iso_639(match v {
|
||||||
_ => {
|
HeaderValue::Text(v) => v.as_ref(),
|
||||||
return None;
|
HeaderValue::TextList(v) => v.first()?,
|
||||||
}
|
_ => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(Language::Unknown)
|
||||||
|
.into()
|
||||||
})
|
})
|
||||||
.unwrap_or(Language::Unknown)
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -569,6 +573,7 @@ trait VisitValues {
|
||||||
fn into_visit_text(self, visitor: impl FnMut(String));
|
fn into_visit_text(self, visitor: impl FnMut(String));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
enum AddressElement {
|
enum AddressElement {
|
||||||
Name,
|
Name,
|
||||||
Address,
|
Address,
|
||||||
|
@ -578,15 +583,7 @@ enum AddressElement {
|
||||||
impl VisitValues for HeaderValue<'_> {
|
impl VisitValues for HeaderValue<'_> {
|
||||||
fn visit_addresses(&self, mut visitor: impl FnMut(AddressElement, &str)) {
|
fn visit_addresses(&self, mut visitor: impl FnMut(AddressElement, &str)) {
|
||||||
match self {
|
match self {
|
||||||
HeaderValue::Address(addr) => {
|
HeaderValue::Address(Address::List(addr_list)) => {
|
||||||
if let Some(name) = &addr.name {
|
|
||||||
visitor(AddressElement::Name, name);
|
|
||||||
}
|
|
||||||
if let Some(addr) = &addr.address {
|
|
||||||
visitor(AddressElement::Address, addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HeaderValue::AddressList(addr_list) => {
|
|
||||||
for addr in addr_list {
|
for addr in addr_list {
|
||||||
if let Some(name) = &addr.name {
|
if let Some(name) = &addr.name {
|
||||||
visitor(AddressElement::Name, name);
|
visitor(AddressElement::Name, name);
|
||||||
|
@ -596,21 +593,7 @@ impl VisitValues for HeaderValue<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HeaderValue::Group(group) => {
|
HeaderValue::Address(Address::Group(groups)) => {
|
||||||
if let Some(name) = &group.name {
|
|
||||||
visitor(AddressElement::GroupName, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
for addr in &group.addresses {
|
|
||||||
if let Some(name) = &addr.name {
|
|
||||||
visitor(AddressElement::Name, name);
|
|
||||||
}
|
|
||||||
if let Some(addr) = &addr.address {
|
|
||||||
visitor(AddressElement::Address, addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HeaderValue::GroupList(groups) => {
|
|
||||||
for group in groups {
|
for group in groups {
|
||||||
if let Some(name) = &group.name {
|
if let Some(name) = &group.name {
|
||||||
visitor(AddressElement::GroupName, name);
|
visitor(AddressElement::GroupName, name);
|
||||||
|
@ -666,10 +649,12 @@ pub trait TrimTextValue {
|
||||||
impl TrimTextValue for HeaderValue<'_> {
|
impl TrimTextValue for HeaderValue<'_> {
|
||||||
fn trim_text(self, length: usize) -> Self {
|
fn trim_text(self, length: usize) -> Self {
|
||||||
match self {
|
match self {
|
||||||
HeaderValue::Address(v) => HeaderValue::Address(v.trim_text(length)),
|
HeaderValue::Address(Address::List(v)) => {
|
||||||
HeaderValue::AddressList(v) => HeaderValue::AddressList(v.trim_text(length)),
|
HeaderValue::Address(Address::List(v.trim_text(length)))
|
||||||
HeaderValue::Group(v) => HeaderValue::Group(v.trim_text(length)),
|
}
|
||||||
HeaderValue::GroupList(v) => HeaderValue::GroupList(v.trim_text(length)),
|
HeaderValue::Address(Address::Group(v)) => {
|
||||||
|
HeaderValue::Address(Address::Group(v.trim_text(length)))
|
||||||
|
}
|
||||||
HeaderValue::Text(v) => HeaderValue::Text(v.trim_text(length)),
|
HeaderValue::Text(v) => HeaderValue::Text(v.trim_text(length)),
|
||||||
HeaderValue::TextList(v) => HeaderValue::TextList(v.trim_text(length)),
|
HeaderValue::TextList(v) => HeaderValue::TextList(v.trim_text(length)),
|
||||||
v => v,
|
v => v,
|
||||||
|
|
|
@ -31,7 +31,7 @@ use jmap_proto::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use mail_parser::{
|
use mail_parser::{
|
||||||
parsers::fields::thread::thread_name, HeaderName, HeaderValue, Message, PartType, RfcHeader,
|
parsers::fields::thread::thread_name, HeaderName, HeaderValue, Message, PartType,
|
||||||
};
|
};
|
||||||
use store::{
|
use store::{
|
||||||
ahash::AHashSet,
|
ahash::AHashSet,
|
||||||
|
@ -103,12 +103,10 @@ impl JMAP {
|
||||||
let mut subject = "";
|
let mut subject = "";
|
||||||
for header in message.root_part().headers().iter().rev() {
|
for header in message.root_part().headers().iter().rev() {
|
||||||
match header.name {
|
match header.name {
|
||||||
HeaderName::Rfc(
|
HeaderName::MessageId
|
||||||
RfcHeader::MessageId
|
| HeaderName::InReplyTo
|
||||||
| RfcHeader::InReplyTo
|
| HeaderName::References
|
||||||
| RfcHeader::References
|
| HeaderName::ResentMessageId => match &header.value {
|
||||||
| RfcHeader::ResentMessageId,
|
|
||||||
) => match &header.value {
|
|
||||||
HeaderValue::Text(id) if id.len() < MAX_ID_LENGTH => {
|
HeaderValue::Text(id) if id.len() < MAX_ID_LENGTH => {
|
||||||
references.push(id.as_ref());
|
references.push(id.as_ref());
|
||||||
}
|
}
|
||||||
|
@ -121,7 +119,7 @@ impl JMAP {
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
HeaderName::Rfc(RfcHeader::Subject) if subject.is_empty() => {
|
HeaderName::Subject if subject.is_empty() => {
|
||||||
subject = thread_name(match &header.value {
|
subject = thread_name(match &header.value {
|
||||||
HeaderValue::Text(text) => text.as_ref(),
|
HeaderValue::Text(text) => text.as_ref(),
|
||||||
HeaderValue::TextList(list) if !list.is_empty() => {
|
HeaderValue::TextList(list) if !list.is_empty() => {
|
||||||
|
|
|
@ -28,7 +28,7 @@ use jmap_proto::{
|
||||||
types::{property::Property, value::Value},
|
types::{property::Property, value::Value},
|
||||||
};
|
};
|
||||||
use mail_parser::{
|
use mail_parser::{
|
||||||
decoders::html::html_to_text, parsers::preview::preview_text, Message, PartType,
|
decoders::html::html_to_text, parsers::preview::preview_text, MessageParser, PartType,
|
||||||
};
|
};
|
||||||
use utils::map::vec_map::VecMap;
|
use utils::map::vec_map::VecMap;
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ impl JMAP {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let message = if let Some(message) = Message::parse(&raw_message) {
|
let message = if let Some(message) = MessageParser::new().parse(&raw_message) {
|
||||||
message
|
message
|
||||||
} else {
|
} else {
|
||||||
response.not_parsable.push(blob_id);
|
response.not_parsable.push(blob_id);
|
||||||
|
|
|
@ -27,7 +27,7 @@ use jmap_proto::{
|
||||||
object::email::QueryArguments,
|
object::email::QueryArguments,
|
||||||
types::{acl::Acl, collection::Collection, keyword::Keyword, property::Property},
|
types::{acl::Acl, collection::Collection, keyword::Keyword, property::Property},
|
||||||
};
|
};
|
||||||
use mail_parser::{HeaderName, RfcHeader};
|
use mail_parser::HeaderName;
|
||||||
use store::{
|
use store::{
|
||||||
fts::{builder::MAX_TOKEN_LENGTH, Language},
|
fts::{builder::MAX_TOKEN_LENGTH, Language},
|
||||||
query::{self},
|
query::{self},
|
||||||
|
@ -158,63 +158,67 @@ impl JMAP {
|
||||||
let header_name = header.next().ok_or_else(|| {
|
let header_name = header.next().ok_or_else(|| {
|
||||||
MethodError::InvalidArguments("Header name is missing.".to_string())
|
MethodError::InvalidArguments("Header name is missing.".to_string())
|
||||||
})?;
|
})?;
|
||||||
if let Some(HeaderName::Rfc(header_name)) = HeaderName::parse(&header_name) {
|
|
||||||
let is_id = matches!(
|
match HeaderName::parse(&header_name) {
|
||||||
header_name,
|
Some(HeaderName::Other(_)) | None => {
|
||||||
RfcHeader::MessageId
|
return Err(MethodError::InvalidArguments(format!(
|
||||||
| RfcHeader::InReplyTo
|
"Querying non-RFC header '{header_name}' is not allowed.",
|
||||||
| RfcHeader::References
|
)));
|
||||||
| RfcHeader::ResentMessageId
|
}
|
||||||
);
|
Some(header_name) => {
|
||||||
let tokens = if let Some(header_value) = header.next() {
|
let is_id = matches!(
|
||||||
let header_num = u8::from(header_name).to_string();
|
header_name,
|
||||||
header_value
|
HeaderName::MessageId
|
||||||
.split_ascii_whitespace()
|
| HeaderName::InReplyTo
|
||||||
.filter_map(|token| {
|
| HeaderName::References
|
||||||
if token.len() < MAX_TOKEN_LENGTH {
|
| HeaderName::ResentMessageId
|
||||||
if is_id {
|
);
|
||||||
format!("{header_num}{token}")
|
let tokens = if let Some(header_value) = header.next() {
|
||||||
|
let header_num = header_name.id().to_string();
|
||||||
|
header_value
|
||||||
|
.split_ascii_whitespace()
|
||||||
|
.filter_map(|token| {
|
||||||
|
if token.len() < MAX_TOKEN_LENGTH {
|
||||||
|
if is_id {
|
||||||
|
format!("{header_num}{token}")
|
||||||
|
} else {
|
||||||
|
format!("{header_num}{}", token.to_lowercase())
|
||||||
|
}
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
format!("{header_num}{}", token.to_lowercase())
|
None
|
||||||
}
|
}
|
||||||
.into()
|
})
|
||||||
} else {
|
.collect::<Vec<_>>()
|
||||||
None
|
} else {
|
||||||
}
|
vec![]
|
||||||
})
|
};
|
||||||
.collect::<Vec<_>>()
|
match tokens.len() {
|
||||||
} else {
|
0 => {
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
match tokens.len() {
|
|
||||||
0 => {
|
|
||||||
filters.push(query::Filter::has_raw_text(
|
|
||||||
Property::Headers,
|
|
||||||
u8::from(header_name).to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
1 => {
|
|
||||||
filters.push(query::Filter::has_raw_text(
|
|
||||||
Property::Headers,
|
|
||||||
tokens.into_iter().next().unwrap(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
filters.push(query::Filter::And);
|
|
||||||
for token in tokens {
|
|
||||||
filters.push(query::Filter::has_raw_text(
|
filters.push(query::Filter::has_raw_text(
|
||||||
Property::Headers,
|
Property::Headers,
|
||||||
token,
|
header_name.id().to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
filters.push(query::Filter::End);
|
1 => {
|
||||||
|
filters.push(query::Filter::has_raw_text(
|
||||||
|
Property::Headers,
|
||||||
|
tokens.into_iter().next().unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
filters.push(query::Filter::And);
|
||||||
|
for token in tokens {
|
||||||
|
filters.push(query::Filter::has_raw_text(
|
||||||
|
Property::Headers,
|
||||||
|
token,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
filters.push(query::Filter::End);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
return Err(MethodError::InvalidArguments(format!(
|
|
||||||
"Querying non-RFC header '{header_name}' is not allowed.",
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-standard
|
// Non-standard
|
||||||
|
|
|
@ -50,7 +50,7 @@ use mail_builder::{
|
||||||
mime::{BodyPart, MimePart},
|
mime::{BodyPart, MimePart},
|
||||||
MessageBuilder,
|
MessageBuilder,
|
||||||
};
|
};
|
||||||
use mail_parser::Message;
|
use mail_parser::MessageParser;
|
||||||
use store::{
|
use store::{
|
||||||
ahash::AHashSet,
|
ahash::AHashSet,
|
||||||
fts::term_index::TokenIndex,
|
fts::term_index::TokenIndex,
|
||||||
|
@ -730,7 +730,7 @@ impl JMAP {
|
||||||
match self
|
match self
|
||||||
.email_ingest(IngestEmail {
|
.email_ingest(IngestEmail {
|
||||||
raw_message: &raw_message,
|
raw_message: &raw_message,
|
||||||
message: Message::parse(&raw_message),
|
message: MessageParser::new().parse(&raw_message),
|
||||||
account_id,
|
account_id,
|
||||||
account_quota,
|
account_quota,
|
||||||
mailbox_ids: mailboxes,
|
mailbox_ids: mailboxes,
|
||||||
|
|
|
@ -29,7 +29,7 @@ use jmap_proto::{
|
||||||
},
|
},
|
||||||
types::{acl::Acl, collection::Collection},
|
types::{acl::Acl, collection::Collection},
|
||||||
};
|
};
|
||||||
use mail_parser::{decoders::html::html_to_text, Message, PartType};
|
use mail_parser::{decoders::html::html_to_text, MessageParser, PartType};
|
||||||
use store::{
|
use store::{
|
||||||
fts::{
|
fts::{
|
||||||
builder::MAX_TOKEN_LENGTH,
|
builder::MAX_TOKEN_LENGTH,
|
||||||
|
@ -150,7 +150,7 @@ impl JMAP {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse message
|
// Parse message
|
||||||
let message = if let Some(message) = Message::parse(&raw_message) {
|
let message = if let Some(message) = MessageParser::new().parse(&raw_message) {
|
||||||
message
|
message
|
||||||
} else {
|
} else {
|
||||||
response.not_found.push(email_id);
|
response.not_found.push(email_id);
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use jmap_proto::types::{state::StateChange, type_state::TypeState};
|
use jmap_proto::types::{state::StateChange, type_state::TypeState};
|
||||||
use mail_parser::Message;
|
use mail_parser::MessageParser;
|
||||||
use store::ahash::AHashMap;
|
use store::ahash::AHashMap;
|
||||||
use utils::ipc::{DeliveryResult, IngestMessage};
|
use utils::ipc::{DeliveryResult, IngestMessage};
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ impl JMAP {
|
||||||
|
|
||||||
self.email_ingest(IngestEmail {
|
self.email_ingest(IngestEmail {
|
||||||
raw_message: &raw_message,
|
raw_message: &raw_message,
|
||||||
message: Message::parse(&raw_message),
|
message: MessageParser::new().parse(&raw_message),
|
||||||
account_id: uid,
|
account_id: uid,
|
||||||
account_quota,
|
account_quota,
|
||||||
mailbox_ids: vec![INBOX_ID],
|
mailbox_ids: vec![INBOX_ID],
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use jmap_proto::types::{collection::Collection, id::Id, keyword::Keyword, property::Property};
|
use jmap_proto::types::{collection::Collection, id::Id, keyword::Keyword, property::Property};
|
||||||
use mail_parser::Message;
|
use mail_parser::MessageParser;
|
||||||
use sieve::{Envelope, Event, Input, Mailbox, Recipient};
|
use sieve::{Envelope, Event, Input, Mailbox, Recipient};
|
||||||
use smtp::core::{NullIo, Session, SessionAddress};
|
use smtp::core::{NullIo, Session, SessionAddress};
|
||||||
use store::{
|
use store::{
|
||||||
|
@ -59,7 +59,7 @@ impl JMAP {
|
||||||
mut active_script: ActiveScript,
|
mut active_script: ActiveScript,
|
||||||
) -> Result<IngestedEmail, IngestError> {
|
) -> Result<IngestedEmail, IngestError> {
|
||||||
// Parse message
|
// Parse message
|
||||||
let message = if let Some(message) = Message::parse(raw_message) {
|
let message = if let Some(message) = MessageParser::new().parse(raw_message) {
|
||||||
message
|
message
|
||||||
} else {
|
} else {
|
||||||
return Err(IngestError::Permanent {
|
return Err(IngestError::Permanent {
|
||||||
|
@ -428,7 +428,9 @@ impl JMAP {
|
||||||
// Parse message if needed
|
// Parse message if needed
|
||||||
let message = if message_id == 0 && !instance.has_message_changed() {
|
let message = if message_id == 0 && !instance.has_message_changed() {
|
||||||
instance.take_message()
|
instance.take_message()
|
||||||
} else if let Some(message) = Message::parse(&sieve_message.raw_message) {
|
} else if let Some(message) =
|
||||||
|
MessageParser::new().parse(sieve_message.raw_message.as_ref())
|
||||||
|
{
|
||||||
message
|
message
|
||||||
} else {
|
} else {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
|
|
|
@ -14,7 +14,7 @@ store = { path = "../store" }
|
||||||
utils = { path = "../utils" }
|
utils = { path = "../utils" }
|
||||||
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", features = ["full_encoding", "ludicrous_mode"] }
|
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", features = ["full_encoding", "ludicrous_mode"] }
|
||||||
mail-send = { git = "https://github.com/stalwartlabs/mail-send", default-features = false, features = ["cram-md5", "skip-ehlo"] }
|
mail-send = { git = "https://github.com/stalwartlabs/mail-send", default-features = false, features = ["cram-md5", "skip-ehlo"] }
|
||||||
sieve-rs = { git = "https://github.com/stalwartlabs/sieve" }
|
sieve-rs = { git = "https://github.com/stalwartlabs/sieve" }
|
||||||
rustls = "0.21.0"
|
rustls = "0.21.0"
|
||||||
rustls-pemfile = "1.0"
|
rustls-pemfile = "1.0"
|
||||||
tokio = { version = "1.23", features = ["full"] }
|
tokio = { version = "1.23", features = ["full"] }
|
||||||
|
|
|
@ -19,7 +19,7 @@ mail-send = { git = "https://github.com/stalwartlabs/mail-send", default-feature
|
||||||
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", features = ["full_encoding", "ludicrous_mode"] }
|
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", features = ["full_encoding", "ludicrous_mode"] }
|
||||||
mail-builder = { git = "https://github.com/stalwartlabs/mail-builder", features = ["ludicrous_mode"] }
|
mail-builder = { git = "https://github.com/stalwartlabs/mail-builder", features = ["ludicrous_mode"] }
|
||||||
smtp-proto = { git = "https://github.com/stalwartlabs/smtp-proto" }
|
smtp-proto = { git = "https://github.com/stalwartlabs/smtp-proto" }
|
||||||
sieve-rs = { git = "https://github.com/stalwartlabs/sieve" }
|
sieve-rs = { git = "https://github.com/stalwartlabs/sieve" }
|
||||||
ahash = { version = "0.8" }
|
ahash = { version = "0.8" }
|
||||||
rustls = "0.21.0"
|
rustls = "0.21.0"
|
||||||
rustls-pemfile = "1.0"
|
rustls-pemfile = "1.0"
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl DnssecResolver {
|
||||||
options: ResolverOpts,
|
options: ResolverOpts,
|
||||||
) -> Result<Self, ResolveError> {
|
) -> Result<Self, ResolveError> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
resolver: AsyncResolver::tokio(config, options)?,
|
resolver: AsyncResolver::tokio(config, options),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ use mail_auth::{
|
||||||
report::{tlsrpt::TlsReport, ActionDisposition, DmarcResult, Feedback, Report},
|
report::{tlsrpt::TlsReport, ActionDisposition, DmarcResult, Feedback, Report},
|
||||||
zip,
|
zip,
|
||||||
};
|
};
|
||||||
use mail_parser::{DateTime, HeaderValue, Message, MimeHeaders, PartType};
|
use mail_parser::{DateTime, MessageParser, MimeHeaders, PartType};
|
||||||
|
|
||||||
use crate::core::SMTP;
|
use crate::core::SMTP;
|
||||||
|
|
||||||
|
@ -65,21 +65,17 @@ impl AnalyzeReport for Arc<SMTP> {
|
||||||
fn analyze_report(&self, message: Arc<Vec<u8>>) {
|
fn analyze_report(&self, message: Arc<Vec<u8>>) {
|
||||||
let core = self.clone();
|
let core = self.clone();
|
||||||
self.worker_pool.spawn(move || {
|
self.worker_pool.spawn(move || {
|
||||||
let message = if let Some(message) = Message::parse(&message) {
|
let message = if let Some(message) = MessageParser::default().parse(message.as_ref()) {
|
||||||
message
|
message
|
||||||
} else {
|
} else {
|
||||||
tracing::debug!(context = "report", "Failed to parse message.");
|
tracing::debug!(context = "report", "Failed to parse message.");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let from = match message.from() {
|
let from = message
|
||||||
HeaderValue::Address(addr) => addr.address.as_ref().map(|a| a.as_ref()),
|
.from()
|
||||||
HeaderValue::AddressList(addr_list) => addr_list
|
.and_then(|a| a.last())
|
||||||
.last()
|
.and_then(|a| a.address())
|
||||||
.and_then(|a| a.address.as_ref())
|
.unwrap_or("unknown");
|
||||||
.map(|a| a.as_ref()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
.unwrap_or("unknown");
|
|
||||||
let mut reports = Vec::new();
|
let mut reports = Vec::new();
|
||||||
|
|
||||||
for part in &message.parts {
|
for part in &message.parts {
|
||||||
|
|
|
@ -22,7 +22,7 @@ managesieve = { path = "../crates/managesieve", features = ["test_mode"] }
|
||||||
smtp-proto = { git = "https://github.com/stalwartlabs/smtp-proto" }
|
smtp-proto = { git = "https://github.com/stalwartlabs/smtp-proto" }
|
||||||
mail-send = { git = "https://github.com/stalwartlabs/mail-send", default-features = false, features = ["cram-md5", "skip-ehlo"] }
|
mail-send = { git = "https://github.com/stalwartlabs/mail-send", default-features = false, features = ["cram-md5", "skip-ehlo"] }
|
||||||
mail-auth = { git = "https://github.com/stalwartlabs/mail-auth", features = ["test"] }
|
mail-auth = { git = "https://github.com/stalwartlabs/mail-auth", features = ["test"] }
|
||||||
sieve-rs = { git = "https://github.com/stalwartlabs/sieve" }
|
sieve-rs = { git = "https://github.com/stalwartlabs/sieve" }
|
||||||
utils = { path = "../crates/utils", features = ["test_mode"] }
|
utils = { path = "../crates/utils", features = ["test_mode"] }
|
||||||
jmap-client = { git = "https://github.com/stalwartlabs/jmap-client", features = ["websockets", "debug", "async"] }
|
jmap-client = { git = "https://github.com/stalwartlabs/jmap-client", features = ["websockets", "debug", "async"] }
|
||||||
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", features = ["full_encoding", "serde_support", "ludicrous_mode"] }
|
mail-parser = { git = "https://github.com/stalwartlabs/mail-parser", features = ["full_encoding", "serde_support", "ludicrous_mode"] }
|
||||||
|
|
|
@ -1123,6 +1123,10 @@
|
||||||
{
|
{
|
||||||
"name": "John (my dear friend)",
|
"name": "John (my dear friend)",
|
||||||
"email": "jdoe@one.test"
|
"email": "jdoe@one.test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "the end of the group",
|
||||||
|
"email": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"header:X-AddressesGroup-Single:asAddresses:all": [
|
"header:X-AddressesGroup-Single:asAddresses:all": [
|
||||||
|
@ -1138,6 +1142,10 @@
|
||||||
{
|
{
|
||||||
"name": "John (my dear friend)",
|
"name": "John (my dear friend)",
|
||||||
"email": "jdoe@one.test"
|
"email": "jdoe@one.test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "the end of the group",
|
||||||
|
"email": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
@ -1161,7 +1169,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": null,
|
"name": null,
|
||||||
"addresses": []
|
"addresses": [
|
||||||
|
{
|
||||||
|
"name": "the end of the group",
|
||||||
|
"email": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"header:X-AddressesGroup-Single:asGroupedAddresses:all": [
|
"header:X-AddressesGroup-Single:asGroupedAddresses:all": [
|
||||||
|
@ -1185,7 +1198,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": null,
|
"name": null,
|
||||||
"addresses": []
|
"addresses": [
|
||||||
|
{
|
||||||
|
"name": "the end of the group",
|
||||||
|
"email": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
|
@ -1089,6 +1089,10 @@
|
||||||
{
|
{
|
||||||
"name": "John (my dear friend)",
|
"name": "John (my dear friend)",
|
||||||
"email": "jdoe@one.test"
|
"email": "jdoe@one.test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "the end of the group",
|
||||||
|
"email": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"header:X-AddressesGroup-Single:asAddresses:all": [
|
"header:X-AddressesGroup-Single:asAddresses:all": [
|
||||||
|
@ -1104,6 +1108,10 @@
|
||||||
{
|
{
|
||||||
"name": "John (my dear friend)",
|
"name": "John (my dear friend)",
|
||||||
"email": "jdoe@one.test"
|
"email": "jdoe@one.test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "the end of the group",
|
||||||
|
"email": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
@ -1127,7 +1135,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": null,
|
"name": null,
|
||||||
"addresses": []
|
"addresses": [
|
||||||
|
{
|
||||||
|
"name": "the end of the group",
|
||||||
|
"email": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"header:X-AddressesGroup-Single:asGroupedAddresses:all": [
|
"header:X-AddressesGroup-Single:asGroupedAddresses:all": [
|
||||||
|
@ -1151,7 +1164,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": null,
|
"name": null,
|
||||||
"addresses": []
|
"addresses": [
|
||||||
|
{
|
||||||
|
"name": "the end of the group",
|
||||||
|
"email": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
|
@ -2,36 +2,36 @@
|
||||||
hostname = "test.example.org"
|
hostname = "test.example.org"
|
||||||
|
|
||||||
[server.listener.jmap]
|
[server.listener.jmap]
|
||||||
bind = ["127.0.0.1:9990"]
|
bind = ["0.0.0.0:9990"]
|
||||||
url = "https://127.0.0.1:9990"
|
url = "https://0.0.0.0:9990"
|
||||||
protocol = "jmap"
|
protocol = "jmap"
|
||||||
max-connections = 8192
|
max-connections = 8192
|
||||||
|
|
||||||
[server.listener.imap]
|
[server.listener.imap]
|
||||||
bind = ["127.0.0.1:9991"]
|
bind = ["0.0.0.0:143"]
|
||||||
protocol = "imap"
|
protocol = "imap"
|
||||||
max-connections = 8192
|
max-connections = 8192
|
||||||
|
|
||||||
[server.listener.imaptls]
|
[server.listener.imaptls]
|
||||||
bind = ["127.0.0.1:9992"]
|
bind = ["0.0.0.0:9992"]
|
||||||
protocol = "imap"
|
protocol = "imap"
|
||||||
max-connections = 8192
|
max-connections = 8192
|
||||||
tls.implicit = true
|
tls.implicit = true
|
||||||
|
|
||||||
[server.listener.sieve]
|
[server.listener.sieve]
|
||||||
bind = ["127.0.0.1:9993"]
|
bind = ["0.0.0.0:9993"]
|
||||||
protocol = "managesieve"
|
protocol = "managesieve"
|
||||||
max-connections = 8192
|
max-connections = 8192
|
||||||
tls.implicit = true
|
tls.implicit = true
|
||||||
|
|
||||||
[server.listener.smtps]
|
[server.listener.smtps]
|
||||||
bind = ['127.0.0.1:9994']
|
bind = ['0.0.0.0:9994']
|
||||||
greeting = 'Test SMTP instance'
|
greeting = 'Test SMTP instance'
|
||||||
protocol = 'smtp'
|
protocol = 'smtp'
|
||||||
tls.implicit = true
|
tls.implicit = true
|
||||||
|
|
||||||
[server.listener.smtp]
|
[server.listener.smtp]
|
||||||
bind = ['127.0.0.1:9995']
|
bind = ['0.0.0.0:587']
|
||||||
greeting = 'Test SMTP instance'
|
greeting = 'Test SMTP instance'
|
||||||
protocol = 'smtp'
|
protocol = 'smtp'
|
||||||
tls.implicit = false
|
tls.implicit = false
|
||||||
|
@ -46,7 +46,7 @@ certificate = "default"
|
||||||
|
|
||||||
[global.tracing]
|
[global.tracing]
|
||||||
method = "stdout"
|
method = "stdout"
|
||||||
level = "info"
|
level = "trace"
|
||||||
|
|
||||||
[session.ehlo]
|
[session.ehlo]
|
||||||
reject-non-fqdn = false
|
reject-non-fqdn = false
|
||||||
|
|
|
@ -28,7 +28,7 @@ use imap_proto::{
|
||||||
protocol::fetch::{BodyContents, DataItem, Section},
|
protocol::fetch::{BodyContents, DataItem, Section},
|
||||||
ResponseCode, StatusResponse,
|
ResponseCode, StatusResponse,
|
||||||
};
|
};
|
||||||
use mail_parser::Message;
|
use mail_parser::MessageParser;
|
||||||
|
|
||||||
use super::resources_dir;
|
use super::resources_dir;
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ fn body_structure() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let raw_message = fs::read(&file_name).unwrap();
|
let raw_message = fs::read(&file_name).unwrap();
|
||||||
let message = Message::parse(&raw_message).unwrap();
|
let message = MessageParser::new().parse(&raw_message).unwrap();
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
// Serialize body and bodystructure
|
// Serialize body and bodystructure
|
||||||
|
|
|
@ -32,7 +32,7 @@ use jmap::{
|
||||||
};
|
};
|
||||||
use jmap_client::client::Client;
|
use jmap_client::client::Client;
|
||||||
use jmap_proto::types::id::Id;
|
use jmap_proto::types::id::Id;
|
||||||
use mail_parser::{Message, MimeHeaders};
|
use mail_parser::{MessageParser, MimeHeaders};
|
||||||
|
|
||||||
use crate::{directory::sql::create_test_user_with_email, jmap::delivery::SmtpConnection};
|
use crate::{directory::sql::create_test_user_with_email, jmap::delivery::SmtpConnection};
|
||||||
|
|
||||||
|
@ -227,7 +227,9 @@ pub async fn import_certs_and_encrypt() {
|
||||||
};
|
};
|
||||||
|
|
||||||
for algo in [Algorithm::Aes128, Algorithm::Aes256] {
|
for algo in [Algorithm::Aes128, Algorithm::Aes256] {
|
||||||
let message = Message::parse(b"Subject: test\r\ntest\r\n").unwrap();
|
let message = MessageParser::new()
|
||||||
|
.parse(b"Subject: test\r\ntest\r\n")
|
||||||
|
.unwrap();
|
||||||
assert!(!message.is_encrypted());
|
assert!(!message.is_encrypted());
|
||||||
params.algo = algo;
|
params.algo = algo;
|
||||||
message.encrypt(¶ms).await.unwrap();
|
message.encrypt(¶ms).await.unwrap();
|
||||||
|
@ -259,7 +261,9 @@ pub fn check_is_encrypted() {
|
||||||
|
|
||||||
for raw_message in messages.split("---") {
|
for raw_message in messages.split("---") {
|
||||||
let is_encrypted = raw_message.contains("TRUE");
|
let is_encrypted = raw_message.contains("TRUE");
|
||||||
let message = Message::parse(raw_message.trim().as_bytes()).unwrap();
|
let message = MessageParser::new()
|
||||||
|
.parse(raw_message.trim().as_bytes())
|
||||||
|
.unwrap();
|
||||||
assert!(message.content_type().is_some());
|
assert!(message.content_type().is_some());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
message.is_encrypted(),
|
message.is_encrypted(),
|
||||||
|
|
|
@ -65,6 +65,7 @@ pub async fn test(server: Arc<JMAP>, client: &mut Client) {
|
||||||
|
|
||||||
// Delivering to individuals
|
// Delivering to individuals
|
||||||
let mut lmtp = SmtpConnection::connect().await;
|
let mut lmtp = SmtpConnection::connect().await;
|
||||||
|
|
||||||
lmtp.ingest(
|
lmtp.ingest(
|
||||||
"bill@example.com",
|
"bill@example.com",
|
||||||
&["jdoe@example.com"],
|
&["jdoe@example.com"],
|
||||||
|
|
|
@ -29,7 +29,7 @@ use jmap_client::{
|
||||||
email::{self, Header, HeaderForm},
|
email::{self, Header, HeaderForm},
|
||||||
};
|
};
|
||||||
use jmap_proto::types::id::Id;
|
use jmap_proto::types::id::Id;
|
||||||
use mail_parser::{HeaderName, RfcHeader};
|
use mail_parser::HeaderName;
|
||||||
|
|
||||||
use crate::jmap::{mailbox::destroy_all_mailboxes, replace_blob_ids};
|
use crate::jmap::{mailbox::destroy_all_mailboxes, replace_blob_ids};
|
||||||
|
|
||||||
|
@ -184,10 +184,10 @@ pub fn all_headers() -> Vec<email::Property> {
|
||||||
let mut properties = Vec::new();
|
let mut properties = Vec::new();
|
||||||
|
|
||||||
for header in [
|
for header in [
|
||||||
HeaderName::Rfc(RfcHeader::From),
|
HeaderName::From,
|
||||||
HeaderName::Rfc(RfcHeader::To),
|
HeaderName::To,
|
||||||
HeaderName::Rfc(RfcHeader::Cc),
|
HeaderName::Cc,
|
||||||
HeaderName::Rfc(RfcHeader::Bcc),
|
HeaderName::Bcc,
|
||||||
HeaderName::Other("X-Address-Single".into()),
|
HeaderName::Other("X-Address-Single".into()),
|
||||||
HeaderName::Other("X-Address".into()),
|
HeaderName::Other("X-Address".into()),
|
||||||
HeaderName::Other("X-AddressList-Single".into()),
|
HeaderName::Other("X-AddressList-Single".into()),
|
||||||
|
@ -228,10 +228,10 @@ pub fn all_headers() -> Vec<email::Property> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for header in [
|
for header in [
|
||||||
HeaderName::Rfc(RfcHeader::ListPost),
|
HeaderName::ListPost,
|
||||||
HeaderName::Rfc(RfcHeader::ListSubscribe),
|
HeaderName::ListSubscribe,
|
||||||
HeaderName::Rfc(RfcHeader::ListUnsubscribe),
|
HeaderName::ListUnsubscribe,
|
||||||
HeaderName::Rfc(RfcHeader::ListOwner),
|
HeaderName::ListOwner,
|
||||||
HeaderName::Other("X-List-Single".into()),
|
HeaderName::Other("X-List-Single".into()),
|
||||||
HeaderName::Other("X-List".into()),
|
HeaderName::Other("X-List".into()),
|
||||||
] {
|
] {
|
||||||
|
@ -258,8 +258,8 @@ pub fn all_headers() -> Vec<email::Property> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for header in [
|
for header in [
|
||||||
HeaderName::Rfc(RfcHeader::Date),
|
HeaderName::Date,
|
||||||
HeaderName::Rfc(RfcHeader::ResentDate),
|
HeaderName::ResentDate,
|
||||||
HeaderName::Other("X-Date-Single".into()),
|
HeaderName::Other("X-Date-Single".into()),
|
||||||
HeaderName::Other("X-Date".into()),
|
HeaderName::Other("X-Date".into()),
|
||||||
] {
|
] {
|
||||||
|
@ -286,8 +286,8 @@ pub fn all_headers() -> Vec<email::Property> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for header in [
|
for header in [
|
||||||
HeaderName::Rfc(RfcHeader::MessageId),
|
HeaderName::MessageId,
|
||||||
HeaderName::Rfc(RfcHeader::References),
|
HeaderName::References,
|
||||||
HeaderName::Other("X-Id-Single".into()),
|
HeaderName::Other("X-Id-Single".into()),
|
||||||
HeaderName::Other("X-Id".into()),
|
HeaderName::Other("X-Id".into()),
|
||||||
] {
|
] {
|
||||||
|
@ -314,8 +314,8 @@ pub fn all_headers() -> Vec<email::Property> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for header in [
|
for header in [
|
||||||
HeaderName::Rfc(RfcHeader::Subject),
|
HeaderName::Subject,
|
||||||
HeaderName::Rfc(RfcHeader::Keywords),
|
HeaderName::Keywords,
|
||||||
HeaderName::Other("X-Text-Single".into()),
|
HeaderName::Other("X-Text-Single".into()),
|
||||||
HeaderName::Other("X-Text".into()),
|
HeaderName::Other("X-Text".into()),
|
||||||
] {
|
] {
|
||||||
|
|
|
@ -30,7 +30,7 @@ use jmap_client::{
|
||||||
email,
|
email,
|
||||||
};
|
};
|
||||||
use jmap_proto::types::{collection::Collection, id::Id};
|
use jmap_proto::types::{collection::Collection, id::Id};
|
||||||
use mail_parser::RfcHeader;
|
use mail_parser::HeaderName;
|
||||||
use store::{ahash::AHashMap, write::BatchBuilder};
|
use store::{ahash::AHashMap, write::BatchBuilder};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -192,7 +192,10 @@ pub async fn query(client: &mut Client) {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Filter::and(vec![
|
Filter::and(vec![
|
||||||
(email::query::Filter::header(RfcHeader::Comments.to_string(), Some("attributed"))),
|
(email::query::Filter::header(
|
||||||
|
HeaderName::Comments.to_string(),
|
||||||
|
Some("attributed"),
|
||||||
|
)),
|
||||||
(email::query::Filter::from("john")),
|
(email::query::Filter::from("john")),
|
||||||
(email::query::Filter::cc("oil")),
|
(email::query::Filter::cc("oil")),
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
use std::{fs, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};
|
use std::{fs, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use mail_auth::AuthenticatedMessage;
|
use mail_auth::AuthenticatedMessage;
|
||||||
use mail_parser::Message;
|
use mail_parser::MessageParser;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use smtp::{
|
use smtp::{
|
||||||
config::{ConfigContext, IfBlock, Milter},
|
config::{ConfigContext, IfBlock, Milter},
|
||||||
|
@ -401,7 +401,7 @@ async fn milter_client_test() {
|
||||||
client.init().await.unwrap();
|
client.init().await.unwrap();
|
||||||
|
|
||||||
let raw_message = load_test_message("arc", "messages");
|
let raw_message = load_test_message("arc", "messages");
|
||||||
let message = Message::parse(raw_message.as_bytes()).unwrap();
|
let message = MessageParser::new().parse(raw_message.as_bytes()).unwrap();
|
||||||
|
|
||||||
let r = client
|
let r = client
|
||||||
.connection(
|
.connection(
|
||||||
|
|
|
@ -232,7 +232,7 @@ async fn dane_test() {
|
||||||
let r = Resolvers {
|
let r = Resolvers {
|
||||||
dns: Resolver::new_cloudflare().unwrap(),
|
dns: Resolver::new_cloudflare().unwrap(),
|
||||||
dnssec: DnssecResolver {
|
dnssec: DnssecResolver {
|
||||||
resolver: AsyncResolver::tokio(conf, opts).unwrap(),
|
resolver: AsyncResolver::tokio(conf, opts),
|
||||||
},
|
},
|
||||||
cache: smtp::core::DnsCache {
|
cache: smtp::core::DnsCache {
|
||||||
tlsa: LruCache::with_capacity(10),
|
tlsa: LruCache::with_capacity(10),
|
||||||
|
|
Loading…
Reference in a new issue