From 689327651f150b4d10a665e8640e496d43f08124 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 27 Nov 2019 01:37:27 +0200 Subject: [PATCH] melib/vcard: add parser for vcard files --- melib/src/addressbook/vcard.rs | 96 ++++++++++++++++++++++++++++++++-- melib/src/parsec.rs | 34 ++++++++++++ melib/src/structs.rs | 4 +- 3 files changed, 128 insertions(+), 6 deletions(-) diff --git a/melib/src/addressbook/vcard.rs b/melib/src/addressbook/vcard.rs index 38d498f2..65488246 100644 --- a/melib/src/addressbook/vcard.rs +++ b/melib/src/addressbook/vcard.rs @@ -23,11 +23,17 @@ use super::*; use crate::chrono::TimeZone; use crate::error::{MeliError, Result}; +use crate::parsec::{match_literal_anycase, one_or_more, peek, prefix, take_until, Parser}; use fnv::FnvHashMap; +use std::convert::TryInto; /* Supported vcard versions */ pub trait VCardVersion: core::fmt::Debug {} +#[derive(Debug)] +pub struct VCardVersionUnknown; +impl VCardVersion for VCardVersionUnknown {} + /// https://tools.ietf.org/html/rfc6350 #[derive(Debug)] pub struct VCardVersion4; @@ -40,7 +46,7 @@ impl VCardVersion for VCardVersion3 {} pub struct CardDeserializer; -static HEADER: &'static str = "BEGIN:VCARD\r\nVERSION:4.0\r\n"; +static HEADER: &'static str = "BEGIN:VCARD\r\n"; //VERSION:4.0\r\n"; static FOOTER: &'static str = "END:VCARD\r\n"; #[derive(Debug)] @@ -106,13 +112,18 @@ impl CardDeserializer { el.params.push(l[value_start..i].to_string()); value_start = i + 1; } + (b';', Stage::Name) => { + name = l[value_start..i].to_string(); + value_start = i + 1; + stage = Stage::Param; + } (b':', Stage::Group) | (b':', Stage::Name) => { name = l[value_start..i].to_string(); has_colon = true; value_start = i + 1; stage = Stage::Value; } - (b':', Stage::Param) if l.as_bytes()[i] != b'\\' => { + (b':', Stage::Param) if l.as_bytes()[i.saturating_sub(1)] != b'\\' => { el.params.push(l[value_start..i].to_string()); has_colon = true; value_start = i + 1; @@ -121,7 +132,6 @@ impl CardDeserializer { _ => {} } } - el.value = l[value_start..].to_string(); if !has_colon { return Err(MeliError::new(format!( "Error while parsing vcard: error at line {}, no colon. {:?}", @@ -134,13 +144,14 @@ impl CardDeserializer { l, el ))); } + el.value = l[value_start..].replace("\\:", ":"); ret.insert(name, el); } Ok(VCard(ret, std::marker::PhantomData::<*const VCardVersion4>)) } } -impl std::convert::TryInto for VCard { +impl TryInto for VCard { type Error = crate::error::MeliError; fn try_into(mut self) -> crate::error::Result { @@ -203,6 +214,9 @@ impl std::convert::TryInto for VCard { card.set_key(val.value); } for (k, v) in self.0.into_iter() { + if k.eq_ignore_ascii_case("VERSION") || k.eq_ignore_ascii_case("N") { + continue; + } card.set_extra_property(&k, v.value); } @@ -210,6 +224,80 @@ impl std::convert::TryInto for VCard { } } +fn parse_card<'a>() -> impl Parser<'a, Vec<&'a str>> { + move |input| { + one_or_more(prefix( + peek(match_literal_anycase(HEADER)), + take_until(match_literal_anycase(FOOTER)), + )) + .parse(input) + } +} + +#[test] +fn test_load_cards() { + /* + let mut contents = String::with_capacity(256); + let p = &std::path::Path::new("/tmp/contacts.vcf"); + use std::io::Read; + contents.clear(); + std::fs::File::open(&p) + .unwrap() + .read_to_string(&mut contents) + .unwrap(); + for s in parse_card().parse(contents.as_str()).unwrap().1 { + println!(""); + println!("{}", s); + println!("{:?}", CardDeserializer::from_str(s)); + println!(""); + } + */ +} + +pub fn load_cards(p: &std::path::Path) -> Result> { + let vcf_dir = std::fs::read_dir(p); + let mut ret: Vec> = Vec::new(); + let mut is_any_valid = false; + if vcf_dir.is_ok() { + let mut contents = String::with_capacity(256); + for f in vcf_dir? { + if f.is_err() { + continue; + } + let f = f?.path(); + if f.is_file() { + use std::io::Read; + contents.clear(); + std::fs::File::open(&f)?.read_to_string(&mut contents)?; + if let Ok((_, c)) = parse_card().parse(contents.as_str()) { + for s in c { + ret.push( + CardDeserializer::from_str(s) + .and_then(TryInto::try_into) + .and_then(|mut card| { + Card::set_external_resource(&mut card, true); + is_any_valid = true; + Ok(card) + }), + ); + } + } + } + } + } + for c in &ret { + if c.is_err() { + debug!(&c); + } + } + if !is_any_valid { + ret.into_iter().collect::>>() + } else { + ret.retain(Result::is_ok); + ret.into_iter().collect::>>() + } +} + #[test] fn test_card() { let j = "BEGIN:VCARD\r\nVERSION:4.0\r\nN:Gump;Forrest;;Mr.;\r\nFN:Forrest Gump\r\nORG:Bubba Gump Shrimp Co.\r\nTITLE:Shrimp Man\r\nPHOTO;MEDIATYPE=image/gif:http://www.example.com/dir_photos/my_photo.gif\r\nTEL;TYPE=work,voice;VALUE=uri:tel:+1-111-555-1212\r\nTEL;TYPE=home,voice;VALUE=uri:tel:+1-404-555-1212\r\nADR;TYPE=WORK;PREF=1;LABEL=\"100 Waters Edge\\nBaytown\\, LA 30314\\nUnited States of America\":;;100 Waters Edge;Baytown;LA;30314;United States of America\r\nADR;TYPE=HOME;LABEL=\"42 Plantation St.\\nBaytown\\, LA 30314\\nUnited States of America\":;;42 Plantation St.;Baytown;LA;30314;United States of America\r\nEMAIL:forrestgump@example.com\r\nREV:20080424T195243Z\r\nx-qq:21588891\r\nEND:VCARD\r\n"; diff --git a/melib/src/parsec.rs b/melib/src/parsec.rs index a447ed54..9ac717b8 100644 --- a/melib/src/parsec.rs +++ b/melib/src/parsec.rs @@ -309,3 +309,37 @@ where Err(_) => Ok((input, None)), } } + +pub fn peek<'a, P, A>(parser: P) -> impl Parser<'a, A> +where + P: Parser<'a, A>, +{ + move |input| match parser.parse(input) { + Ok((_, result)) => Ok((input, result)), + e @ Err(_) => e, + } +} + +pub fn take_until<'a, A, P>(end: P) -> impl Parser<'a, &'a str> +where + P: Parser<'a, A>, +{ + move |input: &'a str| { + let mut offset = 0; + while !input[offset..].is_empty() { + if let Ok((rest, _)) = end.parse(&input[offset..]) { + return Ok(( + rest, + &input[..(offset + input[offset..].len() - rest.len())], + )); + } + while offset != input.len() { + offset += 1; + if input.is_char_boundary(offset) { + break; + } + } + } + Ok((&input[offset..], input)) + } +} diff --git a/melib/src/structs.rs b/melib/src/structs.rs index 569f7dab..ed9f82e4 100644 --- a/melib/src/structs.rs +++ b/melib/src/structs.rs @@ -225,7 +225,7 @@ mod tests { let mut stack = StackVec::from_iter(0..4 * STACK_VEC_CAPACITY); let mut ctr = 0; assert!(stack.iter().all(|&x| { - let ret = (x == ctr); + let ret = x == ctr; ctr += 1; ret })); @@ -234,7 +234,7 @@ mod tests { } ctr = 0; assert!(stack.iter().all(|&x| { - let ret = (x == ctr); + let ret = x == ctr; ctr += 1; ret }));