melib: add struct and parser for mailto: links
parent
43084eda01
commit
ba1d0c42e0
|
@ -26,6 +26,8 @@ use fnv::FnvHashMap;
|
||||||
mod compose;
|
mod compose;
|
||||||
pub use self::compose::*;
|
pub use self::compose::*;
|
||||||
|
|
||||||
|
mod mailto;
|
||||||
|
pub use mailto::*;
|
||||||
mod attachment_types;
|
mod attachment_types;
|
||||||
pub mod attachments;
|
pub mod attachments;
|
||||||
pub use crate::attachments::*;
|
pub use crate::attachments::*;
|
||||||
|
|
|
@ -127,6 +127,11 @@ impl Draft {
|
||||||
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
pub fn set_header(&mut self, header: &str, value: String) {
|
||||||
|
if self.headers.insert(header.to_string(), value).is_none() {
|
||||||
|
self.header_order.push(header.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn new_reply(envelope: &Envelope, bytes: &[u8]) -> Self {
|
pub fn new_reply(envelope: &Envelope, bytes: &[u8]) -> Self {
|
||||||
let mut ret = Draft::default();
|
let mut ret = Draft::default();
|
||||||
ret.headers_mut().insert(
|
ret.headers_mut().insert(
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* meli - parser module
|
||||||
|
*
|
||||||
|
* Copyright 2019 Manos Pitsidianakis
|
||||||
|
*
|
||||||
|
* This file is part of meli.
|
||||||
|
*
|
||||||
|
* meli is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* meli is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Mailto {
|
||||||
|
pub address: Address,
|
||||||
|
pub subject: Option<String>,
|
||||||
|
pub cc: Option<String>,
|
||||||
|
pub bcc: Option<String>,
|
||||||
|
pub body: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Mailto> for Draft {
|
||||||
|
fn from(val: Mailto) -> Self {
|
||||||
|
let mut ret = Draft::default();
|
||||||
|
let Mailto {
|
||||||
|
address,
|
||||||
|
subject,
|
||||||
|
cc,
|
||||||
|
bcc,
|
||||||
|
body,
|
||||||
|
} = val;
|
||||||
|
ret.set_header("Subject", subject.unwrap_or(String::new()));
|
||||||
|
ret.set_header("Cc", cc.unwrap_or(String::new()));
|
||||||
|
ret.set_header("Bcc", bcc.unwrap_or(String::new()));
|
||||||
|
ret.set_body(body.unwrap_or(String::new()));
|
||||||
|
ret.set_header("To", address.to_string());
|
||||||
|
debug!(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for Mailto {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
|
||||||
|
let parse_res = super::parser::mailto(value).to_full_result();
|
||||||
|
if parse_res.is_ok() {
|
||||||
|
Ok(parse_res.unwrap())
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"parser::mailto returned error while parsing {}:\n{:?}",
|
||||||
|
String::from_utf8_lossy(value),
|
||||||
|
parse_res.as_ref().err().unwrap()
|
||||||
|
);
|
||||||
|
Err(format!("{:?}", parse_res.err().unwrap()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mailto() {
|
||||||
|
let test_address = super::parser::address(b"info@example.com")
|
||||||
|
.to_full_result()
|
||||||
|
.unwrap();
|
||||||
|
let mailto = Mailto::try_from(&b"mailto:info@example.com?subject=email%20subject"[0..])
|
||||||
|
.expect("Could not parse mailto link.");
|
||||||
|
let Mailto {
|
||||||
|
ref address,
|
||||||
|
ref subject,
|
||||||
|
ref cc,
|
||||||
|
ref bcc,
|
||||||
|
ref body,
|
||||||
|
} = mailto;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
address,
|
||||||
|
subject.as_ref().map(String::as_str),
|
||||||
|
cc.as_ref().map(String::as_str),
|
||||||
|
bcc.as_ref().map(String::as_str),
|
||||||
|
body.as_ref().map(String::as_str),
|
||||||
|
),
|
||||||
|
(&test_address, Some("email%20subject"), None, None, None)
|
||||||
|
);
|
||||||
|
let mailto = Mailto::try_from(&b"mailto:info@example.com?cc=8cc9@example.com"[0..])
|
||||||
|
.expect("Could not parse mailto link.");
|
||||||
|
let Mailto {
|
||||||
|
ref address,
|
||||||
|
ref subject,
|
||||||
|
ref cc,
|
||||||
|
ref bcc,
|
||||||
|
ref body,
|
||||||
|
} = mailto;
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
address,
|
||||||
|
subject.as_ref().map(String::as_str),
|
||||||
|
cc.as_ref().map(String::as_str),
|
||||||
|
bcc.as_ref().map(String::as_str),
|
||||||
|
body.as_ref().map(String::as_str),
|
||||||
|
),
|
||||||
|
(&test_address, None, Some("8cc9@example.com"), None, None)
|
||||||
|
);
|
||||||
|
let mailto = Mailto::try_from(
|
||||||
|
&b"mailto:info@example.com?bcc=7bcc8@example.com&body=line%20first%0Abut%20not%0Alast"
|
||||||
|
[0..],
|
||||||
|
)
|
||||||
|
.expect("Could not parse mailto link.");
|
||||||
|
let Mailto {
|
||||||
|
ref address,
|
||||||
|
ref subject,
|
||||||
|
ref cc,
|
||||||
|
ref bcc,
|
||||||
|
ref body,
|
||||||
|
} = mailto;
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
address,
|
||||||
|
subject.as_ref().map(String::as_str),
|
||||||
|
cc.as_ref().map(String::as_str),
|
||||||
|
bcc.as_ref().map(String::as_str),
|
||||||
|
body.as_ref().map(String::as_str),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&test_address,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some("7bcc8@example.com"),
|
||||||
|
Some("line first\nbut not\nlast")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -530,7 +530,7 @@ fn group(input: &[u8]) -> IResult<&[u8], Address> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
named!(address<Address>, ws!(alt_complete!(mailbox | group)));
|
named!(pub address<Address>, ws!(alt_complete!(mailbox | group)));
|
||||||
|
|
||||||
named!(pub rfc2822address_list<Vec<Address>>, ws!( separated_list!(is_a!(","), address)));
|
named!(pub rfc2822address_list<Vec<Address>>, ws!( separated_list!(is_a!(","), address)));
|
||||||
|
|
||||||
|
@ -788,6 +788,82 @@ pub fn phrase(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||||
IResult::Done(&input[ptr..], acc)
|
IResult::Done(&input[ptr..], acc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
named!(pub angle_bracket_delimeted_list<Vec<&[u8]>>, separated_nonempty_list!(complete!(is_a!(",")), ws!(complete!(message_id))));
|
||||||
|
|
||||||
|
pub fn mailto(mut input: &[u8]) -> IResult<&[u8], Mailto> {
|
||||||
|
if !input.starts_with(b"mailto:") {
|
||||||
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
||||||
|
}
|
||||||
|
|
||||||
|
input = &input[b"mailto:".len()..];
|
||||||
|
|
||||||
|
let end = input.iter().position(|e| *e == b'?').unwrap_or(input.len());
|
||||||
|
let address: Address;
|
||||||
|
|
||||||
|
if let IResult::Done(rest, addr) = crate::email::parser::address(&input[..end]) {
|
||||||
|
address = addr;
|
||||||
|
input = if input[end..].is_empty() {
|
||||||
|
&input[end..]
|
||||||
|
} else {
|
||||||
|
&input[end + 1..]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut subject = None;
|
||||||
|
let mut cc = None;
|
||||||
|
let mut bcc = None;
|
||||||
|
let mut body = None;
|
||||||
|
while !input.is_empty() {
|
||||||
|
let tag = if let Some(tag_pos) = input.iter().position(|e| *e == b'=') {
|
||||||
|
let ret = &input[0..tag_pos];
|
||||||
|
input = &input[tag_pos + 1..];
|
||||||
|
ret
|
||||||
|
} else {
|
||||||
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let value_end = input.iter().position(|e| *e == b'&').unwrap_or(input.len());
|
||||||
|
|
||||||
|
let value = String::from_utf8_lossy(&input[..value_end]).to_string();
|
||||||
|
match tag {
|
||||||
|
b"subject" if subject.is_none() => {
|
||||||
|
subject = Some(value);
|
||||||
|
}
|
||||||
|
b"cc" if cc.is_none() => {
|
||||||
|
cc = Some(value);
|
||||||
|
}
|
||||||
|
b"bcc" if bcc.is_none() => {
|
||||||
|
bcc = Some(value);
|
||||||
|
}
|
||||||
|
b"body" if body.is_none() => {
|
||||||
|
/* FIXME:
|
||||||
|
* Parse escaped characters properly.
|
||||||
|
*/
|
||||||
|
body = Some(value.replace("%20", " ").replace("%0A", "\n"));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if input[value_end..].is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
input = &input[value_end + 1..];
|
||||||
|
}
|
||||||
|
IResult::Done(
|
||||||
|
input,
|
||||||
|
Mailto {
|
||||||
|
address,
|
||||||
|
subject,
|
||||||
|
cc,
|
||||||
|
bcc,
|
||||||
|
body,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue