mirror of
https://github.com/stalwartlabs/stalwart.git
synced 2026-10-30 00:15:29 +00:00
Clippy fixes
This commit is contained in:
@@ -473,20 +473,19 @@ async fn import_mailboxes(
|
||||
for (path, mailbox) in build_mailbox_tree(&mailboxes) {
|
||||
let id = mailbox.id().unwrap_result("obtain mailbox id");
|
||||
// Find existing mailbox based on role
|
||||
if !matches!(mailbox.role(), Role::None) {
|
||||
if let Some(existing_mailbox) = existing_mailboxes
|
||||
if !matches!(mailbox.role(), Role::None)
|
||||
&& let Some(existing_mailbox) = existing_mailboxes
|
||||
.iter()
|
||||
.find(|m| m.role() == mailbox.role())
|
||||
{
|
||||
id_mappings.insert(
|
||||
id.to_string(),
|
||||
existing_mailbox
|
||||
.id()
|
||||
.unwrap_result("obtain mailbox id")
|
||||
.to_string(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
{
|
||||
id_mappings.insert(
|
||||
id.to_string(),
|
||||
existing_mailbox
|
||||
.id()
|
||||
.unwrap_result("obtain mailbox id")
|
||||
.to_string(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find existing mailbox by name
|
||||
|
||||
@@ -168,7 +168,7 @@ impl AddressMapping {
|
||||
struct Address<'x>(&'x str);
|
||||
|
||||
impl ResolveVariable for Address<'_> {
|
||||
fn resolve_variable(&self, _: u32) -> crate::expr::Variable {
|
||||
fn resolve_variable(&'_ self, _: u32) -> crate::expr::Variable<'_> {
|
||||
Variable::from(self.0)
|
||||
}
|
||||
|
||||
@@ -186,10 +186,10 @@ impl AddressMapping {
|
||||
) -> Cow<'x, str> {
|
||||
match self {
|
||||
AddressMapping::Enable => {
|
||||
if let Some((local_part, domain_part)) = address.rsplit_once('@') {
|
||||
if let Some((local_part, _)) = local_part.split_once('+') {
|
||||
return format!("{}@{}", local_part, domain_part).into();
|
||||
}
|
||||
if let Some((local_part, domain_part)) = address.rsplit_once('@')
|
||||
&& let Some((local_part, _)) = local_part.split_once('+')
|
||||
{
|
||||
return format!("{}@{}", local_part, domain_part).into();
|
||||
}
|
||||
}
|
||||
AddressMapping::Custom(if_block) => {
|
||||
|
||||
@@ -69,32 +69,32 @@ impl Server {
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
if self.is_enterprise_edition() {
|
||||
if let Some(tenant_id) = principal.tenant {
|
||||
// Limit tenant permissions
|
||||
if self.is_enterprise_edition()
|
||||
&& let Some(tenant_id) = principal.tenant
|
||||
{
|
||||
// Limit tenant permissions
|
||||
|
||||
use directory::QueryParams;
|
||||
permissions.intersection(&self.get_role_permissions(tenant_id).await?.enabled);
|
||||
use directory::QueryParams;
|
||||
permissions.intersection(&self.get_role_permissions(tenant_id).await?.enabled);
|
||||
|
||||
// Obtain tenant quota
|
||||
tenant = Some(TenantInfo {
|
||||
id: tenant_id,
|
||||
quota: self
|
||||
.store()
|
||||
.query(QueryParams::id(tenant_id).with_return_member_of(false))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| {
|
||||
trc::SecurityEvent::Unauthorized
|
||||
.into_err()
|
||||
.details("Tenant not found")
|
||||
.id(tenant_id)
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
.quota
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
// Obtain tenant quota
|
||||
tenant = Some(TenantInfo {
|
||||
id: tenant_id,
|
||||
quota: self
|
||||
.store()
|
||||
.query(QueryParams::id(tenant_id).with_return_member_of(false))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| {
|
||||
trc::SecurityEvent::Unauthorized
|
||||
.into_err()
|
||||
.details("Tenant not found")
|
||||
.id(tenant_id)
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
.quota
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
|
||||
// SPDX-SnippetEnd
|
||||
@@ -112,10 +112,9 @@ impl Server {
|
||||
.query(QueryParams::id(group_id).with_return_member_of(false))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
&& group.typ == Type::Group
|
||||
{
|
||||
if group.typ == Type::Group {
|
||||
emails.extend(group.emails);
|
||||
}
|
||||
emails.extend(group.emails);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,26 +167,24 @@ impl Server {
|
||||
}
|
||||
_ => {
|
||||
// Validate API credentials
|
||||
if req.allow_api_access {
|
||||
if let Ok(Some(principal)) = self
|
||||
if req.allow_api_access
|
||||
&& let Ok(Some(principal)) = self
|
||||
.store()
|
||||
.query(
|
||||
QueryParams::credentials(&req.credentials)
|
||||
.with_return_member_of(req.return_member_of),
|
||||
)
|
||||
.await
|
||||
{
|
||||
if principal.typ == Type::ApiKey {
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = principal.name().to_string(),
|
||||
AccountId = principal.id(),
|
||||
SpanId = req.session_id,
|
||||
);
|
||||
&& principal.typ == Type::ApiKey
|
||||
{
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = principal.name().to_string(),
|
||||
AccountId = principal.id(),
|
||||
SpanId = req.session_id,
|
||||
);
|
||||
|
||||
return Ok(principal);
|
||||
}
|
||||
}
|
||||
return Ok(principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,24 +57,23 @@ impl Server {
|
||||
}
|
||||
|
||||
pub async fn is_http_anonymous_request_allowed(&self, addr: &IpAddr) -> trc::Result<()> {
|
||||
if let Some(rate) = &self.core.jmap.rate_anonymous {
|
||||
if !self.is_ip_allowed(addr)
|
||||
&& self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.is_rate_allowed(
|
||||
KV_RATE_LIMIT_HTTP_ANONYMOUS,
|
||||
&ip_to_bytes(addr),
|
||||
rate,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::LimitEvent::TooManyRequests.into_err());
|
||||
}
|
||||
if let Some(rate) = &self.core.jmap.rate_anonymous
|
||||
&& !self.is_ip_allowed(addr)
|
||||
&& self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.is_rate_allowed(
|
||||
KV_RATE_LIMIT_HTTP_ANONYMOUS,
|
||||
&ip_to_bytes(addr),
|
||||
rate,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::LimitEvent::TooManyRequests.into_err());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -331,12 +331,11 @@ impl SpamFilterHeaderConfig {
|
||||
if config
|
||||
.property_or_default(("spam-filter.header", typ, "enable"), "true")
|
||||
.unwrap_or(true)
|
||||
&& let Some(value) = config.value(("spam-filter.header", typ, "name"))
|
||||
{
|
||||
if let Some(value) = config.value(("spam-filter.header", typ, "name")) {
|
||||
let value = value.trim();
|
||||
if !value.is_empty() {
|
||||
*var = value.to_string().into();
|
||||
}
|
||||
let value = value.trim();
|
||||
if !value.is_empty() {
|
||||
*var = value.to_string().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,12 +171,10 @@ impl Tracers {
|
||||
{
|
||||
if let Some(event_type) =
|
||||
config.try_parse_value::<EventType>(("tracing.level", &event_name), &event_name)
|
||||
{
|
||||
if let Some(level) =
|
||||
&& let Some(level) =
|
||||
config.property_require::<Level>(("tracing.level", &event_name))
|
||||
{
|
||||
custom_levels.insert(event_type, level);
|
||||
}
|
||||
{
|
||||
custom_levels.insert(event_type, level);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,28 +529,27 @@ impl Tracers {
|
||||
if config
|
||||
.property_or_default("tracing.history.enable", "false")
|
||||
.unwrap_or(false)
|
||||
&& let Some(store_id) = config.value_require("tracing.history.store")
|
||||
{
|
||||
if let Some(store_id) = config.value_require("tracing.history.store") {
|
||||
if let Some(store) = stores.stores.get(store_id) {
|
||||
let mut tracer = TelemetrySubscriber {
|
||||
id: "history".to_string(),
|
||||
interests: Default::default(),
|
||||
lossy: false,
|
||||
typ: TelemetrySubscriberType::StoreTracer(StoreTracer {
|
||||
store: store.clone(),
|
||||
}),
|
||||
};
|
||||
if let Some(store) = stores.stores.get(store_id) {
|
||||
let mut tracer = TelemetrySubscriber {
|
||||
id: "history".to_string(),
|
||||
interests: Default::default(),
|
||||
lossy: false,
|
||||
typ: TelemetrySubscriberType::StoreTracer(StoreTracer {
|
||||
store: store.clone(),
|
||||
}),
|
||||
};
|
||||
|
||||
for event_type in StoreTracer::default_events() {
|
||||
tracer.interests.set(event_type);
|
||||
global_interests.set(event_type);
|
||||
}
|
||||
|
||||
tracers.push(tracer);
|
||||
} else {
|
||||
let err = format!("Store {store_id} not found");
|
||||
config.new_build_error("tracing.history.store", err);
|
||||
for event_type in StoreTracer::default_events() {
|
||||
tracer.interests.set(event_type);
|
||||
global_interests.set(event_type);
|
||||
}
|
||||
|
||||
tracers.push(tracer);
|
||||
} else {
|
||||
let err = format!("Store {store_id} not found");
|
||||
config.new_build_error("tracing.history.store", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -617,14 +614,12 @@ impl Metrics {
|
||||
.value(("tracer", tracer_id, "type"))
|
||||
.unwrap_or_default()
|
||||
== "log"
|
||||
{
|
||||
if let Some(path) = config
|
||||
&& let Some(path) = config
|
||||
.value(("tracer", tracer_id, "path"))
|
||||
.map(|s| s.to_string())
|
||||
{
|
||||
metrics.log_path = Some(path);
|
||||
break;
|
||||
}
|
||||
{
|
||||
metrics.log_path = Some(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-34
@@ -412,16 +412,16 @@ impl Server {
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
if self.core.is_enterprise_edition() {
|
||||
if let Some(tenant) = quotas.tenant.filter(|tenant| tenant.quota != 0) {
|
||||
let used_quota = self.get_used_quota(tenant.id).await? as u64;
|
||||
if self.core.is_enterprise_edition()
|
||||
&& let Some(tenant) = quotas.tenant.filter(|tenant| tenant.quota != 0)
|
||||
{
|
||||
let used_quota = self.get_used_quota(tenant.id).await? as u64;
|
||||
|
||||
if used_quota + item_size > tenant.quota {
|
||||
return Err(trc::LimitEvent::TenantQuota
|
||||
.into_err()
|
||||
.ctx(trc::Key::Limit, tenant.quota)
|
||||
.ctx(trc::Key::Size, used_quota));
|
||||
}
|
||||
if used_quota + item_size > tenant.quota {
|
||||
return Err(trc::LimitEvent::TenantQuota
|
||||
.into_err()
|
||||
.ctx(trc::Key::Limit, tenant.quota)
|
||||
.ctx(trc::Key::Size, used_quota));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,24 +462,24 @@ impl Server {
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
if self.core.is_enterprise_edition() {
|
||||
if let Some(tenant_id) = principal.tenant() {
|
||||
quotas.tenant = TenantInfo {
|
||||
id: tenant_id,
|
||||
quota: self
|
||||
.core
|
||||
.storage
|
||||
.directory
|
||||
.query(QueryParams::id(tenant_id).with_return_member_of(false))
|
||||
.await
|
||||
.add_context(|err| {
|
||||
err.caused_by(trc::location!()).account_id(tenant_id)
|
||||
})?
|
||||
.map(|tenant| tenant.quota())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
.into();
|
||||
if self.core.is_enterprise_edition()
|
||||
&& let Some(tenant_id) = principal.tenant()
|
||||
{
|
||||
quotas.tenant = TenantInfo {
|
||||
id: tenant_id,
|
||||
quota: self
|
||||
.core
|
||||
.storage
|
||||
.directory
|
||||
.query(QueryParams::id(tenant_id).with_return_member_of(false))
|
||||
.await
|
||||
.add_context(|err| {
|
||||
err.caused_by(trc::location!()).account_id(tenant_id)
|
||||
})?
|
||||
.map(|tenant| tenant.quota())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
.into();
|
||||
}
|
||||
|
||||
// SPDX-SnippetEnd
|
||||
@@ -804,14 +804,14 @@ impl Server {
|
||||
}
|
||||
|
||||
pub async fn cluster_broadcast(&self, event: BroadcastEvent) {
|
||||
if let Some(broadcast_tx) = &self.inner.ipc.broadcast_tx.clone() {
|
||||
if broadcast_tx.send(event).await.is_err() {
|
||||
trc::event!(
|
||||
Server(trc::ServerEvent::ThreadError),
|
||||
Details = "Error sending broadcast event.",
|
||||
CausedBy = trc::location!()
|
||||
);
|
||||
}
|
||||
if let Some(broadcast_tx) = &self.inner.ipc.broadcast_tx.clone()
|
||||
&& broadcast_tx.send(event).await.is_err()
|
||||
{
|
||||
trc::event!(
|
||||
Server(trc::ServerEvent::ThreadError),
|
||||
Details = "Error sending broadcast event.",
|
||||
CausedBy = trc::location!()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -498,7 +498,7 @@ impl<'x> Variable<'x> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> StringCow {
|
||||
pub fn to_string(&'_ self) -> StringCow<'_> {
|
||||
match self {
|
||||
Variable::String(s) => StringCow::Borrowed(s.as_str()),
|
||||
Variable::Integer(n) => StringCow::Owned(n.to_compact_string()),
|
||||
@@ -577,7 +577,7 @@ impl<'x> Variable<'x> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_array(&self) -> Option<&[Variable]> {
|
||||
pub fn as_array(&'_ self) -> Option<&'_ [Variable<'_>]> {
|
||||
match self {
|
||||
Variable::Array(l) => Some(l),
|
||||
_ => None,
|
||||
|
||||
@@ -279,23 +279,23 @@ impl SocketOpts {
|
||||
Details = "Failed to set TCP_NODELAY",
|
||||
);
|
||||
}
|
||||
if let Some(ttl) = self.ttl {
|
||||
if let Err(err) = stream.set_ttl(ttl) {
|
||||
trc::event!(
|
||||
Network(trc::NetworkEvent::SetOptError),
|
||||
Reason = err.to_string(),
|
||||
Details = "Failed to set TTL",
|
||||
);
|
||||
}
|
||||
if let Some(ttl) = self.ttl
|
||||
&& let Err(err) = stream.set_ttl(ttl)
|
||||
{
|
||||
trc::event!(
|
||||
Network(trc::NetworkEvent::SetOptError),
|
||||
Reason = err.to_string(),
|
||||
Details = "Failed to set TTL",
|
||||
);
|
||||
}
|
||||
if self.linger.is_some() {
|
||||
if let Err(err) = stream.set_linger(self.linger) {
|
||||
trc::event!(
|
||||
Network(trc::NetworkEvent::SetOptError),
|
||||
Reason = err.to_string(),
|
||||
Details = "Failed to set LINGER",
|
||||
);
|
||||
}
|
||||
if self.linger.is_some()
|
||||
&& let Err(err) = stream.set_linger(self.linger)
|
||||
{
|
||||
trc::event!(
|
||||
Network(trc::NetworkEvent::SetOptError),
|
||||
Reason = err.to_string(),
|
||||
Details = "Failed to set LINGER",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,14 +462,13 @@ impl BootManager {
|
||||
if config
|
||||
.property_or_default::<bool>("spam-filter.auto-update", "false")
|
||||
.unwrap_or_default()
|
||||
&& let Err(err) = core.storage.config.update_spam_rules(false, false).await
|
||||
{
|
||||
if let Err(err) = core.storage.config.update_spam_rules(false, false).await {
|
||||
trc::event!(
|
||||
Resource(trc::ResourceEvent::Error),
|
||||
Details = "Failed to update spam-filter",
|
||||
CausedBy = err
|
||||
);
|
||||
}
|
||||
trc::event!(
|
||||
Resource(trc::ResourceEvent::Error),
|
||||
Details = "Failed to update spam-filter",
|
||||
CausedBy = err
|
||||
);
|
||||
}
|
||||
|
||||
// Build shared inner
|
||||
|
||||
@@ -45,17 +45,17 @@ impl DavResources {
|
||||
let check_acls = check_acls.into();
|
||||
|
||||
for resource in &self.resources {
|
||||
if resource.document_id == document_id {
|
||||
if let Some(acls) = resource.acls() {
|
||||
for acl in acls {
|
||||
if access_token.is_member(acl.account_id) {
|
||||
let mut grants = acl.grants;
|
||||
grants.intersection(&check_acls);
|
||||
return !grants.is_empty();
|
||||
}
|
||||
if resource.document_id == document_id
|
||||
&& let Some(acls) = resource.acls()
|
||||
{
|
||||
for acl in acls {
|
||||
if access_token.is_member(acl.account_id) {
|
||||
let mut grants = acl.grants;
|
||||
grants.intersection(&check_acls);
|
||||
return !grants.is_empty();
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,15 +66,15 @@ impl DavResources {
|
||||
let mut account_acls = Bitmap::<Acl>::new();
|
||||
|
||||
for resource in &self.resources {
|
||||
if resource.document_id == document_id {
|
||||
if let Some(acls) = resource.acls() {
|
||||
for acl in acls {
|
||||
if access_token.is_member(acl.account_id) {
|
||||
account_acls.union(&acl.grants);
|
||||
}
|
||||
if resource.document_id == document_id
|
||||
&& let Some(acls) = resource.acls()
|
||||
{
|
||||
for acl in acls {
|
||||
if access_token.is_member(acl.account_id) {
|
||||
account_acls.union(&acl.grants);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,23 +57,22 @@ pub(crate) fn spawn_otel_tracer(builder: SubscriberBuilder, mut otel: OtelTracer
|
||||
pending_logs.push(otel.build_log_record(&event));
|
||||
}
|
||||
|
||||
if otel.span_exporter_enable {
|
||||
if let Some(span) = event.inner.span.as_ref() {
|
||||
let span_id = span.span_id().unwrap();
|
||||
if !event.inner.typ.is_span_end() {
|
||||
let events =
|
||||
active_spans.entry(span_id).or_insert_with(Vec::new);
|
||||
if events.len() < MAX_EVENTS {
|
||||
events.push(event);
|
||||
}
|
||||
} else if let Some(events) = active_spans.remove(&span_id) {
|
||||
pending_spans.push(build_span_data(
|
||||
span,
|
||||
&event,
|
||||
events.iter().chain(std::iter::once(&event)),
|
||||
&instrumentation,
|
||||
));
|
||||
if otel.span_exporter_enable
|
||||
&& let Some(span) = event.inner.span.as_ref()
|
||||
{
|
||||
let span_id = span.span_id().unwrap();
|
||||
if !event.inner.typ.is_span_end() {
|
||||
let events = active_spans.entry(span_id).or_insert_with(Vec::new);
|
||||
if events.len() < MAX_EVENTS {
|
||||
events.push(event);
|
||||
}
|
||||
} else if let Some(events) = active_spans.remove(&span_id) {
|
||||
pending_spans.push(build_span_data(
|
||||
span,
|
||||
&event,
|
||||
events.iter().chain(std::iter::once(&event)),
|
||||
&instrumentation,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,18 +90,17 @@ pub(crate) fn spawn_otel_tracer(builder: SubscriberBuilder, mut otel: OtelTracer
|
||||
if !pending_spans.is_empty() || !pending_logs.is_empty() {
|
||||
next_delivery = now + otel.throttle;
|
||||
|
||||
if !pending_spans.is_empty() {
|
||||
if let Err(err) = otel
|
||||
if !pending_spans.is_empty()
|
||||
&& let Err(err) = otel
|
||||
.span_exporter
|
||||
.export(std::mem::take(&mut pending_spans))
|
||||
.await
|
||||
{
|
||||
trc::event!(
|
||||
Telemetry(TelemetryEvent::OtelExporterError),
|
||||
Details = "Failed to export spans",
|
||||
Reason = err.to_string()
|
||||
);
|
||||
}
|
||||
{
|
||||
trc::event!(
|
||||
Telemetry(TelemetryEvent::OtelExporterError),
|
||||
Details = "Failed to export spans",
|
||||
Reason = err.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
if !pending_logs.is_empty() {
|
||||
|
||||
@@ -29,7 +29,7 @@ impl<'x> Tokenizer<'x> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn token(&mut self) -> super::Result<Token> {
|
||||
pub fn token(&'_ mut self) -> super::Result<Token<'_>> {
|
||||
loop {
|
||||
if self.last_is_end {
|
||||
self.last_is_end = false;
|
||||
|
||||
@@ -275,11 +275,11 @@ impl CalendarQueryHandler {
|
||||
let mut matched_any = false;
|
||||
|
||||
for value in entry.values.iter() {
|
||||
if let Some(text) = value.as_text() {
|
||||
if text_match.matches(text) {
|
||||
matched_any = true;
|
||||
break;
|
||||
}
|
||||
if let Some(text) = value.as_text()
|
||||
&& text_match.matches(text)
|
||||
{
|
||||
matched_any = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,31 +443,29 @@ impl CalendarQueryHandler {
|
||||
.unwrap();
|
||||
|
||||
// Limit recurrence override
|
||||
if let Some(limit_recurrence) = &data.limit_recurrence {
|
||||
if component.is_recurrence_override()
|
||||
&& !self.expanded_times.iter().any(|event| {
|
||||
event.comp_id == component_id
|
||||
&& limit_recurrence.is_in_range(
|
||||
component.component_type == ICalendarComponentType::VTodo,
|
||||
event.start,
|
||||
event.end,
|
||||
)
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(limit_recurrence) = &data.limit_recurrence
|
||||
&& component.is_recurrence_override()
|
||||
&& !self.expanded_times.iter().any(|event| {
|
||||
event.comp_id == component_id
|
||||
&& limit_recurrence.is_in_range(
|
||||
component.component_type == ICalendarComponentType::VTodo,
|
||||
event.start,
|
||||
event.end,
|
||||
)
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Limit freebusy
|
||||
if let Some(limit_recurrence) = &data.limit_freebusy {
|
||||
if component.component_type == ICalendarComponentType::VFreebusy
|
||||
&& !self.expanded_times.iter().any(|event| {
|
||||
event.comp_id == component_id
|
||||
&& limit_recurrence.is_in_range(false, event.start, event.end)
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(limit_recurrence) = &data.limit_freebusy
|
||||
&& component.component_type == ICalendarComponentType::VFreebusy
|
||||
&& !self.expanded_times.iter().any(|event| {
|
||||
event.comp_id == component_id
|
||||
&& limit_recurrence.is_in_range(false, event.start, event.end)
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter entries
|
||||
|
||||
@@ -124,11 +124,11 @@ pub(crate) fn vcard_query(card: &ArchivedVCard, filters: &AddressbookFilter) ->
|
||||
let mut matched_any = false;
|
||||
|
||||
for value in entry.values.iter() {
|
||||
if let Some(text) = value.as_text() {
|
||||
if text_match.matches(text) {
|
||||
matched_any = true;
|
||||
break;
|
||||
}
|
||||
if let Some(text) = value.as_text()
|
||||
&& text_match.matches(text)
|
||||
{
|
||||
matched_any = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -526,8 +526,7 @@ impl LockRequestHandler for Server {
|
||||
|
||||
if let Some(document_id) =
|
||||
resource_state.document_id.filter(|&id| id != u32::MAX)
|
||||
{
|
||||
if let Some(archive) = self
|
||||
&& let Some(archive) = self
|
||||
.get_archive(
|
||||
resource_state.account_id,
|
||||
resource_state.collection,
|
||||
@@ -535,22 +534,22 @@ impl LockRequestHandler for Server {
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
resource_state.etag = archive.etag().into();
|
||||
}
|
||||
{
|
||||
resource_state.etag = archive.etag().into();
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch lock token
|
||||
if needs_lock_token && resource_state.lock_tokens.is_empty() {
|
||||
if let Some(idx) = locks.find_cache_pos(self, resource_state).await? {
|
||||
let found_locks = locks
|
||||
.find_locks_by_pos(idx, resource_state, false)?
|
||||
.iter()
|
||||
.map(|(_, lock)| lock.urn().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
resource_state.lock_tokens = found_locks;
|
||||
}
|
||||
if needs_lock_token
|
||||
&& resource_state.lock_tokens.is_empty()
|
||||
&& let Some(idx) = locks.find_cache_pos(self, resource_state).await?
|
||||
{
|
||||
let found_locks = locks
|
||||
.find_locks_by_pos(idx, resource_state, false)?
|
||||
.iter()
|
||||
.map(|(_, lock)| lock.urn().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
resource_state.lock_tokens = found_locks;
|
||||
}
|
||||
|
||||
// Fetch sync token
|
||||
|
||||
@@ -1090,12 +1090,11 @@ impl PropFindRequestHandler for Server {
|
||||
}
|
||||
|
||||
// Add dead properties
|
||||
if skip_not_found {
|
||||
if let Some(dead_properties) =
|
||||
if skip_not_found
|
||||
&& let Some(dead_properties) =
|
||||
dead_properties.filter(|dead_properties| !dead_properties.0.is_empty())
|
||||
{
|
||||
dead_properties.to_dav_values(&mut fields);
|
||||
}
|
||||
{
|
||||
dead_properties.to_dav_values(&mut fields);
|
||||
}
|
||||
|
||||
// Add response
|
||||
@@ -1297,15 +1296,15 @@ async fn get(
|
||||
}
|
||||
}
|
||||
|
||||
if maybe_has_vanished {
|
||||
if let Some(vanished_collection) = sync_collection.vanished_collection() {
|
||||
vanished = server
|
||||
.store()
|
||||
.vanished(account_id, vanished_collection, Query::Since(id))
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
total_changes += vanished.len();
|
||||
}
|
||||
if maybe_has_vanished
|
||||
&& let Some(vanished_collection) = sync_collection.vanished_collection()
|
||||
{
|
||||
vanished = server
|
||||
.store()
|
||||
.vanished(account_id, vanished_collection, Query::Since(id))
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
total_changes += vanished.len();
|
||||
}
|
||||
|
||||
// Truncate changes
|
||||
|
||||
@@ -146,16 +146,15 @@ impl FileCopyMoveRequestHandler for Server {
|
||||
|
||||
// Validate destination ACLs
|
||||
if let Some(document_id) = destination.document_id {
|
||||
if let Some(delete_destination) = &delete_destination {
|
||||
if !access_token.is_member(to_account_id)
|
||||
&& !from_resources.has_access_to_container(
|
||||
access_token,
|
||||
delete_destination.document_id.unwrap(),
|
||||
Acl::Delete,
|
||||
)
|
||||
{
|
||||
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
||||
}
|
||||
if let Some(delete_destination) = &delete_destination
|
||||
&& !access_token.is_member(to_account_id)
|
||||
&& !from_resources.has_access_to_container(
|
||||
access_token,
|
||||
delete_destination.document_id.unwrap(),
|
||||
Acl::Delete,
|
||||
)
|
||||
{
|
||||
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
||||
}
|
||||
|
||||
if !access_token.is_member(to_account_id)
|
||||
|
||||
@@ -235,7 +235,7 @@ impl PropStatBuilder {
|
||||
}
|
||||
|
||||
// Workaround for Apple bug with missing percent encoding in paths
|
||||
pub(crate) fn fix_percent_encoding(path: &str) -> Cow<str> {
|
||||
pub(crate) fn fix_percent_encoding(path: &'_ str) -> Cow<'_, str> {
|
||||
let (parent, name) = if let Some((parent, name)) = path.rsplit_once('/') {
|
||||
(Some(parent), name)
|
||||
} else {
|
||||
|
||||
@@ -45,39 +45,39 @@ impl DirectoryStore for Store {
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(account_id) = account_id {
|
||||
if let Some(mut principal) = self.get_principal(account_id).await? {
|
||||
if let Some(secret) = secret {
|
||||
if !principal.verify_secret(secret, by.only_app_pass).await? {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
if by.return_member_of {
|
||||
let mut roles = vec![];
|
||||
let mut lists = vec![];
|
||||
let mut member_of = vec![];
|
||||
|
||||
for member in self.get_member_of(principal.id).await? {
|
||||
match member.typ {
|
||||
Type::List => lists.push(member.principal_id),
|
||||
Type::Role => roles.push(member.principal_id),
|
||||
_ => member_of.push(member.principal_id),
|
||||
}
|
||||
}
|
||||
|
||||
if !roles.is_empty() {
|
||||
principal.data.push(PrincipalData::Roles(roles));
|
||||
}
|
||||
if !lists.is_empty() {
|
||||
principal.data.push(PrincipalData::Lists(lists));
|
||||
}
|
||||
if !member_of.is_empty() {
|
||||
principal.data.push(PrincipalData::MemberOf(member_of));
|
||||
}
|
||||
}
|
||||
return Ok(Some(principal));
|
||||
if let Some(account_id) = account_id
|
||||
&& let Some(mut principal) = self.get_principal(account_id).await?
|
||||
{
|
||||
if let Some(secret) = secret
|
||||
&& !principal.verify_secret(secret, by.only_app_pass).await?
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if by.return_member_of {
|
||||
let mut roles = vec![];
|
||||
let mut lists = vec![];
|
||||
let mut member_of = vec![];
|
||||
|
||||
for member in self.get_member_of(principal.id).await? {
|
||||
match member.typ {
|
||||
Type::List => lists.push(member.principal_id),
|
||||
Type::Role => roles.push(member.principal_id),
|
||||
_ => member_of.push(member.principal_id),
|
||||
}
|
||||
}
|
||||
|
||||
if !roles.is_empty() {
|
||||
principal.data.push(PrincipalData::Roles(roles));
|
||||
}
|
||||
if !lists.is_empty() {
|
||||
principal.data.push(PrincipalData::Lists(lists));
|
||||
}
|
||||
if !member_of.is_empty() {
|
||||
principal.data.push(PrincipalData::MemberOf(member_of));
|
||||
}
|
||||
}
|
||||
return Ok(Some(principal));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -354,16 +354,15 @@ impl ManageDirectory for Store {
|
||||
principal_create.tenant = tenant_id.into();
|
||||
|
||||
if !matches!(principal_create.typ, Type::Tenant | Type::Domain) {
|
||||
if let Some(domain) = name.split('@').nth(1) {
|
||||
if self
|
||||
if let Some(domain) = name.split('@').nth(1)
|
||||
&& self
|
||||
.get_principal_info(domain)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.filter(|v| v.typ == Type::Domain && v.has_tenant_access(tenant_id.into()))
|
||||
.is_some()
|
||||
{
|
||||
valid_domains.insert(domain.into());
|
||||
}
|
||||
{
|
||||
valid_domains.insert(domain.into());
|
||||
}
|
||||
|
||||
if valid_domains.is_empty() {
|
||||
@@ -524,14 +523,14 @@ impl ManageDirectory for Store {
|
||||
if self.rcpt(&email).await.caused_by(trc::location!())? != RcptType::Invalid {
|
||||
return Err(err_exists(PrincipalField::Emails, email.to_string()));
|
||||
}
|
||||
if let Some(domain) = email.split('@').nth(1) {
|
||||
if valid_domains.insert(domain.into()) {
|
||||
self.get_principal_info(domain)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.filter(|v| v.typ == Type::Domain && v.has_tenant_access(tenant_id))
|
||||
.ok_or_else(|| not_found(domain.to_string()))?;
|
||||
}
|
||||
if let Some(domain) = email.split('@').nth(1)
|
||||
&& valid_domains.insert(domain.into())
|
||||
{
|
||||
self.get_principal_info(domain)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.filter(|v| v.typ == Type::Domain && v.has_tenant_access(tenant_id))
|
||||
.ok_or_else(|| not_found(domain.to_string()))?;
|
||||
}
|
||||
principal_create.emails.push(email);
|
||||
}
|
||||
@@ -1004,8 +1003,8 @@ impl ManageDirectory for Store {
|
||||
if tenant_id.is_some()
|
||||
&& !matches!(principal_type, Type::Tenant | Type::Domain)
|
||||
{
|
||||
if let Some(domain) = new_name.split('@').nth(1) {
|
||||
if self
|
||||
if let Some(domain) = new_name.split('@').nth(1)
|
||||
&& self
|
||||
.get_principal_info(domain)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
@@ -1013,9 +1012,8 @@ impl ManageDirectory for Store {
|
||||
v.typ == Type::Domain && v.has_tenant_access(tenant_id)
|
||||
})
|
||||
.is_some()
|
||||
{
|
||||
valid_domains.insert(domain.to_string());
|
||||
}
|
||||
{
|
||||
valid_domains.insert(domain.to_string());
|
||||
}
|
||||
|
||||
if valid_domains.is_empty() {
|
||||
@@ -1574,19 +1572,18 @@ impl ManageDirectory for Store {
|
||||
for member_id in &members {
|
||||
if !new_members.contains(member_id) {
|
||||
// Update changed principal ids
|
||||
if principal_type != Type::List {
|
||||
if let Some(member_info) = self
|
||||
if principal_type != Type::List
|
||||
&& let Some(member_info) = self
|
||||
.get_principal(*member_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
changed_principals.add_member_change(
|
||||
*member_id,
|
||||
member_info.typ,
|
||||
principal_id,
|
||||
principal_type,
|
||||
);
|
||||
}
|
||||
{
|
||||
changed_principals.add_member_change(
|
||||
*member_id,
|
||||
member_info.typ,
|
||||
principal_id,
|
||||
principal_type,
|
||||
);
|
||||
}
|
||||
|
||||
batch.clear(ValueClass::Directory(DirectoryClass::MemberOf {
|
||||
@@ -2331,16 +2328,14 @@ impl ManageDirectory for Store {
|
||||
|
||||
// Map tenant name
|
||||
#[cfg(feature = "enterprise")]
|
||||
if let Some(tenant_id) = principal.tenant {
|
||||
if fields.is_empty() || fields.contains(&PrincipalField::Tenant) {
|
||||
if let Some(name) = self
|
||||
.get_principal_name(tenant_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
result.set(PrincipalField::Tenant, name);
|
||||
}
|
||||
}
|
||||
if let Some(tenant_id) = principal.tenant
|
||||
&& (fields.is_empty() || fields.contains(&PrincipalField::Tenant))
|
||||
&& let Some(name) = self
|
||||
.get_principal_name(tenant_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
result.set(PrincipalField::Tenant, name);
|
||||
}
|
||||
|
||||
// SPDX-SnippetEnd
|
||||
@@ -2350,10 +2345,10 @@ impl ManageDirectory for Store {
|
||||
(PrincipalField::Name, Some(principal.name)),
|
||||
(PrincipalField::Description, principal.description),
|
||||
] {
|
||||
if let Some(value) = value {
|
||||
if fields.is_empty() || fields.contains(&name) {
|
||||
result.set(name, value);
|
||||
}
|
||||
if let Some(value) = value
|
||||
&& (fields.is_empty() || fields.contains(&name))
|
||||
{
|
||||
result.set(name, value);
|
||||
}
|
||||
}
|
||||
for (name, value) in [
|
||||
@@ -2637,7 +2632,7 @@ impl ChangedPrincipals {
|
||||
self.0.contains_key(&principal_id)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> std::collections::hash_map::Iter<u32, ChangedPrincipal> {
|
||||
pub fn iter(&'_ self) -> std::collections::hash_map::Iter<'_, u32, ChangedPrincipal> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
|
||||
@@ -230,13 +230,12 @@ impl LdapDirectory {
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
for entry in rs {
|
||||
'outer: for (attr, value) in SearchEntry::construct(entry).attrs {
|
||||
if self.mappings.attr_name.contains(&attr) {
|
||||
if let Some(group) = value.into_iter().next() {
|
||||
if !group.is_empty() {
|
||||
name = group;
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
if self.mappings.attr_name.contains(&attr)
|
||||
&& let Some(group) = value.into_iter().next()
|
||||
&& !group.is_empty()
|
||||
{
|
||||
name = group;
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,14 +313,14 @@ impl LdapDirectory {
|
||||
for entry in rs {
|
||||
let entry = SearchEntry::construct(entry);
|
||||
for attr in &self.mappings.attr_name {
|
||||
if let Some(name) = entry.attrs.get(attr).and_then(|v| v.first()) {
|
||||
if !name.is_empty() {
|
||||
return self
|
||||
.data_store
|
||||
.get_or_create_principal_id(name, Type::Individual)
|
||||
.await
|
||||
.map(Some);
|
||||
}
|
||||
if let Some(name) = entry.attrs.get(attr).and_then(|v| v.first())
|
||||
&& !name.is_empty()
|
||||
{
|
||||
return self
|
||||
.data_store
|
||||
.get_or_create_principal_id(name, Type::Individual)
|
||||
.await
|
||||
.map(Some);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,10 +294,10 @@ impl SqlMappings {
|
||||
if let Value::Text(text) = value {
|
||||
principal.emails.push(text.to_lowercase());
|
||||
}
|
||||
} else if name.eq_ignore_ascii_case(&self.column_quota) {
|
||||
if let Value::Integer(quota) = value {
|
||||
principal.quota = (quota as u64).into();
|
||||
}
|
||||
} else if name.eq_ignore_ascii_case(&self.column_quota)
|
||||
&& let Value::Integer(quota) = value
|
||||
{
|
||||
principal.quota = (quota as u64).into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,10 @@ impl Directory {
|
||||
|
||||
pub async fn is_local_domain(&self, domain: &str) -> trc::Result<bool> {
|
||||
// Check cache
|
||||
if let Some(cache) = &self.cache {
|
||||
if let Some(result) = cache.get_domain(domain) {
|
||||
return Ok(result);
|
||||
}
|
||||
if let Some(cache) = &self.cache
|
||||
&& let Some(result) = cache.get_domain(domain)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
let result = match &self.store {
|
||||
@@ -67,10 +67,10 @@ impl Directory {
|
||||
|
||||
pub async fn rcpt(&self, email: &str) -> trc::Result<RcptType> {
|
||||
// Check cache
|
||||
if let Some(cache) = &self.cache {
|
||||
if let Some(result) = cache.get_rcpt(email) {
|
||||
return Ok(result);
|
||||
}
|
||||
if let Some(cache) = &self.cache
|
||||
&& let Some(result) = cache.get_rcpt(email)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
let result = match &self.store {
|
||||
|
||||
@@ -261,6 +261,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[allow(clippy::obfuscated_if_else)]
|
||||
fn print_permissions() {
|
||||
const CHECK: &str = ":white_check_mark:";
|
||||
|
||||
|
||||
@@ -238,13 +238,11 @@ impl Principal {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
if let Some(idx) = permissions
|
||||
.iter_mut()
|
||||
.position(|p| p.permission == permission && p.grant == grant)
|
||||
{
|
||||
permissions.swap_remove(idx);
|
||||
}
|
||||
}) && let Some(idx) = permissions
|
||||
.iter_mut()
|
||||
.position(|p| p.permission == permission && p.grant == grant)
|
||||
{
|
||||
permissions.swap_remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,10 +268,10 @@ impl Principal {
|
||||
}
|
||||
|
||||
// If the principal has no roles, take the ones from the external principal
|
||||
if let Some(roles) = external.roles_mut().filter(|s| !s.is_empty()) {
|
||||
if self.roles().is_empty() {
|
||||
self.data.push(PrincipalData::Roles(std::mem::take(roles)));
|
||||
}
|
||||
if let Some(roles) = external.roles_mut().filter(|s| !s.is_empty())
|
||||
&& self.roles().is_empty()
|
||||
{
|
||||
self.data.push(PrincipalData::Roles(std::mem::take(roles)));
|
||||
}
|
||||
|
||||
if external.description.as_ref().is_some_and(|v| !v.is_empty())
|
||||
|
||||
Vendored
+8
-9
@@ -33,18 +33,17 @@ pub(crate) async fn update_email_cache(
|
||||
};
|
||||
|
||||
for (document_id, is_update) in changed_ids {
|
||||
if *is_update {
|
||||
if let Some(archive) = server
|
||||
if *is_update
|
||||
&& let Some(archive) = server
|
||||
.get_archive(account_id, Collection::Email, *document_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
insert_item(
|
||||
&mut new_cache,
|
||||
*document_id,
|
||||
archive.to_unarchived::<MessageData>()?,
|
||||
);
|
||||
}
|
||||
{
|
||||
insert_item(
|
||||
&mut new_cache,
|
||||
*document_id,
|
||||
archive.to_unarchived::<MessageData>()?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+8
-9
@@ -28,18 +28,17 @@ pub(crate) async fn update_mailbox_cache(
|
||||
};
|
||||
|
||||
for (document_id, is_update) in changed_ids {
|
||||
if *is_update {
|
||||
if let Some(archive) = server
|
||||
if *is_update
|
||||
&& let Some(archive) = server
|
||||
.get_archive(account_id, Collection::Mailbox, *document_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
insert_item(
|
||||
&mut new_cache,
|
||||
*document_id,
|
||||
archive.unarchive::<Mailbox>()?,
|
||||
);
|
||||
}
|
||||
{
|
||||
insert_item(
|
||||
&mut new_cache,
|
||||
*document_id,
|
||||
archive.unarchive::<Mailbox>()?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,23 +136,23 @@ impl EmailDeletion for Server {
|
||||
}
|
||||
|
||||
// Auto-expunge deleted and junk messages
|
||||
if let Some(hold_period) = self.core.jmap.mail_autoexpunge_after {
|
||||
if let Err(err) = self.emails_auto_expunge(account_id, hold_period).await {
|
||||
trc::error!(
|
||||
err.details("Failed to auto-expunge e-mail messages.")
|
||||
.account_id(account_id)
|
||||
);
|
||||
}
|
||||
if let Some(hold_period) = self.core.jmap.mail_autoexpunge_after
|
||||
&& let Err(err) = self.emails_auto_expunge(account_id, hold_period).await
|
||||
{
|
||||
trc::error!(
|
||||
err.details("Failed to auto-expunge e-mail messages.")
|
||||
.account_id(account_id)
|
||||
);
|
||||
}
|
||||
|
||||
// Auto-expunge iMIP messages
|
||||
if let Some(hold_period) = self.core.groupware.itip_inbox_auto_expunge {
|
||||
if let Err(err) = self.itip_auto_expunge(account_id, hold_period).await {
|
||||
trc::error!(
|
||||
err.details("Failed to auto-expunge iTIP messages.")
|
||||
.account_id(account_id)
|
||||
);
|
||||
}
|
||||
if let Some(hold_period) = self.core.groupware.itip_inbox_auto_expunge
|
||||
&& let Err(err) = self.itip_auto_expunge(account_id, hold_period).await
|
||||
{
|
||||
trc::error!(
|
||||
err.details("Failed to auto-expunge iTIP messages.")
|
||||
.account_id(account_id)
|
||||
);
|
||||
}
|
||||
|
||||
// Purge tombstoned messages
|
||||
@@ -164,13 +164,13 @@ impl EmailDeletion for Server {
|
||||
}
|
||||
|
||||
// Purge changelogs
|
||||
if let Some(history) = self.core.jmap.changes_max_history {
|
||||
if let Err(err) = self.delete_changes(account_id, history).await {
|
||||
trc::error!(
|
||||
err.details("Failed to purge changes.")
|
||||
.account_id(account_id)
|
||||
);
|
||||
}
|
||||
if let Some(history) = self.core.jmap.changes_max_history
|
||||
&& let Err(err) = self.delete_changes(account_id, history).await
|
||||
{
|
||||
trc::error!(
|
||||
err.details("Failed to purge changes.")
|
||||
.account_id(account_id)
|
||||
);
|
||||
}
|
||||
|
||||
// Delete lock
|
||||
|
||||
+133
-138
@@ -205,38 +205,36 @@ impl EmailIngest for Server {
|
||||
}
|
||||
|
||||
// If the message is classified as spam, check whether the sender address is present in the user's address book
|
||||
if is_spam && self.core.spam.card_is_ham {
|
||||
if let Some(sender) = message
|
||||
if is_spam
|
||||
&& self.core.spam.card_is_ham
|
||||
&& let Some(sender) = message
|
||||
.from()
|
||||
.and_then(|s| s.first())
|
||||
.and_then(|s| s.address())
|
||||
.and_then(sanitize_email)
|
||||
&& sender != deliver_to
|
||||
&& is_sender_authenticated
|
||||
&& !self
|
||||
.store()
|
||||
.filter(
|
||||
account_id,
|
||||
Collection::ContactCard,
|
||||
vec![Filter::eq(IDX_EMAIL, sender.into_bytes())],
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.results
|
||||
.is_empty()
|
||||
{
|
||||
is_spam = false;
|
||||
if self
|
||||
.core
|
||||
.spam
|
||||
.bayes
|
||||
.as_ref()
|
||||
.is_some_and(|config| config.auto_learn_card_is_ham)
|
||||
{
|
||||
if sender != deliver_to
|
||||
&& is_sender_authenticated
|
||||
&& !self
|
||||
.store()
|
||||
.filter(
|
||||
account_id,
|
||||
Collection::ContactCard,
|
||||
vec![Filter::eq(IDX_EMAIL, sender.into_bytes())],
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.results
|
||||
.is_empty()
|
||||
{
|
||||
is_spam = false;
|
||||
if self
|
||||
.core
|
||||
.spam
|
||||
.bayes
|
||||
.as_ref()
|
||||
.is_some_and(|config| config.auto_learn_card_is_ham)
|
||||
{
|
||||
train_spam = Some(false);
|
||||
}
|
||||
}
|
||||
train_spam = Some(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,75 +313,72 @@ impl EmailIngest for Server {
|
||||
.subtype()
|
||||
.is_some_and(|st| st.eq_ignore_ascii_case("calendar"))
|
||||
&& ct.has_attribute("method")
|
||||
}) {
|
||||
if let Some(itip_message) = part.text_contents() {
|
||||
if itip_message.len()
|
||||
< self.core.groupware.itip_inbound_max_ical_size
|
||||
{
|
||||
if let Some(sender) = sender.get_or_insert_with(|| {
|
||||
message
|
||||
.from()
|
||||
.and_then(|s| s.first())
|
||||
.and_then(|s| s.address())
|
||||
.and_then(sanitize_email)
|
||||
}) {
|
||||
match self
|
||||
.itip_ingest(
|
||||
params.access_token,
|
||||
&resource_token,
|
||||
sender,
|
||||
itip_message,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(message) => {
|
||||
if let Some(message) = message {
|
||||
itip_messages.push(message);
|
||||
}
|
||||
trc::event!(
|
||||
Calendar(
|
||||
trc::CalendarEvent::ItipMessageReceived
|
||||
),
|
||||
SpanId = params.session_id,
|
||||
From = sender.to_string(),
|
||||
AccountId = account_id,
|
||||
);
|
||||
}) && let Some(itip_message) = part.text_contents()
|
||||
{
|
||||
if itip_message.len() < self.core.groupware.itip_inbound_max_ical_size {
|
||||
if let Some(sender) = sender.get_or_insert_with(|| {
|
||||
message
|
||||
.from()
|
||||
.and_then(|s| s.first())
|
||||
.and_then(|s| s.address())
|
||||
.and_then(sanitize_email)
|
||||
}) {
|
||||
match self
|
||||
.itip_ingest(
|
||||
params.access_token,
|
||||
&resource_token,
|
||||
sender,
|
||||
itip_message,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(message) => {
|
||||
if let Some(message) = message {
|
||||
itip_messages.push(message);
|
||||
}
|
||||
Err(ItipIngestError::Message(itip_error)) => {
|
||||
match itip_error {
|
||||
ItipError::NothingToSend
|
||||
| ItipError::OtherSchedulingAgent => (),
|
||||
err => {
|
||||
trc::event!(
|
||||
Calendar(trc::CalendarEvent::ItipMessageError),
|
||||
SpanId = params.session_id,
|
||||
From = sender.to_string(),
|
||||
AccountId = account_id,
|
||||
Details = err.to_string(),
|
||||
)
|
||||
}
|
||||
trc::event!(
|
||||
Calendar(trc::CalendarEvent::ItipMessageReceived),
|
||||
SpanId = params.session_id,
|
||||
From = sender.to_string(),
|
||||
AccountId = account_id,
|
||||
);
|
||||
}
|
||||
Err(ItipIngestError::Message(itip_error)) => {
|
||||
match itip_error {
|
||||
ItipError::NothingToSend
|
||||
| ItipError::OtherSchedulingAgent => (),
|
||||
err => {
|
||||
trc::event!(
|
||||
Calendar(
|
||||
trc::CalendarEvent::ItipMessageError
|
||||
),
|
||||
SpanId = params.session_id,
|
||||
From = sender.to_string(),
|
||||
AccountId = account_id,
|
||||
Details = err.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Err(ItipIngestError::Internal(err)) => {
|
||||
trc::error!(err.caused_by(trc::location!()));
|
||||
}
|
||||
}
|
||||
Err(ItipIngestError::Internal(err)) => {
|
||||
trc::error!(err.caused_by(trc::location!()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
trc::event!(
|
||||
Calendar(trc::CalendarEvent::ItipMessageError),
|
||||
SpanId = params.session_id,
|
||||
From = message
|
||||
.from()
|
||||
.and_then(|a| a.first())
|
||||
.and_then(|a| a.address())
|
||||
.map(|a| a.to_string()),
|
||||
AccountId = account_id,
|
||||
Details = "iMIP message too large",
|
||||
Limit = self.core.groupware.itip_inbound_max_ical_size,
|
||||
Size = itip_message.len(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
trc::event!(
|
||||
Calendar(trc::CalendarEvent::ItipMessageError),
|
||||
SpanId = params.session_id,
|
||||
From = message
|
||||
.from()
|
||||
.and_then(|a| a.first())
|
||||
.and_then(|a| a.address())
|
||||
.map(|a| a.to_string()),
|
||||
AccountId = account_id,
|
||||
Details = "iMIP message too large",
|
||||
Limit = self.core.groupware.itip_inbound_max_ical_size,
|
||||
Size = itip_message.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,12 +510,12 @@ impl EmailIngest for Server {
|
||||
part.offset_end += offset_start as u32;
|
||||
part.offset_header += offset_start as u32;
|
||||
|
||||
if let PartType::Message(sub_message) = &mut part.body {
|
||||
if sub_message.root_part().offset_header != 0 {
|
||||
sub_message.raw_message = raw_message.as_ref().into();
|
||||
part_iter_stack.push(part_iter);
|
||||
part_iter = sub_message.parts.iter_mut();
|
||||
}
|
||||
if let PartType::Message(sub_message) = &mut part.body
|
||||
&& sub_message.root_part().offset_header != 0
|
||||
{
|
||||
sub_message.raw_message = raw_message.as_ref().into();
|
||||
part_iter_stack.push(part_iter);
|
||||
part_iter = sub_message.parts.iter_mut();
|
||||
}
|
||||
} else if let Some(iter) = part_iter_stack.pop() {
|
||||
part_iter = iter;
|
||||
@@ -544,56 +539,56 @@ impl EmailIngest for Server {
|
||||
IngestSource::Smtp { .. } => self.core.jmap.encrypt,
|
||||
IngestSource::Restore => false,
|
||||
};
|
||||
if do_encrypt && !message.is_encrypted() {
|
||||
if let Some(encrypt_params_) = self
|
||||
if do_encrypt
|
||||
&& !message.is_encrypted()
|
||||
&& let Some(encrypt_params_) = self
|
||||
.get_archive_by_property(account_id, Collection::Principal, 0, Property::Parameters)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
let encrypt_params = encrypt_params_
|
||||
.unarchive::<EncryptionParams>()
|
||||
.caused_by(trc::location!())?;
|
||||
match message.encrypt(encrypt_params).await {
|
||||
Ok(new_raw_message) => {
|
||||
raw_message = Cow::from(new_raw_message);
|
||||
raw_message_len = raw_message.len() as u64;
|
||||
message = MessageParser::default()
|
||||
.parse(raw_message.as_ref())
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::MessageIngest(trc::MessageIngestEvent::Error)
|
||||
.ctx(trc::Key::Code, 550)
|
||||
.ctx(
|
||||
trc::Key::Reason,
|
||||
"Failed to parse encrypted e-mail message.",
|
||||
)
|
||||
})?;
|
||||
{
|
||||
let encrypt_params = encrypt_params_
|
||||
.unarchive::<EncryptionParams>()
|
||||
.caused_by(trc::location!())?;
|
||||
match message.encrypt(encrypt_params).await {
|
||||
Ok(new_raw_message) => {
|
||||
raw_message = Cow::from(new_raw_message);
|
||||
raw_message_len = raw_message.len() as u64;
|
||||
message = MessageParser::default()
|
||||
.parse(raw_message.as_ref())
|
||||
.ok_or_else(|| {
|
||||
trc::EventType::MessageIngest(trc::MessageIngestEvent::Error)
|
||||
.ctx(trc::Key::Code, 550)
|
||||
.ctx(
|
||||
trc::Key::Reason,
|
||||
"Failed to parse encrypted e-mail message.",
|
||||
)
|
||||
})?;
|
||||
|
||||
// Remove contents from parsed message
|
||||
for part in &mut message.parts {
|
||||
match &mut part.body {
|
||||
PartType::Text(txt) | PartType::Html(txt) => {
|
||||
*txt = Cow::from("");
|
||||
}
|
||||
PartType::Binary(bin) | PartType::InlineBinary(bin) => {
|
||||
*bin = Cow::from(&[][..]);
|
||||
}
|
||||
PartType::Message(_) => {
|
||||
part.body = PartType::Binary(Cow::from(&[][..]));
|
||||
}
|
||||
PartType::Multipart(_) => (),
|
||||
// Remove contents from parsed message
|
||||
for part in &mut message.parts {
|
||||
match &mut part.body {
|
||||
PartType::Text(txt) | PartType::Html(txt) => {
|
||||
*txt = Cow::from("");
|
||||
}
|
||||
PartType::Binary(bin) | PartType::InlineBinary(bin) => {
|
||||
*bin = Cow::from(&[][..]);
|
||||
}
|
||||
PartType::Message(_) => {
|
||||
part.body = PartType::Binary(Cow::from(&[][..]));
|
||||
}
|
||||
PartType::Multipart(_) => (),
|
||||
}
|
||||
}
|
||||
Err(EncryptMessageError::Error(err)) => {
|
||||
trc::bail!(
|
||||
trc::StoreEvent::CryptoError
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Err(EncryptMessageError::Error(err)) => {
|
||||
trc::bail!(
|
||||
trc::StoreEvent::CryptoError
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,26 +78,25 @@ impl SieveScriptActivate for Server {
|
||||
}
|
||||
|
||||
// Activate script
|
||||
if let Some(document_id) = activate_id {
|
||||
if let Some(sieve_) = self
|
||||
if let Some(document_id) = activate_id
|
||||
&& let Some(sieve_) = self
|
||||
.get_archive(account_id, Collection::SieveScript, document_id)
|
||||
.await?
|
||||
{
|
||||
let sieve = sieve_
|
||||
.to_unarchived::<SieveScript>()
|
||||
.caused_by(trc::location!())?;
|
||||
let mut new_sieve = sieve.deserialize().caused_by(trc::location!())?;
|
||||
new_sieve.is_active = true;
|
||||
batch
|
||||
.update_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
.with_changes(new_sieve)
|
||||
.with_current(sieve),
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
changed_ids.push((document_id, true));
|
||||
}
|
||||
{
|
||||
let sieve = sieve_
|
||||
.to_unarchived::<SieveScript>()
|
||||
.caused_by(trc::location!())?;
|
||||
let mut new_sieve = sieve.deserialize().caused_by(trc::location!())?;
|
||||
new_sieve.is_active = true;
|
||||
batch
|
||||
.update_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
.with_changes(new_sieve)
|
||||
.with_current(sieve),
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
changed_ids.push((document_id, true));
|
||||
}
|
||||
|
||||
// Write changes
|
||||
|
||||
@@ -186,10 +186,10 @@ impl SieveScriptIngest for Server {
|
||||
TRASH_ID
|
||||
} else {
|
||||
let mut mailbox_id = u32::MAX;
|
||||
if let Ok(role) = SpecialUse::parse_value(&role) {
|
||||
if let Some(m) = cache.mailbox_by_role(&role) {
|
||||
mailbox_id = m.document_id;
|
||||
}
|
||||
if let Ok(role) = SpecialUse::parse_value(&role)
|
||||
&& let Some(m) = cache.mailbox_by_role(&role)
|
||||
{
|
||||
mailbox_id = m.document_id;
|
||||
}
|
||||
|
||||
mailbox_id
|
||||
@@ -317,17 +317,17 @@ impl SieveScriptIngest for Server {
|
||||
}
|
||||
|
||||
// Find mailbox by role
|
||||
if let Some(special_use) = special_use {
|
||||
if target_id == u32::MAX {
|
||||
if special_use.eq_ignore_ascii_case("inbox") {
|
||||
target_id = INBOX_ID;
|
||||
} else if special_use.eq_ignore_ascii_case("trash") {
|
||||
target_id = TRASH_ID;
|
||||
} else if let Ok(role) = SpecialUse::parse_value(&special_use) {
|
||||
if let Some(item) = cache.mailbox_by_role(&role) {
|
||||
target_id = item.document_id;
|
||||
}
|
||||
}
|
||||
if let Some(special_use) = special_use
|
||||
&& target_id == u32::MAX
|
||||
{
|
||||
if special_use.eq_ignore_ascii_case("inbox") {
|
||||
target_id = INBOX_ID;
|
||||
} else if special_use.eq_ignore_ascii_case("trash") {
|
||||
target_id = TRASH_ID;
|
||||
} else if let Ok(role) = SpecialUse::parse_value(&special_use)
|
||||
&& let Some(item) = cache.mailbox_by_role(&role)
|
||||
{
|
||||
target_id = item.document_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,54 +80,9 @@ impl ArchivedCalendarEventData {
|
||||
.single()?
|
||||
.timestamp();
|
||||
|
||||
if let Some(alarm_time) = alarm.delta.to_timestamp(start, end, default_tz) {
|
||||
if alarm_time > start_time {
|
||||
if let Some(next) = next_alarm {
|
||||
if alarm_time < next.alarm_time {
|
||||
next_alarm = Some(CalendarAlarm {
|
||||
alarm_id: alarm.id.to_native(),
|
||||
event_id: alarm.parent_id.to_native(),
|
||||
alarm_time,
|
||||
event_start: start_date_naive,
|
||||
event_start_tz: start_tz.as_id(),
|
||||
event_end: end_date_naive,
|
||||
event_end_tz: end_tz.as_id(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
next_alarm = Some(CalendarAlarm {
|
||||
alarm_id: alarm.id.to_native(),
|
||||
event_id: alarm.parent_id.to_native(),
|
||||
alarm_time,
|
||||
event_start: start_date_naive,
|
||||
event_start_tz: start_tz.as_id(),
|
||||
event_end: end_date_naive,
|
||||
event_end_tz: end_tz.as_id(),
|
||||
});
|
||||
}
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single event
|
||||
let start_date_naive = offset_or_count as i64 + base_offset;
|
||||
let end_date_naive = start_date_naive + duration;
|
||||
let start = start_tz
|
||||
.from_local_datetime(
|
||||
&DateTime::from_timestamp(start_date_naive, 0)?.naive_local(),
|
||||
)
|
||||
.single()?
|
||||
.timestamp();
|
||||
let end = end_tz
|
||||
.from_local_datetime(
|
||||
&DateTime::from_timestamp(end_date_naive, 0)?.naive_local(),
|
||||
)
|
||||
.single()?
|
||||
.timestamp();
|
||||
|
||||
if let Some(alarm_time) = alarm.delta.to_timestamp(start, end, default_tz) {
|
||||
if alarm_time > start_time {
|
||||
if let Some(alarm_time) = alarm.delta.to_timestamp(start, end, default_tz)
|
||||
&& alarm_time > start_time
|
||||
{
|
||||
if let Some(next) = next_alarm {
|
||||
if alarm_time < next.alarm_time {
|
||||
next_alarm = Some(CalendarAlarm {
|
||||
@@ -151,6 +106,51 @@ impl ArchivedCalendarEventData {
|
||||
event_end_tz: end_tz.as_id(),
|
||||
});
|
||||
}
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single event
|
||||
let start_date_naive = offset_or_count as i64 + base_offset;
|
||||
let end_date_naive = start_date_naive + duration;
|
||||
let start = start_tz
|
||||
.from_local_datetime(
|
||||
&DateTime::from_timestamp(start_date_naive, 0)?.naive_local(),
|
||||
)
|
||||
.single()?
|
||||
.timestamp();
|
||||
let end = end_tz
|
||||
.from_local_datetime(
|
||||
&DateTime::from_timestamp(end_date_naive, 0)?.naive_local(),
|
||||
)
|
||||
.single()?
|
||||
.timestamp();
|
||||
|
||||
if let Some(alarm_time) = alarm.delta.to_timestamp(start, end, default_tz)
|
||||
&& alarm_time > start_time
|
||||
{
|
||||
if let Some(next) = next_alarm {
|
||||
if alarm_time < next.alarm_time {
|
||||
next_alarm = Some(CalendarAlarm {
|
||||
alarm_id: alarm.id.to_native(),
|
||||
event_id: alarm.parent_id.to_native(),
|
||||
alarm_time,
|
||||
event_start: start_date_naive,
|
||||
event_start_tz: start_tz.as_id(),
|
||||
event_end: end_date_naive,
|
||||
event_end_tz: end_tz.as_id(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
next_alarm = Some(CalendarAlarm {
|
||||
alarm_id: alarm.id.to_native(),
|
||||
event_id: alarm.parent_id.to_native(),
|
||||
alarm_time,
|
||||
event_start: start_date_naive,
|
||||
event_start_tz: start_tz.as_id(),
|
||||
event_end: end_date_naive,
|
||||
event_end_tz: end_tz.as_id(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,10 +160,9 @@ fn decode_plain_auth(token: &str) -> Option<Credentials<String>> {
|
||||
}
|
||||
|
||||
fn decode_bearer_token(token: &str, allow_api_access: bool) -> Option<Credentials<String>> {
|
||||
if allow_api_access {
|
||||
if let Some(token) = token.strip_prefix("api_").and_then(decode_plain_auth) {
|
||||
return Some(token);
|
||||
}
|
||||
if allow_api_access && let Some(token) = token.strip_prefix("api_").and_then(decode_plain_auth)
|
||||
{
|
||||
return Some(token);
|
||||
}
|
||||
|
||||
Some(Credentials::OAuthBearer {
|
||||
|
||||
@@ -203,22 +203,18 @@ impl Autoconfig for Server {
|
||||
.email_to_id(emailaddress)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
if let Ok(Some(principal)) = self
|
||||
&& let Ok(Some(principal)) = self
|
||||
.core
|
||||
.storage
|
||||
.directory
|
||||
.query(QueryParams::id(id).with_return_member_of(false))
|
||||
.await
|
||||
{
|
||||
if principal
|
||||
.emails
|
||||
.first()
|
||||
.is_some_and(|email| email.eq_ignore_ascii_case(emailaddress))
|
||||
{
|
||||
account_name = principal.name;
|
||||
}
|
||||
}
|
||||
&& principal
|
||||
.emails
|
||||
.first()
|
||||
.is_some_and(|email| email.eq_ignore_ascii_case(emailaddress))
|
||||
{
|
||||
account_name = principal.name;
|
||||
}
|
||||
|
||||
Ok((account_name, self.core.network.server_name.clone(), domain))
|
||||
@@ -296,12 +292,11 @@ fn parse_autodiscover_request(bytes: &[u8]) -> Result<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(Event::Text(text)) = reader.read_event_into(&mut buf) {
|
||||
if let Ok(text) = text.unescape() {
|
||||
if text.contains('@') {
|
||||
return Ok(text.trim().to_lowercase());
|
||||
}
|
||||
}
|
||||
if let Ok(Event::Text(text)) = reader.read_event_into(&mut buf)
|
||||
&& let Ok(text) = text.unescape()
|
||||
&& text.contains('@')
|
||||
{
|
||||
return Ok(text.trim().to_lowercase());
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
|
||||
+17
-18
@@ -50,24 +50,23 @@ impl FormHandler for Server {
|
||||
form_data: FormData,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
// Validate rate
|
||||
if let Some(rate) = &form.rate {
|
||||
if !session.remote_ip.is_loopback()
|
||||
&& self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.is_rate_allowed(
|
||||
KV_RATE_LIMIT_CONTACT,
|
||||
&ip_to_bytes(&session.remote_ip),
|
||||
rate,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::LimitEvent::TooManyRequests.into_err());
|
||||
}
|
||||
if let Some(rate) = &form.rate
|
||||
&& !session.remote_ip.is_loopback()
|
||||
&& self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.is_rate_allowed(
|
||||
KV_RATE_LIMIT_CONTACT,
|
||||
&ip_to_bytes(&session.remote_ip),
|
||||
rate,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::LimitEvent::TooManyRequests.into_err());
|
||||
}
|
||||
|
||||
// Validate honeypot
|
||||
|
||||
@@ -168,16 +168,15 @@ impl PrincipalManager for Server {
|
||||
.await?;
|
||||
|
||||
// Set report domain
|
||||
if let Some(report_domain) = report_domain {
|
||||
if let Err(err) = self
|
||||
if let Some(report_domain) = report_domain
|
||||
&& let Err(err) = self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.set([("report.domain", report_domain)], true)
|
||||
.await
|
||||
{
|
||||
trc::error!(err.details("Failed to set report domain"));
|
||||
}
|
||||
{
|
||||
trc::error!(err.details("Failed to set report domain"));
|
||||
}
|
||||
|
||||
// Increment revision
|
||||
@@ -205,20 +204,20 @@ impl PrincipalManager for Server {
|
||||
.unwrap_or_default()
|
||||
.split(',')
|
||||
{
|
||||
if let Some(typ) = Type::parse(typ) {
|
||||
if !types.contains(&typ) {
|
||||
types.push(typ);
|
||||
}
|
||||
if let Some(typ) = Type::parse(typ)
|
||||
&& !types.contains(&typ)
|
||||
{
|
||||
types.push(typ);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse fields
|
||||
let mut fields = Vec::new();
|
||||
for field in params.get("fields").unwrap_or_default().split(',') {
|
||||
if let Some(field) = PrincipalField::try_parse(field) {
|
||||
if !fields.contains(&field) {
|
||||
fields.push(field);
|
||||
}
|
||||
if let Some(field) = PrincipalField::try_parse(field)
|
||||
&& !fields.contains(&field)
|
||||
{
|
||||
fields.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,24 +145,24 @@ impl QueueManagement for Server {
|
||||
|
||||
// Limit to tenant domains
|
||||
#[cfg(feature = "enterprise")]
|
||||
if self.core.is_enterprise_edition() {
|
||||
if let Some(tenant) = access_token.tenant {
|
||||
tenant_domains = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.list_principals(None, tenant.id.into(), &[Type::Domain], false, 0, 0)
|
||||
.await
|
||||
.map(|principals| {
|
||||
principals
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|p| p.name)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.caused_by(trc::location!())?
|
||||
.into();
|
||||
}
|
||||
if self.core.is_enterprise_edition()
|
||||
&& let Some(tenant) = access_token.tenant
|
||||
{
|
||||
tenant_domains = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.list_principals(None, tenant.id.into(), &[Type::Domain], false, 0, 0)
|
||||
.await
|
||||
.map(|principals| {
|
||||
principals
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|p| p.name)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.caused_by(trc::location!())?
|
||||
.into();
|
||||
}
|
||||
|
||||
// SPDX-SnippetEnd
|
||||
@@ -458,7 +458,12 @@ impl QueueManagement for Server {
|
||||
{
|
||||
let mut rua = Vec::new();
|
||||
if let Some(report) = self
|
||||
.generate_tls_aggregate_report(&[event.clone()], &mut rua, None, 0)
|
||||
.generate_tls_aggregate_report(
|
||||
std::slice::from_ref(&event),
|
||||
&mut rua,
|
||||
None,
|
||||
0,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
result = Report::tls(event, report, rua).into();
|
||||
|
||||
@@ -53,24 +53,24 @@ impl ManageReports for Server {
|
||||
|
||||
// Limit to tenant domains
|
||||
#[cfg(feature = "enterprise")]
|
||||
if self.core.is_enterprise_edition() {
|
||||
if let Some(tenant) = access_token.tenant {
|
||||
tenant_domains = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.list_principals(None, tenant.id.into(), &[Type::Domain], false, 0, 0)
|
||||
.await
|
||||
.map(|principals| {
|
||||
principals
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|p| p.name)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.caused_by(trc::location!())?
|
||||
.into();
|
||||
}
|
||||
if self.core.is_enterprise_edition()
|
||||
&& let Some(tenant) = access_token.tenant
|
||||
{
|
||||
tenant_domains = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.list_principals(None, tenant.id.into(), &[Type::Domain], false, 0, 0)
|
||||
.await
|
||||
.map(|principals| {
|
||||
principals
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|p| p.name)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.caused_by(trc::location!())?
|
||||
.into();
|
||||
}
|
||||
|
||||
// SPDX-SnippetEnd
|
||||
@@ -206,12 +206,11 @@ impl ManageReports for Server {
|
||||
}
|
||||
}
|
||||
|
||||
if !batch.is_empty() {
|
||||
if let Err(err) =
|
||||
if !batch.is_empty()
|
||||
&& let Err(err) =
|
||||
server.core.storage.data.write(batch.build_all()).await
|
||||
{
|
||||
trc::error!(err.caused_by(trc::location!()));
|
||||
}
|
||||
{
|
||||
trc::error!(err.caused_by(trc::location!()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,20 +91,20 @@ impl ManageSettings for Server {
|
||||
let mut total = 0;
|
||||
let mut ids = Vec::new();
|
||||
for key in settings.keys() {
|
||||
if let Some(id) = key.strip_suffix(&suffix) {
|
||||
if !id.is_empty() {
|
||||
if !has_filter {
|
||||
if offset == 0 {
|
||||
if limit == 0 || ids.len() < limit {
|
||||
ids.push(id);
|
||||
}
|
||||
} else {
|
||||
offset -= 1;
|
||||
if let Some(id) = key.strip_suffix(&suffix)
|
||||
&& !id.is_empty()
|
||||
{
|
||||
if !has_filter {
|
||||
if offset == 0 {
|
||||
if limit == 0 || ids.len() < limit {
|
||||
ids.push(id);
|
||||
}
|
||||
total += 1;
|
||||
} else {
|
||||
ids.push(id);
|
||||
offset -= 1;
|
||||
}
|
||||
total += 1;
|
||||
} else {
|
||||
ids.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,10 +318,10 @@ impl ManageSettings for Server {
|
||||
{
|
||||
return Err(trc::ManageEvent::AssertFailed.into_err());
|
||||
}
|
||||
} else if let Some((key, _)) = values.first() {
|
||||
if self.core.storage.config.get(key).await?.is_some() {
|
||||
return Err(trc::ManageEvent::AssertFailed.into_err());
|
||||
}
|
||||
} else if let Some((key, _)) = values.first()
|
||||
&& self.core.storage.config.get(key).await?.is_some()
|
||||
{
|
||||
return Err(trc::ManageEvent::AssertFailed.into_err());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -557,16 +557,15 @@ impl ParseHttp for Server {
|
||||
"metrics" => match path.next().unwrap_or_default() {
|
||||
"prometheus" => {
|
||||
if let Some(prometheus) = &self.core.metrics.prometheus {
|
||||
if let Some(auth) = &prometheus.auth {
|
||||
if req
|
||||
if let Some(auth) = &prometheus.auth
|
||||
&& req
|
||||
.authorization_basic()
|
||||
.is_none_or(|secret| secret != auth)
|
||||
{
|
||||
return Err(trc::AuthEvent::Failed
|
||||
.into_err()
|
||||
.details("Invalid or missing credentials.")
|
||||
.caused_by(trc::location!()));
|
||||
}
|
||||
{
|
||||
return Err(trc::AuthEvent::Failed
|
||||
.into_err()
|
||||
.details("Invalid or missing credentials.")
|
||||
.caused_by(trc::location!()));
|
||||
}
|
||||
|
||||
return Ok(Resource::new(
|
||||
|
||||
@@ -220,7 +220,7 @@ mod tests {
|
||||
],
|
||||
}
|
||||
.serialize(),
|
||||
concat!("* CAPABILITY IMAP4rev2 STARTTLS LOGINDISABLED\r\n",).as_bytes()
|
||||
"* CAPABILITY IMAP4rev2 STARTTLS LOGINDISABLED\r\n".as_bytes()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ mod tests {
|
||||
.serialize()
|
||||
)
|
||||
.unwrap(),
|
||||
concat!("* VANISHED 3:5\r\n")
|
||||
"* VANISHED 3:5\r\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ mod tests {
|
||||
}],
|
||||
}],
|
||||
},
|
||||
concat!("* QUOTA \"INBOX\" (STORAGE 1024 1048576)\r\n"),
|
||||
"* QUOTA \"INBOX\" (STORAGE 1024 1048576)\r\n",
|
||||
),
|
||||
(
|
||||
super::Response {
|
||||
|
||||
@@ -235,8 +235,8 @@ mod tests {
|
||||
highest_modseq: None,
|
||||
},
|
||||
"A283",
|
||||
concat!("* ESEARCH (TAG \"A283\") COUNT 3 MIN 2 MAX 11 ALL 2,10:11\r\n",),
|
||||
concat!("* SEARCH 2 10 11\r\n"),
|
||||
"* ESEARCH (TAG \"A283\") COUNT 3 MIN 2 MAX 11 ALL 2,10:11\r\n",
|
||||
"* SEARCH 2 10 11\r\n",
|
||||
),
|
||||
(
|
||||
super::Response {
|
||||
@@ -252,8 +252,8 @@ mod tests {
|
||||
highest_modseq: None,
|
||||
},
|
||||
"A283",
|
||||
concat!("* ESEARCH (TAG \"A283\") ALL 1:3,5,10:13,90,92:99\r\n",),
|
||||
concat!("* SEARCH 1 2 3 5 10 11 12 13 90 92 93 94 95 96 97 98 99\r\n",),
|
||||
"* ESEARCH (TAG \"A283\") ALL 1:3,5,10:13,90,92:99\r\n",
|
||||
"* SEARCH 1 2 3 5 10 11 12 13 90 92 93 94 95 96 97 98 99\r\n",
|
||||
),
|
||||
(
|
||||
super::Response {
|
||||
@@ -267,8 +267,8 @@ mod tests {
|
||||
highest_modseq: None,
|
||||
},
|
||||
"A283",
|
||||
concat!("* ESEARCH (TAG \"A283\")\r\n",),
|
||||
concat!("* SEARCH\r\n"),
|
||||
"* ESEARCH (TAG \"A283\")\r\n",
|
||||
"* SEARCH\r\n",
|
||||
),
|
||||
(
|
||||
super::Response {
|
||||
@@ -282,8 +282,8 @@ mod tests {
|
||||
highest_modseq: 12345.into(),
|
||||
},
|
||||
"A283",
|
||||
concat!("* ESEARCH (TAG \"A283\") ALL 10:13,21 MODSEQ 12345\r\n",),
|
||||
concat!("* SEARCH 10 11 12 13 21 (MODSEQ 12345)\r\n",),
|
||||
"* ESEARCH (TAG \"A283\") ALL 10:13,21 MODSEQ 12345\r\n",
|
||||
"* SEARCH 10 11 12 13 21 (MODSEQ 12345)\r\n",
|
||||
),
|
||||
] {
|
||||
let response_v2 = String::from_utf8(response.clone().serialize(tag)).unwrap();
|
||||
|
||||
@@ -102,9 +102,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8(buf).unwrap(),
|
||||
concat!(
|
||||
"* STATUS \"blurdybloop\" (MESSAGES 231 UIDNEXT 44292 MAILBOXID (abc-123))\r\n",
|
||||
)
|
||||
"* STATUS \"blurdybloop\" (MESSAGES 231 UIDNEXT 44292 MAILBOXID (abc-123))\r\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ mod tests {
|
||||
.serialize()
|
||||
)
|
||||
.unwrap(),
|
||||
concat!("* THREAD (2 10 11)(49)(1 3)\r\n",)
|
||||
"* THREAD (2 10 11)(49)(1 3)\r\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,14 +260,13 @@ impl<T: SessionStream> Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(needs_literal) = needs_literal {
|
||||
if let Err(err) = self
|
||||
if let Some(needs_literal) = needs_literal
|
||||
&& let Err(err) = self
|
||||
.write_bytes(format!("+ Ready for {} bytes.\r\n", needs_literal).into_bytes())
|
||||
.await
|
||||
{
|
||||
self.write_error(err).await;
|
||||
return SessionResult::Close;
|
||||
}
|
||||
{
|
||||
self.write_error(err).await;
|
||||
return SessionResult::Close;
|
||||
}
|
||||
|
||||
SessionResult::Continue
|
||||
@@ -294,25 +293,23 @@ impl<T: SessionStream> Session<T> {
|
||||
async fn is_allowed(&self, request: Request<Command>) -> trc::Result<Request<Command>> {
|
||||
let state = &self.state;
|
||||
// Rate limit request
|
||||
if let State::Authenticated { data } | State::Selected { data, .. } = state {
|
||||
if let Some(rate) = &self.server.core.imap.rate_requests {
|
||||
if data
|
||||
.server
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.is_rate_allowed(
|
||||
KV_RATE_LIMIT_IMAP,
|
||||
&data.account_id.to_be_bytes(),
|
||||
rate,
|
||||
true,
|
||||
)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::LimitEvent::TooManyRequests.into_err());
|
||||
}
|
||||
}
|
||||
if let State::Authenticated { data } | State::Selected { data, .. } = state
|
||||
&& let Some(rate) = &self.server.core.imap.rate_requests
|
||||
&& data
|
||||
.server
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.is_rate_allowed(
|
||||
KV_RATE_LIMIT_IMAP,
|
||||
&data.account_id.to_be_bytes(),
|
||||
rate,
|
||||
true,
|
||||
)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::LimitEvent::TooManyRequests.into_err());
|
||||
}
|
||||
|
||||
match &request.command {
|
||||
|
||||
@@ -316,12 +316,11 @@ impl<T: SessionStream> SessionData<T> {
|
||||
// Add new mailboxes
|
||||
for (mailbox_name, mailbox_id) in new_account.mailbox_names.iter() {
|
||||
if let Some(old_mailbox) = old_account.mailbox_state.get(mailbox_id) {
|
||||
if let Some(mailbox) = new_account.mailbox_state.get(mailbox_id) {
|
||||
if mailbox.total_messages != old_mailbox.total_messages
|
||||
|| mailbox.total_unseen != old_mailbox.total_unseen
|
||||
{
|
||||
changes.changed.push(mailbox_name.clone());
|
||||
}
|
||||
if let Some(mailbox) = new_account.mailbox_state.get(mailbox_id)
|
||||
&& (mailbox.total_messages != old_mailbox.total_messages
|
||||
|| mailbox.total_unseen != old_mailbox.total_unseen)
|
||||
{
|
||||
changes.changed.push(mailbox_name.clone());
|
||||
}
|
||||
} else {
|
||||
changes.added.push(mailbox_name.clone());
|
||||
|
||||
@@ -28,12 +28,12 @@ impl SessionManager for ImapSessionManager {
|
||||
session: SessionData<T>,
|
||||
) -> impl std::future::Future<Output = ()> + Send {
|
||||
async move {
|
||||
if let Ok(mut session) = Session::new(session, self).await {
|
||||
if session.handle_conn().await && session.instance.acceptor.is_tls() {
|
||||
if let Ok(mut session) = session.into_tls().await {
|
||||
session.handle_conn().await;
|
||||
}
|
||||
}
|
||||
if let Ok(mut session) = Session::new(session, self).await
|
||||
&& session.handle_conn().await
|
||||
&& session.instance.acceptor.is_tls()
|
||||
&& let Ok(mut session) = session.into_tls().await
|
||||
{
|
||||
session.handle_conn().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +85,10 @@ impl<T: SessionStream> SessionData<T> {
|
||||
for (pos, &path_item) in params.path.iter().enumerate() {
|
||||
let mut mailbox = email::mailbox::Mailbox::new(path_item).with_parent_id(parent_id);
|
||||
|
||||
if pos == params.path.len() - 1 {
|
||||
if let Some(mailbox_role) = arguments.mailbox_role.map(attr_to_role) {
|
||||
mailbox.role = mailbox_role;
|
||||
}
|
||||
if pos == params.path.len() - 1
|
||||
&& let Some(mailbox_role) = arguments.mailbox_role.map(attr_to_role)
|
||||
{
|
||||
mailbox.role = mailbox_role;
|
||||
}
|
||||
let mailbox_id = next_document_id;
|
||||
next_document_id -= 1;
|
||||
|
||||
+37
-39
@@ -540,33 +540,32 @@ impl<T: SessionStream> SessionData<T> {
|
||||
self.write_bytes(buf).await?;
|
||||
|
||||
// Add to set flags
|
||||
if set_seen_flag {
|
||||
if let Some(data_) = self
|
||||
if set_seen_flag
|
||||
&& let Some(data_) = self
|
||||
.server
|
||||
.get_archive(account_id, Collection::Email, id)
|
||||
.await
|
||||
.imap_ctx(&arguments.tag, trc::location!())?
|
||||
{
|
||||
let data = data_
|
||||
.to_unarchived::<MessageData>()
|
||||
.imap_ctx(&arguments.tag, trc::location!())?;
|
||||
let mut new_data = data
|
||||
.deserialize()
|
||||
.imap_ctx(&arguments.tag, trc::location!())?;
|
||||
new_data.keywords.push(Keyword::Seen);
|
||||
{
|
||||
let data = data_
|
||||
.to_unarchived::<MessageData>()
|
||||
.imap_ctx(&arguments.tag, trc::location!())?;
|
||||
let mut new_data = data
|
||||
.deserialize()
|
||||
.imap_ctx(&arguments.tag, trc::location!())?;
|
||||
new_data.keywords.push(Keyword::Seen);
|
||||
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::Email)
|
||||
.update_document(id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
.with_current(data)
|
||||
.with_changes(new_data),
|
||||
)
|
||||
.imap_ctx(&arguments.tag, trc::location!())?
|
||||
.commit_point();
|
||||
}
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::Email)
|
||||
.update_document(id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
.with_current(data)
|
||||
.with_changes(new_data),
|
||||
)
|
||||
.imap_ctx(&arguments.tag, trc::location!())?
|
||||
.commit_point();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,7 +619,7 @@ impl<T: SessionStream> SessionData<T> {
|
||||
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub trait AsImapDataItem {
|
||||
fn body_structure(&self, decoded: &DecodedParts<'_>, is_extended: bool) -> BodyPart;
|
||||
fn body_structure(&'_ self, decoded: &DecodedParts<'_>, is_extended: bool) -> BodyPart<'_>;
|
||||
fn body_section<'x>(
|
||||
&self,
|
||||
decoded: &'x DecodedParts<'x>,
|
||||
@@ -639,24 +638,24 @@ pub trait AsImapDataItem {
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub trait AsImapDataItemPart {
|
||||
fn as_body_part(
|
||||
&self,
|
||||
&'_ self,
|
||||
decoded: &DecodedParts<'_>,
|
||||
message_id: usize,
|
||||
part_id: usize,
|
||||
is_extended: bool,
|
||||
) -> BodyPart;
|
||||
) -> BodyPart<'_>;
|
||||
|
||||
fn envelope(&self) -> Envelope;
|
||||
fn envelope(&'_ self) -> Envelope<'_>;
|
||||
}
|
||||
|
||||
impl AsImapDataItemPart for ArchivedMessageMetadataContents {
|
||||
fn as_body_part(
|
||||
&self,
|
||||
&'_ self,
|
||||
decoded: &DecodedParts<'_>,
|
||||
message_id: usize,
|
||||
part_id: usize,
|
||||
is_extended: bool,
|
||||
) -> BodyPart {
|
||||
) -> BodyPart<'_> {
|
||||
let part = &self.parts[part_id];
|
||||
let body = decoded.raw_message_section_arch(message_id, part.offset_body, part.offset_end);
|
||||
let (is_multipart, is_text) = match &part.body {
|
||||
@@ -800,7 +799,7 @@ impl AsImapDataItemPart for ArchivedMessageMetadataContents {
|
||||
}
|
||||
}
|
||||
|
||||
fn envelope(&self) -> Envelope {
|
||||
fn envelope(&'_ self) -> Envelope<'_> {
|
||||
let headers = self.root_part();
|
||||
Envelope {
|
||||
date: headers.date(),
|
||||
@@ -847,7 +846,7 @@ impl AsImapDataItemPart for ArchivedMessageMetadataContents {
|
||||
}
|
||||
|
||||
impl AsImapDataItem for ArchivedMessageMetadata {
|
||||
fn body_structure(&self, decoded: &DecodedParts<'_>, is_extended: bool) -> BodyPart {
|
||||
fn body_structure(&'_ self, decoded: &DecodedParts<'_>, is_extended: bool) -> BodyPart<'_> {
|
||||
let mut stack = Vec::new();
|
||||
let base_part = [u16_le::from_native(0)];
|
||||
let mut parts = base_part.as_slice().iter();
|
||||
@@ -941,19 +940,18 @@ impl AsImapDataItem for ArchivedMessageMetadata {
|
||||
None
|
||||
}?;
|
||||
|
||||
if let ArchivedMetadataPartType::Message(nested_message_id) = &part.body {
|
||||
if let Some((
|
||||
if let ArchivedMetadataPartType::Message(nested_message_id) = &part.body
|
||||
&& let Some((
|
||||
_,
|
||||
Section::Part { .. }
|
||||
| Section::Header
|
||||
| Section::HeaderFields { .. }
|
||||
| Section::Text,
|
||||
)) = sections_iter.peek()
|
||||
{
|
||||
message = self.message_id(*nested_message_id);
|
||||
part = message.root_part();
|
||||
message_id = u16::from(nested_message_id) as usize;
|
||||
}
|
||||
{
|
||||
message = self.message_id(*nested_message_id);
|
||||
part = message.root_part();
|
||||
message_id = u16::from(nested_message_id) as usize;
|
||||
}
|
||||
}
|
||||
Section::Header => {
|
||||
@@ -1208,11 +1206,11 @@ fn get_partial_bytes(bytes: &[u8], partial: Option<(u32, u32)>) -> &[u8] {
|
||||
}
|
||||
|
||||
trait AsImapAddress {
|
||||
fn as_imap_address(&self) -> Vec<fetch::Address>;
|
||||
fn as_imap_address(&'_ self) -> Vec<fetch::Address<'_>>;
|
||||
}
|
||||
|
||||
impl AsImapAddress for ArchivedHeaderValue<'_> {
|
||||
fn as_imap_address(&self) -> Vec<fetch::Address> {
|
||||
fn as_imap_address(&'_ self) -> Vec<fetch::Address<'_>> {
|
||||
let mut addresses = Vec::new();
|
||||
|
||||
match self {
|
||||
|
||||
@@ -76,17 +76,17 @@ impl<T: SessionStream> SessionData<T> {
|
||||
// Verify if mailbox is already subscribed/unsubscribed
|
||||
for account in self.mailboxes.lock().iter_mut() {
|
||||
if account.account_id == account_id {
|
||||
if let Some(mailbox) = account.mailbox_state.get(&mailbox_id) {
|
||||
if mailbox.is_subscribed == subscribe {
|
||||
return Err(trc::ImapEvent::Error
|
||||
.into_err()
|
||||
.details(if subscribe {
|
||||
"Mailbox is already subscribed."
|
||||
} else {
|
||||
"Mailbox is already unsubscribed."
|
||||
})
|
||||
.id(tag));
|
||||
}
|
||||
if let Some(mailbox) = account.mailbox_state.get(&mailbox_id)
|
||||
&& mailbox.is_subscribed == subscribe
|
||||
{
|
||||
return Err(trc::ImapEvent::Error
|
||||
.into_err()
|
||||
.details(if subscribe {
|
||||
"Mailbox is already subscribed."
|
||||
} else {
|
||||
"Mailbox is already unsubscribed."
|
||||
})
|
||||
.id(tag));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -89,10 +89,10 @@ impl<T: SessionStream> SessionData<T> {
|
||||
let mut threads: AHashMap<u32, Vec<u32>> = AHashMap::new();
|
||||
let state = mailbox.state.lock();
|
||||
for item in &cache.emails.items {
|
||||
if result_set.results.contains(item.document_id) {
|
||||
if let Some((imap_id, _)) = state.map_result_id(item.document_id, is_uid) {
|
||||
threads.entry(item.thread_id).or_default().push(imap_id);
|
||||
}
|
||||
if result_set.results.contains(item.document_id)
|
||||
&& let Some((imap_id, _)) = state.map_result_id(item.document_id, is_uid)
|
||||
{
|
||||
threads.entry(item.thread_id).or_default().push(imap_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ impl<'x> RequestError<'x> {
|
||||
RequestError::blank(401, "Unauthorized", "You have to authenticate first.")
|
||||
}
|
||||
|
||||
pub fn unknown_capability(capability: &str) -> RequestError {
|
||||
pub fn unknown_capability(capability: &'_ str) -> RequestError<'_> {
|
||||
RequestError {
|
||||
p_type: RequestErrorType::UnknownCapability,
|
||||
limit: None,
|
||||
@@ -204,7 +204,7 @@ impl<'x> RequestError<'x> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn not_json(detail: &str) -> RequestError {
|
||||
pub fn not_json(detail: &'_ str) -> RequestError<'_> {
|
||||
RequestError {
|
||||
p_type: RequestErrorType::NotJSON,
|
||||
limit: None,
|
||||
|
||||
@@ -144,25 +144,25 @@ impl Response {
|
||||
let mut graph = HashMap::with_capacity(request.create.len());
|
||||
for (create_id, object) in request.create.iter_mut() {
|
||||
for data in &mut object.data {
|
||||
if let DataSourceObject::Id { id, .. } = data {
|
||||
if let MaybeReference::Reference(parent_id) = id {
|
||||
match self.created_ids.get(parent_id) {
|
||||
Some(AnyId::Blob(blob_id)) => {
|
||||
*id = MaybeReference::Value(blob_id.clone());
|
||||
}
|
||||
Some(_) => {
|
||||
return Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Id reference {parent_id:?} points to invalid type."
|
||||
)));
|
||||
}
|
||||
None => {
|
||||
graph
|
||||
.entry(create_id.to_string())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(parent_id.to_string());
|
||||
}
|
||||
if let DataSourceObject::Id { id, .. } = data
|
||||
&& let MaybeReference::Reference(parent_id) = id
|
||||
{
|
||||
match self.created_ids.get(parent_id) {
|
||||
Some(AnyId::Blob(blob_id)) => {
|
||||
*id = MaybeReference::Value(blob_id.clone());
|
||||
}
|
||||
Some(_) => {
|
||||
return Err(trc::JmapEvent::InvalidResultReference
|
||||
.into_err()
|
||||
.details(format_compact!(
|
||||
"Id reference {parent_id:?} points to invalid type."
|
||||
)));
|
||||
}
|
||||
None => {
|
||||
graph
|
||||
.entry(create_id.to_string())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(parent_id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,21 +580,20 @@ impl JsonQueryable for Value {
|
||||
) {
|
||||
match pointer.next() {
|
||||
Some(JsonPointerItem::String(n)) => {
|
||||
if let Value::Object(map) = self {
|
||||
if let Some(v) = map
|
||||
if let Value::Object(map) = self
|
||||
&& let Some(v) = map
|
||||
.0
|
||||
.iter()
|
||||
.find_map(|(k, v)| if k.as_str() == n { Some(v) } else { None })
|
||||
{
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
{
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Number(n)) => {
|
||||
if let Value::List(values) = self {
|
||||
if let Some(v) = values.get(*n as usize) {
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
if let Value::List(values) = self
|
||||
&& let Some(v) = values.get(*n as usize)
|
||||
{
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Wildcard) => match self {
|
||||
|
||||
@@ -48,10 +48,10 @@ impl StateManager for Server {
|
||||
if_in_state: &Option<State>,
|
||||
) -> trc::Result<State> {
|
||||
let old_state: State = self.get_state(account_id, collection).await?;
|
||||
if let Some(if_in_state) = if_in_state {
|
||||
if &old_state != if_in_state {
|
||||
return Err(trc::JmapEvent::StateMismatch.into_err());
|
||||
}
|
||||
if let Some(if_in_state) = if_in_state
|
||||
&& &old_state != if_in_state
|
||||
{
|
||||
return Err(trc::JmapEvent::StateMismatch.into_err());
|
||||
}
|
||||
|
||||
Ok(old_state)
|
||||
@@ -69,10 +69,10 @@ impl MessageCacheState for MessageStoreCache {
|
||||
|
||||
fn assert_state(&self, is_mailbox: bool, if_in_state: &Option<State>) -> trc::Result<State> {
|
||||
let old_state: State = self.get_state(is_mailbox);
|
||||
if let Some(if_in_state) = if_in_state {
|
||||
if &old_state != if_in_state {
|
||||
return Err(trc::JmapEvent::StateMismatch.into_err());
|
||||
}
|
||||
if let Some(if_in_state) = if_in_state
|
||||
&& &old_state != if_in_state
|
||||
{
|
||||
return Err(trc::JmapEvent::StateMismatch.into_err());
|
||||
}
|
||||
Ok(old_state)
|
||||
}
|
||||
|
||||
@@ -86,11 +86,11 @@ impl JmapMethods for Server {
|
||||
}
|
||||
|
||||
async fn build_query_response<T: Sync + Send>(
|
||||
&self,
|
||||
&'_ self,
|
||||
result_set: &ResultSet,
|
||||
query_state: State,
|
||||
request: &QueryRequest<T>,
|
||||
) -> trc::Result<(QueryResponse, Option<Pagination>)> {
|
||||
) -> trc::Result<(QueryResponse, Option<Pagination<'_>>)> {
|
||||
let total = result_set.results.len() as usize;
|
||||
let (limit_total, limit) = if let Some(limit) = request.limit {
|
||||
if limit > 0 {
|
||||
@@ -182,11 +182,11 @@ pub trait JmapMethods: Sync + Send {
|
||||
) -> impl Future<Output = trc::Result<RoaringBitmap>> + Send;
|
||||
|
||||
fn build_query_response<T: Sync + Send>(
|
||||
&self,
|
||||
&'_ self,
|
||||
result_set: &ResultSet,
|
||||
query_state: State,
|
||||
request: &QueryRequest<T>,
|
||||
) -> impl Future<Output = trc::Result<(QueryResponse, Option<Pagination>)>> + Send;
|
||||
) -> impl Future<Output = trc::Result<(QueryResponse, Option<Pagination<'_>>)>> + Send;
|
||||
|
||||
fn sort(
|
||||
&self,
|
||||
|
||||
@@ -182,10 +182,10 @@ impl MailboxQuery for Server {
|
||||
if response.total.is_some() {
|
||||
response.total = Some(total);
|
||||
}
|
||||
if let Some(paginate) = &mut paginate {
|
||||
if paginate.limit > total {
|
||||
paginate.limit = total;
|
||||
}
|
||||
if let Some(paginate) = &mut paginate
|
||||
&& paginate.limit > total
|
||||
{
|
||||
paginate.limit = total;
|
||||
}
|
||||
result_set.results = filtered_ids;
|
||||
}
|
||||
|
||||
@@ -391,8 +391,7 @@ impl SieveScriptSet for Server {
|
||||
} else if update
|
||||
.as_ref()
|
||||
.is_none_or(|(_, obj)| obj.inner.name != value)
|
||||
{
|
||||
if let Some(id) = self
|
||||
&& let Some(id) = self
|
||||
.filter(
|
||||
ctx.resource_token.account_id,
|
||||
Collection::SieveScript,
|
||||
@@ -401,14 +400,13 @@ impl SieveScriptSet for Server {
|
||||
.await?
|
||||
.results
|
||||
.min()
|
||||
{
|
||||
return Ok(Err(SetError::already_exists()
|
||||
.with_existing_id(id.into())
|
||||
.with_description(format!(
|
||||
"A sieve script with name '{}' already exists.",
|
||||
value
|
||||
))));
|
||||
}
|
||||
{
|
||||
return Ok(Err(SetError::already_exists()
|
||||
.with_existing_id(id.into())
|
||||
.with_description(format!(
|
||||
"A sieve script with name '{}' already exists.",
|
||||
value
|
||||
))));
|
||||
}
|
||||
|
||||
changes.name = value;
|
||||
|
||||
@@ -103,39 +103,39 @@ impl EmailSubmissionGet for Server {
|
||||
.map(|(k, v)| (k.to_string(), DeliveryStatus::from(v)))
|
||||
.collect::<VecMap<_, _>>();
|
||||
let mut is_pending = false;
|
||||
if let Some(queue_id) = submission.queue_id.as_ref().map(u64::from) {
|
||||
if let Some(queued_message_) = self
|
||||
if let Some(queue_id) = submission.queue_id.as_ref().map(u64::from)
|
||||
&& let Some(queued_message_) = self
|
||||
.read_message_archive(queue_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
let queued_message = queued_message_
|
||||
.unarchive::<Message>()
|
||||
.caused_by(trc::location!())?;
|
||||
for rcpt in queued_message.recipients.iter() {
|
||||
*delivery_status.get_mut_or_insert(rcpt.address().to_string()) =
|
||||
DeliveryStatus {
|
||||
smtp_reply: match &rcpt.status {
|
||||
ArchivedStatus::Completed(reply) => {
|
||||
format_archived_response(&reply.response)
|
||||
}
|
||||
ArchivedStatus::TemporaryFailure(reply)
|
||||
| ArchivedStatus::PermanentFailure(reply) => {
|
||||
format_archived_error_details(reply)
|
||||
}
|
||||
ArchivedStatus::Scheduled => "250 2.1.5 Queued".to_string(),
|
||||
},
|
||||
delivered: match &rcpt.status {
|
||||
ArchivedStatus::Scheduled
|
||||
| ArchivedStatus::TemporaryFailure(_) => Delivered::Queued,
|
||||
ArchivedStatus::Completed(_) => Delivered::Yes,
|
||||
ArchivedStatus::PermanentFailure(_) => Delivered::No,
|
||||
},
|
||||
displayed: false,
|
||||
};
|
||||
}
|
||||
is_pending = true;
|
||||
{
|
||||
let queued_message = queued_message_
|
||||
.unarchive::<Message>()
|
||||
.caused_by(trc::location!())?;
|
||||
for rcpt in queued_message.recipients.iter() {
|
||||
*delivery_status.get_mut_or_insert(rcpt.address().to_string()) =
|
||||
DeliveryStatus {
|
||||
smtp_reply: match &rcpt.status {
|
||||
ArchivedStatus::Completed(reply) => {
|
||||
format_archived_response(&reply.response)
|
||||
}
|
||||
ArchivedStatus::TemporaryFailure(reply)
|
||||
| ArchivedStatus::PermanentFailure(reply) => {
|
||||
format_archived_error_details(reply)
|
||||
}
|
||||
ArchivedStatus::Scheduled => "250 2.1.5 Queued".to_string(),
|
||||
},
|
||||
delivered: match &rcpt.status {
|
||||
ArchivedStatus::Scheduled | ArchivedStatus::TemporaryFailure(_) => {
|
||||
Delivered::Queued
|
||||
}
|
||||
ArchivedStatus::Completed(_) => Delivered::Yes,
|
||||
ArchivedStatus::PermanentFailure(_) => Delivered::No,
|
||||
},
|
||||
displayed: false,
|
||||
};
|
||||
}
|
||||
is_pending = true;
|
||||
}
|
||||
|
||||
let mut result = Object::with_capacity(properties.len());
|
||||
|
||||
@@ -511,17 +511,17 @@ impl EmailSubmissionSet for Server {
|
||||
}
|
||||
if let ArchivedHeaderValue::Address(addr) = &header.value {
|
||||
for address in addr.iter() {
|
||||
if let Some(address) = address.address().and_then(sanitize_email) {
|
||||
if !rcpt_to.iter().any(|rcpt| rcpt.address == address) {
|
||||
submission.envelope.rcpt_to.push(Address {
|
||||
email: address.to_string(),
|
||||
parameters: None,
|
||||
});
|
||||
rcpt_to.push(RcptTo {
|
||||
address,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
if let Some(address) = address.address().and_then(sanitize_email)
|
||||
&& !rcpt_to.iter().any(|rcpt| rcpt.address == address)
|
||||
{
|
||||
submission.envelope.rcpt_to.push(Address {
|
||||
email: address.to_string(),
|
||||
parameters: None,
|
||||
});
|
||||
rcpt_to.push(RcptTo {
|
||||
address,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -696,19 +696,19 @@ fn parse_envelope_address(
|
||||
let mut params_list = VecMap::with_capacity(params.0.len());
|
||||
|
||||
for (k, v) in params.0 {
|
||||
if let Property::_T(k) = k {
|
||||
if !k.is_empty() {
|
||||
if !params_text.is_empty() {
|
||||
params_text.push(' ');
|
||||
}
|
||||
params_text.push_str(&k);
|
||||
if let Value::Text(v) = v {
|
||||
params_text.push('=');
|
||||
params_text.push_str(&v);
|
||||
params_list.append(k, Some(v));
|
||||
} else {
|
||||
params_list.append(k, None);
|
||||
}
|
||||
if let Property::_T(k) = k
|
||||
&& !k.is_empty()
|
||||
{
|
||||
if !params_text.is_empty() {
|
||||
params_text.push(' ');
|
||||
}
|
||||
params_text.push_str(&k);
|
||||
if let Value::Text(v) = v {
|
||||
params_text.push('=');
|
||||
params_text.push_str(&v);
|
||||
params_list.append(k, Some(v));
|
||||
} else {
|
||||
params_list.append(k, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,14 +315,13 @@ impl VacationResponseSet for Server {
|
||||
}
|
||||
} else if !will_destroy.is_empty() {
|
||||
for id in will_destroy {
|
||||
if id.is_singleton() {
|
||||
if let Some(document_id) = self.get_vacation_sieve_script_id(account_id).await?
|
||||
{
|
||||
self.sieve_script_delete(&resource_token, document_id, false, &mut batch)
|
||||
.await?;
|
||||
response.destroyed.push(id);
|
||||
continue;
|
||||
}
|
||||
if id.is_singleton()
|
||||
&& let Some(document_id) = self.get_vacation_sieve_script_id(account_id).await?
|
||||
{
|
||||
self.sieve_script_delete(&resource_token, document_id, false, &mut batch)
|
||||
.await?;
|
||||
response.destroyed.push(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
response.not_destroyed.append(id, SetError::not_found());
|
||||
|
||||
@@ -132,14 +132,13 @@ impl<T: SessionStream> Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(needs_literal) = needs_literal {
|
||||
if let Err(err) = self
|
||||
if let Some(needs_literal) = needs_literal
|
||||
&& let Err(err) = self
|
||||
.write(format!("OK Ready for {} bytes.\r\n", needs_literal).as_bytes())
|
||||
.await
|
||||
{
|
||||
trc::error!(err.span_id(self.session_id));
|
||||
return SessionResult::Close;
|
||||
}
|
||||
{
|
||||
trc::error!(err.span_id(self.session_id));
|
||||
return SessionResult::Close;
|
||||
}
|
||||
|
||||
SessionResult::Continue
|
||||
|
||||
@@ -42,13 +42,12 @@ impl SessionManager for ManageSieveSessionManager {
|
||||
.is_ok()
|
||||
&& session.handle_conn().await
|
||||
&& session.instance.acceptor.is_tls()
|
||||
&& let Ok(mut session) = session.into_tls().await
|
||||
{
|
||||
if let Ok(mut session) = session.into_tls().await {
|
||||
let _ = session
|
||||
.write(&session.handle_capability(SERVER_GREETING).await.unwrap())
|
||||
.await;
|
||||
session.handle_conn().await;
|
||||
}
|
||||
let _ = session
|
||||
.write(&session.handle_capability(SERVER_GREETING).await.unwrap())
|
||||
.await;
|
||||
session.handle_conn().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,27 +236,27 @@ fn convert_envelope(value: &Value) -> Envelope {
|
||||
}
|
||||
|
||||
fn convert_envelope_address(envelope: &Value) -> Option<Address> {
|
||||
if let Value::Object(envelope) = envelope {
|
||||
if let (Value::Text(email), Value::Object(params)) = (
|
||||
if let Value::Object(envelope) = envelope
|
||||
&& let (Value::Text(email), Value::Object(params)) = (
|
||||
envelope.get(&Property::Email),
|
||||
envelope.get(&Property::Parameters),
|
||||
) {
|
||||
let mut addr = Address {
|
||||
email: email.to_string(),
|
||||
parameters: None,
|
||||
};
|
||||
for (k, v) in params.0.iter() {
|
||||
if let Property::_T(k) = &k {
|
||||
if !k.is_empty() {
|
||||
let k = k.to_string();
|
||||
let v = v.as_string().map(|s| s.to_string());
|
||||
)
|
||||
{
|
||||
let mut addr = Address {
|
||||
email: email.to_string(),
|
||||
parameters: None,
|
||||
};
|
||||
for (k, v) in params.0.iter() {
|
||||
if let Property::_T(k) = &k
|
||||
&& !k.is_empty()
|
||||
{
|
||||
let k = k.to_string();
|
||||
let v = v.as_string().map(|s| s.to_string());
|
||||
|
||||
addr.parameters.get_or_insert_default().append(k, v);
|
||||
}
|
||||
}
|
||||
addr.parameters.get_or_insert_default().append(k, v);
|
||||
}
|
||||
return Some(addr);
|
||||
}
|
||||
return Some(addr);
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
@@ -13,7 +13,7 @@ pub struct SpaceTokenizer<'x> {
|
||||
}
|
||||
|
||||
impl SpaceTokenizer<'_> {
|
||||
pub fn new(text: &str, max_token_length: usize) -> SpaceTokenizer {
|
||||
pub fn new(text: &'_ str, max_token_length: usize) -> SpaceTokenizer<'_> {
|
||||
SpaceTokenizer {
|
||||
iterator: text.chars(),
|
||||
token: String::new(),
|
||||
|
||||
@@ -88,11 +88,12 @@ impl<'x> Iterator for TypesTokenizer<'x> {
|
||||
}
|
||||
|
||||
// Try parsing currencies and floating point numbers
|
||||
if self.tokenize_numbers && !last_is_dot {
|
||||
if let Some(num) = self.try_parse_number() {
|
||||
self.peek_advance();
|
||||
return Some(num);
|
||||
}
|
||||
if self.tokenize_numbers
|
||||
&& !last_is_dot
|
||||
&& let Some(num) = self.try_parse_number()
|
||||
{
|
||||
self.peek_advance();
|
||||
return Some(num);
|
||||
}
|
||||
|
||||
self.peek_rewind();
|
||||
|
||||
@@ -15,7 +15,7 @@ pub struct WordTokenizer<'x> {
|
||||
}
|
||||
|
||||
impl WordTokenizer<'_> {
|
||||
pub fn new(text: &str, max_token_length: usize) -> WordTokenizer {
|
||||
pub fn new(text: &'_ str, max_token_length: usize) -> WordTokenizer<'_> {
|
||||
WordTokenizer {
|
||||
max_token_length,
|
||||
text,
|
||||
|
||||
@@ -49,10 +49,9 @@ impl SessionManager for Pop3SessionManager {
|
||||
.is_ok()
|
||||
&& session.handle_conn().await
|
||||
&& session.instance.acceptor.is_tls()
|
||||
&& let Ok(mut session) = session.into_tls().await
|
||||
{
|
||||
if let Ok(mut session) = session.into_tls().await {
|
||||
session.handle_conn().await;
|
||||
}
|
||||
session.handle_conn().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,11 +189,9 @@ impl<T: SessionStream> Session<T> {
|
||||
|
||||
trc::error!(err.span_id(self.session_id));
|
||||
|
||||
if write_err {
|
||||
if let Err(err) = self.write_bytes(response).await {
|
||||
trc::error!(err.span_id(self.session_id));
|
||||
return false;
|
||||
}
|
||||
if write_err && let Err(err) = self.write_bytes(response).await {
|
||||
trc::error!(err.span_id(self.session_id));
|
||||
return false;
|
||||
}
|
||||
|
||||
!disconnect
|
||||
|
||||
@@ -98,11 +98,11 @@ pub fn spawn_housekeeper(inner: Arc<Inner>, mut rx: mpsc::Receiver<HousekeeperEv
|
||||
}
|
||||
|
||||
// OTEL Push Metrics
|
||||
if server.core.network.roles.push_metrics {
|
||||
if let Some(otel) = &server.core.metrics.otel {
|
||||
OtelMetrics::enable_errors();
|
||||
queue.schedule(Instant::now() + otel.interval, ActionClass::OtelMetrics);
|
||||
}
|
||||
if server.core.network.roles.push_metrics
|
||||
&& let Some(otel) = &server.core.metrics.otel
|
||||
{
|
||||
OtelMetrics::enable_errors();
|
||||
queue.schedule(Instant::now() + otel.interval, ActionClass::OtelMetrics);
|
||||
}
|
||||
|
||||
// Calculate expensive metrics
|
||||
@@ -199,13 +199,13 @@ pub fn spawn_housekeeper(inner: Arc<Inner>, mut rx: mpsc::Receiver<HousekeeperEv
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(metrics_store) = enterprise.metrics_store.as_ref() {
|
||||
if !queue.has_action(&ActionClass::InternalMetrics) {
|
||||
queue.schedule(
|
||||
Instant::now() + metrics_store.interval.time_to_next(),
|
||||
ActionClass::InternalMetrics,
|
||||
);
|
||||
}
|
||||
if let Some(metrics_store) = enterprise.metrics_store.as_ref()
|
||||
&& !queue.has_action(&ActionClass::InternalMetrics)
|
||||
{
|
||||
queue.schedule(
|
||||
Instant::now() + metrics_store.interval.time_to_next(),
|
||||
ActionClass::InternalMetrics,
|
||||
);
|
||||
}
|
||||
|
||||
if !enterprise.metrics_alerts.is_empty()
|
||||
@@ -719,17 +719,17 @@ impl Purge for Server {
|
||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
#[cfg(feature = "enterprise")]
|
||||
if let Some(trace_retention) = trace_retention {
|
||||
if let Err(err) = store.purge_spans(trace_retention).await {
|
||||
trc::error!(err.details("Failed to purge tracing spans"));
|
||||
}
|
||||
if let Some(trace_retention) = trace_retention
|
||||
&& let Err(err) = store.purge_spans(trace_retention).await
|
||||
{
|
||||
trc::error!(err.details("Failed to purge tracing spans"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
if let Some(metrics_retention) = metrics_retention {
|
||||
if let Err(err) = store.purge_metrics(metrics_retention).await {
|
||||
trc::error!(err.details("Failed to purge metrics"));
|
||||
}
|
||||
if let Some(metrics_retention) = metrics_retention
|
||||
&& let Err(err) = store.purge_metrics(metrics_retention).await
|
||||
{
|
||||
trc::error!(err.details("Failed to purge metrics"));
|
||||
}
|
||||
// SPDX-SnippetEnd
|
||||
}
|
||||
@@ -767,17 +767,16 @@ impl Purge for Server {
|
||||
);
|
||||
|
||||
// Remove lock
|
||||
if let Some(lock_name) = &lock_name {
|
||||
if let Err(err) = self
|
||||
if let Some(lock_name) = &lock_name
|
||||
&& let Err(err) = self
|
||||
.in_memory_store()
|
||||
.remove_lock(KV_LOCK_HOUSEKEEPER, lock_name)
|
||||
.await
|
||||
{
|
||||
trc::error!(
|
||||
err.details("Failed to delete task lock.")
|
||||
.details(lock_type)
|
||||
);
|
||||
}
|
||||
{
|
||||
trc::error!(
|
||||
err.details("Failed to delete task lock.")
|
||||
.details(lock_type)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,14 +75,12 @@ pub fn spawn_state_manager(inner: Arc<Inner>, mut change_rx: mpsc::Receiver<Stat
|
||||
.access_to
|
||||
.iter()
|
||||
.any(|(id, _)| *id == *shared_account_id)
|
||||
{
|
||||
if let Some(shared_list) =
|
||||
&& let Some(shared_list) =
|
||||
shared_accounts_map.get_mut(shared_account_id)
|
||||
{
|
||||
shared_list.remove(&account_id);
|
||||
if shared_list.is_empty() {
|
||||
shared_accounts_map.remove(shared_account_id);
|
||||
}
|
||||
{
|
||||
shared_list.remove(&account_id);
|
||||
if shared_list.is_empty() {
|
||||
shared_accounts_map.remove(shared_account_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,20 +138,18 @@ pub fn spawn_state_manager(inner: Arc<Inner>, mut change_rx: mpsc::Receiver<Stat
|
||||
broadcast,
|
||||
} => {
|
||||
// Publish event to cluster
|
||||
if broadcast {
|
||||
if let Some(broadcast_tx) = &inner.ipc.broadcast_tx.clone() {
|
||||
if broadcast_tx
|
||||
.send(BroadcastEvent::StateChange(state_change))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
trc::event!(
|
||||
Server(trc::ServerEvent::ThreadError),
|
||||
Details = "Error sending broadcast event.",
|
||||
CausedBy = trc::location!()
|
||||
);
|
||||
}
|
||||
}
|
||||
if broadcast
|
||||
&& let Some(broadcast_tx) = &inner.ipc.broadcast_tx.clone()
|
||||
&& broadcast_tx
|
||||
.send(BroadcastEvent::StateChange(state_change))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
trc::event!(
|
||||
Server(trc::ServerEvent::ThreadError),
|
||||
Details = "Error sending broadcast event.",
|
||||
CausedBy = trc::location!()
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(shared_accounts) = shared_accounts_map.get(&state_change.account_id)
|
||||
@@ -247,14 +243,14 @@ pub fn spawn_state_manager(inner: Arc<Inner>, mut change_rx: mpsc::Receiver<Stat
|
||||
let mut remove_ids = Vec::new();
|
||||
|
||||
for subscriber_id in subscribers.keys() {
|
||||
if let SubscriberId::Push(push_id) = subscriber_id {
|
||||
if !subscriptions.iter().any(|s| {
|
||||
if let SubscriberId::Push(push_id) = subscriber_id
|
||||
&& !subscriptions.iter().any(|s| {
|
||||
matches!(s, UpdateSubscription::Verified(
|
||||
PushSubscription { id, .. }
|
||||
) if id == push_id)
|
||||
}) {
|
||||
remove_ids.push(*subscriber_id);
|
||||
}
|
||||
})
|
||||
{
|
||||
remove_ids.push(*subscriber_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -476,82 +476,79 @@ pub async fn build_itip_template(
|
||||
if let Some(guests) = fields
|
||||
.iter()
|
||||
.find(|e| e.name == ICalendarProperty::Attendee)
|
||||
&& let ArchivedItipValue::Participants(guests) = &guests.value
|
||||
{
|
||||
if let ArchivedItipValue::Participants(guests) = &guests.value {
|
||||
variables.insert_single(
|
||||
CalendarTemplateVariable::AttendeesTitle,
|
||||
locale.calendar_attendees.to_string(),
|
||||
);
|
||||
variables.insert_block(
|
||||
CalendarTemplateVariable::Attendees,
|
||||
guests.iter().map(|guest| {
|
||||
[
|
||||
(
|
||||
CalendarTemplateVariable::Key,
|
||||
if guest.is_organizer {
|
||||
if let Some(name) = guest.name.as_ref() {
|
||||
format!("{name} - {}", locale.calendar_organizer)
|
||||
} else {
|
||||
locale.calendar_organizer.to_string()
|
||||
}
|
||||
variables.insert_single(
|
||||
CalendarTemplateVariable::AttendeesTitle,
|
||||
locale.calendar_attendees.to_string(),
|
||||
);
|
||||
variables.insert_block(
|
||||
CalendarTemplateVariable::Attendees,
|
||||
guests.iter().map(|guest| {
|
||||
[
|
||||
(
|
||||
CalendarTemplateVariable::Key,
|
||||
if guest.is_organizer {
|
||||
if let Some(name) = guest.name.as_ref() {
|
||||
format!("{name} - {}", locale.calendar_organizer)
|
||||
} else {
|
||||
guest
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|n| n.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string()
|
||||
},
|
||||
),
|
||||
(CalendarTemplateVariable::Value, guest.email.to_string()),
|
||||
]
|
||||
}),
|
||||
);
|
||||
}
|
||||
locale.calendar_organizer.to_string()
|
||||
}
|
||||
} else {
|
||||
guest
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|n| n.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_string()
|
||||
},
|
||||
),
|
||||
(CalendarTemplateVariable::Value, guest.email.to_string()),
|
||||
]
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Add RSVP buttons
|
||||
if matches!(
|
||||
summary,
|
||||
ArchivedItipSummary::Invite(_) | ArchivedItipSummary::Update { .. }
|
||||
) {
|
||||
if let Some(rsvp_url) = server
|
||||
.http_rsvp_url(task.account_id, task.document_id, to)
|
||||
.await
|
||||
{
|
||||
variables.insert_single(
|
||||
CalendarTemplateVariable::Rsvp,
|
||||
locale.calendar_reply_as.replace("$name", to),
|
||||
);
|
||||
variables.insert_block(
|
||||
CalendarTemplateVariable::Actions,
|
||||
) && let Some(rsvp_url) = server
|
||||
.http_rsvp_url(task.account_id, task.document_id, to)
|
||||
.await
|
||||
{
|
||||
variables.insert_single(
|
||||
CalendarTemplateVariable::Rsvp,
|
||||
locale.calendar_reply_as.replace("$name", to),
|
||||
);
|
||||
variables.insert_block(
|
||||
CalendarTemplateVariable::Actions,
|
||||
[
|
||||
(
|
||||
ICalendarParticipationStatus::Accepted,
|
||||
locale.calendar_yes.to_string(),
|
||||
"info",
|
||||
),
|
||||
(
|
||||
ICalendarParticipationStatus::Declined,
|
||||
locale.calendar_no.to_string(),
|
||||
"danger",
|
||||
),
|
||||
(
|
||||
ICalendarParticipationStatus::Tentative,
|
||||
locale.calendar_maybe.to_string(),
|
||||
"warning",
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(status, title, color)| {
|
||||
[
|
||||
(
|
||||
ICalendarParticipationStatus::Accepted,
|
||||
locale.calendar_yes.to_string(),
|
||||
"info",
|
||||
),
|
||||
(
|
||||
ICalendarParticipationStatus::Declined,
|
||||
locale.calendar_no.to_string(),
|
||||
"danger",
|
||||
),
|
||||
(
|
||||
ICalendarParticipationStatus::Tentative,
|
||||
locale.calendar_maybe.to_string(),
|
||||
"warning",
|
||||
),
|
||||
(CalendarTemplateVariable::ActionName, title.to_string()),
|
||||
(CalendarTemplateVariable::ActionUrl, rsvp_url.url(&status)),
|
||||
(CalendarTemplateVariable::Color, color.to_string()),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(status, title, color)| {
|
||||
[
|
||||
(CalendarTemplateVariable::ActionName, title.to_string()),
|
||||
(CalendarTemplateVariable::ActionUrl, rsvp_url.url(&status)),
|
||||
(CalendarTemplateVariable::Color, color.to_string()),
|
||||
]
|
||||
}),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Add footer
|
||||
|
||||
@@ -388,38 +388,38 @@ impl<T: SessionStream> Session<T> {
|
||||
}
|
||||
|
||||
// Add Received-SPF header
|
||||
if let Some(spf_output) = &self.data.spf_mail_from {
|
||||
if self
|
||||
if let Some(spf_output) = &self.data.spf_mail_from
|
||||
&& self
|
||||
.server
|
||||
.eval_if(&dc.add_received_spf, self, self.data.session_id)
|
||||
.await
|
||||
.unwrap_or(true)
|
||||
{
|
||||
ReceivedSpf::new(
|
||||
spf_output,
|
||||
self.data.remote_ip,
|
||||
&self.data.helo_domain,
|
||||
&mail_from.address_lcase,
|
||||
&self.hostname,
|
||||
)
|
||||
.write_header(&mut headers);
|
||||
}
|
||||
{
|
||||
ReceivedSpf::new(
|
||||
spf_output,
|
||||
self.data.remote_ip,
|
||||
&self.data.helo_domain,
|
||||
&mail_from.address_lcase,
|
||||
&self.hostname,
|
||||
)
|
||||
.write_header(&mut headers);
|
||||
}
|
||||
|
||||
// ARC Seal
|
||||
if let (Some(arc_sealer), Some(arc_output)) = (arc_sealer, &arc_output) {
|
||||
if !dkim_output.is_empty() && arc_output.can_be_sealed() {
|
||||
match arc_sealer.seal(&auth_message, &auth_results, arc_output) {
|
||||
Ok(set) => {
|
||||
set.write_header(&mut headers);
|
||||
}
|
||||
Err(err) => {
|
||||
trc::error!(
|
||||
trc::Error::from(err)
|
||||
.span_id(self.data.session_id)
|
||||
.details("Failed to ARC seal message")
|
||||
);
|
||||
}
|
||||
if let (Some(arc_sealer), Some(arc_output)) = (arc_sealer, &arc_output)
|
||||
&& !dkim_output.is_empty()
|
||||
&& arc_output.can_be_sealed()
|
||||
{
|
||||
match arc_sealer.seal(&auth_message, &auth_results, arc_output) {
|
||||
Ok(set) => {
|
||||
set.write_header(&mut headers);
|
||||
}
|
||||
Err(err) => {
|
||||
trc::error!(
|
||||
trc::Error::from(err)
|
||||
.span_id(self.data.session_id)
|
||||
.details("Failed to ARC seal message")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,20 +98,18 @@ impl<T: SessionStream> Session<T> {
|
||||
.get_trusted_sieve_script(&name, self.data.session_id)
|
||||
.map(|s| (s, name))
|
||||
})
|
||||
{
|
||||
if let ScriptResult::Reject(message) = self
|
||||
&& let ScriptResult::Reject(message) = self
|
||||
.run_script(
|
||||
script_id,
|
||||
script.clone(),
|
||||
self.build_script_parameters("ehlo"),
|
||||
)
|
||||
.await
|
||||
{
|
||||
self.data.mail_from = None;
|
||||
self.data.helo_domain = prev_helo_domain;
|
||||
self.data.spf_ehlo = None;
|
||||
return self.write(message.as_bytes()).await;
|
||||
}
|
||||
{
|
||||
self.data.mail_from = None;
|
||||
self.data.helo_domain = prev_helo_domain;
|
||||
self.data.spf_ehlo = None;
|
||||
return self.write(message.as_bytes()).await;
|
||||
}
|
||||
|
||||
// Milter filtering
|
||||
|
||||
@@ -47,10 +47,9 @@ impl SessionManager for SmtpSessionManager {
|
||||
&& session.init_conn().await
|
||||
&& session.handle_conn().await
|
||||
&& session.instance.acceptor.is_tls()
|
||||
&& let Ok(mut session) = session.into_tls().await
|
||||
{
|
||||
if let Ok(mut session) = session.into_tls().await {
|
||||
session.handle_conn().await;
|
||||
}
|
||||
session.handle_conn().await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,18 +88,16 @@ impl<T: SessionStream> Session<T> {
|
||||
.get_trusted_sieve_script(&name, self.data.session_id)
|
||||
.map(|s| (s, name))
|
||||
})
|
||||
{
|
||||
if let ScriptResult::Reject(message) = self
|
||||
&& let ScriptResult::Reject(message) = self
|
||||
.run_script(
|
||||
script_id,
|
||||
script.clone(),
|
||||
self.build_script_parameters("connect"),
|
||||
)
|
||||
.await
|
||||
{
|
||||
let _ = self.write(message.as_bytes()).await;
|
||||
return false;
|
||||
}
|
||||
{
|
||||
let _ = self.write(message.as_bytes()).await;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Milter filtering
|
||||
|
||||
@@ -1029,36 +1029,36 @@ impl QueuedMessage {
|
||||
);
|
||||
|
||||
// Verify DANE
|
||||
if let Some(dane_policy) = &dane_policy {
|
||||
if let Err(status) = dane_policy.verify(
|
||||
if let Some(dane_policy) = &dane_policy
|
||||
&& let Err(status) = dane_policy.verify(
|
||||
message.span_id,
|
||||
envelope.mx,
|
||||
smtp_client.tls_connection().peer_certificates(),
|
||||
) {
|
||||
// Report DANE verification failure
|
||||
if let Some(tls_report) = &tls_report {
|
||||
server
|
||||
.schedule_report(TlsEvent {
|
||||
policy: dane_policy.into(),
|
||||
domain: domain.to_string(),
|
||||
failure: FailureDetails::new(
|
||||
ResultType::ValidationFailure,
|
||||
)
|
||||
.with_receiving_mx_hostname(envelope.mx)
|
||||
.with_receiving_ip(remote_ip)
|
||||
.with_failure_reason_code(
|
||||
"No matching certificates found.",
|
||||
)
|
||||
.into(),
|
||||
tls_record: tls_report.record.clone(),
|
||||
interval: tls_report.interval,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
last_status = status;
|
||||
continue 'next_host;
|
||||
)
|
||||
{
|
||||
// Report DANE verification failure
|
||||
if let Some(tls_report) = &tls_report {
|
||||
server
|
||||
.schedule_report(TlsEvent {
|
||||
policy: dane_policy.into(),
|
||||
domain: domain.to_string(),
|
||||
failure: FailureDetails::new(
|
||||
ResultType::ValidationFailure,
|
||||
)
|
||||
.with_receiving_mx_hostname(envelope.mx)
|
||||
.with_receiving_ip(remote_ip)
|
||||
.with_failure_reason_code(
|
||||
"No matching certificates found.",
|
||||
)
|
||||
.into(),
|
||||
tls_record: tls_report.record.clone(),
|
||||
interval: tls_report.interval,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
last_status = status;
|
||||
continue 'next_host;
|
||||
}
|
||||
|
||||
// Report TLS success
|
||||
|
||||
@@ -59,10 +59,10 @@ impl MtaStsLookup for Server {
|
||||
};
|
||||
|
||||
// Check if the policy has been cached
|
||||
if let Some(value) = self.inner.cache.dbs_mta_sts.get(domain) {
|
||||
if value.id == record.id {
|
||||
return Ok(value);
|
||||
}
|
||||
if let Some(value) = self.inner.cache.dbs_mta_sts.get(domain)
|
||||
&& value.id == record.id
|
||||
{
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
// Fetch policy
|
||||
|
||||
@@ -22,10 +22,10 @@ impl VerifyPolicy for Policy {
|
||||
}
|
||||
}
|
||||
MxPattern::StartsWith(domain) => {
|
||||
if let Some((_, suffix)) = mx_host.split_once('.') {
|
||||
if suffix == domain {
|
||||
return true;
|
||||
}
|
||||
if let Some((_, suffix)) = mx_host.split_once('.')
|
||||
&& suffix == domain
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,13 +396,13 @@ impl MessageWrapper {
|
||||
let now = now();
|
||||
|
||||
for rcpt in &mut self.message.recipients {
|
||||
if !rcpt.has_flag(RCPT_DSN_SENT | RCPT_NOTIFY_NEVER) {
|
||||
if let Status::PermanentFailure(err) = &rcpt.status {
|
||||
rcpt.flags |= RCPT_DSN_SENT;
|
||||
let mut dsn = String::new();
|
||||
err.write_dsn_text(&rcpt.address, &mut dsn);
|
||||
is_double_bounce.push(dsn);
|
||||
}
|
||||
if !rcpt.has_flag(RCPT_DSN_SENT | RCPT_NOTIFY_NEVER)
|
||||
&& let Status::PermanentFailure(err) = &rcpt.status
|
||||
{
|
||||
rcpt.flags |= RCPT_DSN_SENT;
|
||||
let mut dsn = String::new();
|
||||
err.write_dsn_text(&rcpt.address, &mut dsn);
|
||||
is_double_bounce.push(dsn);
|
||||
}
|
||||
|
||||
if rcpt.notify.due <= now {
|
||||
@@ -529,12 +529,12 @@ impl Recipient {
|
||||
}
|
||||
|
||||
fn write_dsn_will_retry_until(&self, created: u64, dsn: &mut String) {
|
||||
if let Some(expires) = self.expiration_time(created) {
|
||||
if expires > now() {
|
||||
dsn.push_str("Will-Retry-Until: ");
|
||||
dsn.push_str(&DateTime::from_timestamp(expires as i64).to_rfc822());
|
||||
dsn.push_str("\r\n");
|
||||
}
|
||||
if let Some(expires) = self.expiration_time(created)
|
||||
&& expires > now()
|
||||
{
|
||||
dsn.push_str("Will-Retry-Until: ");
|
||||
dsn.push_str(&DateTime::from_timestamp(expires as i64).to_rfc822());
|
||||
dsn.push_str("\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -624,10 +624,10 @@ impl Status<HostResponse<String>, ErrorDetails> {
|
||||
}
|
||||
|
||||
fn write_dsn_diagnostic(&self, dsn: &mut String) {
|
||||
if let Status::PermanentFailure(err) | Status::TemporaryFailure(err) = self {
|
||||
if let Error::UnexpectedResponse(response) = &err.details {
|
||||
response.response.write_dsn_diagnostic(dsn);
|
||||
}
|
||||
if let Status::PermanentFailure(err) | Status::TemporaryFailure(err) = self
|
||||
&& let Error::UnexpectedResponse(response) = &err.details
|
||||
{
|
||||
response.response.write_dsn_diagnostic(dsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,10 +303,10 @@ impl TlsReporting for Server {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(serialized_size) = serialized_size.as_deref_mut() {
|
||||
if serde::Serialize::serialize(&tls, serialized_size).is_err() {
|
||||
continue;
|
||||
}
|
||||
if let Some(serialized_size) = serialized_size.as_deref_mut()
|
||||
&& serde::Serialize::serialize(&tls, serialized_size).is_err()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Group duplicates
|
||||
|
||||
@@ -24,21 +24,22 @@ pub trait SpamFilterAnalyzeBayes: Sync + Send {
|
||||
|
||||
impl SpamFilterAnalyzeBayes for Server {
|
||||
async fn spam_filter_analyze_bayes_classify(&self, ctx: &mut SpamFilterContext<'_>) {
|
||||
if let Some(config) = &self.core.spam.bayes {
|
||||
if !ctx.result.has_tag("SPAM_TRAP") && !ctx.result.has_tag("TRUSTED_REPLY") {
|
||||
match self.bayes_classify(ctx).await {
|
||||
Ok(Some(score)) => {
|
||||
if score > config.score_spam {
|
||||
ctx.result.add_tag("BAYES_SPAM");
|
||||
} else if score < config.score_ham {
|
||||
ctx.result.add_tag("BAYES_HAM");
|
||||
}
|
||||
}
|
||||
Ok(None) => (),
|
||||
Err(err) => {
|
||||
trc::error!(err.span_id(ctx.input.span_id).caused_by(trc::location!()));
|
||||
if let Some(config) = &self.core.spam.bayes
|
||||
&& !ctx.result.has_tag("SPAM_TRAP")
|
||||
&& !ctx.result.has_tag("TRUSTED_REPLY")
|
||||
{
|
||||
match self.bayes_classify(ctx).await {
|
||||
Ok(Some(score)) => {
|
||||
if score > config.score_spam {
|
||||
ctx.result.add_tag("BAYES_SPAM");
|
||||
} else if score < config.score_ham {
|
||||
ctx.result.add_tag("BAYES_HAM");
|
||||
}
|
||||
}
|
||||
Ok(None) => (),
|
||||
Err(err) => {
|
||||
trc::error!(err.span_id(ctx.input.span_id).caused_by(trc::location!()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ impl SpamFilterAnalyzeDomain for Server {
|
||||
|
||||
// Add DKIM domains
|
||||
for dkim in ctx.input.dkim_result {
|
||||
if dkim.result() == &DkimResult::Pass {
|
||||
if let Some(domain) = dkim.signature().map(|s| &s.d) {
|
||||
domains.insert(ElementLocation::new(
|
||||
CompactString::from_str_to_lowercase(domain),
|
||||
Location::HeaderDkimPass,
|
||||
));
|
||||
}
|
||||
if dkim.result() == &DkimResult::Pass
|
||||
&& let Some(domain) = dkim.signature().map(|s| &s.d)
|
||||
{
|
||||
domains.insert(ElementLocation::new(
|
||||
CompactString::from_str_to_lowercase(domain),
|
||||
Location::HeaderDkimPass,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +59,10 @@ impl SpamFilterAnalyzeDomain for Server {
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Host::Name(name) = host {
|
||||
if let Some(name) = Hostname::new(name.as_ref()).sld {
|
||||
domains
|
||||
.insert(ElementLocation::new(name, Location::HeaderReceived));
|
||||
}
|
||||
if let Host::Name(name) = host
|
||||
&& let Some(name) = Hostname::new(name.as_ref()).sld
|
||||
{
|
||||
domains.insert(ElementLocation::new(name, Location::HeaderReceived));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ impl SpamFilterAnalyzeFrom for Server {
|
||||
ctx.result.add_tag("FROM_HAS_DN");
|
||||
}
|
||||
|
||||
if from_name_trimmed.contains('@') {
|
||||
if let Some(from_name_addr) = TypesTokenizer::new(from_name_trimmed)
|
||||
if from_name_trimmed.contains('@')
|
||||
&& let Some(from_name_addr) = TypesTokenizer::new(from_name_trimmed)
|
||||
.tokenize_numbers(false)
|
||||
.tokenize_urls(false)
|
||||
.tokenize_urls_without_scheme(false)
|
||||
@@ -100,19 +100,18 @@ impl SpamFilterAnalyzeFrom for Server {
|
||||
_ => None,
|
||||
})
|
||||
.next()
|
||||
{
|
||||
if (from_addr_is_valid
|
||||
&& from_name_addr.domain_part.sld != from_addr.domain_part.sld)
|
||||
|| (!env_from_empty
|
||||
&& ctx.output.env_from_addr.domain_part.sld
|
||||
!= from_name_addr.domain_part.sld)
|
||||
|| (env_from_empty
|
||||
&& ctx.output.ehlo_host.sld != from_name_addr.domain_part.sld)
|
||||
{
|
||||
if (from_addr_is_valid
|
||||
&& from_name_addr.domain_part.sld != from_addr.domain_part.sld)
|
||||
|| (!env_from_empty
|
||||
&& ctx.output.env_from_addr.domain_part.sld
|
||||
!= from_name_addr.domain_part.sld)
|
||||
|| (env_from_empty
|
||||
&& ctx.output.ehlo_host.sld != from_name_addr.domain_part.sld)
|
||||
{
|
||||
ctx.result.add_tag("SPOOF_DISPLAY_NAME");
|
||||
} else {
|
||||
ctx.result.add_tag("FROM_NEQ_DISPLAY_NAME");
|
||||
}
|
||||
ctx.result.add_tag("SPOOF_DISPLAY_NAME");
|
||||
} else {
|
||||
ctx.result.add_tag("FROM_NEQ_DISPLAY_NAME");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,10 +187,10 @@ impl SpamFilterAnalyzeFrom for Server {
|
||||
}
|
||||
|
||||
// Check whether read confirmation address is different to from address
|
||||
if let Some(crt) = crt {
|
||||
if crt != from_addr.address {
|
||||
ctx.result.add_tag("HEADER_RCONFIRM_MISMATCH");
|
||||
}
|
||||
if let Some(crt) = crt
|
||||
&& crt != from_addr.address
|
||||
{
|
||||
ctx.result.add_tag("HEADER_RCONFIRM_MISMATCH");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,10 +215,10 @@ impl SpamFilterAnalyzeFrom for Server {
|
||||
}
|
||||
|
||||
// Check whether disposition notification address is different to return path
|
||||
if let Some(dnt) = dnt {
|
||||
if dnt != ctx.output.env_from_addr.address {
|
||||
ctx.result.add_tag("HEADER_FORGED_MDN");
|
||||
}
|
||||
if let Some(dnt) = dnt
|
||||
&& dnt != ctx.output.env_from_addr.address
|
||||
{
|
||||
ctx.result.add_tag("HEADER_FORGED_MDN");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,19 +38,21 @@ impl SpamFilterAnalyzeIp for Server {
|
||||
if let (HeaderName::Received, HeaderValue::Received(received)) =
|
||||
(&header.name, &header.value)
|
||||
{
|
||||
if let Some(ip) = received.from_ip() {
|
||||
if !ip.is_loopback() && !self.is_ip_allowed(&ip) {
|
||||
ips.insert(ElementLocation::new(ip, Location::HeaderReceived));
|
||||
}
|
||||
if let Some(ip) = received.from_ip()
|
||||
&& !ip.is_loopback()
|
||||
&& !self.is_ip_allowed(&ip)
|
||||
{
|
||||
ips.insert(ElementLocation::new(ip, Location::HeaderReceived));
|
||||
}
|
||||
for host in [&received.from, &received.helo, &received.by]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Host::IpAddr(ip) = host {
|
||||
if !ip.is_loopback() && !self.is_ip_allowed(ip) {
|
||||
ips.insert(ElementLocation::new(*ip, Location::HeaderReceived));
|
||||
}
|
||||
if let Host::IpAddr(ip) = host
|
||||
&& !ip.is_loopback()
|
||||
&& !self.is_ip_allowed(ip)
|
||||
{
|
||||
ips.insert(ElementLocation::new(*ip, Location::HeaderReceived));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,15 +366,15 @@ impl SpamFilterAnalyzeMime for Server {
|
||||
if is_attachment {
|
||||
// Has a MIME attachment
|
||||
ctx.result.add_tag("HAS_ATTACHMENT");
|
||||
if ct_full != "application/octet-stream" {
|
||||
if let Some(t) = infer::get(part.contents()) {
|
||||
if t.mime_type() == ct_full {
|
||||
// Known content-type
|
||||
ctx.result.add_tag("MIME_GOOD");
|
||||
} else {
|
||||
// Known bad content-type
|
||||
ctx.result.add_tag("MIME_BAD");
|
||||
}
|
||||
if ct_full != "application/octet-stream"
|
||||
&& let Some(t) = infer::get(part.contents())
|
||||
{
|
||||
if t.mime_type() == ct_full {
|
||||
// Known content-type
|
||||
ctx.result.add_tag("MIME_GOOD");
|
||||
} else {
|
||||
// Known bad content-type
|
||||
ctx.result.add_tag("MIME_BAD");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,22 +48,20 @@ impl SpamFilterAnalyzeReceived for Server {
|
||||
ctx.result.add_tag("RCVD_HELO_USER");
|
||||
} else if let (Some(Host::Name(helo_domain)), Some(ip_rev)) =
|
||||
(helo_domain, ip_rev)
|
||||
&& helo_domain.to_lowercase() != ip_rev.to_lowercase()
|
||||
{
|
||||
if helo_domain.to_lowercase() != ip_rev.to_lowercase() {
|
||||
// HELO domain does not match PTR record
|
||||
ctx.result.add_tag("FORGED_RCVD_TRAIL");
|
||||
}
|
||||
// HELO domain does not match PTR record
|
||||
ctx.result.add_tag("FORGED_RCVD_TRAIL");
|
||||
}
|
||||
|
||||
if let Some(delivered_for) = received.for_().map(|s| s.to_lowercase()) {
|
||||
if ctx
|
||||
if let Some(delivered_for) = received.for_().map(|s| s.to_lowercase())
|
||||
&& ctx
|
||||
.output
|
||||
.all_recipients()
|
||||
.any(|r| r.email.address == delivered_for)
|
||||
{
|
||||
// Recipient appears on Received trail
|
||||
ctx.result.add_tag("PREVIOUSLY_DELIVERED");
|
||||
}
|
||||
{
|
||||
// Recipient appears on Received trail
|
||||
ctx.result.add_tag("PREVIOUSLY_DELIVERED");
|
||||
}
|
||||
|
||||
if matches!(received.from, Some(Host::IpAddr(_))) {
|
||||
|
||||
@@ -101,11 +101,11 @@ impl SpamFilterAnalyzeRecipient for Server {
|
||||
|
||||
// Check for spaces in recipient addresses
|
||||
for token in raw_utf8.split('<') {
|
||||
if let Some((addr, _)) = token.split_once('>') {
|
||||
if addr.starts_with(' ') || addr.ends_with(' ') {
|
||||
ctx.result.add_tag("TO_WRAPPED_IN_SPACES");
|
||||
break;
|
||||
}
|
||||
if let Some((addr, _)) = token.split_once('>')
|
||||
&& (addr.starts_with(' ') || addr.ends_with(' '))
|
||||
{
|
||||
ctx.result.add_tag("TO_WRAPPED_IN_SPACES");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,17 +64,15 @@ impl SpamFilterAnalyzeTrustedReply for Server {
|
||||
if let (Some(hold_time), Some(message_id)) = (
|
||||
self.core.spam.expiry.trusted_reply,
|
||||
ctx.input.message.message_id(),
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.in_memory_store()
|
||||
.key_set(
|
||||
KeyValue::with_prefix(KV_TRUSTED_REPLY, message_id.as_bytes(), vec![])
|
||||
.expires(hold_time),
|
||||
)
|
||||
.await
|
||||
{
|
||||
trc::error!(err.span_id(ctx.input.span_id).caused_by(trc::location!()));
|
||||
}
|
||||
) && let Err(err) = self
|
||||
.in_memory_store()
|
||||
.key_set(
|
||||
KeyValue::with_prefix(KV_TRUSTED_REPLY, message_id.as_bytes(), vec![])
|
||||
.expires(hold_time),
|
||||
)
|
||||
.await
|
||||
{
|
||||
trc::error!(err.span_id(ctx.input.span_id).caused_by(trc::location!()));
|
||||
}
|
||||
|
||||
if self
|
||||
|
||||
@@ -96,14 +96,15 @@ impl SpamFilterAnalyzeUrl for Server {
|
||||
for token in tokens {
|
||||
match token {
|
||||
TokenType::Url(url) | TokenType::UrlNoScheme(url) => {
|
||||
if is_body && !ctx.result.has_tag("RCPT_DOMAIN_IN_BODY") {
|
||||
if let Some(url_parsed) = &url.url_parsed {
|
||||
let host = url_parsed.host.sld_or_default();
|
||||
for rcpt in ctx.output.all_recipients() {
|
||||
if rcpt.email.domain_part.sld_or_default() == host {
|
||||
ctx.result.add_tag("RCPT_DOMAIN_IN_BODY");
|
||||
break;
|
||||
}
|
||||
if is_body
|
||||
&& !ctx.result.has_tag("RCPT_DOMAIN_IN_BODY")
|
||||
&& let Some(url_parsed) = &url.url_parsed
|
||||
{
|
||||
let host = url_parsed.host.sld_or_default();
|
||||
for rcpt in ctx.output.all_recipients() {
|
||||
if rcpt.email.domain_part.sld_or_default() == host {
|
||||
ctx.result.add_tag("RCPT_DOMAIN_IN_BODY");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,9 @@ pub(crate) async fn check_dnsbl(
|
||||
};
|
||||
|
||||
for dnsbl in &server.core.spam.dnsbl.servers {
|
||||
if dnsbl.scope == scope && checks < max_checks {
|
||||
if let Some(tag) = is_dnsbl(
|
||||
if dnsbl.scope == scope
|
||||
&& checks < max_checks
|
||||
&& let Some(tag) = is_dnsbl(
|
||||
server,
|
||||
dnsbl,
|
||||
SpamFilterResolver::new(ctx, resolver, location),
|
||||
@@ -60,9 +61,8 @@ pub(crate) async fn check_dnsbl(
|
||||
&mut checks,
|
||||
)
|
||||
.await
|
||||
{
|
||||
ctx.result.add_tag(tag);
|
||||
}
|
||||
{
|
||||
ctx.result.add_tag(tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -320,13 +320,13 @@ fn html_to_text(input: &str) -> String {
|
||||
continue;
|
||||
}
|
||||
b'>' if in_tag => {
|
||||
if tag_token_pos == 1 {
|
||||
if let Some(tag) = input.get(token_start..token_end + 1) {
|
||||
if tag.eq_ignore_ascii_case(b"style") {
|
||||
in_style = !is_tag_close;
|
||||
} else if tag.eq_ignore_ascii_case(b"script") {
|
||||
in_script = !is_tag_close;
|
||||
}
|
||||
if tag_token_pos == 1
|
||||
&& let Some(tag) = input.get(token_start..token_end + 1)
|
||||
{
|
||||
if tag.eq_ignore_ascii_case(b"style") {
|
||||
in_style = !is_tag_close;
|
||||
} else if tag.eq_ignore_ascii_case(b"script") {
|
||||
in_script = !is_tag_close;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,10 +78,8 @@ impl MysqlStore {
|
||||
conn_pool: Pool::new(opts),
|
||||
};
|
||||
|
||||
if create_tables {
|
||||
if let Err(err) = db.create_tables().await {
|
||||
config.new_build_error(prefix.as_str(), format!("Failed to create tables: {err}"));
|
||||
}
|
||||
if create_tables && let Err(err) = db.create_tables().await {
|
||||
config.new_build_error(prefix.as_str(), format!("Failed to create tables: {err}"));
|
||||
}
|
||||
|
||||
Some(db)
|
||||
|
||||
@@ -65,10 +65,8 @@ impl PostgresStore {
|
||||
.ok()?,
|
||||
};
|
||||
|
||||
if create_tables {
|
||||
if let Err(err) = db.create_tables().await {
|
||||
config.new_build_error(prefix.as_str(), format!("Failed to create tables: {err}"));
|
||||
}
|
||||
if create_tables && let Err(err) = db.create_tables().await {
|
||||
config.new_build_error(prefix.as_str(), format!("Failed to create tables: {err}"));
|
||||
}
|
||||
|
||||
Some(db)
|
||||
|
||||
@@ -431,10 +431,10 @@ impl IsActiveStore for Config {
|
||||
"tracing.history.store",
|
||||
"metrics.history.store",
|
||||
] {
|
||||
if let Some(store_id) = self.value(key) {
|
||||
if store_id == id {
|
||||
return true;
|
||||
}
|
||||
if let Some(store_id) = self.value(key)
|
||||
&& store_id == id
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,10 +71,10 @@ impl JsonQueryable for serde_json::Value {
|
||||
) {
|
||||
match pointer.next() {
|
||||
Some(JsonPointerItem::String(n)) => {
|
||||
if let serde_json::Value::Object(map) = self {
|
||||
if let Some(v) = map.get(n) {
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
if let serde_json::Value::Object(map) = self
|
||||
&& let Some(v) = map.get(n)
|
||||
{
|
||||
v.eval_pointer(pointer, results);
|
||||
}
|
||||
}
|
||||
Some(JsonPointerItem::Number(n)) => match self {
|
||||
|
||||
+14
-16
@@ -504,33 +504,31 @@ impl AssertResult for Vec<String> {
|
||||
}
|
||||
|
||||
fn into_response_code(self) -> String {
|
||||
if let Some((_, code)) = self.last().unwrap().split_once('[') {
|
||||
if let Some((code, _)) = code.split_once(']') {
|
||||
return code.to_string();
|
||||
}
|
||||
if let Some((_, code)) = self.last().unwrap().split_once('[')
|
||||
&& let Some((code, _)) = code.split_once(']')
|
||||
{
|
||||
return code.to_string();
|
||||
}
|
||||
panic!("No response code found in {:?}", self.last().unwrap());
|
||||
}
|
||||
|
||||
fn into_append_uid(self) -> String {
|
||||
if let Some((_, code)) = self.last().unwrap().split_once("[APPENDUID ") {
|
||||
if let Some((code, _)) = code.split_once(']') {
|
||||
if let Some((_, uid)) = code.split_once(' ') {
|
||||
return uid.to_string();
|
||||
}
|
||||
}
|
||||
if let Some((_, code)) = self.last().unwrap().split_once("[APPENDUID ")
|
||||
&& let Some((code, _)) = code.split_once(']')
|
||||
&& let Some((_, uid)) = code.split_once(' ')
|
||||
{
|
||||
return uid.to_string();
|
||||
}
|
||||
panic!("No APPENDUID found in {:?}", self.last().unwrap());
|
||||
}
|
||||
|
||||
fn into_copy_uid(self) -> String {
|
||||
for line in &self {
|
||||
if let Some((_, code)) = line.split_once("[COPYUID ") {
|
||||
if let Some((code, _)) = code.split_once(']') {
|
||||
if let Some((_, uid)) = code.rsplit_once(' ') {
|
||||
return uid.to_string();
|
||||
}
|
||||
}
|
||||
if let Some((_, code)) = line.split_once("[COPYUID ")
|
||||
&& let Some((code, _)) = code.split_once(']')
|
||||
&& let Some((_, uid)) = code.rsplit_once(' ')
|
||||
{
|
||||
return uid.to_string();
|
||||
}
|
||||
}
|
||||
panic!("No COPYUID found in {:?}", self);
|
||||
|
||||
+4
-4
@@ -72,10 +72,10 @@ impl AssertConfig for utils::config::Config {
|
||||
pub fn enable_logging() {
|
||||
use common::config::telemetry::Telemetry;
|
||||
|
||||
if let Ok(level) = std::env::var("LOG") {
|
||||
if !Collector::is_enabled() {
|
||||
Telemetry::test_tracer(level.parse().expect("Invalid log level"));
|
||||
}
|
||||
if let Ok(level) = std::env::var("LOG")
|
||||
&& !Collector::is_enabled()
|
||||
{
|
||||
Telemetry::test_tracer(level.parse().expect("Invalid log level"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user