You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
10KB

  1. /*
  2. * meli - addressbook module
  3. *
  4. * Copyright 2019 Manos Pitsidianakis
  5. *
  6. * This file is part of meli.
  7. *
  8. * meli is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * meli is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with meli. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. /// Convert VCard strings to meli Cards (contacts).
  22. use super::*;
  23. use crate::error::{MeliError, Result};
  24. use crate::parsec::{match_literal_anycase, one_or_more, peek, prefix, take_until, Parser};
  25. use fnv::FnvHashMap;
  26. use std::convert::TryInto;
  27. /* Supported vcard versions */
  28. pub trait VCardVersion: core::fmt::Debug {}
  29. #[derive(Debug)]
  30. pub struct VCardVersionUnknown;
  31. impl VCardVersion for VCardVersionUnknown {}
  32. /// https://tools.ietf.org/html/rfc6350
  33. #[derive(Debug)]
  34. pub struct VCardVersion4;
  35. impl VCardVersion for VCardVersion4 {}
  36. /// https://tools.ietf.org/html/rfc2426
  37. #[derive(Debug)]
  38. pub struct VCardVersion3;
  39. impl VCardVersion for VCardVersion3 {}
  40. pub struct CardDeserializer;
  41. static HEADER: &'static str = "BEGIN:VCARD\r\n"; //VERSION:4.0\r\n";
  42. static FOOTER: &'static str = "END:VCARD\r\n";
  43. #[derive(Debug)]
  44. pub struct VCard<T: VCardVersion>(
  45. fnv::FnvHashMap<String, ContentLine>,
  46. std::marker::PhantomData<*const T>,
  47. );
  48. impl<V: VCardVersion> VCard<V> {
  49. pub fn new_v4() -> VCard<impl VCardVersion> {
  50. VCard(
  51. FnvHashMap::default(),
  52. std::marker::PhantomData::<*const VCardVersion4>,
  53. )
  54. }
  55. }
  56. #[derive(Debug, Default, Clone)]
  57. pub struct ContentLine {
  58. group: Option<String>,
  59. params: Vec<String>,
  60. value: String,
  61. }
  62. impl CardDeserializer {
  63. pub fn from_str(mut input: &str) -> Result<VCard<impl VCardVersion>> {
  64. input = if !input.starts_with(HEADER) || !input.ends_with(FOOTER) {
  65. return Err(MeliError::new(format!("Error while parsing vcard: input does not start or end with correct header and footer. input is:\n{:?}", input)));
  66. } else {
  67. &input[HEADER.len()..input.len() - FOOTER.len()]
  68. };
  69. let mut ret = FnvHashMap::default();
  70. enum Stage {
  71. Group,
  72. Name,
  73. Param,
  74. Value,
  75. }
  76. let mut stage: Stage;
  77. for l in input.lines() {
  78. let mut el = ContentLine::default();
  79. let mut value_start = 0;
  80. let mut has_colon = false;
  81. stage = Stage::Group;
  82. let mut name = String::new();
  83. for i in 0..l.len() {
  84. let byte = l.as_bytes()[i];
  85. match (byte, &stage) {
  86. (b'.', Stage::Group) if l.as_bytes()[i] != b'\\' => {
  87. el.group = Some(l[value_start..i].to_string());
  88. value_start = i + 1;
  89. stage = Stage::Name;
  90. }
  91. (b';', Stage::Group) => {
  92. name = l[value_start..i].to_string();
  93. value_start = i + 1;
  94. stage = Stage::Param;
  95. }
  96. (b';', Stage::Param) => {
  97. el.params.push(l[value_start..i].to_string());
  98. value_start = i + 1;
  99. }
  100. (b';', Stage::Name) => {
  101. name = l[value_start..i].to_string();
  102. value_start = i + 1;
  103. stage = Stage::Param;
  104. }
  105. (b':', Stage::Group) | (b':', Stage::Name) => {
  106. name = l[value_start..i].to_string();
  107. has_colon = true;
  108. value_start = i + 1;
  109. stage = Stage::Value;
  110. }
  111. (b':', Stage::Param) if l.as_bytes()[i.saturating_sub(1)] != b'\\' => {
  112. el.params.push(l[value_start..i].to_string());
  113. has_colon = true;
  114. value_start = i + 1;
  115. stage = Stage::Value;
  116. }
  117. _ => {}
  118. }
  119. }
  120. if !has_colon {
  121. return Err(MeliError::new(format!(
  122. "Error while parsing vcard: error at line {}, no colon. {:?}",
  123. l, el
  124. )));
  125. }
  126. if name.is_empty() {
  127. return Err(MeliError::new(format!(
  128. "Error while parsing vcard: error at line {}, no name for content line. {:?}",
  129. l, el
  130. )));
  131. }
  132. el.value = l[value_start..].replace("\\:", ":");
  133. ret.insert(name, el);
  134. }
  135. Ok(VCard(ret, std::marker::PhantomData::<*const VCardVersion4>))
  136. }
  137. }
  138. impl<V: VCardVersion> TryInto<Card> for VCard<V> {
  139. type Error = crate::error::MeliError;
  140. fn try_into(mut self) -> crate::error::Result<Card> {
  141. let mut card = Card::new();
  142. card.set_id(CardId::Hash({
  143. use std::hash::Hasher;
  144. let mut hasher = std::collections::hash_map::DefaultHasher::new();
  145. if let Some(val) = self.0.get("FN") {
  146. hasher.write(val.value.as_bytes());
  147. }
  148. if let Some(val) = self.0.get("N") {
  149. hasher.write(val.value.as_bytes());
  150. }
  151. if let Some(val) = self.0.get("EMAIL") {
  152. hasher.write(val.value.as_bytes());
  153. }
  154. hasher.finish()
  155. }));
  156. if let Some(val) = self.0.remove("FN") {
  157. card.set_name(val.value);
  158. } else {
  159. return Err(MeliError::new("FN entry missing in VCard."));
  160. }
  161. if let Some(val) = self.0.remove("NICKNAME") {
  162. card.set_additionalname(val.value);
  163. }
  164. if let Some(val) = self.0.remove("BDAY") {
  165. /* 4.3.4. DATE-AND-OR-TIME
  166. Either a DATE-TIME, a DATE, or a TIME value. To allow unambiguous
  167. interpretation, a stand-alone TIME value is always preceded by a "T".
  168. Examples for "date-and-or-time":
  169. 19961022T140000
  170. --1022T1400
  171. ---22T14
  172. 19850412
  173. 1985-04
  174. 1985
  175. --0412
  176. ---12
  177. T102200
  178. T1022
  179. T10
  180. T-2200
  181. T--00
  182. T102200Z
  183. T102200-0800
  184. */
  185. card.birthday = crate::datetime::timestamp_from_string(val.value.as_str(), "%Y%m%d");
  186. }
  187. if let Some(val) = self.0.remove("EMAIL") {
  188. card.set_email(val.value);
  189. }
  190. if let Some(val) = self.0.remove("URL") {
  191. card.set_url(val.value);
  192. }
  193. if let Some(val) = self.0.remove("KEY") {
  194. card.set_key(val.value);
  195. }
  196. for (k, v) in self.0.into_iter() {
  197. if k.eq_ignore_ascii_case("VERSION") || k.eq_ignore_ascii_case("N") {
  198. continue;
  199. }
  200. card.set_extra_property(&k, v.value);
  201. }
  202. Ok(card)
  203. }
  204. }
  205. fn parse_card<'a>() -> impl Parser<'a, Vec<&'a str>> {
  206. move |input| {
  207. one_or_more(prefix(
  208. peek(match_literal_anycase(HEADER)),
  209. take_until(match_literal_anycase(FOOTER)),
  210. ))
  211. .parse(input)
  212. }
  213. }
  214. #[test]
  215. fn test_load_cards() {
  216. /*
  217. let mut contents = String::with_capacity(256);
  218. let p = &std::path::Path::new("/tmp/contacts.vcf");
  219. use std::io::Read;
  220. contents.clear();
  221. std::fs::File::open(&p)
  222. .unwrap()
  223. .read_to_string(&mut contents)
  224. .unwrap();
  225. for s in parse_card().parse(contents.as_str()).unwrap().1 {
  226. println!("");
  227. println!("{}", s);
  228. println!("{:?}", CardDeserializer::from_str(s));
  229. println!("");
  230. }
  231. */
  232. }
  233. pub fn load_cards(p: &std::path::Path) -> Result<Vec<Card>> {
  234. let vcf_dir = std::fs::read_dir(p);
  235. let mut ret: Vec<Result<_>> = Vec::new();
  236. let mut is_any_valid = false;
  237. if vcf_dir.is_ok() {
  238. let mut contents = String::with_capacity(256);
  239. for f in vcf_dir? {
  240. if f.is_err() {
  241. continue;
  242. }
  243. let f = f?.path();
  244. if f.is_file() {
  245. use std::io::Read;
  246. contents.clear();
  247. std::fs::File::open(&f)?.read_to_string(&mut contents)?;
  248. if let Ok((_, c)) = parse_card().parse(contents.as_str()) {
  249. for s in c {
  250. ret.push(
  251. CardDeserializer::from_str(s)
  252. .and_then(TryInto::try_into)
  253. .and_then(|mut card| {
  254. Card::set_external_resource(&mut card, true);
  255. is_any_valid = true;
  256. Ok(card)
  257. }),
  258. );
  259. }
  260. }
  261. }
  262. }
  263. }
  264. for c in &ret {
  265. if c.is_err() {
  266. debug!(&c);
  267. }
  268. }
  269. if !is_any_valid {
  270. ret.into_iter().collect::<Result<Vec<Card>>>()
  271. } else {
  272. ret.retain(Result::is_ok);
  273. ret.into_iter().collect::<Result<Vec<Card>>>()
  274. }
  275. }
  276. #[test]
  277. fn test_card() {
  278. 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";
  279. println!("results = {:#?}", CardDeserializer::from_str(j).unwrap());
  280. }