diff --git a/crates/jmap/src/api/management/queue.rs b/crates/jmap/src/api/management/queue.rs index f7291ed3..51b155d9 100644 --- a/crates/jmap/src/api/management/queue.rs +++ b/crates/jmap/src/api/management/queue.rs @@ -23,6 +23,7 @@ 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::{ @@ -61,6 +62,7 @@ pub struct Message { pub priority: i16, #[serde(skip_serializing_if = "Option::is_none")] pub env_id: Option, + pub blob_hash: String, } #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)] @@ -559,6 +561,7 @@ impl From<&queue::Message> for Message { expires: DateTime::from_timestamp(domain.expires as i64), }) .collect(), + blob_hash: URL_SAFE_NO_PAD.encode::<&[u8]>(message.blob_hash.as_ref()), } } } diff --git a/crates/jmap/src/api/management/stores.rs b/crates/jmap/src/api/management/stores.rs index 23511200..36eba6fb 100644 --- a/crates/jmap/src/api/management/stores.rs +++ b/crates/jmap/src/api/management/stores.rs @@ -21,19 +21,61 @@ * for more details. */ +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; +use common::manager::webadmin::Resource; use hyper::Method; use jmap_proto::error::request::RequestError; use serde_json::json; +use utils::url_params::UrlParams; use crate::{ api::{http::ToHttpResponse, HttpRequest, HttpResponse, JsonResponse}, JMAP, }; +use super::decode_path_element; + impl JMAP { pub async fn handle_manage_store(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse { - match (path.get(1).copied(), req.method()) { - (Some("maintenance"), &Method::GET) => { + match (path.get(1).copied(), path.get(2).copied(), 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 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(), + } + } + (Some("maintenance"), _, &Method::GET) => { match self .core .storage