mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-10-04 01:34:20 +08:00
Improved error handling (part 3)
This commit is contained in:
parent
e74b29189a
commit
d2ad44cf9f
166 changed files with 2806 additions and 2692 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -3623,6 +3623,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tracing",
|
||||
"trc",
|
||||
"utils",
|
||||
]
|
||||
|
||||
|
@ -5572,9 +5573,9 @@ checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
|
|||
|
||||
[[package]]
|
||||
name = "scc"
|
||||
version = "2.1.1"
|
||||
version = "2.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ad2bbb0ae5100a07b7a6f2ed7ab5fd0045551a4c507989b7a620046ea3efdc"
|
||||
checksum = "a4465c22496331e20eb047ff46e7366455bc01c0c02015c4a376de0b2cd3a1af"
|
||||
dependencies = [
|
||||
"sdd",
|
||||
]
|
||||
|
@ -5627,9 +5628,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sdd"
|
||||
version = "0.2.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d"
|
||||
checksum = "1e806d6633ef141556fef75e345275e35652e9c045bbbc21e6ecfce3e9aa2638"
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
|
@ -6416,6 +6417,7 @@ dependencies = [
|
|||
"tokio-rustls 0.26.0",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"trc",
|
||||
"utils",
|
||||
]
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ impl Default for MailAuthConfig {
|
|||
},
|
||||
iprev: IpRevAuthConfig {
|
||||
verify: IfBlock::new::<VerifyStrategy>(
|
||||
"auth.ipref.verify",
|
||||
"auth.iprev.verify",
|
||||
[("local_port == 25", "relaxed")],
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
"disable",
|
||||
|
|
|
@ -255,7 +255,7 @@ impl Core {
|
|||
}
|
||||
Ok(None) => Ok(()),
|
||||
Err(err) => {
|
||||
if err.matches(trc::Cause::MissingTotp) {
|
||||
if err.matches(trc::Cause::Auth(trc::AuthCause::MissingTotp)) {
|
||||
return Err(err);
|
||||
} else {
|
||||
Err(err)
|
||||
|
@ -331,7 +331,7 @@ impl Core {
|
|||
.await;
|
||||
}
|
||||
|
||||
Err(trc::Cause::Authentication.into())
|
||||
Err(trc::AuthCause::Failed.into())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -377,7 +377,7 @@ impl Core {
|
|||
.await;
|
||||
}
|
||||
|
||||
Err(trc::Cause::Banned.into())
|
||||
Err(trc::AuthCause::Banned.into())
|
||||
} else {
|
||||
// Send webhook event
|
||||
if self.has_webhook_subscribers(WebhookType::AuthFailure) {
|
||||
|
@ -394,7 +394,7 @@ impl Core {
|
|||
.await;
|
||||
}
|
||||
|
||||
Err(trc::Cause::Authentication.into())
|
||||
Err(trc::AuthCause::Failed.into())
|
||||
}
|
||||
} else {
|
||||
// Send webhook event
|
||||
|
@ -411,7 +411,7 @@ impl Core {
|
|||
)
|
||||
.await;
|
||||
}
|
||||
Err(trc::Cause::Authentication.into())
|
||||
Err(trc::AuthCause::Failed.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,12 @@ impl Core {
|
|||
{
|
||||
URL_SAFE_NO_PAD
|
||||
.decode(content.as_bytes())
|
||||
.map_err(Into::into)
|
||||
.map_err(|err| {
|
||||
trc::Cause::Acme
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
.details("failed to decode certificate")
|
||||
})
|
||||
.map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
|
|
|
@ -60,7 +60,7 @@ impl Account {
|
|||
I: IntoIterator<Item = &'a S>,
|
||||
{
|
||||
let key_pair = EcdsaKeyPair::from_pkcs8(ALG, key_pair, &SystemRandom::new())
|
||||
.map_err(|err| trc::Cause::Crypto.reason(err).caused_by(trc::location!()))?;
|
||||
.map_err(|err| trc::Cause::Acme.reason(err).caused_by(trc::location!()))?;
|
||||
let contact: Vec<&'a str> = contact.into_iter().map(AsRef::<str>::as_ref).collect();
|
||||
let payload = json!({
|
||||
"termsOfServiceAgreed": true,
|
||||
|
@ -97,13 +97,19 @@ impl Account {
|
|||
)?;
|
||||
let response = https(url.as_ref(), Method::POST, Some(body)).await?;
|
||||
let location = get_header(&response, "Location").ok();
|
||||
let body = response.text().await?;
|
||||
let body = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(|err| trc::Cause::Acme.from_http_error(err))?;
|
||||
Ok((location, body))
|
||||
}
|
||||
|
||||
pub async fn new_order(&self, domains: Vec<String>) -> trc::Result<(String, Order)> {
|
||||
let domains: Vec<Identifier> = domains.into_iter().map(Identifier::Dns).collect();
|
||||
let payload = format!("{{\"identifiers\":{}}}", serde_json::to_string(&domains)?);
|
||||
let payload = format!(
|
||||
"{{\"identifiers\":{}}}",
|
||||
serde_json::to_string(&domains).map_err(|err| trc::Cause::Acme.from_json_error(err))?
|
||||
);
|
||||
let response = self.request(&self.directory.new_order, &payload).await?;
|
||||
let url = response.0.ok_or(
|
||||
trc::Cause::Acme
|
||||
|
@ -111,13 +117,14 @@ impl Account {
|
|||
.details("Missing header")
|
||||
.ctx(trc::Key::Id, "Location"),
|
||||
)?;
|
||||
let order = serde_json::from_str(&response.1)?;
|
||||
let order = serde_json::from_str(&response.1)
|
||||
.map_err(|err| trc::Cause::Acme.from_json_error(err))?;
|
||||
Ok((url, order))
|
||||
}
|
||||
|
||||
pub async fn auth(&self, url: impl AsRef<str>) -> trc::Result<Auth> {
|
||||
let response = self.request(url, "").await?;
|
||||
serde_json::from_str(&response.1).map_err(Into::into)
|
||||
serde_json::from_str(&response.1).map_err(|err| trc::Cause::Acme.from_json_error(err))
|
||||
}
|
||||
|
||||
pub async fn challenge(&self, url: impl AsRef<str>) -> trc::Result<()> {
|
||||
|
@ -126,13 +133,13 @@ impl Account {
|
|||
|
||||
pub async fn order(&self, url: impl AsRef<str>) -> trc::Result<Order> {
|
||||
let response = self.request(&url, "").await?;
|
||||
serde_json::from_str(&response.1).map_err(Into::into)
|
||||
serde_json::from_str(&response.1).map_err(|err| trc::Cause::Acme.from_json_error(err))
|
||||
}
|
||||
|
||||
pub async fn finalize(&self, url: impl AsRef<str>, csr: Vec<u8>) -> trc::Result<Order> {
|
||||
let payload = format!("{{\"csr\":\"{}\"}}", URL_SAFE_NO_PAD.encode(csr));
|
||||
let response = self.request(&url, &payload).await?;
|
||||
serde_json::from_str(&response.1).map_err(Into::into)
|
||||
serde_json::from_str(&response.1).map_err(|err| trc::Cause::Acme.from_json_error(err))
|
||||
}
|
||||
|
||||
pub async fn certificate(&self, url: impl AsRef<str>) -> trc::Result<String> {
|
||||
|
@ -155,12 +162,12 @@ impl Account {
|
|||
params.alg = &PKCS_ECDSA_P256_SHA256;
|
||||
params.custom_extensions = vec![CustomExtension::new_acme_identifier(key_auth.as_ref())];
|
||||
let cert = Certificate::from_params(params)
|
||||
.map_err(|err| trc::Cause::Crypto.caused_by(trc::location!()).reason(err))?;
|
||||
.map_err(|err| trc::Cause::Acme.caused_by(trc::location!()).reason(err))?;
|
||||
|
||||
Ok(Bincode::new(SerializedCert {
|
||||
certificate: cert
|
||||
.serialize_der()
|
||||
.map_err(|err| trc::Cause::Crypto.caused_by(trc::location!()).reason(err))?,
|
||||
.map_err(|err| trc::Cause::Acme.caused_by(trc::location!()).reason(err))?,
|
||||
private_key: cert.serialize_private_key_der(),
|
||||
})
|
||||
.serialize())
|
||||
|
@ -183,9 +190,14 @@ pub struct Directory {
|
|||
|
||||
impl Directory {
|
||||
pub async fn discover(url: impl AsRef<str>) -> trc::Result<Self> {
|
||||
Ok(serde_json::from_str(
|
||||
&https(url, Method::GET, None).await?.text().await?,
|
||||
)?)
|
||||
serde_json::from_str(
|
||||
&https(url, Method::GET, None)
|
||||
.await?
|
||||
.text()
|
||||
.await
|
||||
.map_err(|err| trc::Cause::Acme.from_http_error(err))?,
|
||||
)
|
||||
.map_err(|err| trc::Cause::Acme.from_json_error(err))
|
||||
}
|
||||
pub async fn nonce(&self) -> trc::Result<String> {
|
||||
get_header(
|
||||
|
@ -286,7 +298,10 @@ async fn https(
|
|||
);
|
||||
}
|
||||
|
||||
let mut request = builder.build()?.request(method, url);
|
||||
let mut request = builder
|
||||
.build()
|
||||
.map_err(|err| trc::Cause::Acme.from_http_error(err))?
|
||||
.request(method, url);
|
||||
|
||||
if let Some(body) = body {
|
||||
request = request
|
||||
|
@ -294,12 +309,20 @@ async fn https(
|
|||
.body(body);
|
||||
}
|
||||
|
||||
request.send().await?.assert_success().await
|
||||
request
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| trc::Cause::Acme.from_http_error(err))?
|
||||
.assert_success(trc::Cause::Acme)
|
||||
.await
|
||||
}
|
||||
|
||||
fn get_header(response: &Response, header: &'static str) -> trc::Result<String> {
|
||||
match response.headers().get_all(header).iter().last() {
|
||||
Some(value) => Ok(value.to_str()?.to_string()),
|
||||
Some(value) => Ok(value
|
||||
.to_str()
|
||||
.map_err(|err| trc::Cause::Acme.from_http_str_error(err))?
|
||||
.to_string()),
|
||||
None => Err(trc::Cause::Acme
|
||||
.caused_by(trc::location!())
|
||||
.details("Missing header")
|
||||
|
|
|
@ -23,14 +23,15 @@ pub(crate) fn sign(
|
|||
let combined = format!("{}.{}", &protected, &payload);
|
||||
let signature = key
|
||||
.sign(&SystemRandom::new(), combined.as_bytes())
|
||||
.map_err(|err| trc::Cause::Crypto.caused_by(trc::location!()).reason(err))?;
|
||||
.map_err(|err| trc::Cause::Acme.caused_by(trc::location!()).reason(err))?;
|
||||
let signature = URL_SAFE_NO_PAD.encode(signature.as_ref());
|
||||
let body = Body {
|
||||
protected,
|
||||
payload,
|
||||
signature,
|
||||
};
|
||||
Ok(serde_json::to_string(&body)?)
|
||||
|
||||
serde_json::to_string(&body).map_err(|err| trc::Cause::Acme.from_json_error(err))
|
||||
}
|
||||
|
||||
pub(crate) fn key_authorization(key: &EcdsaKeyPair, token: &str) -> trc::Result<String> {
|
||||
|
@ -84,7 +85,8 @@ impl<'a> Protected<'a> {
|
|||
nonce,
|
||||
url,
|
||||
};
|
||||
let protected = serde_json::to_vec(&protected)?;
|
||||
let protected =
|
||||
serde_json::to_vec(&protected).map_err(|err| trc::Cause::Acme.from_json_error(err))?;
|
||||
Ok(URL_SAFE_NO_PAD.encode(protected))
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +121,8 @@ impl Jwk {
|
|||
x: &self.x,
|
||||
y: &self.y,
|
||||
};
|
||||
let json = serde_json::to_vec(&jwk_thumb)?;
|
||||
let json =
|
||||
serde_json::to_vec(&jwk_thumb).map_err(|err| trc::Cause::Acme.from_json_error(err))?;
|
||||
let hash = digest(&SHA256, &json);
|
||||
Ok(URL_SAFE_NO_PAD.encode(hash))
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ impl Core {
|
|||
params.distinguished_name = DistinguishedName::new();
|
||||
params.alg = &PKCS_ECDSA_P256_SHA256;
|
||||
let cert = rcgen::Certificate::from_params(params)
|
||||
.map_err(|err| trc::Cause::Crypto.caused_by(trc::location!()).reason(err))?;
|
||||
.map_err(|err| trc::Cause::Acme.caused_by(trc::location!()).reason(err))?;
|
||||
|
||||
let (order_url, mut order) = account.new_order(provider.domains.clone()).await?;
|
||||
loop {
|
||||
|
@ -122,7 +122,7 @@ impl Core {
|
|||
}
|
||||
}
|
||||
if order.status == OrderStatus::Processing {
|
||||
return Err(trc::Cause::Timeout
|
||||
return Err(trc::Cause::Acme
|
||||
.caused_by(trc::location!())
|
||||
.details("Order processing timed out"));
|
||||
}
|
||||
|
@ -135,9 +135,9 @@ impl Core {
|
|||
"Sending CSR"
|
||||
);
|
||||
|
||||
let csr = cert.serialize_request_der().map_err(|err| {
|
||||
trc::Cause::Crypto.caused_by(trc::location!()).reason(err)
|
||||
})?;
|
||||
let csr = cert
|
||||
.serialize_request_der()
|
||||
.map_err(|err| trc::Cause::Acme.caused_by(trc::location!()).reason(err))?;
|
||||
order = account.finalize(order.finalize, csr).await?
|
||||
}
|
||||
OrderStatus::Valid { certificate } => {
|
||||
|
@ -165,7 +165,7 @@ impl Core {
|
|||
"Invalid order"
|
||||
);
|
||||
|
||||
return Err(trc::Cause::Invalid.into_err().details("Invalid ACME order"));
|
||||
return Err(trc::Cause::Acme.into_err().details("Invalid ACME order"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -194,8 +194,9 @@ impl Core {
|
|||
.iter()
|
||||
.find(|c| c.typ == challenge_type)
|
||||
.ok_or(
|
||||
trc::Cause::MissingParameter
|
||||
trc::Cause::Acme
|
||||
.into_err()
|
||||
.details("Missing Parameter")
|
||||
.ctx(trc::Key::Id, challenge_type.as_str()),
|
||||
)?;
|
||||
|
||||
|
@ -342,8 +343,9 @@ impl Core {
|
|||
}
|
||||
AuthStatus::Valid => return Ok(()),
|
||||
_ => {
|
||||
return Err(trc::Cause::Authentication
|
||||
return Err(trc::Cause::Acme
|
||||
.into_err()
|
||||
.details("Authentication error")
|
||||
.ctx(trc::Key::Status, auth.status.as_str()))
|
||||
}
|
||||
};
|
||||
|
@ -373,24 +375,25 @@ impl Core {
|
|||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
return Err(trc::Cause::Authentication
|
||||
return Err(trc::Cause::Acme
|
||||
.into_err()
|
||||
.details("Authentication error")
|
||||
.ctx(trc::Key::Status, auth.status.as_str()))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(trc::Cause::Authentication
|
||||
Err(trc::Cause::Acme
|
||||
.into_err()
|
||||
.details("Too many attempts")
|
||||
.details("Too many authentication attempts")
|
||||
.ctx(trc::Key::Id, domain))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_cert(pem: &[u8]) -> trc::Result<(CertifiedKey, [DateTime<Utc>; 2])> {
|
||||
let mut pems = pem::parse_many(pem)
|
||||
.map_err(|err| trc::Cause::Crypto.reason(err).caused_by(trc::location!()))?;
|
||||
.map_err(|err| trc::Cause::Acme.reason(err).caused_by(trc::location!()))?;
|
||||
if pems.len() < 2 {
|
||||
return Err(trc::Cause::Crypto
|
||||
return Err(trc::Cause::Acme
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Size, pems.len())
|
||||
.details("Too few PEMs"));
|
||||
|
@ -399,7 +402,7 @@ fn parse_cert(pem: &[u8]) -> trc::Result<(CertifiedKey, [DateTime<Utc>; 2])> {
|
|||
pems.remove(0).contents(),
|
||||
))) {
|
||||
Ok(pk) => pk,
|
||||
Err(err) => return Err(trc::Cause::Crypto.reason(err).caused_by(trc::location!())),
|
||||
Err(err) => return Err(trc::Cause::Acme.reason(err).caused_by(trc::location!())),
|
||||
};
|
||||
let cert_chain: Vec<CertificateDer> = pems
|
||||
.into_iter()
|
||||
|
@ -414,7 +417,7 @@ fn parse_cert(pem: &[u8]) -> trc::Result<(CertifiedKey, [DateTime<Utc>; 2])> {
|
|||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
Err(err) => return Err(trc::Cause::Crypto.reason(err).caused_by(trc::location!())),
|
||||
Err(err) => return Err(trc::Cause::Acme.reason(err).caused_by(trc::location!())),
|
||||
};
|
||||
let cert = CertifiedKey::new(cert_chain, pk);
|
||||
Ok((cert, validity))
|
||||
|
|
|
@ -77,6 +77,13 @@ pub trait SessionStream: AsyncRead + AsyncWrite + Unpin + 'static + Sync + Send
|
|||
fn tls_version_and_cipher(&self) -> (Cow<'static, str>, Cow<'static, str>);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SessionResult {
|
||||
Continue,
|
||||
Close,
|
||||
UpgradeTls,
|
||||
}
|
||||
|
||||
pub trait SessionManager: Sync + Send + 'static + Clone {
|
||||
fn spawn<T: SessionStream>(
|
||||
&self,
|
||||
|
|
|
@ -1117,19 +1117,19 @@ pub(super) trait DeserializeBytes {
|
|||
impl DeserializeBytes for &[u8] {
|
||||
fn range(&self, range: Range<usize>) -> trc::Result<&[u8]> {
|
||||
self.get(range.start..std::cmp::min(range.end, self.len()))
|
||||
.ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))
|
||||
.ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))
|
||||
}
|
||||
|
||||
fn deserialize_u8(&self, offset: usize) -> trc::Result<u8> {
|
||||
self.get(offset)
|
||||
.copied()
|
||||
.ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))
|
||||
.ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))
|
||||
}
|
||||
|
||||
fn deserialize_leb128<U: Leb128_>(&self) -> trc::Result<U> {
|
||||
self.read_leb128::<U>()
|
||||
.map(|(v, _)| v)
|
||||
.ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))
|
||||
.ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -316,7 +316,8 @@ impl ConfigManager {
|
|||
.await
|
||||
.map_err(|err| {
|
||||
trc::Cause::Configuration
|
||||
.caused_by(trc::Error::from(err))
|
||||
.reason(err)
|
||||
.details("Failed to write local configuration")
|
||||
.ctx(trc::Key::Path, self.cfg_local_path.display().to_string())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ impl WebAdminManager {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn get(&self, path: &str) -> io::Result<Resource<Vec<u8>>> {
|
||||
pub async fn get(&self, path: &str) -> trc::Result<Resource<Vec<u8>>> {
|
||||
let routes = self.routes.load();
|
||||
if let Some(resource) = routes.get(path).or_else(|| routes.get("index.html")) {
|
||||
tokio::fs::read(&resource.contents)
|
||||
|
@ -45,6 +45,12 @@ impl WebAdminManager {
|
|||
content_type: resource.content_type,
|
||||
contents,
|
||||
})
|
||||
.map_err(|err| {
|
||||
trc::ResourceCause::Error
|
||||
.reason(err)
|
||||
.ctx(trc::Key::Path, path.to_string())
|
||||
.caused_by(trc::location!())
|
||||
})
|
||||
} else {
|
||||
Ok(Resource::default())
|
||||
}
|
||||
|
@ -52,42 +58,46 @@ impl WebAdminManager {
|
|||
|
||||
pub async fn unpack(&self, blob_store: &BlobStore) -> trc::Result<()> {
|
||||
// Delete any existing bundles
|
||||
self.bundle_path.clean().await?;
|
||||
self.bundle_path.clean().await.map_err(unpack_error)?;
|
||||
|
||||
// Obtain webadmin bundle
|
||||
let bundle = blob_store
|
||||
.get_blob(WEBADMIN_KEY, 0..usize::MAX)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
trc::Cause::NotFound
|
||||
trc::ResourceCause::NotFound
|
||||
.caused_by(trc::location!())
|
||||
.details("Webadmin bundle not found")
|
||||
})?;
|
||||
|
||||
// Uncompress
|
||||
let mut bundle = zip::ZipArchive::new(Cursor::new(bundle)).map_err(|err| {
|
||||
trc::Cause::Decompress
|
||||
trc::ResourceCause::Error
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
.details("Failed to decompress webadmin bundle")
|
||||
})?;
|
||||
let mut routes = AHashMap::new();
|
||||
for i in 0..bundle.len() {
|
||||
let (file_name, contents) = {
|
||||
let mut file = bundle.by_index(i).map_err(|err| {
|
||||
trc::Cause::Decompress
|
||||
trc::ResourceCause::Error
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
.details("Failed to read file from webadmin bundle")
|
||||
})?;
|
||||
if file.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut contents = Vec::new();
|
||||
file.read_to_end(&mut contents)?;
|
||||
file.read_to_end(&mut contents).map_err(unpack_error)?;
|
||||
(file.name().to_string(), contents)
|
||||
};
|
||||
let path = self.bundle_path.path.join(format!("{i:02}"));
|
||||
tokio::fs::write(&path, contents).await?;
|
||||
tokio::fs::write(&path, contents)
|
||||
.await
|
||||
.map_err(unpack_error)?;
|
||||
|
||||
let resource = Resource {
|
||||
content_type: match file_name
|
||||
|
@ -164,6 +174,12 @@ impl TempDir {
|
|||
}
|
||||
}
|
||||
|
||||
fn unpack_error(err: std::io::Error) -> trc::Error {
|
||||
trc::ResourceCause::Error
|
||||
.reason(err)
|
||||
.details("Failed to unpack webadmin bundle")
|
||||
}
|
||||
|
||||
impl Default for WebAdminManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
|
|
|
@ -38,7 +38,7 @@ impl ImapDirectory {
|
|||
AUTH_XOAUTH2
|
||||
}
|
||||
_ => {
|
||||
trc::bail!(trc::Cause::Unsupported
|
||||
trc::bail!(trc::StoreCause::NotSupported
|
||||
.ctx(
|
||||
trc::Key::Reason,
|
||||
"IMAP server does not offer any supported auth mechanisms."
|
||||
|
@ -58,32 +58,32 @@ impl ImapDirectory {
|
|||
},
|
||||
}
|
||||
} else {
|
||||
Err(trc::Cause::Unsupported
|
||||
Err(trc::StoreCause::NotSupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Imap))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn email_to_ids(&self, _address: &str) -> trc::Result<Vec<u32>> {
|
||||
Err(trc::Cause::Unsupported
|
||||
Err(trc::StoreCause::NotSupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Imap))
|
||||
}
|
||||
|
||||
pub async fn rcpt(&self, _address: &str) -> trc::Result<bool> {
|
||||
Err(trc::Cause::Unsupported
|
||||
Err(trc::StoreCause::NotSupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Imap))
|
||||
}
|
||||
|
||||
pub async fn vrfy(&self, _address: &str) -> trc::Result<Vec<String>> {
|
||||
Err(trc::Cause::Unsupported
|
||||
Err(trc::StoreCause::NotSupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Imap))
|
||||
}
|
||||
|
||||
pub async fn expn(&self, _address: &str) -> trc::Result<Vec<String>> {
|
||||
Err(trc::Cause::Unsupported
|
||||
Err(trc::StoreCause::NotSupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Imap))
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ impl ManageDirectory for Store {
|
|||
return Ok(account_id);
|
||||
}
|
||||
Err(err) => {
|
||||
if err.matches(trc::Cause::AssertValue) && try_count < 3 {
|
||||
if err.is_assertion_failure() && try_count < 3 {
|
||||
try_count += 1;
|
||||
continue;
|
||||
} else {
|
||||
|
@ -422,7 +422,7 @@ impl ManageDirectory for Store {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
return Err(trc::Cause::Unsupported.caused_by(trc::location!()));
|
||||
return Err(trc::ManageCause::NotSupported.caused_by(trc::location!()));
|
||||
}
|
||||
(
|
||||
PrincipalAction::Set,
|
||||
|
@ -762,7 +762,7 @@ impl ManageDirectory for Store {
|
|||
}
|
||||
|
||||
_ => {
|
||||
return Err(trc::Cause::Unsupported.caused_by(trc::location!()));
|
||||
return Err(trc::StoreCause::NotSupported.caused_by(trc::location!()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1055,18 +1055,28 @@ impl From<Principal<String>> for Principal<u32> {
|
|||
}
|
||||
}
|
||||
|
||||
fn err_missing(field: impl Into<trc::Value>) -> trc::Error {
|
||||
trc::Cause::MissingParameter.ctx(trc::Key::Key, field)
|
||||
pub fn err_missing(field: impl Into<trc::Value>) -> trc::Error {
|
||||
trc::ManageCause::MissingParameter.ctx(trc::Key::Key, field)
|
||||
}
|
||||
|
||||
fn err_exists(field: impl Into<trc::Value>, value: impl Into<trc::Value>) -> trc::Error {
|
||||
trc::Cause::AlreadyExists
|
||||
pub fn err_exists(field: impl Into<trc::Value>, value: impl Into<trc::Value>) -> trc::Error {
|
||||
trc::ManageCause::AlreadyExists
|
||||
.ctx(trc::Key::Key, field)
|
||||
.ctx(trc::Key::Value, value)
|
||||
}
|
||||
|
||||
fn not_found(value: impl Into<trc::Value>) -> trc::Error {
|
||||
trc::Cause::NotFound.ctx(trc::Key::Key, value)
|
||||
pub fn not_found(value: impl Into<trc::Value>) -> trc::Error {
|
||||
trc::ManageCause::NotFound.ctx(trc::Key::Key, value)
|
||||
}
|
||||
|
||||
pub fn unsupported(details: impl Into<trc::Value>) -> trc::Error {
|
||||
trc::ManageCause::NotSupported.ctx(trc::Key::Details, details)
|
||||
}
|
||||
|
||||
pub fn error(details: impl Into<trc::Value>, reason: Option<impl Into<trc::Value>>) -> trc::Error {
|
||||
trc::ManageCause::Error
|
||||
.ctx(trc::Key::Details, details)
|
||||
.ctx_opt(trc::Key::Reason, reason)
|
||||
}
|
||||
|
||||
impl From<PrincipalField> for trc::Value {
|
||||
|
|
|
@ -58,7 +58,7 @@ impl Serialize for &Principal<u32> {
|
|||
impl Deserialize for Principal<u32> {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
deserialize(bytes).ok_or_else(|| {
|
||||
trc::Cause::DataCorruption
|
||||
trc::StoreCause::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
|
@ -79,12 +79,12 @@ impl Deserialize for PrincipalIdType {
|
|||
let mut bytes = bytes_.iter();
|
||||
Ok(PrincipalIdType {
|
||||
account_id: bytes.next_leb128().ok_or_else(|| {
|
||||
trc::Cause::DataCorruption
|
||||
trc::StoreCause::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes_)
|
||||
})?,
|
||||
typ: Type::from_u8(*bytes.next().ok_or_else(|| {
|
||||
trc::Cause::DataCorruption
|
||||
trc::StoreCause::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes_)
|
||||
})?),
|
||||
|
|
|
@ -79,7 +79,7 @@ impl LdapDirectory {
|
|||
{
|
||||
Ok(Some(principal)) => principal,
|
||||
Err(err)
|
||||
if err.matches(trc::Cause::Ldap)
|
||||
if err.matches(trc::Cause::Store(trc::StoreCause::Ldap))
|
||||
&& err
|
||||
.value(trc::Key::Code)
|
||||
.and_then(|v| v.to_uint())
|
||||
|
|
|
@ -21,14 +21,14 @@ impl SmtpDirectory {
|
|||
.authenticate(credentials)
|
||||
.await
|
||||
} else {
|
||||
Err(trc::Cause::Unsupported
|
||||
Err(trc::StoreCause::NotSupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Smtp))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn email_to_ids(&self, _address: &str) -> trc::Result<Vec<u32>> {
|
||||
Err(trc::Cause::Unsupported
|
||||
Err(trc::StoreCause::NotSupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Smtp))
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ impl SmtpDirectory {
|
|||
Ok(true)
|
||||
}
|
||||
Severity::PermanentNegativeCompletion => Ok(false),
|
||||
_ => Err(trc::Cause::Unexpected
|
||||
_ => Err(trc::StoreCause::Unexpected
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
.ctx(trc::Key::Code, reply.code())
|
||||
.ctx(trc::Key::Details, reply.message)),
|
||||
|
@ -127,10 +127,10 @@ impl SmtpClient {
|
|||
.split('\n')
|
||||
.map(|p| p.to_string())
|
||||
.collect::<Vec<String>>()),
|
||||
code @ (550 | 551 | 553 | 500 | 502) => Err(trc::Cause::Unsupported
|
||||
code @ (550 | 551 | 553 | 500 | 502) => Err(trc::StoreCause::NotSupported
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
.ctx(trc::Key::Code, code)),
|
||||
code => Err(trc::Cause::Unexpected
|
||||
code => Err(trc::StoreCause::Unexpected
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
.ctx(trc::Key::Code, code)
|
||||
.ctx(trc::Key::Details, reply.message)),
|
||||
|
|
|
@ -58,7 +58,11 @@ impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
|
|||
|
||||
// Token needs to validate with at least one of the TOTP secrets
|
||||
is_totp_verified = TOTP::from_url(secret)
|
||||
.map_err(|err| trc::Cause::Invalid.reason(err).details(secret.to_string()))?
|
||||
.map_err(|err| {
|
||||
trc::AuthCause::Invalid
|
||||
.reason(err)
|
||||
.details(secret.to_string())
|
||||
})?
|
||||
.check_current(totp_token)
|
||||
.unwrap_or(false);
|
||||
}
|
||||
|
@ -82,7 +86,7 @@ impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
|
|||
// Only let the client know if the TOTP code is missing
|
||||
// if the password is correct
|
||||
|
||||
Err(trc::Cause::MissingTotp.into_err())
|
||||
Err(trc::AuthCause::MissingTotp.into_err())
|
||||
} else {
|
||||
// Return the TOTP verification status
|
||||
|
||||
|
@ -124,7 +128,9 @@ async fn verify_hash_prefix(hashed_secret: &str, secret: &str) -> trc::Result<bo
|
|||
.ok();
|
||||
}
|
||||
Err(err) => {
|
||||
tx.send(Err(trc::Cause::Invalid.reason(err).details(hashed_secret)))
|
||||
tx.send(Err(trc::AuthCause::Invalid
|
||||
.reason(err)
|
||||
.details(hashed_secret)))
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
@ -149,7 +155,7 @@ async fn verify_hash_prefix(hashed_secret: &str, secret: &str) -> trc::Result<bo
|
|||
// MD5 based hash
|
||||
Ok(md5_crypt::verify(secret, hashed_secret))
|
||||
} else {
|
||||
Err(trc::Cause::Invalid
|
||||
Err(trc::AuthCause::Invalid
|
||||
.into_err()
|
||||
.details(hashed_secret.to_string()))
|
||||
}
|
||||
|
@ -250,12 +256,12 @@ pub async fn verify_secret_hash(hashed_secret: &str, secret: &str) -> trc::Resul
|
|||
}
|
||||
}
|
||||
"PLAIN" | "plain" | "CLEAR" | "clear" => Ok(hashed_secret == secret),
|
||||
_ => Err(trc::Cause::Invalid
|
||||
_ => Err(trc::AuthCause::Invalid
|
||||
.ctx(trc::Key::Reason, "Unsupported algorithm")
|
||||
.details(hashed_secret.to_string())),
|
||||
}
|
||||
} else {
|
||||
Err(trc::Cause::Invalid
|
||||
Err(trc::AuthCause::Invalid
|
||||
.into_err()
|
||||
.details(hashed_secret.to_string()))
|
||||
}
|
||||
|
|
|
@ -158,10 +158,10 @@ impl IntoError for PoolError<LdapError> {
|
|||
fn into_error(self) -> trc::Error {
|
||||
match self {
|
||||
PoolError::Backend(error) => error.into_error(),
|
||||
PoolError::Timeout(_) => {
|
||||
trc::Cause::Timeout.ctx(trc::Key::Protocol, trc::Protocol::Ldap)
|
||||
}
|
||||
err => trc::Cause::Pool
|
||||
PoolError::Timeout(_) => trc::StoreCause::Pool
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Ldap)
|
||||
.details("Connection timed out"),
|
||||
err => trc::StoreCause::Pool
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Ldap)
|
||||
.reason(err),
|
||||
}
|
||||
|
@ -172,10 +172,10 @@ impl IntoError for PoolError<ImapError> {
|
|||
fn into_error(self) -> trc::Error {
|
||||
match self {
|
||||
PoolError::Backend(error) => error.into_error(),
|
||||
PoolError::Timeout(_) => {
|
||||
trc::Cause::Timeout.ctx(trc::Key::Protocol, trc::Protocol::Imap)
|
||||
}
|
||||
err => trc::Cause::Pool
|
||||
PoolError::Timeout(_) => trc::StoreCause::Pool
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Imap)
|
||||
.details("Connection timed out"),
|
||||
err => trc::StoreCause::Pool
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Imap)
|
||||
.reason(err),
|
||||
}
|
||||
|
@ -186,10 +186,10 @@ impl IntoError for PoolError<mail_send::Error> {
|
|||
fn into_error(self) -> trc::Error {
|
||||
match self {
|
||||
PoolError::Backend(error) => error.into_error(),
|
||||
PoolError::Timeout(_) => {
|
||||
trc::Cause::Timeout.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
}
|
||||
err => trc::Cause::Pool
|
||||
PoolError::Timeout(_) => trc::StoreCause::Pool
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
.details("Connection timed out"),
|
||||
err => trc::StoreCause::Pool
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
.reason(err),
|
||||
}
|
||||
|
@ -211,9 +211,11 @@ impl IntoError for mail_send::Error {
|
|||
impl IntoError for LdapError {
|
||||
fn into_error(self) -> trc::Error {
|
||||
if let LdapError::LdapResult { result } = &self {
|
||||
trc::Cause::Ldap.ctx(trc::Key::Code, result.rc).reason(self)
|
||||
trc::StoreCause::Ldap
|
||||
.ctx(trc::Key::Code, result.rc)
|
||||
.reason(self)
|
||||
} else {
|
||||
trc::Cause::Ldap.reason(self)
|
||||
trc::StoreCause::Ldap.reason(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -250,7 +250,7 @@ impl<T: SessionStream> Session<T> {
|
|||
}) {
|
||||
acl
|
||||
} else {
|
||||
return Err(trc::Cause::DataCorruption
|
||||
return Err(trc::StoreCause::DataCorruption
|
||||
.into_err()
|
||||
.id(arguments.tag)
|
||||
.ctx(trc::Key::Reason, "Invalid mailbox ACL")
|
||||
|
|
|
@ -116,7 +116,7 @@ impl<T: SessionStream> SessionData<T> {
|
|||
last_change_id = Some(email.change_id);
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(if err.matches(trc::Cause::OverQuota) {
|
||||
return Err(if err.matches(trc::Cause::Limit(trc::LimitCause::Quota)) {
|
||||
err.details("Disk quota exceeded.")
|
||||
.code(ResponseCode::OverQuota)
|
||||
} else {
|
||||
|
|
|
@ -25,7 +25,7 @@ impl<T: SessionStream> Session<T> {
|
|||
if !args.params.is_empty() {
|
||||
let challenge = base64_decode(args.params.pop().unwrap().as_bytes())
|
||||
.ok_or_else(|| {
|
||||
trc::Cause::Authentication
|
||||
trc::AuthCause::Error
|
||||
.into_err()
|
||||
.details("Failed to decode challenge.")
|
||||
.id(args.tag.clone())
|
||||
|
@ -38,7 +38,7 @@ impl<T: SessionStream> Session<T> {
|
|||
decode_challenge_oauth(&challenge)
|
||||
}
|
||||
.map_err(|err| {
|
||||
trc::Cause::Authentication
|
||||
trc::AuthCause::Error
|
||||
.into_err()
|
||||
.details(err)
|
||||
.id(args.tag.clone())
|
||||
|
@ -55,7 +55,7 @@ impl<T: SessionStream> Session<T> {
|
|||
self.write_bytes(b"+ \"\"\r\n".to_vec()).await
|
||||
}
|
||||
}
|
||||
_ => Err(trc::Cause::Authentication
|
||||
_ => Err(trc::AuthCause::Error
|
||||
.into_err()
|
||||
.details("Authentication mechanism not supported.")
|
||||
.id(args.tag)
|
||||
|
@ -96,7 +96,7 @@ impl<T: SessionStream> Session<T> {
|
|||
Some(Some(limiter)) => Some(limiter),
|
||||
None => None,
|
||||
Some(None) => {
|
||||
return Err(trc::Cause::TooManyConcurrentRequests.into());
|
||||
return Err(trc::LimitCause::ConcurrentRequest.into_err());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -239,7 +239,7 @@ impl<T: SessionStream> SessionData<T> {
|
|||
changelog.log_child_update(Collection::Mailbox, mailbox_id.mailbox_id);
|
||||
}
|
||||
Err(err) => {
|
||||
if !err.matches(trc::Cause::AssertValue) {
|
||||
if !err.is_assertion_failure() {
|
||||
return Err(err.caused_by(trc::location!()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -516,7 +516,7 @@ impl<T: SessionStream> SessionData<T> {
|
|||
changelog.log_update(Collection::Email, id);
|
||||
}
|
||||
Err(err) => {
|
||||
if !err.matches(trc::Cause::AssertValue) {
|
||||
if !err.is_assertion_failure() {
|
||||
return Err(err.id(arguments.tag));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,7 +239,7 @@ impl<T: SessionStream> SessionData<T> {
|
|||
.await?
|
||||
.and_then(|obj| obj.get(&Property::Cid).as_uint())
|
||||
.ok_or_else(|| {
|
||||
trc::Cause::Unexpected
|
||||
trc::StoreCause::Unexpected
|
||||
.into_err()
|
||||
.details("Mailbox unavailable")
|
||||
.ctx(trc::Key::Reason, "Failed to obtain uid validity")
|
||||
|
|
|
@ -288,7 +288,7 @@ impl<T: SessionStream> SessionData<T> {
|
|||
});
|
||||
}
|
||||
}
|
||||
Err(err) if err.matches(trc::Cause::AssertValue) => {
|
||||
Err(err) if err.is_assertion_failure() => {
|
||||
if try_count < MAX_RETRIES {
|
||||
try_count += 1;
|
||||
continue;
|
||||
|
|
|
@ -136,7 +136,9 @@ impl From<MethodError> for trc::Error {
|
|||
),
|
||||
};
|
||||
|
||||
trc::Cause::Jmap
|
||||
let todo = "fix";
|
||||
|
||||
trc::JmapCause::RequestTooLarge
|
||||
.ctx(trc::Key::Type, typ)
|
||||
.ctx(trc::Key::Details, description)
|
||||
}
|
||||
|
@ -184,7 +186,11 @@ impl Serialize for MethodErrorWrapper {
|
|||
{
|
||||
let mut map = serializer.serialize_map(2.into())?;
|
||||
|
||||
let (error_type, description) = if self.0.matches(trc::Cause::Jmap) {
|
||||
let todo = "fix";
|
||||
let (error_type, description) = if self
|
||||
.0
|
||||
.matches(trc::Cause::Jmap(trc::JmapCause::RequestTooLarge))
|
||||
{
|
||||
(
|
||||
self.0
|
||||
.value(trc::Key::Type)
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
*/
|
||||
|
||||
use crate::{
|
||||
error::method::MethodError,
|
||||
parser::{json::Parser, Error, Ignore, JsonObjectParser, Token},
|
||||
parser::{json::Parser, Ignore, JsonObjectParser, Token},
|
||||
request::{method::MethodObject, RequestProperty},
|
||||
types::{id::Id, property::Property, state::State},
|
||||
};
|
||||
|
@ -55,7 +54,7 @@ pub enum RequestArguments {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for ChangesRequest {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -68,10 +67,9 @@ impl JsonObjectParser for ChangesRequest {
|
|||
MethodObject::EmailSubmission => RequestArguments::EmailSubmission,
|
||||
MethodObject::Quota => RequestArguments::Quota,
|
||||
_ => {
|
||||
return Err(Error::Method(MethodError::UnknownMethod(format!(
|
||||
"{}/changes",
|
||||
parser.ctx
|
||||
))))
|
||||
return Err(trc::JmapCause::UnknownMethod
|
||||
.into_err()
|
||||
.details(format!("{}/changes", parser.ctx)))
|
||||
}
|
||||
},
|
||||
account_id: Id::default(),
|
||||
|
|
|
@ -8,9 +8,9 @@ use serde::Serialize;
|
|||
use utils::map::vec_map::VecMap;
|
||||
|
||||
use crate::{
|
||||
error::{method::MethodError, set::SetError},
|
||||
error::set::SetError,
|
||||
object::Object,
|
||||
parser::{json::Parser, Error, JsonObjectParser, Token},
|
||||
parser::{json::Parser, JsonObjectParser, Token},
|
||||
request::{method::MethodObject, reference::MaybeReference, RequestProperty},
|
||||
types::{
|
||||
blob::BlobId,
|
||||
|
@ -88,7 +88,7 @@ pub enum RequestArguments {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for CopyRequest<RequestArguments> {
|
||||
fn parse(parser: &mut Parser) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -96,10 +96,9 @@ impl JsonObjectParser for CopyRequest<RequestArguments> {
|
|||
arguments: match &parser.ctx {
|
||||
MethodObject::Email => RequestArguments::Email,
|
||||
_ => {
|
||||
return Err(Error::Method(MethodError::UnknownMethod(format!(
|
||||
"{}/copy",
|
||||
parser.ctx
|
||||
))))
|
||||
return Err(trc::JmapCause::UnknownMethod
|
||||
.into_err()
|
||||
.details(format!("{}/copy", parser.ctx)))
|
||||
}
|
||||
},
|
||||
account_id: Id::default(),
|
||||
|
@ -159,7 +158,7 @@ impl JsonObjectParser for CopyRequest<RequestArguments> {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for CopyBlobRequest {
|
||||
fn parse(parser: &mut Parser) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
use crate::{
|
||||
error::method::MethodError,
|
||||
object::{blob, email, Object},
|
||||
parser::{json::Parser, Error, JsonObjectParser, Token},
|
||||
parser::{json::Parser, JsonObjectParser, Token},
|
||||
request::{
|
||||
method::MethodObject,
|
||||
reference::{MaybeReference, ResultReference},
|
||||
|
@ -55,7 +55,7 @@ pub struct GetResponse {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for GetRequest<RequestArguments> {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -73,10 +73,9 @@ impl JsonObjectParser for GetRequest<RequestArguments> {
|
|||
MethodObject::Blob => RequestArguments::Blob(Default::default()),
|
||||
MethodObject::Quota => RequestArguments::Quota,
|
||||
_ => {
|
||||
return Err(Error::Method(MethodError::UnknownMethod(format!(
|
||||
"{}/get",
|
||||
parser.ctx
|
||||
))))
|
||||
return Err(trc::JmapCause::UnknownMethod
|
||||
.into_err()
|
||||
.details(format!("{}/get", parser.ctx)))
|
||||
}
|
||||
},
|
||||
account_id: Id::default(),
|
||||
|
@ -130,11 +129,7 @@ impl JsonObjectParser for GetRequest<RequestArguments> {
|
|||
}
|
||||
|
||||
impl RequestPropertyParser for RequestArguments {
|
||||
fn parse(
|
||||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool> {
|
||||
fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result<bool> {
|
||||
match self {
|
||||
RequestArguments::Email(arguments) => arguments.parse(parser, property),
|
||||
RequestArguments::Blob(arguments) => arguments.parse(parser, property),
|
||||
|
|
|
@ -66,7 +66,7 @@ pub struct ImportEmailResponse {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for ImportEmailRequest {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -104,7 +104,7 @@ impl JsonObjectParser for ImportEmailRequest {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for ImportEmail {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -39,7 +39,7 @@ pub struct BlobInfo {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for BlobLookupRequest {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -44,7 +44,7 @@ pub struct ParseEmailResponse {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for ParseEmailRequest {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -9,9 +9,8 @@ use std::fmt::Display;
|
|||
use store::fts::{FilterItem, FilterType, FtsFilter};
|
||||
|
||||
use crate::{
|
||||
error::method::MethodError,
|
||||
object::{email, mailbox},
|
||||
parser::{json::Parser, Error, Ignore, JsonObjectParser, Token},
|
||||
parser::{json::Parser, Ignore, JsonObjectParser, Token},
|
||||
request::{method::MethodObject, RequestProperty, RequestPropertyParser},
|
||||
types::{date::UTCDate, id::Id, keyword::Keyword, state::State},
|
||||
};
|
||||
|
@ -151,7 +150,7 @@ pub enum RequestArguments {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for QueryRequest<RequestArguments> {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -164,10 +163,9 @@ impl JsonObjectParser for QueryRequest<RequestArguments> {
|
|||
MethodObject::Principal => RequestArguments::Principal,
|
||||
MethodObject::Quota => RequestArguments::Quota,
|
||||
_ => {
|
||||
return Err(Error::Method(MethodError::UnknownMethod(format!(
|
||||
"{}/query",
|
||||
parser.ctx
|
||||
))))
|
||||
return Err(trc::JmapCause::UnknownMethod
|
||||
.into_err()
|
||||
.details(format!("{}/query", parser.ctx)))
|
||||
}
|
||||
},
|
||||
filter: vec![],
|
||||
|
@ -243,7 +241,7 @@ impl JsonObjectParser for QueryRequest<RequestArguments> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_filter(parser: &mut Parser) -> crate::parser::Result<Vec<Filter>> {
|
||||
pub fn parse_filter(parser: &mut Parser) -> trc::Result<Vec<Filter>> {
|
||||
let mut filter = vec![Filter::Close];
|
||||
let mut pos_stack = vec![0];
|
||||
|
||||
|
@ -453,9 +451,9 @@ pub fn parse_filter(parser: &mut Parser) -> crate::parser::Result<Vec<Filter>> {
|
|||
break;
|
||||
}
|
||||
} else {
|
||||
return Err(Error::Method(MethodError::InvalidArguments(
|
||||
"Malformed filter".to_string(),
|
||||
)));
|
||||
return Err(trc::JmapCause::InvalidArguments
|
||||
.into_err()
|
||||
.details("Malformed filter"));
|
||||
}
|
||||
}
|
||||
Token::ArrayEnd => {
|
||||
|
@ -471,7 +469,7 @@ pub fn parse_filter(parser: &mut Parser) -> crate::parser::Result<Vec<Filter>> {
|
|||
Ok(filter)
|
||||
}
|
||||
|
||||
pub fn parse_sort(parser: &mut Parser) -> crate::parser::Result<Vec<Comparator>> {
|
||||
pub fn parse_sort(parser: &mut Parser) -> trc::Result<Vec<Comparator>> {
|
||||
let mut sort = vec![];
|
||||
|
||||
loop {
|
||||
|
@ -527,7 +525,7 @@ pub fn parse_sort(parser: &mut Parser) -> crate::parser::Result<Vec<Comparator>>
|
|||
}
|
||||
|
||||
impl JsonObjectParser for SortProperty {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -667,11 +665,7 @@ impl Display for SortProperty {
|
|||
}
|
||||
|
||||
impl RequestPropertyParser for RequestArguments {
|
||||
fn parse(
|
||||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool> {
|
||||
fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result<bool> {
|
||||
match self {
|
||||
RequestArguments::Email(args) => args.parse(parser, property),
|
||||
RequestArguments::Mailbox(args) => args.parse(parser, property),
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
*/
|
||||
|
||||
use crate::{
|
||||
error::method::MethodError,
|
||||
parser::{json::Parser, Error, Ignore, JsonObjectParser, Token},
|
||||
parser::{json::Parser, Ignore, JsonObjectParser, Token},
|
||||
request::{method::MethodObject, RequestProperty, RequestPropertyParser},
|
||||
types::{id::Id, state::State},
|
||||
};
|
||||
|
@ -60,7 +59,7 @@ impl AddedItem {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for QueryChangesRequest {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -71,10 +70,9 @@ impl JsonObjectParser for QueryChangesRequest {
|
|||
MethodObject::EmailSubmission => RequestArguments::EmailSubmission,
|
||||
MethodObject::Quota => RequestArguments::Quota,
|
||||
_ => {
|
||||
return Err(Error::Method(MethodError::UnknownMethod(format!(
|
||||
"{}/queryChanges",
|
||||
parser.ctx
|
||||
))))
|
||||
return Err(trc::JmapCause::UnknownMethod
|
||||
.into_err()
|
||||
.details(format!("{}/queryChanges", parser.ctx)))
|
||||
}
|
||||
},
|
||||
filter: vec![],
|
||||
|
|
|
@ -48,7 +48,7 @@ pub struct SearchSnippet {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for GetSearchSnippetRequest {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
|||
set::{InvalidProperty, SetError},
|
||||
},
|
||||
object::{email_submission, mailbox, sieve, Object},
|
||||
parser::{json::Parser, Error, JsonObjectParser, Token},
|
||||
parser::{json::Parser, JsonObjectParser, Token},
|
||||
request::{
|
||||
method::MethodObject,
|
||||
reference::{MaybeReference, ResultReference},
|
||||
|
@ -99,7 +99,7 @@ pub struct SetResponse {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for SetRequest<RequestArguments> {
|
||||
fn parse(parser: &mut Parser) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -115,10 +115,9 @@ impl JsonObjectParser for SetRequest<RequestArguments> {
|
|||
MethodObject::VacationResponse => RequestArguments::VacationResponse,
|
||||
MethodObject::SieveScript => RequestArguments::SieveScript(Default::default()),
|
||||
_ => {
|
||||
return Err(Error::Method(MethodError::UnknownMethod(format!(
|
||||
"{}/set",
|
||||
parser.ctx
|
||||
))))
|
||||
return Err(trc::JmapCause::UnknownMethod
|
||||
.into_err()
|
||||
.details(format!("{}/set", parser.ctx)))
|
||||
}
|
||||
},
|
||||
account_id: Id::default(),
|
||||
|
@ -168,7 +167,7 @@ impl JsonObjectParser for SetRequest<RequestArguments> {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for Object<SetValue> {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -385,11 +384,7 @@ impl<T: Into<AnyId>> From<Vec<MaybeReference<T, String>>> for SetValue {
|
|||
}
|
||||
|
||||
impl RequestPropertyParser for RequestArguments {
|
||||
fn parse(
|
||||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool> {
|
||||
fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result<bool> {
|
||||
match self {
|
||||
RequestArguments::Mailbox(args) => args.parse(parser, property),
|
||||
RequestArguments::EmailSubmission(args) => args.parse(parser, property),
|
||||
|
|
|
@ -64,7 +64,7 @@ pub struct BlobUploadResponseObject {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for BlobUploadRequest {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -96,7 +96,7 @@ impl JsonObjectParser for BlobUploadRequest {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for UploadObject {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -140,7 +140,7 @@ impl JsonObjectParser for UploadObject {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for DataSourceObject {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -27,7 +27,7 @@ pub struct ValidateSieveScriptResponse {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for ValidateSieveScriptRequest {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@ impl RequestPropertyParser for GetArguments {
|
|||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool> {
|
||||
) -> trc::Result<bool> {
|
||||
match &property.hash[0] {
|
||||
0x7465_7366_666f => {
|
||||
self.offset = parser
|
||||
|
|
|
@ -29,7 +29,7 @@ impl RequestPropertyParser for GetArguments {
|
|||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool> {
|
||||
) -> trc::Result<bool> {
|
||||
match (&property.hash[0], &property.hash[1]) {
|
||||
(0x7365_6974_7265_706f_7250_7964_6f62, _) => {
|
||||
self.body_properties = <Option<Vec<Property>>>::parse(parser)?;
|
||||
|
@ -66,7 +66,7 @@ impl RequestPropertyParser for QueryArguments {
|
|||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool> {
|
||||
) -> trc::Result<bool> {
|
||||
if property.hash[0] == 0x0073_6461_6572_6854_6573_7061_6c6c_6f63 {
|
||||
self.collapse_threads = parser
|
||||
.next_token::<Ignore>()?
|
||||
|
|
|
@ -25,7 +25,7 @@ impl RequestPropertyParser for SetArguments {
|
|||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool> {
|
||||
) -> trc::Result<bool> {
|
||||
if property.hash[0] == 0x4565_7461_6470_5573_7365_6363_7553_6e6f
|
||||
&& property.hash[1] == 0x6c69_616d
|
||||
{
|
||||
|
|
|
@ -25,7 +25,7 @@ impl RequestPropertyParser for SetArguments {
|
|||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool> {
|
||||
) -> trc::Result<bool> {
|
||||
if property.hash[0] == 0x4565_766f_6d65_5279_6f72_7473_6544_6e6f
|
||||
&& property.hash[1] == 0x0073_6c69_616d
|
||||
{
|
||||
|
@ -44,7 +44,7 @@ impl RequestPropertyParser for QueryArguments {
|
|||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool> {
|
||||
) -> trc::Result<bool> {
|
||||
match &property.hash[0] {
|
||||
0x6565_7254_7341_7472_6f73 => {
|
||||
self.sort_as_tree = parser
|
||||
|
|
|
@ -124,7 +124,7 @@ impl Serialize for Value {
|
|||
impl Deserialize for Value {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
Self::deserialize_from(&mut bytes.iter()).ok_or_else(|| {
|
||||
trc::Cause::DataCorruption
|
||||
trc::StoreCause::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
|
@ -148,7 +148,7 @@ impl Serialize for &Object<Value> {
|
|||
impl Deserialize for Object<Value> {
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
Object::deserialize_from(&mut bytes.iter()).ok_or_else(|| {
|
||||
trc::Cause::DataCorruption
|
||||
trc::StoreCause::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
|
|
|
@ -21,7 +21,7 @@ impl RequestPropertyParser for SetArguments {
|
|||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool> {
|
||||
) -> trc::Result<bool> {
|
||||
if property.hash[0] == 0x7461_7669_7463_4173_7365_6363_7553_6e6f
|
||||
&& property.hash[1] == 0x0074_7069_7263_5365
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
use utils::codec::{base32_custom::BASE32_INVERSE, leb128::Leb128Iterator};
|
||||
|
||||
use super::{json::Parser, Error};
|
||||
use super::json::Parser;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JsonBase32Reader<'x, 'y> {
|
||||
|
@ -38,7 +38,7 @@ impl<'x, 'y> JsonBase32Reader<'x, 'y> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn error(&mut self) -> Error {
|
||||
pub fn error(&mut self) -> trc::Error {
|
||||
self.bytes.error_value()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use utils::map::{
|
|||
use super::{json::Parser, Ignore, JsonObjectParser, Token};
|
||||
|
||||
impl JsonObjectParser for u64 {
|
||||
fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -36,7 +36,7 @@ impl JsonObjectParser for u64 {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for u128 {
|
||||
fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -58,7 +58,7 @@ impl JsonObjectParser for u128 {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for String {
|
||||
fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -177,7 +177,7 @@ impl JsonObjectParser for String {
|
|||
}
|
||||
|
||||
impl<T: JsonObjectParser + Eq> JsonObjectParser for Vec<T> {
|
||||
fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -197,7 +197,7 @@ impl<T: JsonObjectParser + Eq> JsonObjectParser for Vec<T> {
|
|||
}
|
||||
|
||||
impl<T: JsonObjectParser + Eq> JsonObjectParser for Option<Vec<T>> {
|
||||
fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -221,7 +221,7 @@ impl<T: JsonObjectParser + Eq> JsonObjectParser for Option<Vec<T>> {
|
|||
}
|
||||
|
||||
impl<T: JsonObjectParser + Eq + BitmapItem> JsonObjectParser for Bitmap<T> {
|
||||
fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -245,7 +245,7 @@ impl<T: JsonObjectParser + Eq + BitmapItem> JsonObjectParser for Bitmap<T> {
|
|||
}
|
||||
|
||||
impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser for VecMap<K, V> {
|
||||
fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -263,7 +263,7 @@ impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser f
|
|||
impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser
|
||||
for Option<VecMap<K, V>>
|
||||
{
|
||||
fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -284,7 +284,7 @@ impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser
|
|||
}
|
||||
|
||||
impl JsonObjectParser for bool {
|
||||
fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -297,7 +297,7 @@ impl JsonObjectParser for bool {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for Ignore {
|
||||
fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
use std::{fmt::Display, iter::Peekable, slice::Iter};
|
||||
|
||||
use crate::{error::method::MethodError, request::method::MethodObject};
|
||||
use crate::request::method::MethodObject;
|
||||
|
||||
use super::{Error, Ignore, JsonObjectParser, Token};
|
||||
use super::{Ignore, JsonObjectParser, Token};
|
||||
|
||||
const MAX_NESTED_LEVELS: u32 = 16;
|
||||
|
||||
|
@ -40,25 +40,33 @@ impl<'x> Parser<'x> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn error(&self, message: &str) -> Error {
|
||||
format!("{message} at position {}.", self.pos).into()
|
||||
pub fn error(&self, message: &str) -> trc::Error {
|
||||
trc::JmapCause::NotJSON
|
||||
.into_err()
|
||||
.details(format!("{message} at position {}.", self.pos))
|
||||
}
|
||||
|
||||
pub fn error_unterminated(&self) -> Error {
|
||||
format!("Unterminated string at position {pos}.", pos = self.pos).into()
|
||||
pub fn error_unterminated(&self) -> trc::Error {
|
||||
trc::JmapCause::NotJSON.into_err().details(format!(
|
||||
"Unterminated string at position {pos}.",
|
||||
pos = self.pos
|
||||
))
|
||||
}
|
||||
|
||||
pub fn error_utf8(&self) -> Error {
|
||||
format!("Invalid UTF-8 sequence at position {pos}.", pos = self.pos).into()
|
||||
pub fn error_utf8(&self) -> trc::Error {
|
||||
trc::JmapCause::NotJSON.into_err().details(format!(
|
||||
"Invalid UTF-8 sequence at position {pos}.",
|
||||
pos = self.pos
|
||||
))
|
||||
}
|
||||
|
||||
pub fn error_value(&mut self) -> Error {
|
||||
pub fn error_value(&mut self) -> trc::Error {
|
||||
if self.is_eof || self.skip_string() {
|
||||
Error::Method(MethodError::InvalidArguments(format!(
|
||||
trc::JmapCause::InvalidArguments.into_err().details(format!(
|
||||
"Invalid value {:?} at position {}.",
|
||||
String::from_utf8_lossy(self.bytes[self.pos_marker..self.pos - 1].as_ref()),
|
||||
self.pos
|
||||
)))
|
||||
))
|
||||
} else {
|
||||
self.error_unterminated()
|
||||
}
|
||||
|
@ -76,7 +84,7 @@ impl<'x> Parser<'x> {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn next_unescaped(&mut self) -> super::Result<Option<u8>> {
|
||||
pub fn next_unescaped(&mut self) -> trc::Result<Option<u8>> {
|
||||
match self.next_char() {
|
||||
Some(b'"') => {
|
||||
self.is_eof = true;
|
||||
|
@ -112,7 +120,7 @@ impl<'x> Parser<'x> {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn next_token<T: JsonObjectParser>(&mut self) -> super::Result<Token<T>> {
|
||||
pub fn next_token<T: JsonObjectParser>(&mut self) -> trc::Result<Token<T>> {
|
||||
let mut next_ch = self.next_ch.take().or_else(|| self.next_char());
|
||||
|
||||
while let Some(mut ch) = next_ch {
|
||||
|
@ -263,9 +271,7 @@ impl<'x> Parser<'x> {
|
|||
Err(self.error("Unexpected EOF"))
|
||||
}
|
||||
|
||||
pub fn next_dict_key<T: JsonObjectParser + Display + Eq>(
|
||||
&mut self,
|
||||
) -> super::Result<Option<T>> {
|
||||
pub fn next_dict_key<T: JsonObjectParser + Display + Eq>(&mut self) -> trc::Result<Option<T>> {
|
||||
loop {
|
||||
match self.next_token::<T>()? {
|
||||
Token::String(k) => {
|
||||
|
@ -281,11 +287,7 @@ impl<'x> Parser<'x> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn skip_token(
|
||||
&mut self,
|
||||
start_depth_array: u32,
|
||||
start_depth_dict: u32,
|
||||
) -> super::Result<()> {
|
||||
pub fn skip_token(&mut self, start_depth_array: u32, start_depth_dict: u32) -> trc::Result<()> {
|
||||
while {
|
||||
self.next_token::<Ignore>()?;
|
||||
start_depth_array != self.depth_array || start_depth_dict != self.depth_dict
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::error::{method::MethodError, request::RequestError};
|
||||
|
||||
use self::json::Parser;
|
||||
|
||||
pub mod base32;
|
||||
|
@ -32,31 +30,23 @@ pub enum Token<T> {
|
|||
impl<T: PartialEq> Eq for Token<T> {}
|
||||
|
||||
pub trait JsonObjectParser {
|
||||
fn parse(parser: &mut Parser<'_>) -> Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Request(RequestError),
|
||||
Method(MethodError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Ignore {}
|
||||
|
||||
impl<T: Eq> Token<T> {
|
||||
pub fn unwrap_string(self, property: &str) -> Result<T> {
|
||||
pub fn unwrap_string(self, property: &str) -> trc::Result<T> {
|
||||
match self {
|
||||
Token::String(s) => Ok(s),
|
||||
token => Err(token.error(property, "string")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_string_or_null(self, property: &str) -> Result<Option<T>> {
|
||||
pub fn unwrap_string_or_null(self, property: &str) -> trc::Result<Option<T>> {
|
||||
match self {
|
||||
Token::String(s) => Ok(Some(s)),
|
||||
Token::Null => Ok(None),
|
||||
|
@ -64,14 +54,14 @@ impl<T: Eq> Token<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_bool(self, property: &str) -> Result<bool> {
|
||||
pub fn unwrap_bool(self, property: &str) -> trc::Result<bool> {
|
||||
match self {
|
||||
Token::Boolean(v) => Ok(v),
|
||||
token => Err(token.error(property, "boolean")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_bool_or_null(self, property: &str) -> Result<Option<bool>> {
|
||||
pub fn unwrap_bool_or_null(self, property: &str) -> trc::Result<Option<bool>> {
|
||||
match self {
|
||||
Token::Boolean(v) => Ok(Some(v)),
|
||||
Token::Null => Ok(None),
|
||||
|
@ -79,7 +69,7 @@ impl<T: Eq> Token<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_usize_or_null(self, property: &str) -> Result<Option<usize>> {
|
||||
pub fn unwrap_usize_or_null(self, property: &str) -> trc::Result<Option<usize>> {
|
||||
match self {
|
||||
Token::Integer(v) if v >= 0 => Ok(Some(v as usize)),
|
||||
Token::Float(v) if v >= 0.0 => Ok(Some(v as usize)),
|
||||
|
@ -88,7 +78,7 @@ impl<T: Eq> Token<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_uint_or_null(self, property: &str) -> Result<Option<u64>> {
|
||||
pub fn unwrap_uint_or_null(self, property: &str) -> trc::Result<Option<u64>> {
|
||||
match self {
|
||||
Token::Integer(v) if v >= 0 => Ok(Some(v as u64)),
|
||||
Token::Float(v) if v >= 0.0 => Ok(Some(v as u64)),
|
||||
|
@ -97,7 +87,7 @@ impl<T: Eq> Token<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_int_or_null(self, property: &str) -> Result<Option<i64>> {
|
||||
pub fn unwrap_int_or_null(self, property: &str) -> trc::Result<Option<i64>> {
|
||||
match self {
|
||||
Token::Integer(v) => Ok(Some(v)),
|
||||
Token::Float(v) => Ok(Some(v as i64)),
|
||||
|
@ -106,7 +96,7 @@ impl<T: Eq> Token<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_ints_or_null(self, property: &str) -> Result<Option<i32>> {
|
||||
pub fn unwrap_ints_or_null(self, property: &str) -> trc::Result<Option<i32>> {
|
||||
match self {
|
||||
Token::Integer(v) => Ok(Some(v as i32)),
|
||||
Token::Float(v) => Ok(Some(v as i32)),
|
||||
|
@ -115,7 +105,7 @@ impl<T: Eq> Token<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn assert(self, token: Token<T>) -> Result<()> {
|
||||
pub fn assert(self, token: Token<T>) -> trc::Result<()> {
|
||||
if self == token {
|
||||
Ok(())
|
||||
} else {
|
||||
|
@ -123,22 +113,22 @@ impl<T: Eq> Token<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn assert_jmap(self, token: Token<T>) -> Result<()> {
|
||||
pub fn assert_jmap(self, token: Token<T>) -> trc::Result<()> {
|
||||
if self == token {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Request(RequestError::not_request(format!(
|
||||
Err(trc::JmapCause::NotRequest.into_err().details(format!(
|
||||
"Invalid JMAP request: expected '{token}', got '{self}'."
|
||||
))))
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(&self, property: &str, expected: &str) -> Error {
|
||||
Error::Method(MethodError::InvalidArguments(if !property.is_empty() {
|
||||
pub fn error(&self, property: &str, expected: &str) -> trc::Error {
|
||||
trc::JmapCause::InvalidArguments.into_err().details(if !property.is_empty() {
|
||||
format!("Invalid argument for '{property:?}': expected '{expected}', got '{self}'.",)
|
||||
} else {
|
||||
format!("Invalid argument: expected '{expected}', got '{self}'.")
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,18 +138,6 @@ impl Display for Ignore {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(s: String) -> Self {
|
||||
Error::Request(RequestError::not_json(&s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Error {
|
||||
fn from(s: &str) -> Self {
|
||||
Error::Request(RequestError::not_json(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Display for Token<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
use utils::map::vec_map::VecMap;
|
||||
|
||||
use crate::{
|
||||
error::request::RequestError,
|
||||
parser::{json::Parser, Error, JsonObjectParser},
|
||||
parser::{json::Parser, JsonObjectParser},
|
||||
response::serialize::serialize_hex,
|
||||
types::{id::Id, type_state::DataType},
|
||||
};
|
||||
|
@ -315,7 +314,7 @@ impl WebSocketCapabilities {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for Capability {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -343,18 +342,19 @@ impl JsonObjectParser for Capability {
|
|||
0x0061_746f_7571 => Ok(Capability::Quota),
|
||||
_ => Err(parser.error_capability()),
|
||||
},
|
||||
Err(Error::Method(_)) => Err(parser.error_capability()),
|
||||
Err(err @ Error::Request(_)) => Err(err),
|
||||
Err(err) if err.is_jmap_method_error() => Err(parser.error_capability()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> Parser<'x> {
|
||||
fn error_capability(&mut self) -> Error {
|
||||
fn error_capability(&mut self) -> trc::Error {
|
||||
if self.is_eof || self.skip_string() {
|
||||
Error::Request(RequestError::unknown_capability(&String::from_utf8_lossy(
|
||||
self.bytes[self.pos_marker..self.pos - 1].as_ref(),
|
||||
)))
|
||||
trc::JmapCause::UnknownCapability.into_err().details(
|
||||
String::from_utf8_lossy(self.bytes[self.pos_marker..self.pos - 1].as_ref())
|
||||
.into_owned(),
|
||||
)
|
||||
} else {
|
||||
self.error_unterminated()
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ pub struct Echo {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for Echo {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -48,7 +48,7 @@ pub enum MethodFunction {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for MethodName {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -77,7 +77,7 @@ pub enum RequestMethod {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for RequestProperty {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -116,5 +116,5 @@ pub trait RequestPropertyParser {
|
|||
&mut self,
|
||||
parser: &mut Parser,
|
||||
property: RequestProperty,
|
||||
) -> crate::parser::Result<bool>;
|
||||
) -> trc::Result<bool>;
|
||||
}
|
||||
|
|
|
@ -7,10 +7,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
error::{
|
||||
method::MethodError,
|
||||
request::{RequestError, RequestLimitError},
|
||||
},
|
||||
method::{
|
||||
changes::ChangesRequest,
|
||||
copy::{CopyBlobRequest, CopyRequest},
|
||||
|
@ -25,7 +21,7 @@ use crate::{
|
|||
upload::BlobUploadRequest,
|
||||
validate::ValidateSieveScriptRequest,
|
||||
},
|
||||
parser::{json::Parser, Error, Ignore, JsonObjectParser, Token},
|
||||
parser::{json::Parser, Ignore, JsonObjectParser, Token},
|
||||
types::any_id::AnyId,
|
||||
};
|
||||
|
||||
|
@ -37,7 +33,7 @@ use super::{
|
|||
};
|
||||
|
||||
impl Request {
|
||||
pub fn parse(json: &[u8], max_calls: usize, max_size: usize) -> Result<Self, RequestError> {
|
||||
pub fn parse(json: &[u8], max_calls: usize, max_size: usize) -> trc::Result<Self> {
|
||||
if json.len() <= max_size {
|
||||
let mut request = Request {
|
||||
using: 0,
|
||||
|
@ -54,10 +50,12 @@ impl Request {
|
|||
if found_valid_keys {
|
||||
Ok(request)
|
||||
} else {
|
||||
Err(RequestError::not_request("Invalid JMAP request"))
|
||||
Err(trc::JmapCause::NotRequest
|
||||
.into_err()
|
||||
.details("Invalid JMAP request"))
|
||||
}
|
||||
} else {
|
||||
Err(RequestError::limit(RequestLimitError::SizeRequest))
|
||||
Err(trc::LimitCause::SizeRequest.into_err())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +64,7 @@ impl Request {
|
|||
parser: &mut Parser,
|
||||
max_calls: usize,
|
||||
key: u128,
|
||||
) -> Result<bool, RequestError> {
|
||||
) -> trc::Result<bool> {
|
||||
match key {
|
||||
0x0067_6e69_7375 => {
|
||||
parser.next_token::<Ignore>()?.assert(Token::ArrayStart)?;
|
||||
|
@ -77,7 +75,7 @@ impl Request {
|
|||
}
|
||||
Token::Comma => (),
|
||||
Token::ArrayEnd => break,
|
||||
token => return Err(token.error("capability", &token.to_string()).into()),
|
||||
token => return Err(token.error("capability", &token.to_string())),
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
|
@ -92,20 +90,28 @@ impl Request {
|
|||
Token::Comma => continue,
|
||||
Token::ArrayEnd => break,
|
||||
_ => {
|
||||
return Err(RequestError::not_request("Invalid JMAP request"));
|
||||
return Err(trc::JmapCause::NotRequest
|
||||
.into_err()
|
||||
.details("Invalid JMAP request"));
|
||||
}
|
||||
};
|
||||
if self.method_calls.len() < max_calls {
|
||||
let method_name = match parser.next_token::<MethodName>() {
|
||||
Ok(Token::String(method)) => method,
|
||||
Ok(_) => {
|
||||
return Err(RequestError::not_request("Invalid JMAP request"));
|
||||
return Err(trc::JmapCause::NotRequest
|
||||
.into_err()
|
||||
.details("Invalid JMAP request"));
|
||||
}
|
||||
Err(Error::Method(MethodError::InvalidArguments(_))) => {
|
||||
Err(err)
|
||||
if err.matches(trc::Cause::Jmap(
|
||||
trc::JmapCause::InvalidArguments,
|
||||
)) =>
|
||||
{
|
||||
MethodName::error()
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err.into());
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
parser.next_token::<Ignore>()?.assert_jmap(Token::Comma)?;
|
||||
|
@ -169,19 +175,19 @@ impl Request {
|
|||
(MethodFunction::Echo, MethodObject::Core) => {
|
||||
Echo::parse(parser).map(RequestMethod::Echo)
|
||||
}
|
||||
_ => Err(Error::Method(MethodError::UnknownMethod(
|
||||
method_name.to_string(),
|
||||
))),
|
||||
_ => Err(trc::JmapCause::UnknownMethod
|
||||
.into_err()
|
||||
.details(method_name.to_string())),
|
||||
};
|
||||
|
||||
let method = match method {
|
||||
Ok(method) => method,
|
||||
Err(Error::Method(err)) => {
|
||||
Err(err) if !err.is_jmap_method_error() => {
|
||||
parser.skip_token(start_depth_array, start_depth_dict)?;
|
||||
RequestMethod::Error(err.into())
|
||||
RequestMethod::Error(err)
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err.into());
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -196,7 +202,7 @@ impl Request {
|
|||
name: method_name,
|
||||
});
|
||||
} else {
|
||||
return Err(RequestError::limit(RequestLimitError::CallsIn));
|
||||
return Err(trc::LimitCause::CallsIn.into_err());
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
|
@ -221,15 +227,6 @@ impl Request {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Error> for RequestError {
|
||||
fn from(value: Error) -> Self {
|
||||
match value {
|
||||
Error::Request(err) => err,
|
||||
Error::Method(err) => RequestError::not_request(err.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::request::Request;
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use crate::{
|
||||
error::method::MethodError,
|
||||
parser::{json::Parser, Error, JsonObjectParser, Token},
|
||||
parser::{json::Parser, JsonObjectParser, Token},
|
||||
types::{id::Id, pointer::JSONPointer},
|
||||
};
|
||||
|
||||
|
@ -45,7 +44,7 @@ impl<V, R> MaybeReference<V, R> {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for ResultReference {
|
||||
fn parse(parser: &mut Parser) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -81,15 +80,15 @@ impl JsonObjectParser for ResultReference {
|
|||
path,
|
||||
})
|
||||
} else {
|
||||
Err(Error::Method(MethodError::InvalidResultReference(
|
||||
"Missing required fields".into(),
|
||||
)))
|
||||
Err(trc::JmapCause::InvalidResultReference
|
||||
.into_err()
|
||||
.details("Missing required fields"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JsonObjectParser> JsonObjectParser for MaybeReference<T, String> {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::{borrow::Cow, collections::HashMap};
|
|||
|
||||
use crate::{
|
||||
error::request::{RequestError, RequestErrorType, RequestLimitError},
|
||||
parser::{json::Parser, Error, JsonObjectParser, Token},
|
||||
parser::{json::Parser, JsonObjectParser, Token},
|
||||
request::Call,
|
||||
response::{serialize::serialize_hex, Response, ResponseMethod},
|
||||
types::{any_id::AnyId, id::Id, state::State, type_state::DataType},
|
||||
|
@ -108,11 +108,7 @@ enum MessageType {
|
|||
}
|
||||
|
||||
impl WebSocketMessage {
|
||||
pub fn parse(
|
||||
json: &[u8],
|
||||
max_calls: usize,
|
||||
max_size: usize,
|
||||
) -> Result<Self, WebSocketRequestError> {
|
||||
pub fn parse(json: &[u8], max_calls: usize, max_size: usize) -> trc::Result<Self> {
|
||||
if json.len() <= max_size {
|
||||
let mut message_type = MessageType::None;
|
||||
let mut request = WebSocketRequest {
|
||||
|
@ -174,10 +170,12 @@ impl WebSocketMessage {
|
|||
MessageType::PushDisable if !found_request_keys && !found_push_keys => {
|
||||
Ok(WebSocketMessage::PushDisable)
|
||||
}
|
||||
_ => Err(RequestError::not_request("Invalid WebSocket JMAP request").into()),
|
||||
_ => Err(trc::JmapCause::NotRequest
|
||||
.into_err()
|
||||
.details("Invalid WebSocket JMAP request")),
|
||||
}
|
||||
} else {
|
||||
Err(RequestError::limit(RequestLimitError::SizeRequest).into())
|
||||
Err(trc::LimitCause::SizeRequest.into_err())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,12 +203,6 @@ impl From<RequestError> for WebSocketRequestError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Error> for WebSocketRequestError {
|
||||
fn from(value: Error) -> Self {
|
||||
RequestError::from(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl WebSocketResponse {
|
||||
pub fn from_response(response: Response, request_id: Option<String>) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -27,7 +27,7 @@ pub enum Acl {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for Acl {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -96,7 +96,7 @@ impl From<&AnyId> for Value {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for AnyId {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -38,7 +38,7 @@ pub struct BlobSection {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for BlobId {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@ pub struct UTCDate {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for UTCDate {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@ impl Default for Id {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for Id {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -61,7 +61,7 @@ pub enum Keyword {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for Keyword {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -29,7 +29,7 @@ pub enum MaybeUnparsable<V> {
|
|||
}
|
||||
|
||||
impl<V: JsonObjectParser> JsonObjectParser for MaybeUnparsable<V> {
|
||||
fn parse(parser: &mut Parser) -> crate::parser::Result<Self> {
|
||||
fn parse(parser: &mut Parser) -> trc::Result<Self> {
|
||||
match V::parse(parser) {
|
||||
Ok(value) => Ok(MaybeUnparsable::Value(value)),
|
||||
Err(_) if parser.is_eof || parser.skip_string() => Ok(MaybeUnparsable::ParseError(
|
||||
|
|
|
@ -26,7 +26,7 @@ enum TokenType {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for JSONPointer {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@ use mail_parser::HeaderName;
|
|||
use serde::Serialize;
|
||||
use store::write::{DeserializeFrom, SerializeInto};
|
||||
|
||||
use crate::parser::{json::Parser, Error, JsonObjectParser};
|
||||
use crate::parser::{json::Parser, JsonObjectParser};
|
||||
|
||||
use super::{acl::Acl, id::Id, keyword::Keyword, value::Value};
|
||||
|
||||
|
@ -153,7 +153,7 @@ pub trait IntoProperty: Eq + Display {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for Property {
|
||||
fn parse(parser: &mut Parser) -> crate::parser::Result<Self> {
|
||||
fn parse(parser: &mut Parser) -> trc::Result<Self> {
|
||||
let mut first_char = 0;
|
||||
let mut hash = 0;
|
||||
let mut shift = 0;
|
||||
|
@ -190,7 +190,7 @@ impl JsonObjectParser for Property {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for SetProperty {
|
||||
fn parse(parser: &mut Parser) -> crate::parser::Result<Self> {
|
||||
fn parse(parser: &mut Parser) -> trc::Result<Self> {
|
||||
let mut first_char = 0;
|
||||
let mut hash = 0;
|
||||
let mut shift = 0;
|
||||
|
@ -251,7 +251,7 @@ impl JsonObjectParser for SetProperty {
|
|||
Ok(id) => {
|
||||
patch.push(Value::Id(id));
|
||||
}
|
||||
Err(Error::Method(_)) => {
|
||||
Err(err) if err.is_jmap_method_error() => {
|
||||
property = parser.invalid_property()?;
|
||||
}
|
||||
Err(err) => {
|
||||
|
@ -262,7 +262,7 @@ impl JsonObjectParser for SetProperty {
|
|||
Ok(keyword) => {
|
||||
patch.push(Value::Keyword(keyword));
|
||||
}
|
||||
Err(Error::Method(_)) => {
|
||||
Err(err) if err.is_jmap_method_error() => {
|
||||
property = parser.invalid_property()?;
|
||||
}
|
||||
Err(err) => {
|
||||
|
@ -290,7 +290,7 @@ impl JsonObjectParser for SetProperty {
|
|||
Ok(acl) => {
|
||||
patch.push(Value::UnsignedInt(acl as u64));
|
||||
}
|
||||
Err(Error::Method(_)) => {
|
||||
Err(err) if err.is_jmap_method_error() => {
|
||||
property = parser.invalid_property()?;
|
||||
}
|
||||
Err(err) => {
|
||||
|
@ -469,7 +469,7 @@ fn parse_property(first_char: u8, hash: u128) -> Option<Property> {
|
|||
})
|
||||
}
|
||||
|
||||
fn parse_header_property(parser: &mut Parser) -> crate::parser::Result<Property> {
|
||||
fn parse_header_property(parser: &mut Parser) -> trc::Result<Property> {
|
||||
let hdr_start_pos = parser.pos;
|
||||
let mut has_next = false;
|
||||
|
||||
|
@ -553,7 +553,7 @@ fn parse_sub_property(
|
|||
parser: &mut Parser,
|
||||
first_char: u8,
|
||||
parent_hash: u128,
|
||||
) -> crate::parser::Result<Property> {
|
||||
) -> trc::Result<Property> {
|
||||
let mut hash = 0;
|
||||
let mut shift = 0;
|
||||
|
||||
|
@ -585,7 +585,7 @@ fn parse_sub_property(
|
|||
}
|
||||
|
||||
impl JsonObjectParser for ObjectProperty {
|
||||
fn parse(parser: &mut Parser) -> crate::parser::Result<Self> {
|
||||
fn parse(parser: &mut Parser) -> trc::Result<Self> {
|
||||
let mut first_char = 0;
|
||||
let mut hash = 0;
|
||||
let mut shift = 0;
|
||||
|
@ -707,7 +707,7 @@ impl JsonObjectParser for ObjectProperty {
|
|||
}
|
||||
|
||||
impl<'x> Parser<'x> {
|
||||
fn invalid_property(&mut self) -> crate::parser::Result<Property> {
|
||||
fn invalid_property(&mut self) -> trc::Result<Property> {
|
||||
if self.is_eof || self.skip_string() {
|
||||
Ok(Property::_T(
|
||||
String::from_utf8_lossy(self.bytes[self.pos_marker..self.pos - 1].as_ref())
|
||||
|
|
|
@ -72,7 +72,7 @@ impl From<Option<ChangeId>> for State {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for State {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -85,7 +85,7 @@ impl From<DataType> for u64 {
|
|||
}
|
||||
|
||||
impl JsonObjectParser for DataType {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -78,7 +78,7 @@ impl Value {
|
|||
pub fn parse<K: JsonObjectParser + IntoProperty, V: JsonObjectParser + IntoValue>(
|
||||
token: Token<V>,
|
||||
parser: &mut Parser<'_>,
|
||||
) -> crate::parser::Result<Self> {
|
||||
) -> trc::Result<Self> {
|
||||
Ok(match token {
|
||||
Token::String(v) => v.into_value(),
|
||||
Token::DictStart => {
|
||||
|
@ -114,7 +114,7 @@ impl Value {
|
|||
pub fn from_property(
|
||||
parser: &mut Parser<'_>,
|
||||
property: &Property,
|
||||
) -> crate::parser::Result<Self> {
|
||||
) -> trc::Result<Self> {
|
||||
match &property {
|
||||
Property::BlobId => Ok(parser
|
||||
.next_token::<BlobId>()?
|
||||
|
@ -327,7 +327,7 @@ impl Value {
|
|||
}
|
||||
|
||||
impl<T: JsonObjectParser + Display + Eq> JsonObjectParser for SetValueMap<T> {
|
||||
fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
|
||||
fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -8,7 +8,6 @@ use std::fmt::Write;
|
|||
|
||||
use common::manager::webadmin::Resource;
|
||||
use directory::QueryBy;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use quick_xml::events::Event;
|
||||
use quick_xml::Reader;
|
||||
use utils::url_params::UrlParams;
|
||||
|
@ -18,22 +17,15 @@ use crate::{api::http::ToHttpResponse, JMAP};
|
|||
use super::{HttpRequest, HttpResponse};
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_autoconfig_request(&self, req: &HttpRequest) -> HttpResponse {
|
||||
pub async fn handle_autoconfig_request(&self, req: &HttpRequest) -> trc::Result<HttpResponse> {
|
||||
// Obtain parameters
|
||||
let params = UrlParams::new(req.uri().query());
|
||||
let emailaddress = params
|
||||
.get("emailaddress")
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
let (account_name, server_name, domain) =
|
||||
match self.autoconfig_parameters(&emailaddress).await {
|
||||
Ok(result) => result,
|
||||
Err(err) => return err.into_http_response(),
|
||||
};
|
||||
let services = match self.core.storage.config.get_services().await {
|
||||
Ok(services) => services,
|
||||
Err(err) => return err.into_http_response(),
|
||||
};
|
||||
let (account_name, server_name, domain) = self.autoconfig_parameters(&emailaddress).await?;
|
||||
let services = self.core.storage.config.get_services().await?;
|
||||
|
||||
// Build XML response
|
||||
let mut config = String::with_capacity(1024);
|
||||
|
@ -75,30 +67,27 @@ impl JMAP {
|
|||
);
|
||||
config.push_str("</clientConfig>\n");
|
||||
|
||||
Resource {
|
||||
Ok(Resource {
|
||||
content_type: "application/xml; charset=utf-8",
|
||||
contents: config.into_bytes(),
|
||||
}
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
}
|
||||
|
||||
pub async fn handle_autodiscover_request(&self, body: Option<Vec<u8>>) -> HttpResponse {
|
||||
pub async fn handle_autodiscover_request(
|
||||
&self,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
// Obtain parameters
|
||||
let emailaddress = match parse_autodiscover_request(body.as_deref().unwrap_or_default()) {
|
||||
Ok(emailaddress) => emailaddress,
|
||||
Err(err) => {
|
||||
return RequestError::blank(400, "Failed to parse autodiscover request", err)
|
||||
.into_http_response()
|
||||
}
|
||||
};
|
||||
let (account_name, server_name, _) = match self.autoconfig_parameters(&emailaddress).await {
|
||||
Ok(result) => result,
|
||||
Err(err) => return err.into_http_response(),
|
||||
};
|
||||
let services = match self.core.storage.config.get_services().await {
|
||||
Ok(services) => services,
|
||||
Err(err) => return err.into_http_response(),
|
||||
};
|
||||
let emailaddress = parse_autodiscover_request(body.as_deref().unwrap_or_default())
|
||||
.map_err(|err| {
|
||||
trc::ResourceCause::BadParameters
|
||||
.into_err()
|
||||
.details("Failed to parse autodiscover request")
|
||||
.ctx(trc::Key::Reason, err)
|
||||
})?;
|
||||
let (account_name, server_name, _) = self.autoconfig_parameters(&emailaddress).await?;
|
||||
let services = self.core.storage.config.get_services().await?;
|
||||
|
||||
// Build XML response
|
||||
let mut config = String::with_capacity(1024);
|
||||
|
@ -158,36 +147,35 @@ impl JMAP {
|
|||
let _ = writeln!(&mut config, "\t</Response>");
|
||||
let _ = writeln!(&mut config, "</Autodiscover>");
|
||||
|
||||
Resource {
|
||||
Ok(Resource {
|
||||
content_type: "application/xml; charset=utf-8",
|
||||
contents: config.into_bytes(),
|
||||
}
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
}
|
||||
|
||||
async fn autoconfig_parameters<'x>(
|
||||
&self,
|
||||
emailaddress: &'x str,
|
||||
) -> Result<(String, String, &'x str), RequestError> {
|
||||
let domain = if let Some((_, domain)) = emailaddress.rsplit_once('@') {
|
||||
domain
|
||||
} else {
|
||||
return Err(RequestError::invalid_parameters());
|
||||
};
|
||||
) -> trc::Result<(String, String, &'x str)> {
|
||||
let (_, domain) = emailaddress.rsplit_once('@').ok_or_else(|| {
|
||||
trc::ResourceCause::BadParameters
|
||||
.into_err()
|
||||
.details("Missing domain in email address")
|
||||
})?;
|
||||
|
||||
// Obtain server name
|
||||
let server_name = if let Ok(Some(server_name)) = self
|
||||
let server_name = self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.get("lookup.default.hostname")
|
||||
.await
|
||||
{
|
||||
server_name
|
||||
} else {
|
||||
tracing::error!("Autoconfig request failed: Server name not configured");
|
||||
return Err(RequestError::internal_server_error());
|
||||
};
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
trc::Cause::Configuration
|
||||
.into_err()
|
||||
.details("Server name not configured")
|
||||
})?;
|
||||
|
||||
// Find the account name by e-mail address
|
||||
let mut account_name = emailaddress.to_string();
|
||||
|
|
|
@ -14,12 +14,12 @@ use hyper::{
|
|||
body::{Bytes, Frame},
|
||||
header, StatusCode,
|
||||
};
|
||||
use jmap_proto::{error::request::RequestError, types::type_state::DataType};
|
||||
use jmap_proto::types::type_state::DataType;
|
||||
use utils::map::bitmap::Bitmap;
|
||||
|
||||
use crate::{auth::AccessToken, JMAP, LONG_SLUMBER};
|
||||
|
||||
use super::{http::ToHttpResponse, HttpRequest, HttpResponse, StateChangeResponse};
|
||||
use super::{HttpRequest, HttpResponse, StateChangeResponse};
|
||||
|
||||
struct Ping {
|
||||
interval: Duration,
|
||||
|
@ -32,7 +32,7 @@ impl JMAP {
|
|||
&self,
|
||||
req: HttpRequest,
|
||||
access_token: Arc<AccessToken>,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
// Parse query
|
||||
let mut ping = 0;
|
||||
let mut types = Bitmap::default();
|
||||
|
@ -49,7 +49,7 @@ impl JMAP {
|
|||
} else if let Ok(type_state) = DataType::try_from(type_state) {
|
||||
types.insert(type_state);
|
||||
} else {
|
||||
return RequestError::invalid_parameters().into_http_response();
|
||||
return Err(trc::ResourceCause::BadParameters.into_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,13 +58,13 @@ impl JMAP {
|
|||
close_after_state = true;
|
||||
}
|
||||
"no" => {}
|
||||
_ => return RequestError::invalid_parameters().into_http_response(),
|
||||
_ => return Err(trc::ResourceCause::BadParameters.into_err()),
|
||||
},
|
||||
"ping" => match value.parse::<u32>() {
|
||||
Ok(value) => {
|
||||
ping = value;
|
||||
}
|
||||
Err(_) => return RequestError::invalid_parameters().into_http_response(),
|
||||
Err(_) => return Err(trc::ResourceCause::BadParameters.into_err()),
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
@ -92,17 +92,11 @@ impl JMAP {
|
|||
let throttle = self.core.jmap.event_source_throttle;
|
||||
|
||||
// Register with state manager
|
||||
let mut change_rx = if let Ok(change_rx) = self
|
||||
let mut change_rx = self
|
||||
.subscribe_state_manager(access_token.primary_id(), types)
|
||||
.await
|
||||
{
|
||||
let todo = "return error";
|
||||
change_rx
|
||||
} else {
|
||||
return RequestError::internal_server_error().into_http_response();
|
||||
};
|
||||
.await?;
|
||||
|
||||
hyper::Response::builder()
|
||||
Ok(hyper::Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, "text/event-stream")
|
||||
.header(header::CACHE_CONTROL, "no-store")
|
||||
|
@ -160,6 +154,6 @@ impl JMAP {
|
|||
};
|
||||
}
|
||||
})))
|
||||
.unwrap()
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ use hyper::{
|
|||
};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use jmap_proto::{
|
||||
error::request::{RequestError, RequestLimitError},
|
||||
error::request::RequestError,
|
||||
request::{capability::Session, Request},
|
||||
response::Response,
|
||||
types::{blob::BlobId, id::Id},
|
||||
|
@ -51,28 +51,19 @@ impl JMAP {
|
|||
&self,
|
||||
mut req: HttpRequest,
|
||||
session: HttpSessionData,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
let mut path = req.uri().path().split('/');
|
||||
path.next();
|
||||
|
||||
match path.next().unwrap_or_default() {
|
||||
"jmap" => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
match self.authenticate_headers(&req, session.remote_ip).await {
|
||||
Ok(session) => session,
|
||||
Err(err) => {
|
||||
return if req.method() != Method::OPTIONS {
|
||||
err.into_http_response()
|
||||
} else {
|
||||
StatusCode::NO_CONTENT.into_http_response()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match (path.next().unwrap_or_default(), req.method()) {
|
||||
("", &Method::POST) => {
|
||||
return match fetch_body(
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, session.remote_ip).await?;
|
||||
|
||||
let request = fetch_body(
|
||||
&mut req,
|
||||
if !access_token.is_super_user() {
|
||||
self.core.jmap.upload_max_size
|
||||
|
@ -81,7 +72,7 @@ impl JMAP {
|
|||
},
|
||||
)
|
||||
.await
|
||||
.ok_or_else(|| RequestError::limit(RequestLimitError::SizeRequest))
|
||||
.ok_or_else(|| trc::LimitCause::SizeRequest.into_err())
|
||||
.and_then(|bytes| {
|
||||
//let c = println!("<- {}", String::from_utf8_lossy(&bytes));
|
||||
|
||||
|
@ -90,34 +81,25 @@ impl JMAP {
|
|||
self.core.jmap.request_max_calls,
|
||||
self.core.jmap.request_max_size,
|
||||
)
|
||||
}) {
|
||||
Ok(request) => {
|
||||
match self
|
||||
.handle_request(request, access_token, &session.instance)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
/*let c = println!(
|
||||
"-> {}",
|
||||
serde_json::to_string_pretty(&response).unwrap()
|
||||
);*/
|
||||
})?;
|
||||
|
||||
response.into_http_response()
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
};
|
||||
return Ok(self
|
||||
.handle_request(request, access_token, &session.instance)
|
||||
.await
|
||||
.into_http_response());
|
||||
}
|
||||
("download", &Method::GET) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, session.remote_ip).await?;
|
||||
|
||||
if let (Some(_), Some(blob_id), Some(name)) = (
|
||||
path.next().and_then(|p| Id::from_bytes(p.as_bytes())),
|
||||
path.next().and_then(BlobId::from_base32),
|
||||
path.next(),
|
||||
) {
|
||||
return match self.blob_download(&blob_id, &access_token).await {
|
||||
Ok(Some(blob)) => DownloadResponse {
|
||||
return match self.blob_download(&blob_id, &access_token).await? {
|
||||
Some(blob) => Ok(DownloadResponse {
|
||||
filename: name.to_string(),
|
||||
content_type: req
|
||||
.uri()
|
||||
|
@ -130,15 +112,16 @@ impl JMAP {
|
|||
.unwrap_or("application/octet-stream".to_string()),
|
||||
blob,
|
||||
}
|
||||
.into_http_response(),
|
||||
Ok(None) => RequestError::not_found().into_http_response(),
|
||||
Err(_) => {
|
||||
RequestError::internal_server_error().into_http_response()
|
||||
}
|
||||
.into_http_response()),
|
||||
None => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
};
|
||||
}
|
||||
}
|
||||
("upload", &Method::POST) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, session.remote_ip).await?;
|
||||
|
||||
if let Some(account_id) =
|
||||
path.next().and_then(|p| Id::from_bytes(p.as_bytes()))
|
||||
{
|
||||
|
@ -152,32 +135,34 @@ impl JMAP {
|
|||
)
|
||||
.await
|
||||
{
|
||||
Some(bytes) => {
|
||||
match self
|
||||
.blob_upload(
|
||||
account_id,
|
||||
req.headers()
|
||||
.get(CONTENT_TYPE)
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.unwrap_or("application/octet-stream"),
|
||||
&bytes,
|
||||
access_token,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => response.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
}
|
||||
None => RequestError::limit(RequestLimitError::SizeUpload)
|
||||
.into_http_response(),
|
||||
Some(bytes) => Ok(self
|
||||
.blob_upload(
|
||||
account_id,
|
||||
req.headers()
|
||||
.get(CONTENT_TYPE)
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.unwrap_or("application/octet-stream"),
|
||||
&bytes,
|
||||
access_token,
|
||||
)
|
||||
.await?
|
||||
.into_http_response()),
|
||||
None => Err(trc::LimitCause::SizeUpload.into_err()),
|
||||
};
|
||||
}
|
||||
}
|
||||
("eventsource", &Method::GET) => {
|
||||
return self.handle_event_source(req, access_token).await
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, session.remote_ip).await?;
|
||||
|
||||
return self.handle_event_source(req, access_token).await;
|
||||
}
|
||||
("ws", &Method::GET) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, session.remote_ip).await?;
|
||||
|
||||
return self
|
||||
.upgrade_websocket_connection(
|
||||
req,
|
||||
|
@ -187,7 +172,7 @@ impl JMAP {
|
|||
.await;
|
||||
}
|
||||
(_, &Method::OPTIONS) => {
|
||||
return StatusCode::NO_CONTENT.into_http_response();
|
||||
return Ok(StatusCode::NO_CONTENT.into_http_response());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
@ -196,31 +181,24 @@ impl JMAP {
|
|||
("jmap", &Method::GET) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
match self.authenticate_headers(&req, session.remote_ip).await {
|
||||
Ok(session) => session,
|
||||
Err(err) => return err.into_http_response(),
|
||||
};
|
||||
self.authenticate_headers(&req, session.remote_ip).await?;
|
||||
|
||||
return match self
|
||||
return Ok(self
|
||||
.handle_session_resource(
|
||||
session.resolve_url(&self.core).await,
|
||||
access_token,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(session) => session.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
};
|
||||
.await?
|
||||
.into_http_response());
|
||||
}
|
||||
("oauth-authorization-server", &Method::GET) => {
|
||||
// Limit anonymous requests
|
||||
return match self.is_anonymous_allowed(&session.remote_ip).await {
|
||||
Ok(_) => JsonResponse::new(OAuthMetadata::new(
|
||||
session.resolve_url(&self.core).await,
|
||||
))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
};
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
|
||||
return Ok(JsonResponse::new(OAuthMetadata::new(
|
||||
session.resolve_url(&self.core).await,
|
||||
))
|
||||
.into_http_response());
|
||||
}
|
||||
("acme-challenge", &Method::GET) if self.core.has_acme_http_providers() => {
|
||||
if let Some(token) = path.next() {
|
||||
|
@ -229,27 +207,26 @@ impl JMAP {
|
|||
.storage
|
||||
.lookup
|
||||
.key_get::<String>(format!("acme:{token}").into_bytes())
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
Ok(Some(proof)) => Resource {
|
||||
Some(proof) => Ok(Resource {
|
||||
content_type: "text/plain",
|
||||
contents: proof.into_bytes(),
|
||||
}
|
||||
.into_http_response(),
|
||||
Ok(None) => RequestError::not_found().into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
.into_http_response()),
|
||||
None => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
};
|
||||
}
|
||||
}
|
||||
("mta-sts.txt", &Method::GET) => {
|
||||
if let Some(policy) = self.core.build_mta_sts_policy() {
|
||||
return Resource {
|
||||
return Ok(Resource {
|
||||
content_type: "text/plain",
|
||||
contents: policy.to_string().into_bytes(),
|
||||
}
|
||||
.into_http_response();
|
||||
.into_http_response());
|
||||
} else {
|
||||
return RequestError::not_found().into_http_response();
|
||||
return Err(trc::ResourceCause::NotFound.into_err());
|
||||
}
|
||||
}
|
||||
("mail-v1.xml", &Method::GET) => {
|
||||
|
@ -263,46 +240,40 @@ impl JMAP {
|
|||
}
|
||||
}
|
||||
(_, &Method::OPTIONS) => {
|
||||
return StatusCode::NO_CONTENT.into_http_response();
|
||||
return Ok(StatusCode::NO_CONTENT.into_http_response());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
"auth" => match (path.next().unwrap_or_default(), req.method()) {
|
||||
("device", &Method::POST) => {
|
||||
return match self.is_anonymous_allowed(&session.remote_ip).await {
|
||||
Ok(_) => {
|
||||
self.handle_device_auth(&mut req, session.resolve_url(&self.core).await)
|
||||
.await
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
|
||||
return self
|
||||
.handle_device_auth(&mut req, session.resolve_url(&self.core).await)
|
||||
.await;
|
||||
}
|
||||
("token", &Method::POST) => {
|
||||
return match self.is_anonymous_allowed(&session.remote_ip).await {
|
||||
Ok(_) => self.handle_token_request(&mut req).await,
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
|
||||
return self.handle_token_request(&mut req).await;
|
||||
}
|
||||
(_, &Method::OPTIONS) => {
|
||||
return StatusCode::NO_CONTENT.into_http_response();
|
||||
return Ok(StatusCode::NO_CONTENT.into_http_response());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
"api" => {
|
||||
// Allow CORS preflight requests
|
||||
if req.method() == Method::OPTIONS {
|
||||
return StatusCode::NO_CONTENT.into_http_response();
|
||||
return Ok(StatusCode::NO_CONTENT.into_http_response());
|
||||
}
|
||||
|
||||
// Authenticate user
|
||||
return match self.authenticate_headers(&req, session.remote_ip).await {
|
||||
Ok((_, access_token)) => {
|
||||
let body = fetch_body(&mut req, 1024 * 1024).await;
|
||||
self.handle_api_manage_request(&req, body, access_token)
|
||||
.await
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
};
|
||||
let (_, access_token) = self.authenticate_headers(&req, session.remote_ip).await?;
|
||||
let body = fetch_body(&mut req, 1024 * 1024).await;
|
||||
return self
|
||||
.handle_api_manage_request(&req, body, access_token)
|
||||
.await;
|
||||
}
|
||||
"mail" => {
|
||||
if req.method() == Method::GET
|
||||
|
@ -321,43 +292,45 @@ impl JMAP {
|
|||
}
|
||||
}
|
||||
"robots.txt" => {
|
||||
return Resource {
|
||||
return Ok(Resource {
|
||||
content_type: "text/plain",
|
||||
contents: b"User-agent: *\nDisallow: /\n".to_vec(),
|
||||
}
|
||||
.into_http_response();
|
||||
.into_http_response());
|
||||
}
|
||||
"healthz" => match path.next().unwrap_or_default() {
|
||||
"live" => {
|
||||
return StatusCode::OK.into_http_response();
|
||||
return Ok(StatusCode::OK.into_http_response());
|
||||
}
|
||||
"ready" => {
|
||||
return {
|
||||
return Ok({
|
||||
if !self.core.storage.data.is_none() {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
}
|
||||
}
|
||||
.into_http_response();
|
||||
.into_http_response());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => {
|
||||
let path = req.uri().path();
|
||||
return match self
|
||||
let resource = self
|
||||
.inner
|
||||
.webadmin
|
||||
.get(path.strip_prefix('/').unwrap_or(path))
|
||||
.await
|
||||
{
|
||||
Ok(resource) if !resource.is_empty() => resource.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
.await?;
|
||||
|
||||
return if !resource.is_empty() {
|
||||
Ok(resource.into_http_response())
|
||||
} else {
|
||||
Err(trc::ResourceCause::NotFound.into_err())
|
||||
};
|
||||
}
|
||||
}
|
||||
RequestError::not_found().into_http_response()
|
||||
|
||||
Err(trc::ResourceCause::NotFound.into_err())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -403,7 +376,7 @@ impl JmapInstance {
|
|||
};
|
||||
|
||||
// Parse HTTP request
|
||||
let mut response = jmap
|
||||
let mut response = match jmap
|
||||
.parse_http_request(
|
||||
req,
|
||||
HttpSessionData {
|
||||
|
@ -415,7 +388,19 @@ impl JmapInstance {
|
|||
is_tls,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
{
|
||||
Ok(response) => response,
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
parent: &span,
|
||||
event = "error",
|
||||
context = "http",
|
||||
reason = %err,
|
||||
);
|
||||
err.into_http_response()
|
||||
}
|
||||
};
|
||||
|
||||
// Add custom headers
|
||||
if !jmap.core.jmap.http_headers.is_empty() {
|
||||
|
@ -534,7 +519,7 @@ impl ToHttpResponse for trc::Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToHttpResponse for std::io::Error {
|
||||
/*impl ToHttpResponse for std::io::Error {
|
||||
fn into_http_response(self) -> HttpResponse {
|
||||
tracing::error!(context = "i/o", error = %self, "I/O error");
|
||||
|
||||
|
@ -551,7 +536,7 @@ impl ToHttpResponse for serde_json::Error {
|
|||
)
|
||||
.into_http_response()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
impl<T: serde::Serialize> JsonResponse<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use common::config::smtp::auth::simple_pem_parse;
|
||||
use directory::backend::internal::manage;
|
||||
use hyper::Method;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use mail_auth::{
|
||||
common::crypto::{Ed25519Key, RsaKey, Sha256},
|
||||
dkim::generate::DkimKeyPair,
|
||||
|
@ -22,10 +22,7 @@ use serde_json::json;
|
|||
use store::write::now;
|
||||
|
||||
use crate::{
|
||||
api::{
|
||||
http::ToHttpResponse, management::ManagementApiError, HttpRequest, HttpResponse,
|
||||
JsonResponse,
|
||||
},
|
||||
api::{http::ToHttpResponse, HttpRequest, HttpResponse, JsonResponse},
|
||||
JMAP,
|
||||
};
|
||||
|
||||
|
@ -51,22 +48,21 @@ impl JMAP {
|
|||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
match *req.method() {
|
||||
Method::GET => self.handle_get_public_key(path).await,
|
||||
Method::POST => self.handle_create_signature(body).await,
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_get_public_key(&self, path: Vec<&str>) -> HttpResponse {
|
||||
async fn handle_get_public_key(&self, path: Vec<&str>) -> trc::Result<HttpResponse> {
|
||||
let signature_id = match path.get(1) {
|
||||
Some(signature_id) => decode_path_element(signature_id),
|
||||
None => {
|
||||
return RequestError::not_found().into_http_response();
|
||||
return Err(trc::ResourceCause::NotFound.into_err());
|
||||
}
|
||||
};
|
||||
let todo = "bubble up error and log them";
|
||||
|
||||
let (pk, algo) = match (
|
||||
self.core
|
||||
|
@ -82,27 +78,26 @@ impl JMAP {
|
|||
.map(|algo| algo.and_then(|algo| algo.parse::<Algorithm>().ok())),
|
||||
) {
|
||||
(Ok(Some(pk)), Ok(Some(algorithm))) => (pk, algorithm),
|
||||
(Err(err), _) | (_, Err(err)) => return err.into_http_response(),
|
||||
_ => return RequestError::not_found().into_http_response(),
|
||||
(Err(err), _) | (_, Err(err)) => return Err(err.caused_by(trc::location!())),
|
||||
_ => return Err(trc::ResourceCause::NotFound.into_err()),
|
||||
};
|
||||
|
||||
match obtain_dkim_public_key(algo, &pk) {
|
||||
Ok(data) => JsonResponse::new(json!({
|
||||
Ok(data) => Ok(JsonResponse::new(json!({
|
||||
"data": data,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => ManagementApiError::Other {
|
||||
details: err.into(),
|
||||
}
|
||||
.into_http_response(),
|
||||
.into_http_response()),
|
||||
Err(details) => Err(manage::error(details, None::<u32>)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_create_signature(&self, body: Option<Vec<u8>>) -> HttpResponse {
|
||||
async fn handle_create_signature(&self, body: Option<Vec<u8>>) -> trc::Result<HttpResponse> {
|
||||
let request =
|
||||
match serde_json::from_slice::<DkimSignature>(body.as_deref().unwrap_or_default()) {
|
||||
Ok(request) => request,
|
||||
Err(err) => return err.into_http_response(),
|
||||
Err(err) => {
|
||||
return Err(trc::Cause::Resource(trc::ResourceCause::BadParameters).reason(err))
|
||||
}
|
||||
};
|
||||
|
||||
let algo_str = match request.algorithm {
|
||||
|
@ -127,35 +122,27 @@ impl JMAP {
|
|||
});
|
||||
|
||||
// Make sure the signature does not exist already
|
||||
match self
|
||||
if let Some(value) = self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.get(&format!("signature.{id}.private-key"))
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
Ok(None) => (),
|
||||
Ok(Some(value)) => {
|
||||
return ManagementApiError::FieldAlreadyExists {
|
||||
field: format!("signature.{id}.private-key").into(),
|
||||
value: value.into(),
|
||||
}
|
||||
.into_http_response();
|
||||
}
|
||||
Err(err) => return err.into_http_response(),
|
||||
return Err(manage::err_exists(
|
||||
format!("signature.{id}.private-key"),
|
||||
value,
|
||||
));
|
||||
}
|
||||
|
||||
// Create signature
|
||||
match self
|
||||
.create_dkim_key(request.algorithm, id, request.domain, selector)
|
||||
.await
|
||||
{
|
||||
Ok(_) => JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
self.create_dkim_key(request.algorithm, id, request.domain, selector)
|
||||
.await?;
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
|
||||
async fn create_dkim_key(
|
||||
|
@ -177,7 +164,10 @@ impl JMAP {
|
|||
Algorithm::Rsa => DkimKeyPair::generate_rsa(2048),
|
||||
Algorithm::Ed25519 => DkimKeyPair::generate_ed25519(),
|
||||
}
|
||||
.map_err(|err| trc::Cause::Crypto.reason(err).caused_by(trc::location!()))?
|
||||
.map_err(|err| {
|
||||
manage::error("Failed to generate key", err.to_string().into())
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
.private_key(),
|
||||
)
|
||||
.unwrap_or_default()
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
use directory::backend::internal::manage::ManageDirectory;
|
||||
|
||||
use hyper::Method;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sha1::Digest;
|
||||
|
@ -35,7 +34,11 @@ struct DnsRecord {
|
|||
}
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_manage_domain(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
|
||||
pub async fn handle_manage_domain(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
match (path.get(1), req.method()) {
|
||||
(None, &Method::GET) => {
|
||||
// List domains
|
||||
|
@ -44,82 +47,78 @@ impl JMAP {
|
|||
let page: usize = params.parse("page").unwrap_or(0);
|
||||
let limit: usize = params.parse("limit").unwrap_or(0);
|
||||
|
||||
match self.core.storage.data.list_domains(filter).await {
|
||||
Ok(domains) => {
|
||||
let (total, domains) = if limit > 0 {
|
||||
let offset = page.saturating_sub(1) * limit;
|
||||
(
|
||||
domains.len(),
|
||||
domains.into_iter().skip(offset).take(limit).collect(),
|
||||
)
|
||||
} else {
|
||||
(domains.len(), domains)
|
||||
};
|
||||
let domains = self.core.storage.data.list_domains(filter).await?;
|
||||
let (total, domains) = if limit > 0 {
|
||||
let offset = page.saturating_sub(1) * limit;
|
||||
(
|
||||
domains.len(),
|
||||
domains.into_iter().skip(offset).take(limit).collect(),
|
||||
)
|
||||
} else {
|
||||
(domains.len(), domains)
|
||||
};
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": {
|
||||
"items": domains,
|
||||
"total": total,
|
||||
},
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": {
|
||||
"items": domains,
|
||||
"total": total,
|
||||
},
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(Some(domain), &Method::GET) => {
|
||||
// Obtain DNS records
|
||||
let domain = decode_path_element(domain);
|
||||
match self.build_dns_records(domain.as_ref()).await {
|
||||
Ok(records) => JsonResponse::new(json!({
|
||||
"data": records,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": self.build_dns_records(domain.as_ref()).await?,
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(Some(domain), &Method::POST) => {
|
||||
// Create domain
|
||||
let domain = decode_path_element(domain);
|
||||
match self.core.storage.data.create_domain(domain.as_ref()).await {
|
||||
Ok(_) => {
|
||||
// Set default domain name if missing
|
||||
if matches!(
|
||||
self.core.storage.config.get("lookup.default.domain").await,
|
||||
Ok(None)
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.set([("lookup.default.domain", domain.as_ref())])
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to set default domain name: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.create_domain(domain.as_ref())
|
||||
.await?;
|
||||
// Set default domain name if missing
|
||||
if self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.get("lookup.default.domain")
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
self.core
|
||||
.storage
|
||||
.config
|
||||
.set([("lookup.default.domain", domain.as_ref())])
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(Some(domain), &Method::DELETE) => {
|
||||
// Delete domain
|
||||
let domain = decode_path_element(domain);
|
||||
match self.core.storage.data.delete_domain(domain.as_ref()).await {
|
||||
Ok(_) => JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.delete_domain(domain.as_ref())
|
||||
.await?;
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,10 +14,11 @@ use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
|||
use common::enterprise::undelete::DeletedBlob;
|
||||
use directory::backend::internal::manage::ManageDirectory;
|
||||
use hyper::Method;
|
||||
use jmap_proto::{error::request::RequestError, types::collection::Collection};
|
||||
use jmap_proto::types::collection::Collection;
|
||||
use mail_parser::{DateTime, MessageParser};
|
||||
use serde_json::json;
|
||||
use store::write::{BatchBuilder, BlobOp, ValueClass};
|
||||
use trc::AddContext;
|
||||
use utils::{url_params::UrlParams, BlobHash};
|
||||
|
||||
use crate::{
|
||||
|
@ -53,10 +54,10 @@ impl JMAP {
|
|||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
match path.get(1).copied().unwrap_or_default() {
|
||||
"undelete" => self.handle_undelete_api_request(req, path, body).await,
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,72 +66,76 @@ impl JMAP {
|
|||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
match (path.get(2).copied(), req.method()) {
|
||||
(Some(account_name), &Method::GET) => {
|
||||
match self.core.storage.data.get_account_id(account_name).await {
|
||||
Ok(Some(account_id)) => match self.core.list_deleted(account_id).await {
|
||||
Ok(mut deleted) => {
|
||||
let params = UrlParams::new(req.uri().query());
|
||||
let limit = params.parse::<usize>("limit").unwrap_or_default();
|
||||
let mut offset = params
|
||||
.parse::<usize>("page")
|
||||
.unwrap_or_default()
|
||||
.saturating_sub(1)
|
||||
* limit;
|
||||
let account_id = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.get_account_id(account_name)
|
||||
.await?
|
||||
.ok_or_else(|| trc::ResourceCause::NotFound.into_err())?;
|
||||
let mut deleted = self.core.list_deleted(account_id).await?;
|
||||
|
||||
// Sort ascending by deleted_at
|
||||
let total = deleted.len();
|
||||
deleted.sort_by(|a, b| a.deleted_at.cmp(&b.deleted_at));
|
||||
let mut results =
|
||||
Vec::with_capacity(if limit > 0 { limit } else { total });
|
||||
let params = UrlParams::new(req.uri().query());
|
||||
let limit = params.parse::<usize>("limit").unwrap_or_default();
|
||||
let mut offset = params
|
||||
.parse::<usize>("page")
|
||||
.unwrap_or_default()
|
||||
.saturating_sub(1)
|
||||
* limit;
|
||||
|
||||
for blob in deleted {
|
||||
if offset == 0 {
|
||||
results.push(DeletedBlob {
|
||||
hash: URL_SAFE_NO_PAD.encode(blob.hash.as_slice()),
|
||||
size: blob.size,
|
||||
deleted_at: DateTime::from_timestamp(
|
||||
blob.deleted_at as i64,
|
||||
)
|
||||
.to_rfc3339(),
|
||||
expires_at: DateTime::from_timestamp(
|
||||
blob.expires_at as i64,
|
||||
)
|
||||
.to_rfc3339(),
|
||||
collection: Collection::from(blob.collection).to_string(),
|
||||
});
|
||||
if results.len() == limit {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
offset -= 1;
|
||||
}
|
||||
}
|
||||
// Sort ascending by deleted_at
|
||||
let total = deleted.len();
|
||||
deleted.sort_by(|a, b| a.deleted_at.cmp(&b.deleted_at));
|
||||
let mut results = Vec::with_capacity(if limit > 0 { limit } else { total });
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data":{
|
||||
"items": results,
|
||||
"total": total,
|
||||
},
|
||||
}))
|
||||
.into_http_response()
|
||||
for blob in deleted {
|
||||
if offset == 0 {
|
||||
results.push(DeletedBlob {
|
||||
hash: URL_SAFE_NO_PAD.encode(blob.hash.as_slice()),
|
||||
size: blob.size,
|
||||
deleted_at: DateTime::from_timestamp(blob.deleted_at as i64)
|
||||
.to_rfc3339(),
|
||||
expires_at: DateTime::from_timestamp(blob.expires_at as i64)
|
||||
.to_rfc3339(),
|
||||
collection: Collection::from(blob.collection).to_string(),
|
||||
});
|
||||
if results.len() == limit {
|
||||
break;
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
},
|
||||
Ok(None) => RequestError::not_found().into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
} else {
|
||||
offset -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data":{
|
||||
"items": results,
|
||||
"total": total,
|
||||
},
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(Some(account_name), &Method::POST) => {
|
||||
match self.core.storage.data.get_account_id(account_name).await {
|
||||
Ok(Some(account_id)) => {
|
||||
match serde_json::from_slice::<Vec<UndeleteRequest<String, String, String>>>(
|
||||
body.as_deref().unwrap_or_default(),
|
||||
)
|
||||
.ok()
|
||||
.and_then(|request| {
|
||||
request.into_iter().map(|request| {
|
||||
let account_id = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.get_account_id(account_name)
|
||||
.await?
|
||||
.ok_or_else(|| trc::ResourceCause::NotFound.into_err())?;
|
||||
|
||||
let requests =
|
||||
serde_json::from_slice::<Vec<UndeleteRequest<String, String, String>>>(
|
||||
body.as_deref().unwrap_or_default(),
|
||||
)
|
||||
.ok()
|
||||
.and_then(|request| {
|
||||
request
|
||||
.into_iter()
|
||||
.map(|request| {
|
||||
UndeleteRequest {
|
||||
hash: BlobHash::try_from_hash_slice(
|
||||
URL_SAFE_NO_PAD
|
||||
|
@ -139,99 +144,100 @@ impl JMAP {
|
|||
.as_slice(),
|
||||
)
|
||||
.ok()?,
|
||||
collection: Collection::from_str(
|
||||
request.collection.as_str(),
|
||||
)
|
||||
.ok()?,
|
||||
collection: Collection::from_str(request.collection.as_str())
|
||||
.ok()?,
|
||||
time: DateTime::parse_rfc3339(request.time.as_str())?
|
||||
.to_timestamp(),
|
||||
cancel_deletion: if let Some(cancel_deletion) = request.cancel_deletion {
|
||||
cancel_deletion: if let Some(cancel_deletion) =
|
||||
request.cancel_deletion
|
||||
{
|
||||
DateTime::parse_rfc3339(cancel_deletion.as_str())?
|
||||
.to_timestamp().into()
|
||||
.to_timestamp()
|
||||
.into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
.into()
|
||||
}).collect::<Option<Vec<_>>>()
|
||||
}) {
|
||||
Some(requests) => {
|
||||
let mut results = Vec::with_capacity(requests.len());
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.with_account_id(account_id);
|
||||
for request in requests {
|
||||
match request.collection {
|
||||
Collection::Email => {
|
||||
match self.get_blob(&request.hash, 0..usize::MAX).await
|
||||
{
|
||||
Ok(Some(bytes)) => {
|
||||
match self
|
||||
.email_ingest(IngestEmail {
|
||||
raw_message: &bytes,
|
||||
message: MessageParser::new().parse(&bytes),
|
||||
account_id,
|
||||
account_quota: 0,
|
||||
mailbox_ids: vec![INBOX_ID],
|
||||
keywords: vec![],
|
||||
received_at: (request.time as u64).into(),
|
||||
source: IngestSource::Smtp,
|
||||
encrypt: false,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
results.push(UndeleteResponse::Success);
|
||||
if let Some(cancel_deletion) = request.cancel_deletion {
|
||||
batch.clear(ValueClass::Blob(BlobOp::Reserve { hash: request.hash, until: cancel_deletion as u64 }));
|
||||
}
|
||||
},
|
||||
Err(mut err) if err.matches(trc::Cause::Ingest) => {
|
||||
results.push(UndeleteResponse::Error { reason: err.take_value(trc::Key::Reason)
|
||||
.and_then(|v| v.into_string())
|
||||
.unwrap().into_owned() });
|
||||
}
|
||||
Err(_) => {
|
||||
return RequestError::internal_server_error().into_http_response();
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
results.push(UndeleteResponse::NotFound);
|
||||
},
|
||||
Err(_) => {
|
||||
return RequestError::internal_server_error().into_http_response();
|
||||
},
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()
|
||||
})
|
||||
.ok_or_else(|| trc::ResourceCause::BadParameters.into_err())?;
|
||||
|
||||
let mut results = Vec::with_capacity(requests.len());
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.with_account_id(account_id);
|
||||
for request in requests {
|
||||
match request.collection {
|
||||
Collection::Email => {
|
||||
match self.get_blob(&request.hash, 0..usize::MAX).await? {
|
||||
Some(bytes) => {
|
||||
match self
|
||||
.email_ingest(IngestEmail {
|
||||
raw_message: &bytes,
|
||||
message: MessageParser::new().parse(&bytes),
|
||||
account_id,
|
||||
account_quota: 0,
|
||||
mailbox_ids: vec![INBOX_ID],
|
||||
keywords: vec![],
|
||||
received_at: (request.time as u64).into(),
|
||||
source: IngestSource::Smtp,
|
||||
encrypt: false,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
results.push(UndeleteResponse::Success);
|
||||
if let Some(cancel_deletion) = request.cancel_deletion {
|
||||
batch.clear(ValueClass::Blob(BlobOp::Reserve {
|
||||
hash: request.hash,
|
||||
until: cancel_deletion as u64,
|
||||
}));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
Err(mut err) if err.matches(trc::Cause::Ingest) => {
|
||||
results.push(UndeleteResponse::Error {
|
||||
reason: "Unsupported collection".to_string(),
|
||||
reason: err
|
||||
.take_value(trc::Key::Reason)
|
||||
.and_then(|v| v.into_string())
|
||||
.unwrap()
|
||||
.into_owned(),
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err.caused_by(trc::location!()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commit batch
|
||||
if !batch.is_empty() {
|
||||
match self.core.storage.data.write(batch.build()).await {
|
||||
Ok(_) => (),
|
||||
Err(err) => return err.into_http_response(),
|
||||
}
|
||||
None => {
|
||||
results.push(UndeleteResponse::NotFound);
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": results,
|
||||
}))
|
||||
.into_http_response()
|
||||
},
|
||||
None => RequestError::invalid_parameters().into_http_response(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
results.push(UndeleteResponse::Error {
|
||||
reason: "Unsupported collection".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(None) => RequestError::not_found().into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
|
||||
// Commit batch
|
||||
if !batch.is_empty() {
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.write(batch.build())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": results,
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::{
|
|||
};
|
||||
|
||||
use chrono::DateTime;
|
||||
use directory::backend::internal::manage;
|
||||
use rev_lines::RevLines;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
@ -16,8 +17,6 @@ use crate::{
|
|||
JMAP,
|
||||
};
|
||||
|
||||
use super::ManagementApiError;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LogEntry {
|
||||
timestamp: String,
|
||||
|
@ -26,18 +25,15 @@ struct LogEntry {
|
|||
}
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_view_logs(&self, req: &HttpRequest) -> HttpResponse {
|
||||
pub async fn handle_view_logs(&self, req: &HttpRequest) -> trc::Result<HttpResponse> {
|
||||
// Obtain log file path
|
||||
let path = match self.core.storage.config.get("tracer.log.path").await {
|
||||
Ok(Some(path)) => path,
|
||||
Ok(None) => {
|
||||
return ManagementApiError::Unsupported {
|
||||
details: "Tracer log path not configured".into(),
|
||||
}
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => return err.into_http_response(),
|
||||
};
|
||||
let path = self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.get("tracer.log.path")
|
||||
.await?
|
||||
.ok_or_else(|| manage::unsupported("Tracer log path not configured"))?;
|
||||
|
||||
let params = UrlParams::new(req.uri().query());
|
||||
let filter = params.get("filter").unwrap_or_default().to_string();
|
||||
|
@ -51,25 +47,23 @@ impl JMAP {
|
|||
let _ = tx.send(read_log_files(path, &filter, offset, limit));
|
||||
});
|
||||
|
||||
match rx.await {
|
||||
Ok(result) => match result {
|
||||
Ok((total, items)) => JsonResponse::new(json!({
|
||||
"data": {
|
||||
"items": items,
|
||||
"total": total,
|
||||
},
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
let (total, items) = rx
|
||||
.await
|
||||
.map_err(|err| trc::Cause::Thread.reason(err).caused_by(trc::location!()))?
|
||||
.map_err(|err| {
|
||||
trc::ManageCause::Error
|
||||
.reason(err)
|
||||
.details("Failed to read log files")
|
||||
.caused_by(trc::location!())
|
||||
})?;
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": {
|
||||
"items": items,
|
||||
"total": total,
|
||||
},
|
||||
Err(_) => {
|
||||
tracing::warn!(context = "view_logs", event = "error", "Thread join error");
|
||||
ManagementApiError::Other {
|
||||
details: "Thread join error".into(),
|
||||
}
|
||||
.into_http_response()
|
||||
}
|
||||
}
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@ pub mod stores;
|
|||
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use directory::backend::internal::manage;
|
||||
use hyper::Method;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{http::ToHttpResponse, HttpRequest, HttpResponse, JsonResponse};
|
||||
use super::{HttpRequest, HttpResponse};
|
||||
use crate::{auth::AccessToken, JMAP};
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
@ -57,7 +57,7 @@ impl JMAP {
|
|||
req: &HttpRequest,
|
||||
body: Option<Vec<u8>>,
|
||||
access_token: Arc<AccessToken>,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
let path = req.uri().path().split('/').skip(2).collect::<Vec<_>>();
|
||||
let is_superuser = access_token.is_super_user();
|
||||
|
||||
|
@ -76,10 +76,7 @@ impl JMAP {
|
|||
}
|
||||
"sieve" if is_superuser => self.handle_run_sieve(req, path, body).await,
|
||||
"restart" if is_superuser && req.method() == Method::GET => {
|
||||
ManagementApiError::Unsupported {
|
||||
details: "Restart is not yet supported".into(),
|
||||
}
|
||||
.into_http_response()
|
||||
Err(manage::unsupported("Restart is not yet supported"))
|
||||
}
|
||||
"oauth" => self.handle_oauth_api_request(access_token, body).await,
|
||||
"account" => match (path.get(1).copied().unwrap_or_default(), req.method()) {
|
||||
|
@ -89,7 +86,7 @@ impl JMAP {
|
|||
("auth", &Method::POST) => {
|
||||
self.handle_account_auth_post(req, access_token, body).await
|
||||
}
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
},
|
||||
|
||||
// SPDX-SnippetBegin
|
||||
|
@ -109,34 +106,13 @@ impl JMAP {
|
|||
if self.core.is_enterprise_edition() {
|
||||
self.handle_enterprise_api_request(req, path, body).await
|
||||
} else {
|
||||
ManagementApiError::Unsupported {
|
||||
details: "This feature is only available in the Enterprise version".into(),
|
||||
}
|
||||
.into_http_response()
|
||||
Err(manage::unsupported(
|
||||
"This feature is only available in the Enterprise version",
|
||||
))
|
||||
}
|
||||
}
|
||||
// SPDX-SnippetEnd
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHttpResponse for ManagementApiError {
|
||||
fn into_http_response(self) -> super::HttpResponse {
|
||||
JsonResponse::new(self).into_http_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for ManagementApiError {
|
||||
fn from(details: Cow<'static, str>) -> Self {
|
||||
ManagementApiError::Other { details }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ManagementApiError {
|
||||
fn from(details: String) -> Self {
|
||||
ManagementApiError::Other {
|
||||
details: details.into(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,14 @@ use std::sync::Arc;
|
|||
|
||||
use directory::{
|
||||
backend::internal::{
|
||||
lookup::DirectoryStore, manage::ManageDirectory, PrincipalAction, PrincipalField,
|
||||
PrincipalUpdate, PrincipalValue, SpecialSecrets,
|
||||
lookup::DirectoryStore,
|
||||
manage::{self, ManageDirectory},
|
||||
PrincipalAction, PrincipalField, PrincipalUpdate, PrincipalValue, SpecialSecrets,
|
||||
},
|
||||
DirectoryInner, Principal, QueryBy, Type,
|
||||
};
|
||||
|
||||
use hyper::{header, Method, StatusCode};
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use hyper::{header, Method};
|
||||
use serde_json::json;
|
||||
use utils::url_params::UrlParams;
|
||||
|
||||
|
@ -25,7 +25,7 @@ use crate::{
|
|||
JMAP,
|
||||
};
|
||||
|
||||
use super::{decode_path_element, ManagementApiError};
|
||||
use super::decode_path_element;
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PrincipalResponse {
|
||||
|
@ -80,47 +80,41 @@ impl JMAP {
|
|||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
match (path.get(1), req.method()) {
|
||||
(None, &Method::POST) => {
|
||||
// Make sure the current directory supports updates
|
||||
if let Some(response) = self.assert_supported_directory() {
|
||||
return response;
|
||||
}
|
||||
self.assert_supported_directory()?;
|
||||
|
||||
// Create principal
|
||||
match serde_json::from_slice::<PrincipalResponse>(
|
||||
let principal = serde_json::from_slice::<PrincipalResponse>(
|
||||
body.as_deref().unwrap_or_default(),
|
||||
) {
|
||||
Ok(principal) => {
|
||||
match self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.create_account(
|
||||
Principal {
|
||||
id: principal.id,
|
||||
typ: principal.typ,
|
||||
quota: principal.quota,
|
||||
name: principal.name,
|
||||
secrets: principal.secrets,
|
||||
emails: principal.emails,
|
||||
member_of: principal.member_of,
|
||||
description: principal.description,
|
||||
},
|
||||
principal.members,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(account_id) => JsonResponse::new(json!({
|
||||
"data": account_id,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => into_directory_response(err),
|
||||
}
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
)
|
||||
.map_err(|err| {
|
||||
trc::Cause::Resource(trc::ResourceCause::BadParameters).from_json_error(err)
|
||||
})?;
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.create_account(
|
||||
Principal {
|
||||
id: principal.id,
|
||||
typ: principal.typ,
|
||||
quota: principal.quota,
|
||||
name: principal.name,
|
||||
secrets: principal.secrets,
|
||||
emails: principal.emails,
|
||||
member_of: principal.member_of,
|
||||
description: principal.description,
|
||||
},
|
||||
principal.members,
|
||||
)
|
||||
.await?,
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(None, &Method::GET) => {
|
||||
// List principal ids
|
||||
|
@ -130,187 +124,137 @@ impl JMAP {
|
|||
let page: usize = params.parse("page").unwrap_or(0);
|
||||
let limit: usize = params.parse("limit").unwrap_or(0);
|
||||
|
||||
match self.core.storage.data.list_accounts(filter, typ).await {
|
||||
Ok(accounts) => {
|
||||
let (total, accounts) = if limit > 0 {
|
||||
let offset = page.saturating_sub(1) * limit;
|
||||
(
|
||||
accounts.len(),
|
||||
accounts.into_iter().skip(offset).take(limit).collect(),
|
||||
)
|
||||
} else {
|
||||
(accounts.len(), accounts)
|
||||
};
|
||||
let accounts = self.core.storage.data.list_accounts(filter, typ).await?;
|
||||
let (total, accounts) = if limit > 0 {
|
||||
let offset = page.saturating_sub(1) * limit;
|
||||
(
|
||||
accounts.len(),
|
||||
accounts.into_iter().skip(offset).take(limit).collect(),
|
||||
)
|
||||
} else {
|
||||
(accounts.len(), accounts)
|
||||
};
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": {
|
||||
"items": accounts,
|
||||
"total": total,
|
||||
},
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => into_directory_response(err),
|
||||
}
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": {
|
||||
"items": accounts,
|
||||
"total": total,
|
||||
},
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(Some(name), method) => {
|
||||
// Fetch, update or delete principal
|
||||
let name = decode_path_element(name);
|
||||
let account_id = match self.core.storage.data.get_account_id(name.as_ref()).await {
|
||||
Ok(Some(account_id)) => account_id,
|
||||
Ok(None) => {
|
||||
return RequestError::blank(
|
||||
StatusCode::NOT_FOUND.as_u16(),
|
||||
"Not found",
|
||||
"Account not found.",
|
||||
)
|
||||
.into_http_response();
|
||||
}
|
||||
Err(err) => {
|
||||
return into_directory_response(err);
|
||||
}
|
||||
};
|
||||
let account_id = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.get_account_id(name.as_ref())
|
||||
.await?
|
||||
.ok_or_else(|| trc::ManageCause::NotFound.into_err())?;
|
||||
|
||||
match *method {
|
||||
Method::GET => {
|
||||
let result = match self
|
||||
let principal = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.query(QueryBy::Id(account_id), true)
|
||||
.await
|
||||
{
|
||||
Ok(Some(principal)) => {
|
||||
self.core.storage.data.map_group_ids(principal).await
|
||||
}
|
||||
Ok(None) => {
|
||||
return RequestError::blank(
|
||||
StatusCode::NOT_FOUND.as_u16(),
|
||||
"Not found",
|
||||
"Account not found.",
|
||||
)
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
};
|
||||
.await?
|
||||
.ok_or_else(|| trc::ManageCause::NotFound.into_err())?;
|
||||
let principal = self.core.storage.data.map_group_ids(principal).await?;
|
||||
|
||||
match result {
|
||||
Ok(principal) => {
|
||||
// Obtain quota usage
|
||||
let mut principal = PrincipalResponse::from(principal);
|
||||
principal.used_quota =
|
||||
self.get_used_quota(account_id).await.unwrap_or_default()
|
||||
as u64;
|
||||
// Obtain quota usage
|
||||
let mut principal = PrincipalResponse::from(principal);
|
||||
principal.used_quota = self.get_used_quota(account_id).await? as u64;
|
||||
|
||||
// Obtain member names
|
||||
for member_id in self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.get_members(account_id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
{
|
||||
if let Ok(Some(member_principal)) = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.query(QueryBy::Id(member_id), false)
|
||||
.await
|
||||
{
|
||||
principal.members.push(member_principal.name);
|
||||
}
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": principal,
|
||||
}))
|
||||
.into_http_response()
|
||||
// Obtain member names
|
||||
for member_id in self.core.storage.data.get_members(account_id).await? {
|
||||
if let Some(member_principal) = self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.query(QueryBy::Id(member_id), false)
|
||||
.await?
|
||||
{
|
||||
principal.members.push(member_principal.name);
|
||||
}
|
||||
Err(err) => into_directory_response(err),
|
||||
}
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": principal,
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
Method::DELETE => {
|
||||
// Remove FTS index
|
||||
if let Err(err) = self.core.storage.fts.remove_all(account_id).await {
|
||||
return err.into_http_response();
|
||||
}
|
||||
self.core.storage.fts.remove_all(account_id).await;
|
||||
|
||||
// Delete account
|
||||
match self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.delete_account(QueryBy::Id(account_id))
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
// Remove entries from cache
|
||||
self.inner.sessions.retain(|_, id| id.item != account_id);
|
||||
.await?;
|
||||
// Remove entries from cache
|
||||
self.inner.sessions.retain(|_, id| id.item != account_id);
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => into_directory_response(err),
|
||||
}
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
Method::PATCH => {
|
||||
match serde_json::from_slice::<Vec<PrincipalUpdate>>(
|
||||
let changes = serde_json::from_slice::<Vec<PrincipalUpdate>>(
|
||||
body.as_deref().unwrap_or_default(),
|
||||
) {
|
||||
Ok(changes) => {
|
||||
// Make sure the current directory supports updates
|
||||
if let Some(response) = self.assert_supported_directory() {
|
||||
if changes.iter().any(|change| {
|
||||
!matches!(
|
||||
change.field,
|
||||
PrincipalField::Quota | PrincipalField::Description
|
||||
)
|
||||
}) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
let is_password_change = changes
|
||||
.iter()
|
||||
.any(|change| matches!(change.field, PrincipalField::Secrets));
|
||||
)
|
||||
.map_err(|err| {
|
||||
trc::Cause::Resource(trc::ResourceCause::BadParameters)
|
||||
.from_json_error(err)
|
||||
})?;
|
||||
|
||||
match self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.update_account(QueryBy::Id(account_id), changes)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
if is_password_change {
|
||||
// Remove entries from cache
|
||||
self.inner
|
||||
.sessions
|
||||
.retain(|_, id| id.item != account_id);
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => into_directory_response(err),
|
||||
}
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
// Make sure the current directory supports updates
|
||||
if changes.iter().any(|change| {
|
||||
!matches!(
|
||||
change.field,
|
||||
PrincipalField::Quota | PrincipalField::Description
|
||||
)
|
||||
}) {
|
||||
self.assert_supported_directory()?;
|
||||
}
|
||||
|
||||
let is_password_change = changes
|
||||
.iter()
|
||||
.any(|change| matches!(change.field, PrincipalField::Secrets));
|
||||
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.update_account(QueryBy::Id(account_id), changes)
|
||||
.await?;
|
||||
if is_password_change {
|
||||
// Remove entries from cache
|
||||
self.inner.sessions.retain(|_, id| id.item != account_id);
|
||||
}
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_account_auth_get(&self, access_token: Arc<AccessToken>) -> HttpResponse {
|
||||
pub async fn handle_account_auth_get(
|
||||
&self,
|
||||
access_token: Arc<AccessToken>,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
let mut response = AccountAuthResponse {
|
||||
otp_auth: false,
|
||||
is_admin: access_token.is_super_user(),
|
||||
|
@ -318,35 +262,29 @@ impl JMAP {
|
|||
};
|
||||
|
||||
if access_token.primary_id() != u32::MAX {
|
||||
match self
|
||||
let principal = self
|
||||
.core
|
||||
.storage
|
||||
.directory
|
||||
.query(QueryBy::Id(access_token.primary_id()), false)
|
||||
.await
|
||||
{
|
||||
Ok(Some(principal)) => {
|
||||
for secret in principal.secrets {
|
||||
if secret.is_otp_auth() {
|
||||
response.otp_auth = true;
|
||||
} else if let Some((app_name, _)) =
|
||||
secret.strip_prefix("$app$").and_then(|s| s.split_once('$'))
|
||||
{
|
||||
response.app_passwords.push(app_name.to_string());
|
||||
}
|
||||
}
|
||||
.await?
|
||||
.ok_or_else(|| trc::ManageCause::NotFound.into_err())?;
|
||||
|
||||
for secret in principal.secrets {
|
||||
if secret.is_otp_auth() {
|
||||
response.otp_auth = true;
|
||||
} else if let Some((app_name, _)) =
|
||||
secret.strip_prefix("$app$").and_then(|s| s.split_once('$'))
|
||||
{
|
||||
response.app_passwords.push(app_name.to_string());
|
||||
}
|
||||
Ok(None) => {
|
||||
return RequestError::not_found().into_http_response();
|
||||
}
|
||||
Err(err) => return into_directory_response(err),
|
||||
}
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": response,
|
||||
}))
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
}
|
||||
|
||||
pub async fn handle_account_auth_post(
|
||||
|
@ -354,16 +292,18 @@ impl JMAP {
|
|||
req: &HttpRequest,
|
||||
access_token: Arc<AccessToken>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
// Parse request
|
||||
let requests = match serde_json::from_slice::<Vec<AccountAuthRequest>>(
|
||||
body.as_deref().unwrap_or_default(),
|
||||
) {
|
||||
Ok(request) => request,
|
||||
Err(err) => return err.into_http_response(),
|
||||
};
|
||||
let requests =
|
||||
serde_json::from_slice::<Vec<AccountAuthRequest>>(body.as_deref().unwrap_or_default())
|
||||
.map_err(|err| {
|
||||
trc::Cause::Resource(trc::ResourceCause::BadParameters).from_json_error(err)
|
||||
})?;
|
||||
|
||||
if requests.is_empty() {
|
||||
return RequestError::invalid_parameters().into_http_response();
|
||||
return Err(trc::Cause::Resource(trc::ResourceCause::BadParameters)
|
||||
.into_err()
|
||||
.details("Empty request"));
|
||||
}
|
||||
|
||||
// Make sure the user authenticated using Basic auth
|
||||
|
@ -380,50 +320,41 @@ impl JMAP {
|
|||
.and_then(|h| h.to_str().ok())
|
||||
.map_or(true, |header| !header.to_lowercase().starts_with("basic "))
|
||||
{
|
||||
return ManagementApiError::Other {
|
||||
details: "Password changes only allowed using Basic auth".into(),
|
||||
}
|
||||
.into_http_response();
|
||||
return Err(manage::error(
|
||||
"Password changes only allowed using Basic auth",
|
||||
None::<u32>,
|
||||
));
|
||||
}
|
||||
|
||||
// Handle Fallback admin password changes
|
||||
if access_token.is_super_user() && access_token.primary_id() == u32::MAX {
|
||||
match requests.into_iter().next().unwrap() {
|
||||
AccountAuthRequest::SetPassword { password } => {
|
||||
return match self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.config
|
||||
.set([("authentication.fallback-admin.secret", password)])
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
// Remove entries from cache
|
||||
self.inner.sessions.retain(|_, id| id.item != u32::MAX);
|
||||
.await?;
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
};
|
||||
// Remove entries from cache
|
||||
self.inner.sessions.retain(|_, id| id.item != u32::MAX);
|
||||
|
||||
return Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response());
|
||||
}
|
||||
_ => {
|
||||
return ManagementApiError::Other {
|
||||
details:
|
||||
"Fallback administrator accounts do not support 2FA or AppPasswords"
|
||||
.into(),
|
||||
}
|
||||
.into_http_response()
|
||||
return Err(manage::error(
|
||||
"Fallback administrator accounts do not support 2FA or AppPasswords",
|
||||
None::<u32>,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the current directory supports updates
|
||||
if let Some(response) = self.assert_supported_directory() {
|
||||
return response;
|
||||
}
|
||||
self.assert_supported_directory()?;
|
||||
|
||||
// Build actions
|
||||
let mut actions = Vec::with_capacity(requests.len());
|
||||
|
@ -459,42 +390,38 @@ impl JMAP {
|
|||
}
|
||||
|
||||
// Update password
|
||||
match self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.update_account(QueryBy::Id(access_token.primary_id()), actions)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
// Remove entries from cache
|
||||
self.inner
|
||||
.sessions
|
||||
.retain(|_, id| id.item != access_token.primary_id());
|
||||
.await?;
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => into_directory_response(err),
|
||||
}
|
||||
// Remove entries from cache
|
||||
self.inner
|
||||
.sessions
|
||||
.retain(|_, id| id.item != access_token.primary_id());
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
|
||||
pub fn assert_supported_directory(&self) -> Option<HttpResponse> {
|
||||
ManagementApiError::UnsupportedDirectoryOperation {
|
||||
class: match &self.core.storage.directory.store {
|
||||
DirectoryInner::Internal(_) => return None,
|
||||
DirectoryInner::Ldap(_) => "LDAP",
|
||||
DirectoryInner::Sql(_) => "SQL",
|
||||
DirectoryInner::Imap(_) => "IMAP",
|
||||
DirectoryInner::Smtp(_) => "SMTP",
|
||||
DirectoryInner::Memory(_) => "In-Memory",
|
||||
}
|
||||
.into(),
|
||||
}
|
||||
.into_http_response()
|
||||
.into()
|
||||
pub fn assert_supported_directory(&self) -> trc::Result<()> {
|
||||
let todo = "update webadmin";
|
||||
|
||||
let class = match &self.core.storage.directory.store {
|
||||
DirectoryInner::Internal(_) => return Ok(()),
|
||||
DirectoryInner::Ldap(_) => "LDAP",
|
||||
DirectoryInner::Sql(_) => "SQL",
|
||||
DirectoryInner::Imap(_) => "IMAP",
|
||||
DirectoryInner::Smtp(_) => "SMTP",
|
||||
DirectoryInner::Memory(_) => "In-Memory",
|
||||
};
|
||||
|
||||
Err(manage::unsupported(format!(
|
||||
"Requested action is unsupported for {class} directories.",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -515,7 +442,8 @@ impl From<Principal<String>> for PrincipalResponse {
|
|||
}
|
||||
}
|
||||
|
||||
fn into_directory_response(mut error: trc::Error) -> HttpResponse {
|
||||
/*
|
||||
fn into_directory_response(mut error: trc::Error) -> trc::Result<HttpResponse> {
|
||||
let response = match error.as_ref() {
|
||||
trc::Cause::MissingParameter => ManagementApiError::FieldMissing {
|
||||
field: error
|
||||
|
@ -533,7 +461,7 @@ fn into_directory_response(mut error: trc::Error) -> HttpResponse {
|
|||
.and_then(|v| v.into_string())
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
trc::Cause::NotFound => ManagementApiError::NotFound {
|
||||
trc::StoreCause::NotFound.into_err() => ManagementApiError::NotFound {
|
||||
item: error
|
||||
.take_value(trc::Key::Key)
|
||||
.and_then(|v| v.into_string())
|
||||
|
@ -559,3 +487,4 @@ fn into_directory_response(mut error: trc::Error) -> HttpResponse {
|
|||
|
||||
JsonResponse::new(response).into_http_response()
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -8,7 +8,6 @@ use std::str::FromStr;
|
|||
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
||||
use hyper::Method;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use mail_auth::{
|
||||
dmarc::URI,
|
||||
mta_sts::ReportUri,
|
||||
|
@ -104,7 +103,11 @@ pub enum Report {
|
|||
}
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_manage_queue(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
|
||||
pub async fn handle_manage_queue(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
let params = UrlParams::new(req.uri().query());
|
||||
|
||||
match (
|
||||
|
@ -138,8 +141,7 @@ impl JMAP {
|
|||
let mut offset = page.saturating_sub(1) * limit;
|
||||
let mut total = 0;
|
||||
let mut total_returned = 0;
|
||||
let _ = self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.iterate(
|
||||
|
@ -193,9 +195,9 @@ impl JMAP {
|
|||
Ok(max_total == 0 || total < max_total)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
if values {
|
||||
Ok(if values {
|
||||
JsonResponse::new(json!({
|
||||
"data":{
|
||||
"items": result_values,
|
||||
|
@ -210,7 +212,7 @@ impl JMAP {
|
|||
},
|
||||
}))
|
||||
}
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
}
|
||||
("messages", Some(queue_id), &Method::GET) => {
|
||||
if let Some(message) = self
|
||||
|
@ -218,12 +220,12 @@ impl JMAP {
|
|||
.read_message(queue_id.parse().unwrap_or_default())
|
||||
.await
|
||||
{
|
||||
JsonResponse::new(json!({
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": Message::from(&message),
|
||||
}))
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
} else {
|
||||
RequestError::not_found().into_http_response()
|
||||
Err(trc::ResourceCause::NotFound.into_err())
|
||||
}
|
||||
}
|
||||
("messages", Some(queue_id), &Method::PATCH) => {
|
||||
|
@ -265,12 +267,12 @@ impl JMAP {
|
|||
let _ = self.smtp.inner.queue_tx.send(queue::Event::Reload).await;
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": found,
|
||||
}))
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
} else {
|
||||
RequestError::not_found().into_http_response()
|
||||
Err(trc::ResourceCause::NotFound.into_err())
|
||||
}
|
||||
}
|
||||
("messages", Some(queue_id), &Method::DELETE) => {
|
||||
|
@ -345,12 +347,12 @@ impl JMAP {
|
|||
found = true;
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": found,
|
||||
}))
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
} else {
|
||||
RequestError::not_found().into_http_response()
|
||||
Err(trc::ResourceCause::NotFound.into_err())
|
||||
}
|
||||
}
|
||||
("reports", None, &Method::GET) => {
|
||||
|
@ -387,8 +389,7 @@ impl JMAP {
|
|||
let mut offset = page.saturating_sub(1) * limit;
|
||||
let mut total = 0;
|
||||
let mut total_returned = 0;
|
||||
let _ = self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.iterate(
|
||||
|
@ -422,15 +423,15 @@ impl JMAP {
|
|||
Ok(max_total == 0 || total < max_total)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
JsonResponse::new(json!({
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": {
|
||||
"items": result,
|
||||
"total": total,
|
||||
},
|
||||
}))
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
}
|
||||
("reports", Some(report_id), &Method::GET) => {
|
||||
let mut result = None;
|
||||
|
@ -438,20 +439,20 @@ impl JMAP {
|
|||
match report_id {
|
||||
QueueClass::DmarcReportHeader(event) => {
|
||||
let mut rua = Vec::new();
|
||||
if let Ok(Some(report)) = self
|
||||
if let Some(report) = self
|
||||
.smtp
|
||||
.generate_dmarc_aggregate_report(&event, &mut rua, None)
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
result = Report::dmarc(event, report, rua).into();
|
||||
}
|
||||
}
|
||||
QueueClass::TlsReportHeader(event) => {
|
||||
let mut rua = Vec::new();
|
||||
if let Ok(Some(report)) = self
|
||||
if let Some(report) = self
|
||||
.smtp
|
||||
.generate_tls_aggregate_report(&[event.clone()], &mut rua, None)
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
result = Report::tls(event, report, rua).into();
|
||||
}
|
||||
|
@ -461,12 +462,12 @@ impl JMAP {
|
|||
}
|
||||
|
||||
if let Some(result) = result {
|
||||
JsonResponse::new(json!({
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": result,
|
||||
}))
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
} else {
|
||||
RequestError::not_found().into_http_response()
|
||||
Err(trc::ResourceCause::NotFound.into_err())
|
||||
}
|
||||
}
|
||||
("reports", Some(report_id), &Method::DELETE) => {
|
||||
|
@ -481,15 +482,15 @@ impl JMAP {
|
|||
_ => (),
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": true,
|
||||
}))
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
} else {
|
||||
RequestError::not_found().into_http_response()
|
||||
Err(trc::ResourceCause::NotFound.into_err())
|
||||
}
|
||||
}
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
use hyper::Method;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use serde_json::json;
|
||||
use utils::url_params::UrlParams;
|
||||
|
||||
|
@ -16,107 +15,88 @@ use crate::{
|
|||
};
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_manage_reload(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
|
||||
pub async fn handle_manage_reload(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
match (path.get(1).copied(), req.method()) {
|
||||
(Some("lookup"), &Method::GET) => {
|
||||
match self.core.reload_lookups().await {
|
||||
Ok(result) => {
|
||||
// Update core
|
||||
if let Some(core) = result.new_core {
|
||||
self.shared_core.store(core.into());
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": result.config,
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
let result = self.core.reload_lookups().await?;
|
||||
// Update core
|
||||
if let Some(core) = result.new_core {
|
||||
self.shared_core.store(core.into());
|
||||
}
|
||||
}
|
||||
(Some("certificate"), &Method::GET) => match self.core.reload_certificates().await {
|
||||
Ok(result) => JsonResponse::new(json!({
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": result.config,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
},
|
||||
.into_http_response())
|
||||
}
|
||||
(Some("certificate"), &Method::GET) => Ok(JsonResponse::new(json!({
|
||||
"data": self.core.reload_certificates().await?.config,
|
||||
}))
|
||||
.into_http_response()),
|
||||
(Some("server.blocked-ip"), &Method::GET) => {
|
||||
match self.core.reload_blocked_ips().await {
|
||||
Ok(result) => {
|
||||
// Increment version counter
|
||||
self.core.network.blocked_ips.increment_version();
|
||||
let result = self.core.reload_blocked_ips().await?;
|
||||
// Increment version counter
|
||||
self.core.network.blocked_ips.increment_version();
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": result.config,
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": result.config,
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(_, &Method::GET) => {
|
||||
match self.core.reload().await {
|
||||
Ok(result) => {
|
||||
if !UrlParams::new(req.uri().query()).has_key("dry-run") {
|
||||
if let Some(core) = result.new_core {
|
||||
// Update core
|
||||
self.shared_core.store(core.into());
|
||||
let result = self.core.reload().await?;
|
||||
if !UrlParams::new(req.uri().query()).has_key("dry-run") {
|
||||
if let Some(core) = result.new_core {
|
||||
// Update core
|
||||
self.shared_core.store(core.into());
|
||||
|
||||
// Increment version counter
|
||||
self.inner.increment_config_version();
|
||||
}
|
||||
|
||||
// Reload ACME
|
||||
if let Err(err) =
|
||||
self.inner.housekeeper_tx.send(Event::AcmeReload).await
|
||||
{
|
||||
tracing::warn!(
|
||||
"Failed to send ACME reload event to housekeeper: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": result.config,
|
||||
}))
|
||||
.into_http_response()
|
||||
// Increment version counter
|
||||
self.inner.increment_config_version();
|
||||
}
|
||||
|
||||
// Reload ACME
|
||||
if let Err(err) = self.inner.housekeeper_tx.send(Event::AcmeReload).await {
|
||||
tracing::warn!("Failed to send ACME reload event to housekeeper: {}", err);
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": result.config,
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_manage_update(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
|
||||
pub async fn handle_manage_update(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
match (path.get(1).copied(), req.method()) {
|
||||
(Some("spam-filter"), &Method::GET) => {
|
||||
match self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.update_config_resource("spam-filter")
|
||||
.await
|
||||
{
|
||||
Ok(result) => JsonResponse::new(json!({
|
||||
"data": result,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
}
|
||||
(Some("spam-filter"), &Method::GET) => Ok(JsonResponse::new(json!({
|
||||
"data": self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.update_config_resource("spam-filter")
|
||||
.await?,
|
||||
}))
|
||||
.into_http_response()),
|
||||
(Some("webadmin"), &Method::GET) => {
|
||||
match self.inner.webadmin.update_and_unpack(&self.core).await {
|
||||
Ok(_) => JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
self.inner.webadmin.update_and_unpack(&self.core).await?;
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
use hyper::Method;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use mail_auth::report::{
|
||||
tlsrpt::{FailureDetails, Policy, TlsReport},
|
||||
Feedback,
|
||||
|
@ -32,7 +31,11 @@ enum ReportType {
|
|||
}
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_manage_reports(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
|
||||
pub async fn handle_manage_reports(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
match (
|
||||
path.get(1).copied().unwrap_or_default(),
|
||||
path.get(2).copied().map(decode_path_element),
|
||||
|
@ -89,8 +92,7 @@ impl JMAP {
|
|||
let mut offset = page.saturating_sub(1) * limit;
|
||||
let mut total = 0;
|
||||
let mut last_id = 0;
|
||||
let result = self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.iterate(
|
||||
|
@ -141,17 +143,15 @@ impl JMAP {
|
|||
Ok(max_total == 0 || total < max_total)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Ok(_) => JsonResponse::new(json!({
|
||||
"data": {
|
||||
"items": results,
|
||||
"total": total,
|
||||
},
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
.await?;
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": {
|
||||
"items": results,
|
||||
"total": total,
|
||||
},
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(class @ ("dmarc" | "tls" | "arf"), Some(report_id), &Method::GET) => {
|
||||
if let Some(report_id) = parse_incoming_report_id(class, report_id.as_ref()) {
|
||||
|
@ -163,14 +163,13 @@ impl JMAP {
|
|||
.get_value::<Bincode<IncomingReport<TlsReport>>>(ValueKey::from(
|
||||
ValueClass::Report(report_id),
|
||||
))
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
Ok(Some(report)) => JsonResponse::new(json!({
|
||||
Some(report) => Ok(JsonResponse::new(json!({
|
||||
"data": report.inner,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Ok(None) => RequestError::not_found().into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
.into_http_response()),
|
||||
None => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
},
|
||||
ReportClass::Dmarc { .. } => match self
|
||||
.core
|
||||
|
@ -179,14 +178,13 @@ impl JMAP {
|
|||
.get_value::<Bincode<IncomingReport<mail_auth::report::Report>>>(
|
||||
ValueKey::from(ValueClass::Report(report_id)),
|
||||
)
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
Ok(Some(report)) => JsonResponse::new(json!({
|
||||
Some(report) => Ok(JsonResponse::new(json!({
|
||||
"data": report.inner,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Ok(None) => RequestError::not_found().into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
.into_http_response()),
|
||||
None => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
},
|
||||
ReportClass::Arf { .. } => match self
|
||||
.core
|
||||
|
@ -195,35 +193,34 @@ impl JMAP {
|
|||
.get_value::<Bincode<IncomingReport<Feedback>>>(ValueKey::from(
|
||||
ValueClass::Report(report_id),
|
||||
))
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
Ok(Some(report)) => JsonResponse::new(json!({
|
||||
Some(report) => Ok(JsonResponse::new(json!({
|
||||
"data": report.inner,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Ok(None) => RequestError::not_found().into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
.into_http_response()),
|
||||
None => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
RequestError::not_found().into_http_response()
|
||||
Err(trc::ResourceCause::NotFound.into_err())
|
||||
}
|
||||
}
|
||||
(class @ ("dmarc" | "tls" | "arf"), Some(report_id), &Method::DELETE) => {
|
||||
if let Some(report_id) = parse_incoming_report_id(class, report_id.as_ref()) {
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.clear(ValueClass::Report(report_id));
|
||||
let result = self.core.storage.data.write(batch.build()).await.is_ok();
|
||||
self.core.storage.data.write(batch.build()).await?;
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": result,
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": true,
|
||||
}))
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
} else {
|
||||
RequestError::not_found().into_http_response()
|
||||
Err(trc::ResourceCause::NotFound.into_err())
|
||||
}
|
||||
}
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
use hyper::Method;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use serde_json::json;
|
||||
use store::ahash::AHashMap;
|
||||
use utils::{config::ConfigKey, url_params::UrlParams};
|
||||
|
@ -15,7 +14,7 @@ use crate::{
|
|||
JMAP,
|
||||
};
|
||||
|
||||
use super::{decode_path_element, ManagementApiError};
|
||||
use super::decode_path_element;
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
|
@ -39,7 +38,7 @@ impl JMAP {
|
|||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
match (path.get(1).copied(), req.method()) {
|
||||
(Some("group"), &Method::GET) => {
|
||||
// List settings
|
||||
|
@ -71,103 +70,101 @@ impl JMAP {
|
|||
params.parse::<usize>("page").unwrap_or(0).saturating_sub(1) * limit;
|
||||
let has_filter = !filter.is_empty();
|
||||
|
||||
match self.core.storage.config.list(&prefix, true).await {
|
||||
Ok(settings) => if !suffix.is_empty() && !settings.is_empty() {
|
||||
// Obtain record ids
|
||||
let mut total = 0;
|
||||
let mut ids = Vec::new();
|
||||
for (key, _) in &settings {
|
||||
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;
|
||||
}
|
||||
total += 1;
|
||||
} else {
|
||||
ids.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group settings by record id
|
||||
let mut records = Vec::new();
|
||||
for id in ids {
|
||||
let mut record = AHashMap::new();
|
||||
let prefix = format!("{id}.");
|
||||
record.insert("_id".to_string(), id.to_string());
|
||||
for (k, v) in &settings {
|
||||
if let Some(k) = k.strip_prefix(&prefix) {
|
||||
if field.map_or(true, |field| field == k) {
|
||||
record.insert(k.to_string(), v.to_string());
|
||||
}
|
||||
} else if record.len() > 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if has_filter {
|
||||
if record
|
||||
.iter()
|
||||
.any(|(_, v)| v.to_lowercase().contains(&filter))
|
||||
{
|
||||
let settings = self.core.storage.config.list(&prefix, true).await?;
|
||||
if !suffix.is_empty() && !settings.is_empty() {
|
||||
// Obtain record ids
|
||||
let mut total = 0;
|
||||
let mut ids = Vec::new();
|
||||
for (key, _) in &settings {
|
||||
if let Some(id) = key.strip_suffix(&suffix) {
|
||||
if !id.is_empty() {
|
||||
if !has_filter {
|
||||
if offset == 0 {
|
||||
if limit == 0 || records.len() < limit {
|
||||
records.push(record);
|
||||
if limit == 0 || ids.len() < limit {
|
||||
ids.push(id);
|
||||
}
|
||||
} else {
|
||||
offset -= 1;
|
||||
}
|
||||
total += 1;
|
||||
} else {
|
||||
ids.push(id);
|
||||
}
|
||||
} else {
|
||||
records.push(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group settings by record id
|
||||
let mut records = Vec::new();
|
||||
for id in ids {
|
||||
let mut record = AHashMap::new();
|
||||
let prefix = format!("{id}.");
|
||||
record.insert("_id".to_string(), id.to_string());
|
||||
for (k, v) in &settings {
|
||||
if let Some(k) = k.strip_prefix(&prefix) {
|
||||
if field.map_or(true, |field| field == k) {
|
||||
record.insert(k.to_string(), v.to_string());
|
||||
}
|
||||
} else if record.len() > 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": {
|
||||
"total": total,
|
||||
"items": records,
|
||||
},
|
||||
}))
|
||||
} else {
|
||||
let total = settings.len();
|
||||
let items = settings
|
||||
.into_iter()
|
||||
.filter_map(|(k, v)| {
|
||||
if filter.is_empty()
|
||||
|| k.to_lowercase().contains(&filter)
|
||||
|| v.to_lowercase().contains(&filter)
|
||||
{
|
||||
let k =
|
||||
k.strip_prefix(&prefix).map(|k| k.to_string()).unwrap_or(k);
|
||||
Some(json!({
|
||||
"_id": k,
|
||||
"_value": v,
|
||||
}))
|
||||
if has_filter {
|
||||
if record
|
||||
.iter()
|
||||
.any(|(_, v)| v.to_lowercase().contains(&filter))
|
||||
{
|
||||
if offset == 0 {
|
||||
if limit == 0 || records.len() < limit {
|
||||
records.push(record);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
offset -= 1;
|
||||
}
|
||||
})
|
||||
.skip(offset)
|
||||
.take(if limit == 0 { total } else { limit })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": {
|
||||
"total": total,
|
||||
"items": items,
|
||||
},
|
||||
}))
|
||||
total += 1;
|
||||
}
|
||||
} else {
|
||||
records.push(record);
|
||||
}
|
||||
}
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": {
|
||||
"total": total,
|
||||
"items": records,
|
||||
},
|
||||
}))
|
||||
.into_http_response())
|
||||
} else {
|
||||
let total = settings.len();
|
||||
let items = settings
|
||||
.into_iter()
|
||||
.filter_map(|(k, v)| {
|
||||
if filter.is_empty()
|
||||
|| k.to_lowercase().contains(&filter)
|
||||
|| v.to_lowercase().contains(&filter)
|
||||
{
|
||||
let k = k.strip_prefix(&prefix).map(|k| k.to_string()).unwrap_or(k);
|
||||
Some(json!({
|
||||
"_id": k,
|
||||
"_value": v,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.skip(offset)
|
||||
.take(if limit == 0 { total } else { limit })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": {
|
||||
"total": total,
|
||||
"items": items,
|
||||
},
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
}
|
||||
(Some("list"), &Method::GET) => {
|
||||
|
@ -186,25 +183,21 @@ impl JMAP {
|
|||
let limit: usize = params.parse("limit").unwrap_or(0);
|
||||
let offset = params.parse::<usize>("page").unwrap_or(0).saturating_sub(1) * limit;
|
||||
|
||||
match self.core.storage.config.list(&prefix, true).await {
|
||||
Ok(settings) => {
|
||||
let total = settings.len();
|
||||
let items = settings
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(if limit == 0 { total } else { limit })
|
||||
.collect::<AHashMap<_, _>>();
|
||||
let settings = self.core.storage.config.list(&prefix, true).await?;
|
||||
let total = settings.len();
|
||||
let items = settings
|
||||
.into_iter()
|
||||
.skip(offset)
|
||||
.take(if limit == 0 { total } else { limit })
|
||||
.collect::<AHashMap<_, _>>();
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": {
|
||||
"total": total,
|
||||
"items": items,
|
||||
},
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": {
|
||||
"total": total,
|
||||
"items": items,
|
||||
},
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(Some("keys"), &Method::GET) => {
|
||||
// Obtain keys
|
||||
|
@ -217,19 +210,11 @@ impl JMAP {
|
|||
.get("prefixes")
|
||||
.map(|s| s.split(',').collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
let mut err = None;
|
||||
let mut results = AHashMap::with_capacity(keys.len());
|
||||
|
||||
for key in keys {
|
||||
match self.core.storage.config.get(key).await {
|
||||
Ok(Some(value)) => {
|
||||
results.insert(key.to_string(), value);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err_) => {
|
||||
err = err_.into();
|
||||
break;
|
||||
}
|
||||
if let Some(value) = self.core.storage.config.get(key).await? {
|
||||
results.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
for prefix in prefixes {
|
||||
|
@ -238,134 +223,88 @@ impl JMAP {
|
|||
} else {
|
||||
prefix.to_string()
|
||||
};
|
||||
match self.core.storage.config.list(&prefix, false).await {
|
||||
Ok(values) => {
|
||||
results.extend(values);
|
||||
}
|
||||
Err(err_) => {
|
||||
err = err_.into();
|
||||
break;
|
||||
}
|
||||
}
|
||||
results.extend(self.core.storage.config.list(&prefix, false).await?);
|
||||
}
|
||||
|
||||
match err {
|
||||
None => JsonResponse::new(json!({
|
||||
"data": results,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Some(err) => err.into_http_response(),
|
||||
}
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": results,
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(Some(prefix), &Method::DELETE) if !prefix.is_empty() => {
|
||||
let prefix = decode_path_element(prefix);
|
||||
|
||||
match self.core.storage.config.clear(prefix.as_ref()).await {
|
||||
Ok(_) => JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
self.core.storage.config.clear(prefix.as_ref()).await?;
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
(None, &Method::POST) => {
|
||||
match serde_json::from_slice::<Vec<UpdateSettings>>(
|
||||
let changes = serde_json::from_slice::<Vec<UpdateSettings>>(
|
||||
body.as_deref().unwrap_or_default(),
|
||||
) {
|
||||
Ok(changes) => {
|
||||
let mut result = Ok(true);
|
||||
)
|
||||
.map_err(|err| {
|
||||
trc::Cause::Resource(trc::ResourceCause::BadParameters).from_json_error(err)
|
||||
})?;
|
||||
|
||||
'next: for change in changes {
|
||||
match change {
|
||||
UpdateSettings::Delete { keys } => {
|
||||
for key in keys {
|
||||
result =
|
||||
self.core.storage.config.clear(key).await.map(|_| true);
|
||||
if result.is_err() {
|
||||
break 'next;
|
||||
}
|
||||
}
|
||||
}
|
||||
UpdateSettings::Clear { prefix } => {
|
||||
result = self
|
||||
for change in changes {
|
||||
match change {
|
||||
UpdateSettings::Delete { keys } => {
|
||||
for key in keys {
|
||||
self.core.storage.config.clear(key).await?;
|
||||
}
|
||||
}
|
||||
UpdateSettings::Clear { prefix } => {
|
||||
self.core.storage.config.clear_prefix(&prefix).await?;
|
||||
}
|
||||
UpdateSettings::Insert {
|
||||
prefix,
|
||||
values,
|
||||
assert_empty,
|
||||
} => {
|
||||
if assert_empty {
|
||||
if let Some(prefix) = &prefix {
|
||||
if !self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.clear_prefix(&prefix)
|
||||
.await
|
||||
.map(|_| true);
|
||||
if result.is_err() {
|
||||
break;
|
||||
.list(&format!("{prefix}."), true)
|
||||
.await?
|
||||
.is_empty()
|
||||
{
|
||||
return Err(trc::ManageCause::AssertFailed.into_err());
|
||||
}
|
||||
}
|
||||
UpdateSettings::Insert {
|
||||
prefix,
|
||||
values,
|
||||
assert_empty,
|
||||
} => {
|
||||
if assert_empty {
|
||||
if let Some(prefix) = &prefix {
|
||||
result = self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.list(&format!("{prefix}."), true)
|
||||
.await
|
||||
.map(|items| items.is_empty());
|
||||
|
||||
if matches!(result, Ok(false) | Err(_)) {
|
||||
break;
|
||||
}
|
||||
} else if let Some((key, _)) = values.first() {
|
||||
result = self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.get(key)
|
||||
.await
|
||||
.map(|items| items.is_none());
|
||||
|
||||
if matches!(result, Ok(false) | Err(_)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = self
|
||||
.core
|
||||
.storage
|
||||
.config
|
||||
.set(values.into_iter().map(|(key, value)| ConfigKey {
|
||||
key: if let Some(prefix) = &prefix {
|
||||
format!("{prefix}.{key}")
|
||||
} else {
|
||||
key
|
||||
},
|
||||
value,
|
||||
}))
|
||||
.await
|
||||
.map(|_| true);
|
||||
if result.is_err() {
|
||||
break;
|
||||
} else if let Some((key, _)) = values.first() {
|
||||
if self.core.storage.config.get(key).await?.is_some() {
|
||||
return Err(trc::ManageCause::AssertFailed.into_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match result {
|
||||
Ok(true) => JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response(),
|
||||
Ok(false) => JsonResponse::new(ManagementApiError::AssertFailed)
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
self.core
|
||||
.storage
|
||||
.config
|
||||
.set(values.into_iter().map(|(key, value)| ConfigKey {
|
||||
key: if let Some(prefix) = &prefix {
|
||||
format!("{prefix}.{key}")
|
||||
} else {
|
||||
key
|
||||
},
|
||||
value,
|
||||
}))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ use std::time::SystemTime;
|
|||
|
||||
use common::{scripts::ScriptModification, IntoString};
|
||||
use hyper::Method;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use serde_json::json;
|
||||
use sieve::{runtime::Variable, Envelope};
|
||||
use smtp::scripts::{ScriptParameters, ScriptResult};
|
||||
|
@ -42,7 +41,7 @@ impl JMAP {
|
|||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
let script = match (
|
||||
path.get(1)
|
||||
.and_then(|name| self.core.sieve.scripts.get(*name))
|
||||
|
@ -51,7 +50,7 @@ impl JMAP {
|
|||
) {
|
||||
(Some(script), &Method::POST) => script,
|
||||
_ => {
|
||||
return RequestError::not_found().into_http_response();
|
||||
return Err(trc::ResourceCause::NotFound.into_err());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -112,9 +111,9 @@ impl JMAP {
|
|||
ScriptResult::Discard => Response::Discard,
|
||||
};
|
||||
|
||||
JsonResponse::new(json!({
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": result,
|
||||
}))
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
|||
use common::manager::webadmin::Resource;
|
||||
use directory::backend::internal::manage::ManageDirectory;
|
||||
use hyper::Method;
|
||||
use jmap_proto::error::request::RequestError;
|
||||
use serde_json::json;
|
||||
use utils::url_params::UrlParams;
|
||||
|
||||
|
@ -21,7 +20,11 @@ use crate::{
|
|||
use super::decode_path_element;
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_manage_store(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
|
||||
pub async fn handle_manage_store(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
path: Vec<&str>,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
match (
|
||||
path.get(1).copied(),
|
||||
path.get(2).copied(),
|
||||
|
@ -29,41 +32,36 @@ impl JMAP {
|
|||
req.method(),
|
||||
) {
|
||||
(Some("blobs"), Some(blob_hash), _, &Method::GET) => {
|
||||
match URL_SAFE_NO_PAD.decode(decode_path_element(blob_hash).as_bytes()) {
|
||||
Ok(blob_hash) => {
|
||||
match self
|
||||
.core
|
||||
.storage
|
||||
.blob
|
||||
.get_blob(&blob_hash, 0..usize::MAX)
|
||||
.await
|
||||
{
|
||||
Ok(Some(contents)) => {
|
||||
let params = UrlParams::new(req.uri().query());
|
||||
let offset = params.parse("offset").unwrap_or(0);
|
||||
let limit = params.parse("limit").unwrap_or(usize::MAX);
|
||||
let blob_hash = URL_SAFE_NO_PAD
|
||||
.decode(decode_path_element(blob_hash).as_bytes())
|
||||
.map_err(|err| {
|
||||
trc::Cause::Resource(trc::ResourceCause::BadParameters)
|
||||
.from_base64_error(err)
|
||||
})?;
|
||||
let contents = self
|
||||
.core
|
||||
.storage
|
||||
.blob
|
||||
.get_blob(&blob_hash, 0..usize::MAX)
|
||||
.await?
|
||||
.ok_or_else(|| trc::ManageCause::NotFound.into_err())?;
|
||||
let params = UrlParams::new(req.uri().query());
|
||||
let offset = params.parse("offset").unwrap_or(0);
|
||||
let limit = params.parse("limit").unwrap_or(usize::MAX);
|
||||
let contents = if offset == 0 && limit == usize::MAX {
|
||||
contents
|
||||
} else {
|
||||
contents
|
||||
.get(offset..std::cmp::min(offset + limit, contents.len()))
|
||||
.unwrap_or_default()
|
||||
.to_vec()
|
||||
};
|
||||
|
||||
let contents = if offset == 0 && limit == usize::MAX {
|
||||
contents
|
||||
} else {
|
||||
contents
|
||||
.get(offset..std::cmp::min(offset + limit, contents.len()))
|
||||
.unwrap_or_default()
|
||||
.to_vec()
|
||||
};
|
||||
|
||||
Resource {
|
||||
content_type: "application/octet-stream",
|
||||
contents,
|
||||
}
|
||||
.into_http_response()
|
||||
}
|
||||
Ok(None) => RequestError::not_found().into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
}
|
||||
Err(_) => RequestError::invalid_parameters().into_http_response(),
|
||||
Ok(Resource {
|
||||
content_type: "application/octet-stream",
|
||||
contents,
|
||||
}
|
||||
.into_http_response())
|
||||
}
|
||||
(Some("purge"), Some("blob"), _, &Method::GET) => {
|
||||
self.housekeeper_request(Event::Purge(PurgeType::Blobs {
|
||||
|
@ -77,7 +75,7 @@ impl JMAP {
|
|||
if let Some(store) = self.core.storage.stores.get(id) {
|
||||
store.clone()
|
||||
} else {
|
||||
return RequestError::not_found().into_http_response();
|
||||
return Err(trc::ResourceCause::NotFound.into_err());
|
||||
}
|
||||
} else {
|
||||
self.core.storage.data.clone()
|
||||
|
@ -91,7 +89,7 @@ impl JMAP {
|
|||
if let Some(store) = self.core.storage.lookups.get(id) {
|
||||
store.clone()
|
||||
} else {
|
||||
return RequestError::not_found().into_http_response();
|
||||
return Err(trc::ResourceCause::NotFound.into_err());
|
||||
}
|
||||
} else {
|
||||
self.core.storage.lookup.clone()
|
||||
|
@ -102,17 +100,13 @@ impl JMAP {
|
|||
}
|
||||
(Some("purge"), Some("account"), id, &Method::GET) => {
|
||||
let account_id = if let Some(id) = id {
|
||||
match self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.get_account_id(decode_path_element(id).as_ref())
|
||||
.await
|
||||
{
|
||||
Ok(Some(id)) => id.into(),
|
||||
Ok(None) => return RequestError::not_found().into_http_response(),
|
||||
Err(err) => return err.into_http_response(),
|
||||
}
|
||||
.await?
|
||||
.ok_or_else(|| trc::ManageCause::NotFound.into_err())?
|
||||
.into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -120,20 +114,20 @@ impl JMAP {
|
|||
self.housekeeper_request(Event::Purge(PurgeType::Account(account_id)))
|
||||
.await
|
||||
}
|
||||
_ => RequestError::not_found().into_http_response(),
|
||||
_ => Err(trc::ResourceCause::NotFound.into_err()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn housekeeper_request(&self, event: Event) -> HttpResponse {
|
||||
match self.inner.housekeeper_tx.send(event).await {
|
||||
Ok(_) => JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(_) => {
|
||||
tracing::error!("Failed to send housekeeper event");
|
||||
RequestError::internal_server_error().into_http_response()
|
||||
}
|
||||
}
|
||||
async fn housekeeper_request(&self, event: Event) -> trc::Result<HttpResponse> {
|
||||
self.inner.housekeeper_tx.send(event).await.map_err(|err| {
|
||||
trc::Cause::Thread
|
||||
.reason(err)
|
||||
.details("Failed to send housekeeper event")
|
||||
})?;
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::sync::Arc;
|
|||
|
||||
use common::listener::ServerInstance;
|
||||
use jmap_proto::{
|
||||
error::{method::MethodError, request::RequestError},
|
||||
error::method::MethodError,
|
||||
method::{
|
||||
get, query,
|
||||
set::{self},
|
||||
|
@ -26,7 +26,7 @@ impl JMAP {
|
|||
request: Request,
|
||||
access_token: Arc<AccessToken>,
|
||||
instance: &Arc<ServerInstance>,
|
||||
) -> Result<Response, RequestError> {
|
||||
) -> Response {
|
||||
let mut response = Response::new(
|
||||
access_token.state(),
|
||||
request.created_ids.unwrap_or_default(),
|
||||
|
@ -104,7 +104,7 @@ impl JMAP {
|
|||
response.created_ids.clear();
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
response
|
||||
}
|
||||
|
||||
async fn handle_method_call(
|
||||
|
|
|
@ -8,10 +8,10 @@ use std::sync::Arc;
|
|||
|
||||
use directory::QueryBy;
|
||||
use jmap_proto::{
|
||||
error::request::RequestError,
|
||||
request::capability::{Capability, Session},
|
||||
types::{acl::Acl, collection::Collection, id::Id},
|
||||
};
|
||||
use trc::AddContext;
|
||||
|
||||
use crate::{auth::AccessToken, JMAP};
|
||||
|
||||
|
@ -20,7 +20,7 @@ impl JMAP {
|
|||
&self,
|
||||
base_url: String,
|
||||
access_token: Arc<AccessToken>,
|
||||
) -> Result<Session, RequestError> {
|
||||
) -> trc::Result<Session> {
|
||||
let mut session = Session::new(base_url, &self.core.jmap.capabilities);
|
||||
session.set_state(access_token.state());
|
||||
session.set_primary_account(
|
||||
|
@ -41,7 +41,8 @@ impl JMAP {
|
|||
&& self
|
||||
.shared_documents(&access_token, *id, Collection::Mailbox, Acl::AddItems)
|
||||
.await
|
||||
.map_or(true, |ids| ids.is_empty());
|
||||
.caused_by(trc::location!())?
|
||||
.is_empty();
|
||||
|
||||
session.add_account(
|
||||
(*id).into(),
|
||||
|
@ -50,7 +51,7 @@ impl JMAP {
|
|||
.directory
|
||||
.query(QueryBy::Id(*id), false)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.caused_by(trc::location!())?
|
||||
.map(|p| p.name)
|
||||
.unwrap_or_else(|| Id::from(*id).to_string()),
|
||||
is_personal,
|
||||
|
|
|
@ -49,7 +49,7 @@ impl JMAP {
|
|||
let acl = Bitmap::<Acl>::from(acl_item.permissions);
|
||||
let collection = Collection::from(acl_item.to_collection);
|
||||
if !collection.is_valid() {
|
||||
return Err(trc::Cause::DataCorruption
|
||||
return Err(trc::StoreCause::DataCorruption
|
||||
.ctx(trc::Key::Reason, "Corrupted collection found in ACL key.")
|
||||
.details(format!("{acl_item:?}"))
|
||||
.account_id(grant_account_id)
|
||||
|
|
|
@ -48,7 +48,7 @@ impl JMAP {
|
|||
self.authenticate_plain(&account, &secret, remote_ip, ServerProtocol::Http)
|
||||
.await?
|
||||
} else {
|
||||
return Err(trc::Cause::Authentication
|
||||
return Err(trc::AuthCause::Error
|
||||
.into_err()
|
||||
.details("Failed to decode Basic auth request.")
|
||||
.id(token)
|
||||
|
@ -65,7 +65,7 @@ impl JMAP {
|
|||
} else {
|
||||
// Enforce anonymous rate limit
|
||||
self.is_anonymous_allowed(&remote_ip).await?;
|
||||
return Err(trc::Cause::Authentication
|
||||
return Err(trc::AuthCause::Error
|
||||
.into_err()
|
||||
.reason("Unsupported authentication mechanism.")
|
||||
.details(token)
|
||||
|
@ -87,7 +87,7 @@ impl JMAP {
|
|||
// Enforce anonymous rate limit
|
||||
self.is_anonymous_allowed(&remote_ip).await?;
|
||||
|
||||
Err(trc::Cause::Authentication
|
||||
Err(trc::AuthCause::Error
|
||||
.into_err()
|
||||
.details("Missing Authorization header.")
|
||||
.caused_by(trc::location!()))
|
||||
|
@ -147,7 +147,7 @@ impl JMAP {
|
|||
{
|
||||
Ok(principal) => Ok(AccessToken::new(principal)),
|
||||
Err(err) => {
|
||||
if !err.matches(trc::Cause::MissingTotp) {
|
||||
if !err.matches(trc::Cause::Auth(trc::AuthCause::MissingTotp)) {
|
||||
let _ = self.is_auth_allowed_hard(&remote_ip).await;
|
||||
}
|
||||
Err(err)
|
||||
|
@ -164,7 +164,7 @@ impl JMAP {
|
|||
.await
|
||||
{
|
||||
Ok(Some(principal)) => self.update_access_token(AccessToken::new(principal)).await,
|
||||
Ok(None) => Err(trc::Cause::Authentication
|
||||
Ok(None) => Err(trc::AuthCause::Error
|
||||
.into_err()
|
||||
.details("Account not found.")
|
||||
.caused_by(trc::location!())),
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hyper::StatusCode;
|
||||
use rand::distributions::Standard;
|
||||
use serde_json::json;
|
||||
use store::{
|
||||
|
@ -16,10 +15,7 @@ use store::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
api::{
|
||||
http::ToHttpResponse, management::ManagementApiError, HtmlResponse, HttpRequest,
|
||||
HttpResponse, JsonResponse,
|
||||
},
|
||||
api::{http::ToHttpResponse, HttpRequest, HttpResponse, JsonResponse},
|
||||
auth::{oauth::OAuthStatus, AccessToken},
|
||||
JMAP,
|
||||
};
|
||||
|
@ -34,155 +30,133 @@ impl JMAP {
|
|||
&self,
|
||||
access_token: Arc<AccessToken>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> HttpResponse {
|
||||
match serde_json::from_slice::<OAuthCodeRequest>(body.as_deref().unwrap_or_default()) {
|
||||
Ok(request) => {
|
||||
let response = match request {
|
||||
OAuthCodeRequest::Code {
|
||||
client_id,
|
||||
redirect_uri,
|
||||
} => {
|
||||
// Validate clientId
|
||||
if client_id.len() > CLIENT_ID_MAX_LEN {
|
||||
return ManagementApiError::Other {
|
||||
details: "Client ID is invalid.".into(),
|
||||
}
|
||||
.into_http_response();
|
||||
} else if redirect_uri
|
||||
.as_ref()
|
||||
.map_or(false, |uri| !uri.starts_with("https://"))
|
||||
{
|
||||
return ManagementApiError::Other {
|
||||
details: "Redirect URI must be HTTPS.".into(),
|
||||
}
|
||||
.into_http_response();
|
||||
}
|
||||
) -> trc::Result<HttpResponse> {
|
||||
let request =
|
||||
serde_json::from_slice::<OAuthCodeRequest>(body.as_deref().unwrap_or_default())
|
||||
.map_err(|err| {
|
||||
trc::Cause::Resource(trc::ResourceCause::BadParameters).from_json_error(err)
|
||||
})?;
|
||||
|
||||
// Generate client code
|
||||
let client_code = thread_rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(DEVICE_CODE_LEN)
|
||||
.map(char::from)
|
||||
.collect::<String>();
|
||||
let response = match request {
|
||||
OAuthCodeRequest::Code {
|
||||
client_id,
|
||||
redirect_uri,
|
||||
} => {
|
||||
// Validate clientId
|
||||
if client_id.len() > CLIENT_ID_MAX_LEN {
|
||||
return Err(trc::ManageCause::Error
|
||||
.into_err()
|
||||
.details("Client ID is invalid."));
|
||||
} else if redirect_uri
|
||||
.as_ref()
|
||||
.map_or(false, |uri| !uri.starts_with("https://"))
|
||||
{
|
||||
return Err(trc::ManageCause::Error
|
||||
.into_err()
|
||||
.details("Redirect URI must be HTTPS."));
|
||||
}
|
||||
|
||||
// Serialize OAuth code
|
||||
let value = Bincode::new(OAuthCode {
|
||||
status: OAuthStatus::Authorized,
|
||||
account_id: access_token.primary_id(),
|
||||
client_id,
|
||||
params: redirect_uri.unwrap_or_default(),
|
||||
})
|
||||
.serialize();
|
||||
// Generate client code
|
||||
let client_code = thread_rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(DEVICE_CODE_LEN)
|
||||
.map(char::from)
|
||||
.collect::<String>();
|
||||
|
||||
// Insert client code
|
||||
if let Err(err) = self
|
||||
.core
|
||||
// Serialize OAuth code
|
||||
let value = Bincode::new(OAuthCode {
|
||||
status: OAuthStatus::Authorized,
|
||||
account_id: access_token.primary_id(),
|
||||
client_id,
|
||||
params: redirect_uri.unwrap_or_default(),
|
||||
})
|
||||
.serialize();
|
||||
|
||||
// Insert client code
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_set(
|
||||
format!("oauth:{client_code}").into_bytes(),
|
||||
value,
|
||||
self.core.jmap.oauth_expiry_auth_code.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let is_enterprise = false;
|
||||
#[cfg(feature = "enterprise")]
|
||||
let is_enterprise = self.core.is_enterprise_edition();
|
||||
|
||||
json!({
|
||||
"data": {
|
||||
"code": client_code,
|
||||
"is_admin": access_token.is_super_user(),
|
||||
"is_enterprise": is_enterprise,
|
||||
},
|
||||
})
|
||||
}
|
||||
OAuthCodeRequest::Device { code } => {
|
||||
let mut success = false;
|
||||
|
||||
// Obtain code
|
||||
if let Some(mut auth_code) = self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_get::<Bincode<OAuthCode>>(format!("oauth:{code}").into_bytes())
|
||||
.await?
|
||||
{
|
||||
if auth_code.inner.status == OAuthStatus::Pending {
|
||||
auth_code.inner.status = OAuthStatus::Authorized;
|
||||
auth_code.inner.account_id = access_token.primary_id();
|
||||
let device_code = std::mem::take(&mut auth_code.inner.params);
|
||||
success = true;
|
||||
|
||||
// Delete issued user code
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_delete(format!("oauth:{code}").into_bytes())
|
||||
.await?;
|
||||
|
||||
// Update device code status
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_set(
|
||||
format!("oauth:{client_code}").into_bytes(),
|
||||
value,
|
||||
format!("oauth:{device_code}").into_bytes(),
|
||||
auth_code.serialize(),
|
||||
self.core.jmap.oauth_expiry_auth_code.into(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return err.into_http_response();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let is_enterprise = false;
|
||||
#[cfg(feature = "enterprise")]
|
||||
let is_enterprise = self.core.is_enterprise_edition();
|
||||
|
||||
json!({
|
||||
"data": {
|
||||
"code": client_code,
|
||||
"is_admin": access_token.is_super_user(),
|
||||
"is_enterprise": is_enterprise,
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
OAuthCodeRequest::Device { code } => {
|
||||
let mut success = false;
|
||||
}
|
||||
|
||||
// Obtain code
|
||||
match self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_get::<Bincode<OAuthCode>>(format!("oauth:{code}").into_bytes())
|
||||
.await
|
||||
{
|
||||
Ok(Some(mut auth_code))
|
||||
if auth_code.inner.status == OAuthStatus::Pending =>
|
||||
{
|
||||
auth_code.inner.status = OAuthStatus::Authorized;
|
||||
auth_code.inner.account_id = access_token.primary_id();
|
||||
let device_code = std::mem::take(&mut auth_code.inner.params);
|
||||
success = true;
|
||||
|
||||
// Delete issued user code
|
||||
if let Err(err) = self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_delete(format!("oauth:{code}").into_bytes())
|
||||
.await
|
||||
{
|
||||
return err.into_http_response();
|
||||
}
|
||||
|
||||
// Update device code status
|
||||
if let Err(err) = self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_set(
|
||||
format!("oauth:{device_code}").into_bytes(),
|
||||
auth_code.serialize(),
|
||||
self.core.jmap.oauth_expiry_auth_code.into(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return err.into_http_response();
|
||||
}
|
||||
}
|
||||
Err(err) => return err.into_http_response(),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
json!({
|
||||
"data": success,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
JsonResponse::new(response).into_http_response()
|
||||
json!({
|
||||
"data": success,
|
||||
})
|
||||
}
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(JsonResponse::new(response).into_http_response())
|
||||
}
|
||||
|
||||
pub async fn handle_device_auth(
|
||||
&self,
|
||||
req: &mut HttpRequest,
|
||||
base_url: impl AsRef<str>,
|
||||
) -> HttpResponse {
|
||||
) -> trc::Result<HttpResponse> {
|
||||
// Parse form
|
||||
let client_id = match FormData::from_request(req, MAX_POST_LEN)
|
||||
.await
|
||||
.map(|mut p| p.remove("client_id"))
|
||||
{
|
||||
Ok(Some(client_id)) if client_id.len() < CLIENT_ID_MAX_LEN => client_id,
|
||||
Err(err) => return err,
|
||||
_ => {
|
||||
return HtmlResponse::with_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Client ID is invalid.".to_string(),
|
||||
)
|
||||
.into_http_response();
|
||||
}
|
||||
};
|
||||
let client_id = FormData::from_request(req, MAX_POST_LEN)
|
||||
.await?
|
||||
.remove("client_id")
|
||||
.filter(|client_id| client_id.len() < CLIENT_ID_MAX_LEN)
|
||||
.ok_or_else(|| {
|
||||
trc::ResourceCause::BadParameters
|
||||
.into_err()
|
||||
.details("Client ID is missing.")
|
||||
})?;
|
||||
|
||||
// Generate device code
|
||||
let device_code = thread_rng()
|
||||
|
@ -215,8 +189,7 @@ impl JMAP {
|
|||
.serialize();
|
||||
|
||||
// Insert device code
|
||||
if let Err(err) = self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_set(
|
||||
|
@ -224,14 +197,10 @@ impl JMAP {
|
|||
oauth_code.clone(),
|
||||
self.core.jmap.oauth_expiry_user_code.into(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return err.into_http_response();
|
||||
}
|
||||
.await?;
|
||||
|
||||
// Insert user code
|
||||
if let Err(err) = self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_set(
|
||||
|
@ -239,14 +208,11 @@ impl JMAP {
|
|||
oauth_code,
|
||||
self.core.jmap.oauth_expiry_user_code.into(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return err.into_http_response();
|
||||
}
|
||||
.await?;
|
||||
|
||||
// Build response
|
||||
let base_url = base_url.as_ref();
|
||||
JsonResponse::new(DeviceAuthResponse {
|
||||
Ok(JsonResponse::new(DeviceAuthResponse {
|
||||
verification_uri: format!("{}/authorize", base_url),
|
||||
verification_uri_complete: format!("{}/authorize/?code={}", base_url, user_code),
|
||||
device_code,
|
||||
|
@ -254,6 +220,6 @@ impl JMAP {
|
|||
expires_in: self.core.jmap.oauth_expiry_user_code,
|
||||
interval: 5,
|
||||
})
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use hyper::{header::CONTENT_TYPE, StatusCode};
|
||||
use hyper::header::CONTENT_TYPE;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::api::{
|
||||
http::{fetch_body, ToHttpResponse},
|
||||
HtmlResponse, HttpRequest, HttpResponse,
|
||||
};
|
||||
use crate::api::{http::fetch_body, HttpRequest};
|
||||
|
||||
pub mod auth;
|
||||
pub mod token;
|
||||
|
@ -205,7 +202,7 @@ pub struct FormData {
|
|||
}
|
||||
|
||||
impl FormData {
|
||||
pub async fn from_request(req: &mut HttpRequest, max_len: usize) -> Result<Self, HttpResponse> {
|
||||
pub async fn from_request(req: &mut HttpRequest, max_len: usize) -> trc::Result<Self> {
|
||||
match (
|
||||
req.headers()
|
||||
.get(CONTENT_TYPE)
|
||||
|
@ -229,11 +226,9 @@ impl FormData {
|
|||
}
|
||||
Ok(FormData { fields })
|
||||
}
|
||||
_ => Err(HtmlResponse::with_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Invalid post request".to_string(),
|
||||
)
|
||||
.into_http_response()),
|
||||
_ => Err(trc::ResourceCause::BadParameters
|
||||
.into_err()
|
||||
.details("Invalid post request")),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,12 +30,9 @@ use super::{
|
|||
|
||||
impl JMAP {
|
||||
// Token endpoint
|
||||
pub async fn handle_token_request(&self, req: &mut HttpRequest) -> HttpResponse {
|
||||
pub async fn handle_token_request(&self, req: &mut HttpRequest) -> trc::Result<HttpResponse> {
|
||||
// Parse form
|
||||
let params = match FormData::from_request(req, MAX_POST_LEN).await {
|
||||
Ok(params) => params,
|
||||
Err(err) => return err,
|
||||
};
|
||||
let params = FormData::from_request(req, MAX_POST_LEN).await?;
|
||||
let grant_type = params.get("grant_type").unwrap_or_default();
|
||||
|
||||
let mut response = TokenResponse::error(ErrorType::InvalidGrant);
|
||||
|
@ -52,38 +49,35 @@ impl JMAP {
|
|||
.storage
|
||||
.lookup
|
||||
.key_get::<Bincode<OAuthCode>>(format!("oauth:{code}").into_bytes())
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
Ok(Some(auth_code)) => {
|
||||
Some(auth_code) => {
|
||||
let oauth = auth_code.inner;
|
||||
if client_id != oauth.client_id || redirect_uri != oauth.params {
|
||||
TokenResponse::error(ErrorType::InvalidClient)
|
||||
} else if oauth.status == OAuthStatus::Authorized {
|
||||
// Mark this token as issued
|
||||
if let Err(err) = self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_delete(format!("oauth:{code}").into_bytes())
|
||||
.await
|
||||
{
|
||||
return err.into_http_response();
|
||||
}
|
||||
.await?;
|
||||
|
||||
// Issue token
|
||||
self.issue_token(oauth.account_id, &oauth.client_id, true)
|
||||
.await
|
||||
.map(TokenResponse::Granted)
|
||||
.unwrap_or_else(|err| {
|
||||
tracing::error!("Failed to generate OAuth token: {}", err);
|
||||
TokenResponse::error(ErrorType::InvalidRequest)
|
||||
})
|
||||
.map_err(|err| {
|
||||
trc::AuthCause::Error
|
||||
.into_err()
|
||||
.details(err)
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
} else {
|
||||
TokenResponse::error(ErrorType::InvalidGrant)
|
||||
}
|
||||
}
|
||||
Ok(None) => TokenResponse::error(ErrorType::AccessDenied),
|
||||
Err(err) => return err.into_http_response(),
|
||||
None => TokenResponse::error(ErrorType::AccessDenied),
|
||||
}
|
||||
} else {
|
||||
TokenResponse::error(ErrorType::InvalidClient)
|
||||
|
@ -100,9 +94,9 @@ impl JMAP {
|
|||
.storage
|
||||
.lookup
|
||||
.key_get::<Bincode<OAuthCode>>(format!("oauth:{device_code}").into_bytes())
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
Ok(Some(auth_code)) => {
|
||||
Some(auth_code) => {
|
||||
let oauth = auth_code.inner;
|
||||
response = if oauth.client_id != client_id {
|
||||
TokenResponse::error(ErrorType::InvalidClient)
|
||||
|
@ -110,27 +104,22 @@ impl JMAP {
|
|||
match oauth.status {
|
||||
OAuthStatus::Authorized => {
|
||||
// Mark this token as issued
|
||||
if let Err(err) = self
|
||||
.core
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_delete(format!("oauth:{device_code}").into_bytes())
|
||||
.await
|
||||
{
|
||||
return err.into_http_response();
|
||||
}
|
||||
.await?;
|
||||
|
||||
// Issue token
|
||||
self.issue_token(oauth.account_id, &oauth.client_id, true)
|
||||
.await
|
||||
.map(TokenResponse::Granted)
|
||||
.unwrap_or_else(|err| {
|
||||
tracing::error!(
|
||||
"Failed to generate OAuth token: {}",
|
||||
err
|
||||
);
|
||||
TokenResponse::error(ErrorType::InvalidRequest)
|
||||
})
|
||||
.map_err(|err| {
|
||||
trc::AuthCause::Error
|
||||
.into_err()
|
||||
.details(err)
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
}
|
||||
OAuthStatus::Pending => {
|
||||
TokenResponse::error(ErrorType::AuthorizationPending)
|
||||
|
@ -141,37 +130,36 @@ impl JMAP {
|
|||
}
|
||||
};
|
||||
}
|
||||
Ok(None) => (),
|
||||
Err(err) => return err.into_http_response(),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
} else if grant_type.eq_ignore_ascii_case("refresh_token") {
|
||||
let todo = "return error";
|
||||
if let Some(refresh_token) = params.get("refresh_token") {
|
||||
if let Ok((account_id, client_id, time_left)) = self
|
||||
let (account_id, client_id, time_left) = self
|
||||
.validate_access_token("refresh_token", refresh_token)
|
||||
.await?;
|
||||
|
||||
// TODO: implement revoking client ids
|
||||
response = self
|
||||
.issue_token(
|
||||
account_id,
|
||||
&client_id,
|
||||
time_left <= self.core.jmap.oauth_expiry_refresh_token_renew,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// TODO: implement revoking client ids
|
||||
response = self
|
||||
.issue_token(
|
||||
account_id,
|
||||
&client_id,
|
||||
time_left <= self.core.jmap.oauth_expiry_refresh_token_renew,
|
||||
)
|
||||
.await
|
||||
.map(TokenResponse::Granted)
|
||||
.unwrap_or_else(|err| {
|
||||
tracing::debug!("Failed to refresh OAuth token: {}", err);
|
||||
TokenResponse::error(ErrorType::InvalidGrant)
|
||||
});
|
||||
}
|
||||
.map(TokenResponse::Granted)
|
||||
.map_err(|err| {
|
||||
trc::AuthCause::Error
|
||||
.into_err()
|
||||
.details(err)
|
||||
.caused_by(trc::location!())
|
||||
})?;
|
||||
} else {
|
||||
response = TokenResponse::error(ErrorType::InvalidRequest);
|
||||
}
|
||||
}
|
||||
|
||||
JsonResponse::with_status(
|
||||
Ok(JsonResponse::with_status(
|
||||
if response.is_error() {
|
||||
StatusCode::BAD_REQUEST
|
||||
} else {
|
||||
|
@ -179,7 +167,7 @@ impl JMAP {
|
|||
},
|
||||
response,
|
||||
)
|
||||
.into_http_response()
|
||||
.into_http_response())
|
||||
}
|
||||
|
||||
async fn password_hash(&self, account_id: u32) -> Result<String, &'static str> {
|
||||
|
@ -293,7 +281,8 @@ impl JMAP {
|
|||
) -> trc::Result<(u32, String, u64)> {
|
||||
// Base64 decode token
|
||||
let token = base64_decode(token_.as_bytes()).ok_or_else(|| {
|
||||
trc::Cause::Authentication
|
||||
trc::AuthCause::Error
|
||||
.into_err()
|
||||
.ctx(trc::Key::Reason, "Failed to decode token")
|
||||
.details(token_.to_string())
|
||||
})?;
|
||||
|
@ -309,7 +298,8 @@ impl JMAP {
|
|||
.into()
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
trc::Cause::Authentication
|
||||
trc::AuthCause::Error
|
||||
.into_err()
|
||||
.ctx(trc::Key::Reason, "Failed to decode token")
|
||||
.details(token_.to_string())
|
||||
})?;
|
||||
|
@ -321,14 +311,16 @@ impl JMAP {
|
|||
.unwrap_or(0)
|
||||
.saturating_sub(946684800); // Jan 1, 2000
|
||||
if expiry <= now {
|
||||
return Err(trc::Cause::Authentication.ctx(trc::Key::Reason, "Token expired"));
|
||||
return Err(trc::AuthCause::Error
|
||||
.into_err()
|
||||
.ctx(trc::Key::Reason, "Token expired"));
|
||||
}
|
||||
|
||||
// Obtain password hash
|
||||
let password_hash = self
|
||||
.password_hash(account_id)
|
||||
.await
|
||||
.map_err(|err| trc::Cause::Authentication.ctx(trc::Key::Details, err))?;
|
||||
.map_err(|err| trc::AuthCause::Error.into_err().ctx(trc::Key::Details, err))?;
|
||||
|
||||
// Build context
|
||||
let key = self.core.jmap.oauth_key.clone();
|
||||
|
@ -357,7 +349,8 @@ impl JMAP {
|
|||
&nonce,
|
||||
)
|
||||
.map_err(|err| {
|
||||
trc::Cause::Authentication
|
||||
trc::AuthCause::Error
|
||||
.into_err()
|
||||
.ctx(trc::Key::Details, "Failed to decode token")
|
||||
.reason(err)
|
||||
})?;
|
||||
|
|
|
@ -64,12 +64,12 @@ impl JMAP {
|
|||
} else if access_token.is_super_user() {
|
||||
Ok(InFlight::default())
|
||||
} else {
|
||||
Err(trc::Cause::TooManyConcurrentRequests.into())
|
||||
Err(trc::LimitCause::ConcurrentRequest.into_err())
|
||||
}
|
||||
} else if access_token.is_super_user() {
|
||||
Ok(InFlight::default())
|
||||
} else {
|
||||
Err(trc::Cause::TooManyRequests.into())
|
||||
Err(trc::LimitCause::TooManyRequests.into_err())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ impl JMAP {
|
|||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::Cause::TooManyRequests.into());
|
||||
return Err(trc::LimitCause::TooManyRequests.into_err());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -100,7 +100,7 @@ impl JMAP {
|
|||
} else if access_token.is_super_user() {
|
||||
Ok(InFlight::default())
|
||||
} else {
|
||||
Err(trc::Cause::TooManyConcurrentUploads.into())
|
||||
Err(trc::LimitCause::ConcurrentUpload.into_err())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ impl JMAP {
|
|||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::Cause::TooManyAuthAttempts.into());
|
||||
return Err(trc::AuthCause::TooManyAttempts.into_err());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -134,7 +134,7 @@ impl JMAP {
|
|||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::Cause::TooManyAuthAttempts.into());
|
||||
return Err(trc::AuthCause::TooManyAttempts.into_err());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -211,7 +211,8 @@ impl JMAP {
|
|||
&& used.count + 1 > self.core.jmap.upload_tmp_quota_amount))
|
||||
&& !access_token.is_super_user()
|
||||
{
|
||||
let err = Err(trc::Cause::OverBlobQuota
|
||||
let err = Err(trc::LimitCause::BlobQuota
|
||||
.into_err()
|
||||
.ctx(trc::Key::Size, self.core.jmap.upload_tmp_quota_size)
|
||||
.ctx(trc::Key::Total, self.core.jmap.upload_tmp_quota_amount));
|
||||
|
||||
|
|
|
@ -28,7 +28,8 @@ impl JMAP {
|
|||
|
||||
pub fn generate_snowflake_id(&self) -> trc::Result<u64> {
|
||||
self.inner.snowflake_id.generate().ok_or_else(|| {
|
||||
trc::Cause::Unexpected
|
||||
trc::StoreCause::Unexpected
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Reason, "Failed to generate snowflake id.")
|
||||
})
|
||||
|
@ -57,7 +58,7 @@ impl JMAP {
|
|||
|
||||
pub async fn delete_changes(&self, account_id: u32, before: Duration) -> trc::Result<()> {
|
||||
let reference_cid = self.inner.snowflake_id.past_id(before).ok_or_else(|| {
|
||||
trc::Cause::Unexpected
|
||||
trc::StoreCause::Unexpected
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Reason, "Failed to generate reference change id.")
|
||||
})?;
|
||||
|
|
|
@ -7,15 +7,13 @@
|
|||
use std::{borrow::Cow, collections::BTreeSet, fmt::Display, io::Cursor, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
api::{http::ToHttpResponse, management::ManagementApiError, HttpResponse, JsonResponse},
|
||||
api::{http::ToHttpResponse, HttpResponse, JsonResponse},
|
||||
auth::AccessToken,
|
||||
JMAP,
|
||||
};
|
||||
use aes::cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit};
|
||||
use jmap_proto::{
|
||||
error::request::RequestError,
|
||||
types::{collection::Collection, property::Property},
|
||||
};
|
||||
use directory::backend::internal::manage;
|
||||
use jmap_proto::types::{collection::Collection, property::Property};
|
||||
use mail_builder::{encoders::base64::base64_encode_mime, mime::make_boundary};
|
||||
use mail_parser::{decoders::base64::base64_decode, Message, MessageParser, MimeHeaders};
|
||||
use openpgp::{
|
||||
|
@ -608,12 +606,16 @@ impl Deserialize for EncryptionParams {
|
|||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
let version = *bytes
|
||||
.first()
|
||||
.ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))?;
|
||||
.ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))?;
|
||||
match version {
|
||||
1 if bytes.len() > 1 => bincode::deserialize(&bytes[1..])
|
||||
.map_err(|err| trc::Error::from(err).caused_by(trc::location!())),
|
||||
1 if bytes.len() > 1 => bincode::deserialize(&bytes[1..]).map_err(|err| {
|
||||
trc::Cause::Store(trc::StoreCause::Deserialize)
|
||||
.from_bincode_error(err)
|
||||
.caused_by(trc::location!())
|
||||
}),
|
||||
|
||||
_ => Err(trc::Cause::Deserialize
|
||||
_ => Err(trc::StoreCause::Deserialize
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, version as u64)),
|
||||
}
|
||||
|
@ -627,66 +629,48 @@ impl ToBitmaps for &EncryptionParams {
|
|||
}
|
||||
|
||||
impl JMAP {
|
||||
pub async fn handle_crypto_get(&self, access_token: Arc<AccessToken>) -> HttpResponse {
|
||||
match self
|
||||
pub async fn handle_crypto_get(
|
||||
&self,
|
||||
access_token: Arc<AccessToken>,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
let params = self
|
||||
.get_property::<EncryptionParams>(
|
||||
access_token.primary_id(),
|
||||
Collection::Principal,
|
||||
0,
|
||||
Property::Parameters,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(params) => {
|
||||
let ec = params
|
||||
.map(|params| {
|
||||
let method = params.method;
|
||||
let algo = params.algo;
|
||||
let mut certs = Vec::new();
|
||||
certs.extend_from_slice(b"-----STALWART CERTIFICATE-----\r\n");
|
||||
let _ = base64_encode_mime(
|
||||
&Bincode::new(params).serialize(),
|
||||
&mut certs,
|
||||
false,
|
||||
);
|
||||
certs.extend_from_slice(b"\r\n");
|
||||
let certs = String::from_utf8(certs).unwrap_or_default();
|
||||
.await?;
|
||||
let ec = params
|
||||
.map(|params| {
|
||||
let method = params.method;
|
||||
let algo = params.algo;
|
||||
let mut certs = Vec::new();
|
||||
certs.extend_from_slice(b"-----STALWART CERTIFICATE-----\r\n");
|
||||
let _ = base64_encode_mime(&Bincode::new(params).serialize(), &mut certs, false);
|
||||
certs.extend_from_slice(b"\r\n");
|
||||
let certs = String::from_utf8(certs).unwrap_or_default();
|
||||
|
||||
match method {
|
||||
EncryptionMethod::PGP => EncryptionType::PGP { algo, certs },
|
||||
EncryptionMethod::SMIME => EncryptionType::SMIME { algo, certs },
|
||||
}
|
||||
})
|
||||
.unwrap_or(EncryptionType::Disabled);
|
||||
match method {
|
||||
EncryptionMethod::PGP => EncryptionType::PGP { algo, certs },
|
||||
EncryptionMethod::SMIME => EncryptionType::SMIME { algo, certs },
|
||||
}
|
||||
})
|
||||
.unwrap_or(EncryptionType::Disabled);
|
||||
|
||||
JsonResponse::new(json!({
|
||||
"data": ec,
|
||||
}))
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
context = "store",
|
||||
event = "error",
|
||||
reason = ?err,
|
||||
"Database error while fetching encryption parameters"
|
||||
);
|
||||
|
||||
RequestError::internal_server_error().into_http_response()
|
||||
}
|
||||
}
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": ec,
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
|
||||
pub async fn handle_crypto_post(
|
||||
&self,
|
||||
access_token: Arc<AccessToken>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> HttpResponse {
|
||||
let request =
|
||||
match serde_json::from_slice::<EncryptionType>(body.as_deref().unwrap_or_default()) {
|
||||
Ok(request) => request,
|
||||
Err(err) => return err.into_http_response(),
|
||||
};
|
||||
) -> trc::Result<HttpResponse> {
|
||||
let request = serde_json::from_slice::<EncryptionType>(body.as_deref().unwrap_or_default())
|
||||
.map_err(|err| trc::ResourceCause::BadParameters.into_err().reason(err))?;
|
||||
|
||||
let (method, algo, certs) = match request {
|
||||
EncryptionType::PGP { algo, certs } => (EncryptionMethod::PGP, algo, certs),
|
||||
|
@ -699,32 +683,27 @@ impl JMAP {
|
|||
.with_collection(Collection::Principal)
|
||||
.update_document(0)
|
||||
.value(Property::Parameters, (), F_VALUE | F_CLEAR);
|
||||
return match self.core.storage.data.write(batch.build()).await {
|
||||
Ok(_) => JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
};
|
||||
self.core.storage.data.write(batch.build()).await?;
|
||||
return Ok(JsonResponse::new(json!({
|
||||
"data": (),
|
||||
}))
|
||||
.into_http_response());
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure Encryption is enabled
|
||||
if !self.core.jmap.encrypt {
|
||||
return ManagementApiError::Unsupported {
|
||||
details: "Encryption-at-rest has been disabled by the system administrator".into(),
|
||||
}
|
||||
.into_http_response();
|
||||
return Err(manage::unsupported(
|
||||
"Encryption-at-rest has been disabled by the system administrator",
|
||||
));
|
||||
}
|
||||
|
||||
// Parse certificates
|
||||
let params = match try_parse_certs(method, certs.into_bytes()) {
|
||||
Ok(certs) => EncryptionParams {
|
||||
method,
|
||||
algo,
|
||||
certs,
|
||||
},
|
||||
Err(err) => return ManagementApiError::from(err).into_http_response(),
|
||||
let params = EncryptionParams {
|
||||
method,
|
||||
algo,
|
||||
certs: try_parse_certs(method, certs.into_bytes())
|
||||
.map_err(|err| manage::error(err, None::<u32>))?,
|
||||
};
|
||||
|
||||
// Try a test encryption
|
||||
|
@ -734,7 +713,7 @@ impl JMAP {
|
|||
.encrypt(¶ms)
|
||||
.await
|
||||
{
|
||||
return ManagementApiError::from(message).into_http_response();
|
||||
return Err(manage::error(message, None::<u32>));
|
||||
}
|
||||
|
||||
// Save encryption params
|
||||
|
@ -745,13 +724,12 @@ impl JMAP {
|
|||
.with_collection(Collection::Principal)
|
||||
.update_document(0)
|
||||
.value(Property::Parameters, ¶ms, F_VALUE);
|
||||
match self.core.storage.data.write(batch.build()).await {
|
||||
Ok(_) => JsonResponse::new(json!({
|
||||
"data": num_certs,
|
||||
}))
|
||||
.into_http_response(),
|
||||
Err(err) => err.into_http_response(),
|
||||
}
|
||||
self.core.storage.data.write(batch.build()).await?;
|
||||
|
||||
Ok(JsonResponse::new(json!({
|
||||
"data": num_certs,
|
||||
}))
|
||||
.into_http_response())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -337,7 +337,8 @@ impl JMAP {
|
|||
return Ok(());
|
||||
}
|
||||
let reference_cid = self.inner.snowflake_id.past_id(period).ok_or_else(|| {
|
||||
trc::Cause::Unexpected
|
||||
trc::StoreCause::Unexpected
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Reason, "Failed to generate reference cid.")
|
||||
})?;
|
||||
|
|
|
@ -129,7 +129,7 @@ impl JMAP {
|
|||
response.created.append(id, email.into());
|
||||
}
|
||||
Err(mut err) => match err.as_ref() {
|
||||
trc::Cause::OverQuota => {
|
||||
trc::Cause::Limit(trc::LimitCause::Quota) => {
|
||||
response.not_created.append(
|
||||
id,
|
||||
SetError::new(SetErrorType::OverQuota)
|
||||
|
|
|
@ -84,7 +84,7 @@ impl JMAP {
|
|||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
return Err(trc::Cause::OverQuota.into_err());
|
||||
return Err(trc::LimitCause::Quota.into_err());
|
||||
}
|
||||
|
||||
// Parse message
|
||||
|
@ -224,7 +224,10 @@ impl JMAP {
|
|||
}
|
||||
}
|
||||
Err(EncryptMessageError::Error(err)) => {
|
||||
trc::bail!(trc::Cause::Crypto.caused_by(trc::location!()).reason(err));
|
||||
trc::bail!(trc::StoreCause::Crypto
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.reason(err));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -494,7 +497,7 @@ impl JMAP {
|
|||
|
||||
match self.core.storage.data.write(batch.build()).await {
|
||||
Ok(_) => return Ok(Some(thread_id)),
|
||||
Err(err) if err.matches(trc::Cause::AssertValue) && try_count < MAX_RETRIES => {
|
||||
Err(err) if err.is_assertion_failure() && try_count < MAX_RETRIES => {
|
||||
let backoff = rand::thread_rng().gen_range(50..=300);
|
||||
tokio::time::sleep(Duration::from_millis(backoff)).await;
|
||||
try_count += 1;
|
||||
|
|
|
@ -726,7 +726,7 @@ impl JMAP {
|
|||
Ok(message) => {
|
||||
response.created.insert(id, message.into());
|
||||
}
|
||||
Err(err) if err.matches(trc::Cause::OverQuota) => {
|
||||
Err(err) if err.matches(trc::Cause::Limit(trc::LimitCause::Quota)) => {
|
||||
response.not_created.append(
|
||||
id,
|
||||
SetError::new(SetErrorType::OverQuota)
|
||||
|
@ -962,7 +962,7 @@ impl JMAP {
|
|||
// Add to updated list
|
||||
response.updated.append(id, None);
|
||||
}
|
||||
Err(err) if err.matches(trc::Cause::AssertValue) => {
|
||||
Err(err) if err.is_assertion_failure() => {
|
||||
response.not_updated.append(
|
||||
id,
|
||||
SetError::forbidden().with_description(
|
||||
|
|
|
@ -128,7 +128,7 @@ impl JMAP {
|
|||
ctx.mailbox_ids.insert(document_id);
|
||||
ctx.response.created(id, document_id);
|
||||
}
|
||||
Err(err) if err.matches(trc::Cause::AssertValue) => {
|
||||
Err(err) if err.is_assertion_failure() => {
|
||||
ctx.response.not_created.append(
|
||||
id,
|
||||
SetError::forbidden().with_description(
|
||||
|
@ -221,7 +221,7 @@ impl JMAP {
|
|||
Ok(_) => {
|
||||
changes.log_update(Collection::Mailbox, document_id);
|
||||
}
|
||||
Err(err) if err.matches(trc::Cause::AssertValue) => {
|
||||
Err(err) if err.is_assertion_failure() => {
|
||||
ctx.response.not_updated.append(id, SetError::forbidden().with_description(
|
||||
"Another process modified this mailbox, please try again.",
|
||||
));
|
||||
|
@ -391,7 +391,7 @@ impl JMAP {
|
|||
Collection::Email,
|
||||
Id::from_parts(thread_id, message_id),
|
||||
),
|
||||
Err(err) if err.matches(trc::Cause::AssertValue) => {
|
||||
Err(err) if err.is_assertion_failure() => {
|
||||
return Ok(Err(SetError::forbidden().with_description(
|
||||
concat!(
|
||||
"Another process modified a message in this mailbox ",
|
||||
|
@ -469,7 +469,7 @@ impl JMAP {
|
|||
changes.log_delete(Collection::Mailbox, document_id);
|
||||
Ok(Ok(did_remove_emails))
|
||||
}
|
||||
Err(err) if err.matches(trc::Cause::AssertValue) => Ok(Err(SetError::forbidden()
|
||||
Err(err) if err.is_assertion_failure() => Ok(Err(SetError::forbidden()
|
||||
.with_description(concat!(
|
||||
"Another process modified this mailbox ",
|
||||
"while deleting it, please try again."
|
||||
|
|
|
@ -128,7 +128,8 @@ impl JMAP {
|
|||
})
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
trc::Cause::NotFound
|
||||
trc::StoreCause::NotFound
|
||||
.into_err()
|
||||
.caused_by(trc::location!())
|
||||
.document_id(document_id)
|
||||
})?;
|
||||
|
@ -138,7 +139,7 @@ impl JMAP {
|
|||
.get(&Property::Expires)
|
||||
.and_then(|p| p.as_date())
|
||||
.ok_or_else(|| {
|
||||
trc::Cause::Unexpected
|
||||
trc::StoreCause::Unexpected
|
||||
.caused_by(trc::location!())
|
||||
.document_id(document_id)
|
||||
})?
|
||||
|
@ -174,7 +175,7 @@ impl JMAP {
|
|||
.remove(&Property::Value)
|
||||
.and_then(|p| p.try_unwrap_string())
|
||||
.ok_or_else(|| {
|
||||
trc::Cause::Unexpected
|
||||
trc::StoreCause::Unexpected
|
||||
.caused_by(trc::location!())
|
||||
.document_id(document_id)
|
||||
})?;
|
||||
|
@ -183,7 +184,7 @@ impl JMAP {
|
|||
.remove(&Property::Url)
|
||||
.and_then(|p| p.try_unwrap_string())
|
||||
.ok_or_else(|| {
|
||||
trc::Cause::Unexpected
|
||||
trc::StoreCause::Unexpected
|
||||
.caused_by(trc::location!())
|
||||
.document_id(document_id)
|
||||
})?;
|
||||
|
|
|
@ -222,7 +222,7 @@ impl JMAP {
|
|||
.set(event.value_class(), (now() + INDEX_LOCK_EXPIRY).serialize());
|
||||
match self.core.storage.data.write(batch.build()).await {
|
||||
Ok(_) => true,
|
||||
Err(err) if err.matches(trc::Cause::AssertValue) => {
|
||||
Err(err) if err.is_assertion_failure() => {
|
||||
tracing::trace!(
|
||||
context = "queue",
|
||||
event = "locked",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue