Initial commit

This commit is contained in:
Maurus Decimus
2026-05-29 18:02:15 +02:00
commit 576073f8c9
263 changed files with 78638 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*/
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use crate::db::blobs;
use crate::error::Error;
use crate::exchange_ews::error::EwsError;
use crate::exchange_ews::parse::parse_get_attachment_inline;
use crate::exchange_ews::xml::get_attachment_body;
use super::items::ItemRunCtx;
#[derive(Debug, Clone)]
pub struct FetchedAttachment {
pub attachment_id: String,
pub bytes: Vec<u8>,
pub media_type: String,
}
pub fn fetch_contact_photo(
conn: &rusqlite::Connection,
ctx: &ItemRunCtx<'_>,
attachment_id: &str,
) -> Result<Option<(i64, String)>, Error> {
let Some(att) = fetch_attachment_bytes(ctx, attachment_id).map_err(Error::from)? else {
return Ok(None);
};
let blob_id = intern_attachment(conn, &att.bytes)?;
Ok(Some((blob_id, att.media_type)))
}
pub fn fetch_attachment_bytes(
ctx: &ItemRunCtx<'_>,
attachment_id: &str,
) -> Result<Option<FetchedAttachment>, EwsError> {
let mut out = fetch_attachments(ctx, &[attachment_id])?;
Ok(out.pop())
}
pub fn fetch_attachments(
ctx: &ItemRunCtx<'_>,
attachment_ids: &[&str],
) -> Result<Vec<FetchedAttachment>, EwsError> {
if attachment_ids.is_empty() {
return Ok(Vec::new());
}
let batch = ctx.attachment_batch.max(1);
let mut out: Vec<FetchedAttachment> = Vec::with_capacity(attachment_ids.len());
for chunk in attachment_ids.chunks(batch) {
let body = get_attachment_body(chunk);
let resp = ctx.client.call(ctx.url, "GetAttachment", &body)?;
let inline = parse_get_attachment_inline(&resp.body)?;
for att in inline {
let cleaned = strip_ascii_whitespace(att.content_base64.as_bytes());
if cleaned.is_empty() {
continue;
}
let bytes = STANDARD.decode(&cleaned).map_err(|e| {
EwsError::Malformed(format!("attachment {}: base64: {e}", att.attachment_id))
})?;
let media_type = att
.content_type
.unwrap_or_else(|| "application/octet-stream".to_owned());
out.push(FetchedAttachment {
attachment_id: att.attachment_id,
bytes,
media_type,
});
}
}
Ok(out)
}
pub fn intern_attachment(conn: &rusqlite::Connection, bytes: &[u8]) -> Result<i64, Error> {
blobs::intern_blob(conn, bytes).map_err(|e| Error::Partial(e.to_string()))
}
fn strip_ascii_whitespace(input: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(input.len());
for b in input {
if !matches!(b, b' ' | b'\t' | b'\n' | b'\r') {
out.push(*b);
}
}
out
}
#[cfg(test)]
mod tests {
use super::strip_ascii_whitespace;
#[test]
fn strips_whitespace_efficiently() {
let input = b"AB CD\nEF\tGH\r\nIJ";
let cleaned = strip_ascii_whitespace(input);
assert_eq!(cleaned, b"ABCDEFGHIJ");
}
}