1#![doc = include_str!("../README.md")]
7#![deny(rust_2018_idioms)]
8#![forbid(unsafe_code)]
9use common::tokenizer::{StopChar, Token};
10use icalendar::{ICalendar, ICalendarComponentType};
11use std::{
12 borrow::Cow,
13 iter::{Enumerate, Peekable},
14 slice::Iter,
15};
16use vcard::VCard;
17
18pub mod common;
19pub mod datecalc;
20pub mod icalendar;
21#[cfg(feature = "jmap")]
22pub mod jscalendar;
23#[cfg(feature = "jmap")]
24pub mod jscontact;
25pub mod vcard;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28#[non_exhaustive]
29pub enum Entry {
30 VCard(VCard),
31 ICalendar(ICalendar),
32 InvalidLine(String),
33 UnexpectedComponentEnd {
34 expected: ICalendarComponentType,
35 found: ICalendarComponentType,
36 },
37 UnterminatedComponent(Cow<'static, str>),
38 TooManyComponents,
39 Eof,
40}
41
42pub struct Parser<'x> {
43 pub(crate) input: &'x [u8],
44 pub(crate) iter: Peekable<Enumerate<Iter<'x, u8>>>,
45 pub(crate) strict: bool,
46 pub(crate) stop_colon: bool,
47 pub(crate) stop_semicolon: bool,
48 pub(crate) stop_comma: bool,
49 pub(crate) stop_equal: bool,
50 pub(crate) stop_dot: bool,
51 pub(crate) unfold_qp: bool,
52 pub(crate) unquote: bool,
53 pub(crate) skip_ws: bool,
54 pub(crate) token_buf: Vec<Token<'x>>,
55}
56
57impl<'x> Parser<'x> {
58 pub fn new(input: &'x str) -> Self {
59 let input = input.as_bytes();
60 Self {
61 input,
62 iter: input.iter().enumerate().peekable(),
63 strict: false,
64 stop_colon: true,
65 stop_semicolon: true,
66 stop_comma: true,
67 stop_equal: true,
68 stop_dot: false,
69 unfold_qp: false,
70 unquote: true,
71 skip_ws: false,
72 token_buf: Vec::with_capacity(10),
73 }
74 }
75
76 pub fn strict(mut self) -> Self {
77 self.strict = true;
78 self
79 }
80
81 pub fn entry(&mut self) -> Entry {
82 self.expect_iana_token();
83
84 loop {
85 if let Some(token) = self.token() {
86 if (token.text.eq_ignore_ascii_case(b"BEGIN")
87 || token.text.eq_ignore_ascii_case("\u{feff}BEGIN".as_bytes()))
88 && token.stop_char == StopChar::Colon
89 {
90 if let Some(token) = self.token() {
91 if token.stop_char == StopChar::Lf {
92 hashify::fnc_map_ignore_case!(token.text.as_ref(),
93 b"VCARD" => { return self.vcard(); },
94 b"VCALENDAR" => { return self.icalendar(ICalendarComponentType::VCalendar); },
95 b"VEVENT" => { return self.icalendar(ICalendarComponentType::VEvent); },
96 b"VTODO" => { return self.icalendar(ICalendarComponentType::VTodo); },
97 b"VJOURNAL" => { return self.icalendar(ICalendarComponentType::VJournal); },
98 b"VFREEBUSY" => { return self.icalendar(ICalendarComponentType::VFreebusy); },
99 b"VTIMEZONE" => { return self.icalendar(ICalendarComponentType::VTimezone); },
100 b"VALARM" => { return self.icalendar(ICalendarComponentType::VAlarm); },
101 b"STANDARD" => { return self.icalendar(ICalendarComponentType::Standard); },
102 b"DAYLIGHT" => { return self.icalendar(ICalendarComponentType::Daylight); },
103 b"VAVAILABILITY" => { return self.icalendar(ICalendarComponentType::VAvailability); },
104 b"AVAILABLE" => { return self.icalendar(ICalendarComponentType::Available); },
105 b"PARTICIPANT" => { return self.icalendar(ICalendarComponentType::Participant); },
106 b"VLOCATION" => { return self.icalendar(ICalendarComponentType::VLocation); },
107 b"VRESOURCE" => { return self.icalendar(ICalendarComponentType::VResource); },
108 _ => {
109 return self.icalendar(ICalendarComponentType::Other(token.into_string()));
110 }
111 )
112 }
113 } else {
114 return Entry::Eof;
115 }
116 }
117
118 let token_start = token.start;
119 let mut token_end = token.end;
120
121 if token.stop_char != StopChar::Lf {
122 self.expect_single_value();
123 while let Some(token) = self.token() {
124 token_end = token.end;
125 if token.stop_char == StopChar::Lf {
126 break;
127 }
128 }
129 } else if token.text.is_empty() {
130 continue;
131 }
132
133 return Entry::InvalidLine(
134 std::str::from_utf8(
135 self.input.get(token_start..=token_end).unwrap_or_default(),
136 )
137 .unwrap_or_default()
138 .to_string(),
139 );
140 } else {
141 return Entry::Eof;
142 }
143 }
144 }
145}