Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::changes::state::StateManager;
use common::Server;
use email::sieve::{SieveScript, ingest::SieveScriptIngest};
use jmap_proto::{
method::get::{GetRequest, GetResponse},
object::sieve::{Sieve, SieveProperty, SieveValue},
};
use jmap_tools::{Map, Value};
use std::future::Future;
use store::{
ValueKey,
write::{AlignedBytes, Archive},
};
use trc::AddContext;
use types::{
blob::{BlobClass, BlobId, BlobSection},
collection::{Collection, SyncCollection},
field::SieveField,
};
pub trait SieveScriptGet: Sync + Send {
fn sieve_script_get(
&self,
request: GetRequest<Sieve>,
) -> impl Future<Output = trc::Result<GetResponse<Sieve>>> + Send;
}
impl SieveScriptGet for Server {
async fn sieve_script_get(
&self,
mut request: GetRequest<Sieve>,
) -> trc::Result<GetResponse<Sieve>> {
let (ids, not_found_ids) = request.unwrap_ids(self.core.jmap.get_max_objects)?;
let properties = request.unwrap_properties(&[
SieveProperty::Id,
SieveProperty::Name,
SieveProperty::BlobId,
SieveProperty::IsActive,
]);
let account_id = request.account_id.document_id();
let script_ids = self
.document_ids(account_id, Collection::SieveScript, SieveField::Name)
.await?;
let ids = if let Some(ids) = ids {
ids
} else {
script_ids
.iter()
.take(self.core.jmap.get_max_objects)
.map(Into::into)
.collect::<Vec<_>>()
};
let mut response = GetResponse {
account_id: request.account_id.into(),
state: self
.get_state(account_id, SyncCollection::SieveScript)
.await?
.into(),
list: Vec::with_capacity(ids.len()),
not_found: not_found_ids,
};
let active_script_id = self.sieve_script_get_active_id(account_id).await?;
for id in ids {
// Obtain the sieve script object
let document_id = id.document_id();
if !script_ids.contains(document_id) {
response.push_not_found(id);
continue;
}
let sieve_ = if let Some(sieve) = self
.store()
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
account_id,
Collection::SieveScript,
document_id,
))
.await?
{
sieve
} else {
response.push_not_found(id);
continue;
};
let sieve = sieve_
.unarchive::<SieveScript>()
.caused_by(trc::location!())?;
let mut result = Map::with_capacity(properties.len());
for property in &properties {
match property {
SieveProperty::Id => {
result.insert_unchecked(SieveProperty::Id, id);
}
SieveProperty::Name => {
result.insert_unchecked(SieveProperty::Name, &sieve.name);
}
SieveProperty::IsActive => {
result.insert_unchecked(
SieveProperty::IsActive,
active_script_id == Some(document_id),
);
}
SieveProperty::BlobId => {
let blob_id = BlobId {
hash: (&sieve.blob_hash).into(),
class: BlobClass::Linked {
account_id,
collection: Collection::SieveScript.into(),
document_id,
},
section: BlobSection {
size: u32::from(sieve.size) as usize,
..Default::default()
}
.into(),
};
result.insert_unchecked(
SieveProperty::BlobId,
Value::Element(SieveValue::BlobId(blob_id)),
);
}
}
}
response.list.push(result.into());
}
Ok(response)
}
}
+10
View File
@@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod get;
pub mod query;
pub mod set;
pub mod validate;
+203
View File
@@ -0,0 +1,203 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{api::query::QueryResponseBuilder, changes::state::StateManager};
use common::Server;
use email::sieve::ingest::SieveScriptIngest;
use jmap_proto::{
method::query::{Filter, QueryRequest, QueryResponse},
object::sieve::{Sieve, SieveComparator, SieveFilter},
};
use std::future::Future;
use store::{
IndexKeyPrefix, IterateParams, U32_LEN,
roaring::RoaringBitmap,
search::{SearchFilter, SearchQuery},
write::{SearchIndex, key::DeserializeBigEndian},
};
use trc::AddContext;
use types::{
collection::{Collection, SyncCollection},
field::SieveField,
};
pub trait SieveScriptQuery: Sync + Send {
fn sieve_script_query(
&self,
request: QueryRequest<Sieve>,
) -> impl Future<Output = trc::Result<QueryResponse>> + Send;
}
impl SieveScriptQuery for Server {
async fn sieve_script_query(
&self,
mut request: QueryRequest<Sieve>,
) -> trc::Result<QueryResponse> {
let account_id = request.account_id.document_id();
let mut filters = Vec::with_capacity(request.filter.len());
let active_script_id = if request
.filter
.iter()
.any(|f| matches!(f, Filter::Property(SieveFilter::IsActive(_))))
|| request.sort.as_ref().is_some_and(|s| {
s.iter()
.any(|c| matches!(c.property, SieveComparator::IsActive))
}) {
self.sieve_script_get_active_id(account_id).await?
} else {
None
};
let mut document_ids = RoaringBitmap::new();
let mut names = Vec::new();
self.store()
.iterate(
IterateParams::new(
IndexKeyPrefix {
account_id,
collection: Collection::SieveScript.into(),
field: SieveField::Name.into(),
},
IndexKeyPrefix {
account_id,
collection: Collection::SieveScript.into(),
field: u8::from(SieveField::Name) + 1,
},
)
.no_values(),
|key, _| {
let document_id = key.deserialize_be_u32(key.len() - U32_LEN)?;
names.push((
document_id,
key.get(IndexKeyPrefix::len()..key.len() - U32_LEN)
.and_then(|v| std::str::from_utf8(v).ok())
.unwrap_or_default()
.to_string(),
));
document_ids.insert(document_id);
Ok(true)
},
)
.await
.caused_by(trc::location!())?;
for cond in std::mem::take(&mut request.filter) {
match cond {
Filter::Property(cond) => match cond {
SieveFilter::Name(name) => {
let name = name.to_lowercase();
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
names
.iter()
.filter_map(|(id, n)| (n.contains(&name)).then_some(*id))
.collect::<Vec<_>>(),
)));
}
SieveFilter::IsActive(is_active) => {
if is_active {
if let Some(active_script_id) = active_script_id {
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter([
active_script_id,
])));
} else {
// No active script, so no results
filters.push(SearchFilter::is_in_set(RoaringBitmap::new()));
}
} else {
let mut inactive_set = document_ids.clone();
if let Some(active_script_id) = active_script_id {
inactive_set.remove(active_script_id);
}
filters.push(SearchFilter::is_in_set(inactive_set));
}
}
SieveFilter::_T(other) => {
return Err(trc::JmapEvent::UnsupportedFilter.into_err().details(other));
}
},
Filter::And => {
filters.push(SearchFilter::And);
}
Filter::Or => {
filters.push(SearchFilter::Or);
}
Filter::Not => {
filters.push(SearchFilter::Not);
}
Filter::Close => {
filters.push(SearchFilter::End);
}
}
}
// Parse sort criteria
let mut sort_by_active = None;
for comparator in request
.sort
.take()
.filter(|s| !s.is_empty())
.unwrap_or_default()
{
match comparator.property {
SieveComparator::Name => {
if !comparator.is_ascending {
names.reverse();
}
}
SieveComparator::IsActive => {
sort_by_active = Some(comparator.is_ascending);
}
SieveComparator::_T(other) => {
return Err(trc::JmapEvent::UnsupportedSort.into_err().details(other));
}
};
}
let mut results = SearchQuery::new(SearchIndex::InMemory)
.with_filters(filters)
.with_mask(document_ids)
.filter()
.into_bitmap();
let mut response = QueryResponseBuilder::new(
results.len() as usize,
self.core.jmap.query_max_results,
self.get_state(account_id, SyncCollection::SieveScript)
.await?,
&request,
);
if !results.is_empty() {
if matches!(sort_by_active, Some(true))
&& results.remove(active_script_id.unwrap_or_default())
&& !response.add(0, active_script_id.unwrap())
{
return response.build();
}
let mut last_id = None;
for (document_id, _) in names {
if results.contains(document_id) {
if sort_by_active.is_some() && Some(document_id) == active_script_id {
last_id = Some(document_id);
} else if !response.add(0, document_id) {
return response.build();
}
}
}
if let Some(active_id) = last_id {
response.add(0, active_id);
}
}
response.build()
}
}
+589
View File
@@ -0,0 +1,589 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{blob::download::BlobDownload, changes::state::StateManager};
use common::{
Server,
auth::{AccessToken, AccountCache},
storage::index::ObjectIndexBuilder,
};
use email::sieve::{
ArchivedSieveScript, SieveScript, delete::SieveScriptDelete, ingest::SieveScriptIngest,
};
use http_proto::HttpSessionData;
use jmap_proto::{
error::set::{SetError, SetErrorType},
method::set::{SetRequest, SetResponse},
object::sieve::{Sieve, SieveProperty, SieveValue},
references::resolve::ResolveCreatedReference,
request::{MaybeInvalid, reference::MaybeIdReference},
types::state::State,
};
use jmap_tools::{Key, Map, Value};
use rand::distr::Alphanumeric;
use registry::schema::enums::StorageQuota;
use sieve::compiler::ErrorType;
use std::future::Future;
use store::{
Serialize, SerializeInfallible, ValueKey,
rand::{RngExt, rng},
write::{AlignedBytes, Archive, Archiver, BatchBuilder},
};
use trc::AddContext;
use types::{
blob::{BlobClass, BlobId, BlobSection},
collection::{Collection, SyncCollection},
field::{PrincipalField, SieveField},
id::Id,
};
pub struct SetContext<'x> {
account_id: u32,
access_token: &'x AccessToken,
account_cache: &'x AccountCache,
response: SetResponse<Sieve>,
}
pub trait SieveScriptSet: Sync + Send {
fn sieve_script_set(
&self,
request: SetRequest<'_, Sieve>,
access_token: &AccessToken,
session: &HttpSessionData,
) -> impl Future<Output = trc::Result<SetResponse<Sieve>>> + Send;
#[allow(clippy::type_complexity)]
fn sieve_set_item<'x>(
&self,
changes_: Value<'_, SieveProperty, SieveValue>,
update: Option<(u32, Archive<&'x ArchivedSieveScript>)>,
ctx: &SetContext,
session_id: u64,
) -> impl Future<Output = trc::Result<Result<SetItemResponse<'x>, SetError<SieveProperty>>>> + Send;
}
impl SieveScriptSet for Server {
async fn sieve_script_set(
&self,
mut request: SetRequest<'_, Sieve>,
access_token: &AccessToken,
session: &HttpSessionData,
) -> trc::Result<SetResponse<Sieve>> {
let account_id = request.account_id.document_id();
let sieve_ids = self
.document_ids(account_id, Collection::SieveScript, SieveField::Name)
.await?;
let account = self.account(account_id).await.caused_by(trc::location!())?;
let mut ctx = SetContext {
account_id,
access_token,
account_cache: &account,
response: SetResponse::from_request(&request, self.core.jmap.set_max_objects)?
.with_state(
self.assert_state(
account_id,
SyncCollection::SieveScript,
&request.if_in_state,
)
.await?,
),
};
let will_destroy = ctx.response.collect_will_destroy(request.unwrap_destroy());
// Validate active script id
if let Some(MaybeIdReference::Id(id)) = &request.arguments.on_success_activate_script
&& !sieve_ids.contains(id.document_id())
{
request.arguments.on_success_activate_script = None;
}
// Process creates
let mut batch = BatchBuilder::new();
let mut activations = Vec::new();
for (id, object) in request.unwrap_create() {
if sieve_ids.len()
< self.object_quota(account.object_quotas(), StorageQuota::MaxSieveScripts) as u64
{
match self
.sieve_set_item(object, None, &ctx, session.session_id)
.await?
{
Ok(mut result) => {
// Store blob
let sieve = &mut result.builder.changes_mut().unwrap();
let (blob_hash, blob_hold) = self
.put_temporary_blob(
account_id,
result.blob_update.as_ref().unwrap(),
60,
)
.await?;
sieve.blob_hash = blob_hash;
let blob_size = sieve.size as usize;
let blob_hash = sieve.blob_hash.clone();
// Write record
let document_id = self
.store()
.assign_document_ids(account_id, Collection::SieveScript, 1)
.await
.caused_by(trc::location!())?;
batch
.with_account_id(account_id)
.with_collection(Collection::SieveScript)
.with_document(document_id)
.custom(
result
.builder
.with_changed_by(ctx.access_token.account_tenant_ids()),
)
.caused_by(trc::location!())?
.clear(blob_hold)
.commit_point();
// Set isActive if needed
if let Some(set_item) = result.set_item {
activations.push((document_id, set_item));
}
let mut result = Map::with_capacity(1)
.with_key_value(SieveProperty::Id, SieveValue::Id(document_id.into()))
.with_key_value(
SieveProperty::BlobId,
SieveValue::BlobId(BlobId {
hash: blob_hash,
class: BlobClass::Linked {
account_id,
collection: Collection::SieveScript.into(),
document_id,
},
section: BlobSection {
size: blob_size,
..Default::default()
}
.into(),
}),
);
// Update active script if needed
if let Some(MaybeIdReference::Reference(id_ref)) =
&request.arguments.on_success_activate_script
&& id_ref == &id
{
request.arguments.on_success_activate_script =
Some(MaybeIdReference::Id(Id::from(document_id)));
result.insert_unchecked(SieveProperty::IsActive, true);
}
// Add result with updated blobId
ctx.response.created.insert(id, result.into());
}
Err(err) => {
ctx.response.not_created.append(id, err);
}
}
} else {
ctx.response.not_created.append(
id,
SetError::new(SetErrorType::OverQuota).with_description(concat!(
"There are too many sieve scripts, ",
"please delete some before adding a new one."
)),
);
}
}
// Process updates
'update: for (id, object) in request.unwrap_update() {
let id = match id {
MaybeInvalid::Value(id) => id,
invalid => {
ctx.response
.not_updated
.append(invalid, SetError::not_found());
continue 'update;
}
};
// Make sure id won't be destroyed
if will_destroy.contains(&id) {
ctx.response
.not_updated
.append(id, SetError::will_destroy());
continue 'update;
}
// Obtain sieve script
let document_id = id.document_id();
if let Some(sieve_) = self
.store()
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
account_id,
Collection::SieveScript,
document_id,
))
.await?
{
let sieve = sieve_
.to_unarchived::<SieveScript>()
.caused_by(trc::location!())?;
match self
.sieve_set_item(
object,
(document_id, sieve).into(),
&ctx,
session.session_id,
)
.await?
{
Ok(mut result) => {
// Prepare write batch
batch
.with_account_id(account_id)
.with_collection(Collection::SieveScript)
.with_document(document_id);
let blob_id = if let Some(blob) = result.blob_update.take() {
// Store blob
let sieve = &mut result.builder.changes_mut().unwrap();
let (blob_hash, blob_hold) =
self.put_temporary_blob(account_id, &blob, 60).await?;
sieve.blob_hash = blob_hash;
batch.clear(blob_hold);
BlobId {
hash: sieve.blob_hash.clone(),
class: BlobClass::Linked {
account_id,
collection: Collection::SieveScript.into(),
document_id,
},
section: BlobSection {
size: sieve.size as usize,
..Default::default()
}
.into(),
}
.into()
} else {
None
};
// Set isActive if needed
if let Some(set_item) = result.set_item {
activations.push((document_id, set_item));
}
// Write record
batch
.custom(
result
.builder
.with_changed_by(ctx.access_token.account_tenant_ids()),
)
.caused_by(trc::location!())?
.commit_point();
// Update blobId property if needed
let mut result = Map::with_capacity(1);
if let Some(blob_id) = blob_id {
result.insert_unchecked(
SieveProperty::BlobId,
SieveValue::BlobId(blob_id),
);
}
// Add active script property if needed
if let Some(MaybeIdReference::Id(id)) =
&request.arguments.on_success_activate_script
&& document_id == id.document_id()
{
result.insert_unchecked(SieveProperty::IsActive, true);
}
// Add result
ctx.response.updated.append(
id,
if !result.is_empty() {
Value::Object(result).into()
} else {
None
},
);
}
Err(err) => {
ctx.response.not_updated.append(id, err);
continue 'update;
}
}
} else {
ctx.response.not_updated.append(id, SetError::not_found());
}
}
// Process deletions
let active_script_id = self.sieve_script_get_active_id(account_id).await?;
for id in will_destroy {
let document_id = id.document_id();
if sieve_ids.contains(document_id) {
if active_script_id != Some(document_id) {
if self
.sieve_script_delete(account_id, document_id, ctx.access_token, &mut batch)
.await?
{
ctx.response.destroyed.push(id);
} else {
ctx.response.not_destroyed.append(id, SetError::not_found());
}
} else {
ctx.response.not_destroyed.append(
id,
SetError::new(SetErrorType::ScriptIsActive)
.with_description("Deactivate Sieve script before deletion."),
);
}
} else {
ctx.response.not_destroyed.append(id, SetError::not_found());
}
}
// Non-standard script activation handling
let mut on_success_deactivate_script = request
.arguments
.on_success_deactivate_script
.unwrap_or(false);
if activations.len() == 1 {
let (document_id, set_item) = activations[0];
let is_active = active_script_id.is_some_and(|active_id| active_id == document_id);
if set_item {
if request.arguments.on_success_activate_script.is_none() && !is_active {
request.arguments.on_success_activate_script =
Some(MaybeIdReference::Id(document_id.into()));
}
} else if !on_success_deactivate_script && is_active {
on_success_deactivate_script = true;
}
}
// Activate / deactivate scripts
if ctx.response.not_created.is_empty()
&& ctx.response.not_updated.is_empty()
&& ctx.response.not_destroyed.is_empty()
&& (request.arguments.on_success_activate_script.is_some()
|| on_success_deactivate_script)
{
if let Some(MaybeIdReference::Id(id)) = request.arguments.on_success_activate_script {
batch
.with_account_id(account_id)
.with_collection(Collection::Principal)
.with_document(0)
.set(PrincipalField::ActiveScriptId, id.document_id().serialize());
} else if on_success_deactivate_script {
batch
.with_account_id(account_id)
.with_collection(Collection::Principal)
.with_document(0)
.clear(PrincipalField::ActiveScriptId);
}
}
// Write changes
if !batch.is_empty()
&& let Ok(change_id) = self
.commit_batch(batch)
.await
.caused_by(trc::location!())?
.last_change_id(account_id)
{
ctx.response.new_state = State::Exact(change_id).into();
}
Ok(ctx.response)
}
#[allow(clippy::blocks_in_conditions)]
async fn sieve_set_item<'x>(
&self,
changes_: Value<'_, SieveProperty, SieveValue>,
update: Option<(u32, Archive<&'x ArchivedSieveScript>)>,
ctx: &SetContext<'_>,
session_id: u64,
) -> trc::Result<Result<SetItemResponse<'x>, SetError<SieveProperty>>> {
// Vacation script cannot be modified
if update
.as_ref()
.is_some_and(|(_, obj)| obj.inner.name.eq_ignore_ascii_case("vacation"))
{
return Ok(Err(SetError::forbidden().with_description(concat!(
"The 'vacation' script cannot be modified, ",
"use VacationResponse/set instead."
))));
}
// Parse properties
let mut set_item = None;
let mut changes = update
.as_ref()
.map(|(_, obj)| obj.deserialize().unwrap_or_default())
.unwrap_or_default();
let mut blob_id = None;
for (property, mut value) in changes_.into_expanded_object() {
if let Err(err) = ctx.response.resolve_self_references(&mut value, 0, false) {
return Ok(Err(err));
};
match (&property, value) {
(Key::Property(SieveProperty::Name), Value::Str(value)) => {
if value.len() > self.core.email.sieve_max_script_name {
return Ok(Err(SetError::invalid_properties()
.with_property(property.into_owned())
.with_description("Script name is too long.")));
} else if value.eq_ignore_ascii_case("vacation") {
return Ok(Err(SetError::forbidden()
.with_property(property.into_owned())
.with_description(
"The 'vacation' name is reserved, please use a different name.",
)));
} else if update
.as_ref()
.is_none_or(|(_, obj)| obj.inner.name != value.as_ref())
&& let Some(id) = self
.document_ids_matching(
ctx.account_id,
Collection::SieveScript,
SieveField::Name,
value.as_bytes(),
)
.await?
.min()
{
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.into_owned();
}
(
Key::Property(SieveProperty::BlobId),
Value::Element(SieveValue::BlobId(value)),
) => {
blob_id = value.into();
continue;
}
(Key::Property(SieveProperty::Name), Value::Null) => {
continue;
}
(Key::Property(SieveProperty::IsActive), Value::Bool(value)) => {
set_item = Some(value);
continue;
}
(Key::Property(SieveProperty::Id), value) => {
if update
.as_ref()
.map(|(document_id, _)| Id::from(*document_id))
.is_none_or(|expected| !crate::matches_id(&value, expected))
{
return Ok(Err(SetError::invalid_properties()
.with_property(SieveProperty::Id)
.with_description("The id property is immutable.".to_string())));
}
}
_ => {
return Ok(Err(SetError::invalid_properties()
.with_property(property.into_owned())
.with_description("Invalid property or value.".to_string())));
}
}
}
if update.is_none() {
// Add name if missing
if changes.name.is_empty() {
changes.name = rng()
.sample_iter(Alphanumeric)
.take(15)
.map(char::from)
.collect::<String>();
}
}
let blob_update = if let Some(blob_id) = blob_id {
if update.as_ref().is_none_or( |(document_id, _)| {
!matches!(blob_id.class, BlobClass::Linked { account_id, collection, document_id: d } if account_id == ctx.account_id && collection == u8::from(Collection::SieveScript) && *document_id == d)
}) {
// Check access
if let Some(mut bytes) = self.blob_download(&blob_id, ctx.access_token).await? {
// Check quota
match self
.has_available_quota(ctx.account_cache, bytes.len() as u64)
.await
{
Ok(_) => (),
Err(err) => {
if err.matches(trc::EventType::Limit(trc::LimitEvent::Quota))
|| err.matches(trc::EventType::Limit(trc::LimitEvent::TenantQuota))
{
trc::error!(err.account_id(ctx.account_id).span_id(session_id));
return Ok(Err(SetError::over_quota()));
} else {
return Err(err);
}
}
}
// Compile script
match self.core.sieve.untrusted_compiler.compile(&bytes) {
Ok(script) => {
changes.size = bytes.len() as u32;
bytes.extend(Archiver::new(script).untrusted().serialize().caused_by(trc::location!())?);
bytes.into()
}
Err(err) => {
return Ok(Err(SetError::new(
if let ErrorType::ScriptTooLong = &err.error_type() {
SetErrorType::TooLarge
} else {
SetErrorType::InvalidScript
},
)
.with_description(err.to_string())));
}
}
} else {
return Ok(Err(SetError::new(SetErrorType::BlobNotFound)
.with_property(SieveProperty::BlobId)
.with_description("Blob does not exist.")));
}
} else {
None
}
} else if update.is_none() {
return Ok(Err(SetError::invalid_properties()
.with_property(SieveProperty::BlobId)
.with_description("Missing blobId.")));
} else {
None
};
// Validate
Ok(Ok(SetItemResponse {
builder: ObjectIndexBuilder::new()
.with_changes(changes)
.with_current_opt(update.map(|(_, current)| current)),
blob_update,
set_item,
}))
}
}
pub struct SetItemResponse<'x> {
builder: ObjectIndexBuilder<&'x ArchivedSieveScript, SieveScript>,
blob_update: Option<Vec<u8>>,
set_item: Option<bool>,
}
+50
View File
@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::blob::download::BlobDownload;
use common::{Server, auth::AccessToken};
use jmap_proto::{
error::set::{SetError, SetErrorType},
method::validate::{ValidateSieveScriptRequest, ValidateSieveScriptResponse},
request::MaybeInvalid,
};
use std::future::Future;
pub trait SieveScriptValidate: Sync + Send {
fn sieve_script_validate(
&self,
request: ValidateSieveScriptRequest,
access_token: &AccessToken,
) -> impl Future<Output = trc::Result<ValidateSieveScriptResponse>> + Send;
}
impl SieveScriptValidate for Server {
async fn sieve_script_validate(
&self,
request: ValidateSieveScriptRequest,
access_token: &AccessToken,
) -> trc::Result<ValidateSieveScriptResponse> {
Ok(ValidateSieveScriptResponse {
account_id: request.account_id,
error: match request.blob_id {
MaybeInvalid::Value(blob_id) => {
match self
.blob_download(&blob_id, access_token)
.await?
.map(|bytes| self.core.sieve.untrusted_compiler.compile(&bytes))
{
Some(Ok(_)) => None,
Some(Err(err)) => SetError::new(SetErrorType::InvalidScript)
.with_description(err.to_string())
.into(),
None => SetError::new(SetErrorType::BlobNotFound).into(),
}
}
MaybeInvalid::Invalid(_) => SetError::new(SetErrorType::BlobNotFound).into(),
},
})
}
}