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::IResult;
18
use nom::Parser;
19

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

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

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

            
39
7
        input = i;
40
    }
41

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

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

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

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

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

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

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

            
144
6
    Ok((input, CalendarComponent::IanaComp { name, lines }))
145
223
}
146

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

            
162
40
    if name != end_name {
163
        return Err(nom::Err::Error(
164
            Error::new(
165
                input,
166
                InnerError::MismatchedComponentEnd(name.to_vec(), end_name.to_vec()),
167
            )
168
            .into(),
169
        ));
170
40
    }
171
40

            
172
40
    Ok((input, CalendarComponent::XComp { name, lines }))
173
263
}
174

            
175
#[cfg(test)]
176
trait ReprStr {
177
    fn repr_str(&self) -> &str;
178
}
179

            
180
#[cfg(test)]
181
impl ReprStr for &[u8] {
182
    fn repr_str(&self) -> &str {
183
        unsafe { std::str::from_utf8_unchecked(self) }
184
    }
185
}
186

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

            
194
    let mut result = String::new();
195

            
196
    let input = input.repr_str();
197

            
198
    for (i, (substring, kind)) in e.errors.iter().enumerate() {
199
        let substring = substring.repr_str();
200
        let offset = input.offset(substring);
201

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

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

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

            
229
            // Find the full line after that newline
230
            let line = input[line_begin..]
231
                .lines()
232
                .next()
233
                .unwrap_or(&input[line_begin..])
234
                .trim_end();
235

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

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

            
302
    result
303
}
304

            
305
#[cfg(test)]
306
mod tests {
307
    use super::*;
308
    use crate::parser::clear_errors;
309
    use crate::parser::first_pass::content_line_first_pass;
310
    use crate::parser::types::VersionProperty;
311
    use crate::test_utils::check_rem;
312
    use nom::combinator::complete;
313

            
314
    #[test]
315
2
    fn minimal_ical_stream_test() {
316
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";
317
2
        let (rem, ical) = ical_stream::<Error>(input).unwrap();
318
2
        check_rem(rem, 0);
319
2
        assert_eq!(ical.len(), 1);
320
2
        assert_eq!(ical[0].properties.len(), 2);
321
2
        assert_eq!(
322
2
            ical[0].properties[0],
323
2
            CalendarProperty::Version(VersionProperty {
324
2
                other_params: vec![],
325
2
                min_version: None,
326
2
                max_version: b"2.0",
327
2
            })
328
2
        );
329
2
        assert_eq!(ical[0].components.len(), 1);
330
2
    }
331

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

            
337
        let (input, first) = content_line_first_pass::<Error>(input.as_bytes()).unwrap();
338
        check_rem(input, 0);
339

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

            
357
        unsafe { clear_errors() };
358
    }
359
}