melib: add struct and parser for mailto: links
parent
43084eda01
commit
ba1d0c42e0
|
@ -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::*;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)));
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
|
|
Loading…
Reference in New Issue