1
use crate::parser::component::{
2
    component_event, component_free_busy, component_journal, component_timezone, component_todo,
3
};
4
use crate::parser::property::{
5
    prop_calendar_scale, prop_iana, prop_method, prop_product_id, prop_version, prop_x,
6
};
7
use crate::parser::types::CalendarComponent;
8
use crate::parser::types::CalendarProperty;
9
use crate::parser::types::ICalendar;
10
use crate::parser::{content_line, iana_token, x_name, Error, InnerError};
11
use nom::branch::alt;
12
use nom::bytes::streaming::tag;
13
use nom::character::streaming::crlf;
14
use nom::combinator::{cut, eof, verify};
15
use nom::error::ParseError;
16
use nom::multi::{many0, many1};
17
use nom::sequence::tuple;
18
use nom::IResult;
19
use nom::Parser;
20

            
21
/// The top-level parser for an iCalendar stream.
22
///
23
/// This recognizes a list of [ical_object]s, separated by whitespace.
24
7
pub fn ical_stream<'a, E>(mut input: &'a [u8]) -> IResult<&'a [u8], Vec<ICalendar<'a>>, E>
25
7
where
26
7
    E: ParseError<&'a [u8]>
27
7
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
28
7
        + From<Error<'a>>,
29
7
{
30
7
    let mut out = Vec::new();
31

            
32
    loop {
33
14
        if eof::<_, Error>(input).is_ok() {
34
7
            break;
35
7
        }
36

            
37
7
        let (i, ical) = ical_object(input)?;
38
7
        out.push(ical);
39
7

            
40
7
        input = i;
41
    }
42

            
43
7
    Ok((input, out))
44
7
}
45

            
46
/// The top-level parser for an iCalendar object.
47
///
48
/// This recognizes a single iCalendar object.
49
217
pub fn ical_object<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], ICalendar<'a>, E>
50
217
where
51
217
    E: ParseError<&'a [u8]>
52
217
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
53
217
        + From<Error<'a>>,
54
217
{
55
217
    let (input, (_, body, _)) = tuple((
56
217
        tag("BEGIN:VCALENDAR\r\n"),
57
217
        ical_body,
58
217
        tag("END:VCALENDAR\r\n"),
59
217
    ))(input)?;
60

            
61
217
    Ok((input, body))
62
217
}
63

            
64
217
fn ical_body<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], ICalendar<'a>, E>
65
217
where
66
217
    E: ParseError<&'a [u8]>
67
217
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
68
217
        + From<Error<'a>>,
69
217
{
70
217
    let (input, (properties, components)) = tuple((many0(ical_cal_prop), many1(component)))(input)?;
71

            
72
217
    Ok((
73
217
        input,
74
217
        ICalendar {
75
217
            properties,
76
217
            components,
77
217
        },
78
217
    ))
79
217
}
80

            
81
665
fn ical_cal_prop<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], CalendarProperty<'a>, E>
82
665
where
83
665
    E: ParseError<&'a [u8]>
84
665
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
85
665
        + From<Error<'a>>,
86
665
{
87
665
    alt((
88
665
        prop_product_id.map(CalendarProperty::ProductId),
89
665
        prop_version.map(CalendarProperty::Version),
90
665
        prop_calendar_scale.map(CalendarProperty::CalendarScale),
91
665
        prop_method.map(CalendarProperty::Method),
92
665
        prop_x.map(CalendarProperty::XProperty),
93
665
        prop_iana.map(CalendarProperty::IanaProperty),
94
665
    ))
95
665
    .parse(input)
96
665
}
97

            
98
469
fn component<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], CalendarComponent<'a>, E>
99
469
where
100
469
    E: ParseError<&'a [u8]>
101
469
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
102
469
        + From<Error<'a>>,
103
469
{
104
469
    alt((
105
469
        component_event,
106
469
        component_todo,
107
469
        component_journal,
108
469
        component_free_busy,
109
469
        component_timezone,
110
469
        x_comp,
111
469
        iana_comp,
112
469
    ))(input)
113
469
}
114

            
115
223
fn iana_comp<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], CalendarComponent<'a>, E>
116
223
where
117
223
    E: ParseError<&'a [u8]> + From<Error<'a>>,
118
223
{
119
223
    let (input, (_, name, _, lines, _, end_name, _)) = tuple((
120
223
        tag("BEGIN:"),
121
223
        iana_token,
122
223
        crlf,
123
225
        cut(many1(verify(content_line, |line| {
124
14
            line.property_name != "END".as_bytes()
125
225
        }))),
126
223
        tag("END:"),
127
223
        iana_token,
128
223
        tag("\r\n"),
129
223
    ))(input)?;
130

            
131
6
    if name != end_name {
132
        return Err(nom::Err::Error(
133
            Error::new(
134
                input,
135
                InnerError::MismatchedComponentEnd(name.to_vec(), end_name.to_vec()),
136
            )
137
            .into(),
138
        ));
139
6
    }
140
6

            
141
6
    Ok((input, CalendarComponent::IanaComp { name, lines }))
142
223
}
143

            
144
263
fn x_comp<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], CalendarComponent<'a>, E>
145
263
where
146
263
    E: ParseError<&'a [u8]> + From<Error<'a>>,
147
263
{
148
263
    let (input, (_, name, _, lines, _, end_name, _)) = tuple((
149
263
        tag("BEGIN:"),
150
263
        x_name,
151
263
        crlf,
152
263
        cut(many1(verify(content_line, |cl| cl.property_name != b"END"))),
153
263
        tag("END:"),
154
263
        x_name,
155
263
        crlf,
156
263
    ))(input)?;
157

            
158
40
    if name != end_name {
159
        return Err(nom::Err::Error(
160
            Error::new(
161
                input,
162
                InnerError::MismatchedComponentEnd(name.to_vec(), end_name.to_vec()),
163
            )
164
            .into(),
165
        ));
166
40
    }
167
40

            
168
40
    Ok((input, CalendarComponent::XComp { name, lines }))
169
263
}
170

            
171
#[cfg(test)]
172
trait ReprStr {
173
    fn repr_str(&self) -> &str;
174
}
175

            
176
#[cfg(test)]
177
impl ReprStr for &[u8] {
178
    fn repr_str(&self) -> &str {
179
        unsafe { std::str::from_utf8_unchecked(self) }
180
    }
181
}
182

            
183
// Borrowed from `nom` and modified (somewhat poorly!) to work with byte arrays rather than strings.
184
#[cfg(test)]
185
fn convert_error_mod<I: ReprStr>(input: I, e: nom::error::VerboseError<I>) -> String {
186
    use nom::error::VerboseErrorKind;
187
    use nom::Offset;
188
    use std::fmt::Write;
189

            
190
    let mut result = String::new();
191

            
192
    let input = input.repr_str();
193

            
194
    for (i, (substring, kind)) in e.errors.iter().enumerate() {
195
        let substring = substring.repr_str();
196
        let offset = input.offset(substring);
197

            
198
        if input.is_empty() {
199
            match kind {
200
                VerboseErrorKind::Char(c) => {
201
                    write!(&mut result, "{}: expected '{}', got empty input\n\n", i, c)
202
                }
203
                VerboseErrorKind::Context(s) => {
204
                    write!(&mut result, "{}: in {}, got empty input\n\n", i, s)
205
                }
206
                VerboseErrorKind::Nom(e) => {
207
                    write!(&mut result, "{}: in {:?}, got empty input\n\n", i, e)
208
                }
209
            }
210
        } else {
211
            let prefix = &input.as_bytes()[..offset];
212

            
213
            // Count the number of newlines in the first `offset` bytes of input
214
            let line_number = prefix.iter().filter(|&&b| b == b'\n').count() + 1;
215

            
216
            // Find the line that includes the subslice:
217
            // Find the *last* newline before the substring starts
218
            let line_begin = prefix
219
                .iter()
220
                .rev()
221
                .position(|&b| b == b'\n')
222
                .map(|pos| offset - pos)
223
                .unwrap_or(0);
224

            
225
            // Find the full line after that newline
226
            let line = input[line_begin..]
227
                .lines()
228
                .next()
229
                .unwrap_or(&input[line_begin..])
230
                .trim_end();
231

            
232
            // The (1-indexed) column number is the offset of our substring into that line
233
            let column_number = line.offset(substring) + 1;
234

            
235
            match kind {
236
                VerboseErrorKind::Char(c) => {
237
                    if let Some(actual) = substring.chars().next() {
238
                        write!(
239
                            &mut result,
240
                            "{i}: at line {line_number}:\n\
241
               {line}\n\
242
               {caret:>column$}\n\
243
               expected '{expected}', found {actual}\n\n",
244
                            i = i,
245
                            line_number = line_number,
246
                            line = line,
247
                            caret = '^',
248
                            column = column_number,
249
                            expected = c,
250
                            actual = actual,
251
                        )
252
                    } else {
253
                        write!(
254
                            &mut result,
255
                            "{i}: at line {line_number}:\n\
256
               {line}\n\
257
               {caret:>column$}\n\
258
               expected '{expected}', got end of input\n\n",
259
                            i = i,
260
                            line_number = line_number,
261
                            line = line,
262
                            caret = '^',
263
                            column = column_number,
264
                            expected = c,
265
                        )
266
                    }
267
                }
268
                VerboseErrorKind::Context(s) => write!(
269
                    &mut result,
270
                    "{i}: at line {line_number}, in {context}:\n\
271
             {line}\n\
272
             {caret:>column$}\n\n",
273
                    i = i,
274
                    line_number = line_number,
275
                    context = s,
276
                    line = line,
277
                    caret = '^',
278
                    column = column_number,
279
                ),
280
                VerboseErrorKind::Nom(e) => write!(
281
                    &mut result,
282
                    "{i}: at line {line_number}, in {nom_err:?}:\n\
283
             {line}\n\
284
             {caret:>column$}\n\n",
285
                    i = i,
286
                    line_number = line_number,
287
                    nom_err = e,
288
                    line = line,
289
                    caret = '^',
290
                    column = column_number,
291
                ),
292
            }
293
        }
294
        // Because `write!` to a `String` is infallible, this `unwrap` is fine.
295
        .unwrap();
296
    }
297

            
298
    result
299
}
300

            
301
#[cfg(test)]
302
mod tests {
303
    use super::*;
304
    use crate::parser::clear_errors;
305
    use crate::parser::first_pass::content_line_first_pass;
306
    use crate::parser::types::VersionProperty;
307
    use crate::test_utils::check_rem;
308
    use nom::combinator::complete;
309
    use nom::error::VerboseError;
310

            
311
    #[test]
312
2
    fn minimal_ical_stream_test() {
313
2
        let input = b"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:test\r\nBEGIN:x-com\r\nx-prop:I'm a property\r\nEND:x-com\r\nEND:VCALENDAR\r\n";
314
2
        let (rem, ical) = ical_stream::<Error>(input).unwrap();
315
2
        check_rem(rem, 0);
316
2
        assert_eq!(ical.len(), 1);
317
2
        assert_eq!(ical[0].properties.len(), 2);
318
2
        assert_eq!(
319
2
            ical[0].properties[0],
320
2
            CalendarProperty::Version(VersionProperty {
321
2
                other_params: vec![],
322
2
                min_version: None,
323
2
                max_version: b"2.0",
324
2
            })
325
2
        );
326
2
        assert_eq!(ical[0].components.len(), 1);
327
2
    }
328

            
329
    #[test]
330
    #[ignore = "Requires a real file"]
331
    fn real_file() {
332
        let input = std::fs::read_to_string("test_data.ics").unwrap();
333

            
334
        let (input, first) = content_line_first_pass::<Error>(input.as_bytes()).unwrap();
335
        check_rem(input, 0);
336

            
337
        let r = complete::<_, _, VerboseError<&[u8]>, _>(ical_stream).parse(&first);
338
        match r {
339
            Err(nom::Err::Error(e) | nom::Err::Failure(e)) => {
340
                println!("fail:\n\n {}", convert_error_mod(first.as_slice(), e));
341
            }
342
            Ok((rem, ical)) => {
343
                println!("Got an OK result");
344
                check_rem(rem, 0);
345
                println!("Calendars: {:?}", ical.len());
346
                println!("Components: {:?}", ical[0].components.len());
347
            }
348
            e => {
349
                panic!("unexpected result: {:?}", e)
350
            }
351
        }
352

            
353
        unsafe { clear_errors() };
354
    }
355
}