summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-06-18 21:58:55 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-06-18 21:59:50 +0300
commitba1d0c42e0af56c5a74c3b506f0a952afa974872 (patch)
tree804b6e66470d1a46cdb94dea349f7c761aaf7777
parent43084eda01926908f884c3f17547568524e6e7e1 (diff)
melib: add struct and parser for mailto: links
-rw-r--r--melib/src/email.rs2
-rw-r--r--melib/src/email/compose.rs5
-rw-r--r--melib/src/email/mailto.rs148
-rw-r--r--melib/src/email/parser.rs78
4 files changed, 232 insertions, 1 deletions
diff --git a/melib/src/email.rs b/melib/src/email.rs
index b5eb5800..9c3c68b3 100644
--- a/melib/src/email.rs
+++ b/melib/src/email.rs
@@ -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::*;
diff --git a/melib/src/email/compose.rs b/melib/src/email/compose.rs
index fdfb6099..af785cc2 100644
--- a/melib/src/email/compose.rs
+++ b/melib/src/email/compose.rs
@@ -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(
diff --git a/melib/src/email/mailto.rs b/melib/src/email/mailto.rs
new file mode 100644
index 00000000..890fbcc8
--- /dev/null
+++ b/melib/src/email/mailto.rs
@@ -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")
+ )
+ );
+ }
+}
diff --git a/melib/src/email/parser.rs b/melib/src/email/parser.rs
index cba49644..4d2e376b 100644
--- a/melib/src/email/parser.rs
+++ b/melib/src/email/parser.rs
@@ -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 {