server: add functioning download attachment handler
This commit is contained in:
parent
ff1c3f5791
commit
9a5dc20f83
@ -91,23 +91,24 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for InlineAttachmentResponder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DownloadPartResponder {
|
struct DownloadAttachmentResponder(Attachment);
|
||||||
bytes: Vec<u8>,
|
|
||||||
filename: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r, 'o: 'r> Responder<'r, 'o> for DownloadPartResponder {
|
impl<'r, 'o: 'r> Responder<'r, 'o> for DownloadAttachmentResponder {
|
||||||
fn respond_to(self, _: &'r Request<'_>) -> rocket::response::Result<'o> {
|
fn respond_to(self, _: &'r Request<'_>) -> rocket::response::Result<'o> {
|
||||||
let mut resp = Response::build();
|
let mut resp = Response::build();
|
||||||
if let Some(filename) = self.filename {
|
if let Some(filename) = self.0.filename {
|
||||||
info!("filename {:?}", filename);
|
info!("filename {:?}", filename);
|
||||||
resp.header(Header::new(
|
resp.header(Header::new(
|
||||||
"Content-Disposition",
|
"Content-Disposition",
|
||||||
format!(r#"attachment; filename="{}""#, filename),
|
format!(r#"attachment; filename="{}""#, filename),
|
||||||
))
|
));
|
||||||
.header(ContentType::Binary);
|
|
||||||
}
|
}
|
||||||
resp.sized_body(self.bytes.len(), Cursor::new(self.bytes))
|
if let Some(content_type) = self.0.content_type {
|
||||||
|
if let Some(ct) = ContentType::parse_flexible(&content_type) {
|
||||||
|
resp.header(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.sized_body(self.0.bytes.len(), Cursor::new(self.0.bytes))
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,7 +130,6 @@ async fn view_attachment(
|
|||||||
.map(|s| s.parse().expect("not a usize"))
|
.map(|s| s.parse().expect("not a usize"))
|
||||||
.collect();
|
.collect();
|
||||||
let attachment = attachment_bytes(nm, &mid, &idx)?;
|
let attachment = attachment_bytes(nm, &mid, &idx)?;
|
||||||
// TODO: plumb Content-Type, or just create wrappers for serving the Attachment type
|
|
||||||
Ok(InlineAttachmentResponder(attachment))
|
Ok(InlineAttachmentResponder(attachment))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ async fn download_attachment(
|
|||||||
nm: &State<Notmuch>,
|
nm: &State<Notmuch>,
|
||||||
id: &str,
|
id: &str,
|
||||||
idx: &str,
|
idx: &str,
|
||||||
) -> Result<DownloadPartResponder, Debug<ServerError>> {
|
) -> Result<DownloadAttachmentResponder, Debug<ServerError>> {
|
||||||
let mid = if id.starts_with("id:") {
|
let mid = if id.starts_with("id:") {
|
||||||
id.to_string()
|
id.to_string()
|
||||||
} else {
|
} else {
|
||||||
@ -150,30 +150,7 @@ async fn download_attachment(
|
|||||||
.map(|s| s.parse().expect("not a usize"))
|
.map(|s| s.parse().expect("not a usize"))
|
||||||
.collect();
|
.collect();
|
||||||
let attachment = attachment_bytes(nm, &mid, &idx)?;
|
let attachment = attachment_bytes(nm, &mid, &idx)?;
|
||||||
// TODO(wathiede): use walk_attachments from graphql to fill this out
|
Ok(DownloadAttachmentResponder(attachment))
|
||||||
Ok(DownloadPartResponder {
|
|
||||||
bytes: attachment.bytes,
|
|
||||||
filename: attachment.filename,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/original/<id>/part/<part>")]
|
|
||||||
async fn original_part(
|
|
||||||
nm: &State<Notmuch>,
|
|
||||||
id: &str,
|
|
||||||
part: usize,
|
|
||||||
) -> Result<DownloadPartResponder, Debug<NotmuchError>> {
|
|
||||||
let mid = if id.starts_with("id:") {
|
|
||||||
id.to_string()
|
|
||||||
} else {
|
|
||||||
format!("id:{}", id)
|
|
||||||
};
|
|
||||||
let meta = nm.show_part(&mid, part)?;
|
|
||||||
let res = nm.show_original_part(&mid, part)?;
|
|
||||||
Ok(DownloadPartResponder {
|
|
||||||
bytes: res,
|
|
||||||
filename: meta.filename,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/original/<id>")]
|
#[get("/original/<id>")]
|
||||||
@ -240,7 +217,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
routes![
|
routes![
|
||||||
original_part,
|
|
||||||
original,
|
original,
|
||||||
refresh,
|
refresh,
|
||||||
search_all,
|
search_all,
|
||||||
|
|||||||
@ -86,8 +86,10 @@ pub struct Message {
|
|||||||
// X-Attachment-Id: f_lponoluo1
|
// X-Attachment-Id: f_lponoluo1
|
||||||
#[derive(Default, Debug, SimpleObject)]
|
#[derive(Default, Debug, SimpleObject)]
|
||||||
pub struct Attachment {
|
pub struct Attachment {
|
||||||
|
pub id: String,
|
||||||
|
pub idx: String,
|
||||||
pub filename: Option<String>,
|
pub filename: Option<String>,
|
||||||
pub size: Option<usize>,
|
pub size: usize,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
pub content_id: Option<String>,
|
pub content_id: Option<String>,
|
||||||
pub disposition: DispositionType,
|
pub disposition: DispositionType,
|
||||||
@ -375,7 +377,7 @@ impl QueryRoot {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
// TODO(wathiede): parse message and fill out attachments
|
// TODO(wathiede): parse message and fill out attachments
|
||||||
let attachments = extract_attachments(&m)?;
|
let attachments = extract_attachments(&m, &id)?;
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
id,
|
id,
|
||||||
from,
|
from,
|
||||||
@ -656,17 +658,17 @@ fn walk_attachments<T, F: Fn(&ParsedMail, &[usize]) -> Option<T>>(
|
|||||||
// TODO(wathiede): make this walk_attachments that takes a closure.
|
// TODO(wathiede): make this walk_attachments that takes a closure.
|
||||||
// Then implement one closure for building `Attachment` and imlement another that can be used to
|
// Then implement one closure for building `Attachment` and imlement another that can be used to
|
||||||
// get the bytes for serving attachments of HTTP
|
// get the bytes for serving attachments of HTTP
|
||||||
fn extract_attachments(m: &ParsedMail) -> Result<Vec<Attachment>, Error> {
|
fn extract_attachments(m: &ParsedMail, id: &str) -> Result<Vec<Attachment>, Error> {
|
||||||
let mut attachments = Vec::new();
|
let mut attachments = Vec::new();
|
||||||
for sp in &m.subparts {
|
for (idx, sp) in m.subparts.iter().enumerate() {
|
||||||
if let Some(attachment) = extract_attachment(sp) {
|
if let Some(attachment) = extract_attachment(sp, id, &[idx]) {
|
||||||
attachments.push(attachment);
|
attachments.push(attachment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(attachments)
|
Ok(attachments)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_attachment(m: &ParsedMail) -> Option<Attachment> {
|
fn extract_attachment(m: &ParsedMail, id: &str, idx: &[usize]) -> Option<Attachment> {
|
||||||
let pcd = m.get_content_disposition();
|
let pcd = m.get_content_disposition();
|
||||||
// TODO: do we need to handle empty filename attachments, or should we change the definition of
|
// TODO: do we need to handle empty filename attachments, or should we change the definition of
|
||||||
// Attachment::filename?
|
// Attachment::filename?
|
||||||
@ -684,12 +686,15 @@ fn extract_attachment(m: &ParsedMail) -> Option<Attachment> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
return Some(Attachment {
|
return Some(Attachment {
|
||||||
|
id: id.to_string(),
|
||||||
|
idx: idx
|
||||||
|
.iter()
|
||||||
|
.map(|i| i.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("."),
|
||||||
disposition: pcd.disposition.into(),
|
disposition: pcd.disposition.into(),
|
||||||
filename: Some(filename),
|
filename: Some(filename),
|
||||||
size: pcd
|
size: bytes.len(),
|
||||||
.params
|
|
||||||
.get("size")
|
|
||||||
.map(|s| s.parse().unwrap_or_default()),
|
|
||||||
// TODO: what is the default for ctype?
|
// TODO: what is the default for ctype?
|
||||||
// TODO: do we want to use m.ctype.params for anything?
|
// TODO: do we want to use m.ctype.params for anything?
|
||||||
content_type: Some(m.ctype.mimetype.clone()),
|
content_type: Some(m.ctype.mimetype.clone()),
|
||||||
@ -841,7 +846,7 @@ pub fn attachment_bytes(nm: &Notmuch, id: &str, idx: &[usize]) -> Result<Attachm
|
|||||||
if let Some(attachment) = walk_attachments(&m, |sp, cur_idx| {
|
if let Some(attachment) = walk_attachments(&m, |sp, cur_idx| {
|
||||||
info!("checking {cur_idx:?}=={idx:?}");
|
info!("checking {cur_idx:?}=={idx:?}");
|
||||||
if cur_idx == idx {
|
if cur_idx == idx {
|
||||||
let attachment = extract_attachment(&sp).unwrap_or(Attachment {
|
let attachment = extract_attachment(&sp, id, idx).unwrap_or(Attachment {
|
||||||
..Attachment::default()
|
..Attachment::default()
|
||||||
});
|
});
|
||||||
return Some(attachment);
|
return Some(attachment);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user