melib: add struct and parser for mailto: links

embed
Manos Pitsidianakis 2019-06-18 21:58:55 +03:00
parent 43084eda01
commit ba1d0c42e0
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
4 changed files with 232 additions and 1 deletions

View File

@ -26,6 +26,8 @@ use fnv::FnvHashMap;
mod compose;
pub use self::compose::*;
mod mailto;
pub use mailto::*;
mod attachment_types;
pub mod attachments;
pub use crate::attachments::*;

View File

@ -127,6 +127,11 @@ impl Draft {
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 {
let mut ret = Draft::default();
ret.headers_mut().insert(

View File

@ -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")
)
);
}
}

View File

@ -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)));
@ -788,6 +788,82 @@ pub fn phrase(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
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)]
mod tests {