1
pub(crate) mod component;
2
pub(crate) mod recur;
3
pub(crate) mod uri;
4
pub(crate) mod value;
5

            
6
use crate::parser::param::{other_params, property_params};
7
use crate::parser::types::{
8
    CalendarScaleProperty, IanaProperty, MethodProperty, ProductIdProperty, VersionProperty,
9
    XProperty,
10
};
11
use crate::parser::{iana_token, prop_value_text, value, x_name, Error};
12
use crate::single;
13
pub use component::*;
14
use nom::branch::alt;
15
use nom::bytes::complete::tag_no_case;
16
use nom::bytes::streaming::tag;
17
use nom::character::streaming::char;
18
use nom::combinator::{cut, recognize, verify};
19
use nom::error::ParseError;
20
use nom::{AsChar, IResult, Parser};
21

            
22
669
pub fn prop_product_id<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], ProductIdProperty<'a>, E>
23
669
where
24
669
    E: ParseError<&'a [u8]> + From<Error<'a>>,
25
669
{
26
669
    let (input, (_, params, _, value, _)) = (
27
669
        tag_no_case("PRODID"),
28
669
        cut(other_params),
29
669
        char(':'),
30
669
        prop_value_text,
31
669
        tag("\r\n"),
32
669
    )
33
669
        .parse(input)?;
34

            
35
183
    Ok((
36
183
        input,
37
183
        ProductIdProperty {
38
183
            other_params: params,
39
183
            value,
40
183
        },
41
183
    ))
42
669
}
43

            
44
494
pub fn prop_version<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], VersionProperty<'a>, E>
45
494
where
46
494
    E: ParseError<&'a [u8]> + From<Error<'a>>,
47
494
{
48
494
    let (input, (_, params, _, (min_ver, max_ver), _)) = (
49
494
        tag_no_case("VERSION"),
50
494
        cut(other_params),
51
494
        char(':'),
52
494
        alt((
53
494
            (
54
494
                recognize((
55
494
                    single(AsChar::is_dec_digit),
56
494
                    char('.'),
57
494
                    single(AsChar::is_dec_digit),
58
494
                )),
59
494
                char(';'),
60
494
                recognize((
61
494
                    single(AsChar::is_dec_digit),
62
494
                    char('.'),
63
494
                    single(AsChar::is_dec_digit),
64
494
                )),
65
494
            )
66
494
                .map(|(min_ver, _, max_ver)| (Some(min_ver), max_ver)),
67
494
            recognize((
68
494
                single(AsChar::is_dec_digit),
69
494
                char('.'),
70
494
                single(AsChar::is_dec_digit),
71
494
            ))
72
494
            .map(|v| (Option::<&[u8]>::None, v)),
73
494
        )),
74
494
        tag("\r\n"),
75
494
    )
76
494
        .parse(input)?;
77

            
78
187
    Ok((
79
187
        input,
80
187
        VersionProperty {
81
187
            other_params: params,
82
187
            min_version: min_ver,
83
187
            max_version: max_ver,
84
187
        },
85
187
    ))
86
494
}
87

            
88
311
pub fn prop_calendar_scale<'a, E>(
89
311
    input: &'a [u8],
90
311
) -> IResult<&'a [u8], CalendarScaleProperty<'a>, E>
91
311
where
92
311
    E: ParseError<&'a [u8]> + From<Error<'a>>,
93
311
{
94
311
    let (input, (_, params, _, value, _)) = (
95
311
        tag_no_case("CALSCALE"),
96
311
        cut(other_params),
97
311
        char(':'),
98
311
        prop_value_text,
99
311
        tag("\r\n"),
100
311
    )
101
311
        .parse(input)?;
102

            
103
6
    Ok((
104
6
        input,
105
6
        CalendarScaleProperty {
106
6
            other_params: params,
107
6
            value,
108
6
        },
109
6
    ))
110
311
}
111

            
112
309
pub fn prop_method<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], MethodProperty<'a>, E>
113
309
where
114
309
    E: ParseError<&'a [u8]> + From<Error<'a>>,
115
309
{
116
309
    let (input, (_, params, _, value, _)) = (
117
309
        tag_no_case("METHOD"),
118
309
        cut(other_params),
119
309
        char(':'),
120
309
        iana_token,
121
309
        tag("\r\n"),
122
309
    )
123
309
        .parse(input)?;
124

            
125
92
    Ok((
126
92
        input,
127
92
        MethodProperty {
128
92
            other_params: params,
129
92
            value,
130
92
        },
131
92
    ))
132
309
}
133

            
134
707
pub fn prop_x<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], XProperty<'a>, E>
135
707
where
136
707
    E: ParseError<&'a [u8]>
137
707
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
138
707
        + From<Error<'a>>,
139
707
{
140
90
    let (input, (name, params, _, value, _)) =
141
707
        (x_name, cut(property_params), char(':'), value, tag("\r\n")).parse(input)?;
142

            
143
90
    Ok((
144
90
        input,
145
90
        XProperty {
146
90
            name,
147
90
            params,
148
90
            value,
149
90
        },
150
90
    ))
151
707
}
152

            
153
621
pub fn prop_iana<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], IanaProperty<'a>, E>
154
621
where
155
621
    E: ParseError<&'a [u8]>
156
621
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
157
621
        + From<Error<'a>>,
158
621
{
159
621
    let (input, (name, params, _, value, _)) = (
160
621
        verify(iana_token, |t: &[u8]| {
161
621
            // Not ideal, but in order to avoid IANA names colliding with ical structure, filter these values out
162
621
            t != b"BEGIN" && t != b"END"
163
621
        }),
164
621
        cut(property_params),
165
621
        char(':'),
166
621
        value,
167
621
        tag("\r\n"),
168
621
    )
169
621
        .parse(input)?;
170

            
171
74
    Ok((
172
74
        input,
173
74
        IanaProperty {
174
74
            name,
175
74
            params,
176
74
            value,
177
74
        },
178
74
    ))
179
621
}
180

            
181
#[cfg(test)]
182
mod tests {
183
    use super::*;
184
    use crate::common::Value;
185
    use crate::parser::types::ParamValue;
186
    use crate::test_utils::check_rem;
187

            
188
    #[test]
189
2
    fn product_id_property() {
190
2
        let (rem, prop) =
191
2
            prop_product_id::<Error>(b"PRODID:-//ABC Corporation//NONSGML My Product//EN\r\n;")
192
2
                .unwrap();
193
2
        check_rem(rem, 1);
194
2
        assert!(prop.other_params.is_empty());
195
2
        assert_eq!(prop.value, b"-//ABC Corporation//NONSGML My Product//EN");
196
2
    }
197

            
198
    #[test]
199
2
    fn product_id_property_with_params() {
200
2
        let (rem, prop) = prop_product_id::<Error>(
201
2
            b"PRODID;x-prop=val:-//ABC Corporation//NONSGML My Product//EN\r\n;",
202
2
        )
203
2
        .unwrap();
204
2
        check_rem(rem, 1);
205
2
        assert_eq!(prop.other_params.len(), 1);
206
2
        assert_eq!(
207
2
            prop.other_params[0],
208
2
            ParamValue::Other {
209
2
                name: b"x-prop",
210
2
                value: b"val"
211
2
            }
212
2
        );
213
2
        assert_eq!(prop.value, b"-//ABC Corporation//NONSGML My Product//EN");
214
2
    }
215

            
216
    #[test]
217
2
    fn version_property() {
218
2
        let input = b"VERSION:2.0\r\n;";
219
2
        let (rem, prop) = prop_version::<Error>(input).unwrap();
220
2
        check_rem(rem, 1);
221
2
        assert!(prop.other_params.is_empty());
222
2
        assert_eq!(prop.min_version, None);
223
2
        assert_eq!(prop.max_version, b"2.0");
224
2
    }
225

            
226
    #[test]
227
2
    fn version_property_with_param() {
228
2
        let input = b"VERSION;x-prop=val:2.0\r\n;";
229
2
        let (rem, prop) = prop_version::<Error>(input).unwrap();
230
2
        check_rem(rem, 1);
231
2
        assert_eq!(prop.other_params.len(), 1);
232
2
        assert_eq!(
233
2
            prop.other_params[0],
234
2
            ParamValue::Other {
235
2
                name: b"x-prop",
236
2
                value: b"val"
237
2
            }
238
2
        );
239
2
        assert_eq!(prop.min_version, None);
240
2
        assert_eq!(prop.max_version, b"2.0");
241
2
    }
242

            
243
    #[test]
244
2
    fn version_property_with_newer_version() {
245
2
        let input = b"VERSION:3.1\r\n;";
246
2
        let (rem, prop) = prop_version::<Error>(input).unwrap();
247
2
        check_rem(rem, 1);
248
2
        assert!(prop.other_params.is_empty());
249
2
        assert_eq!(prop.min_version, None);
250
2
        assert_eq!(prop.max_version, b"3.1");
251
2
    }
252

            
253
    #[test]
254
2
    fn version_property_with_version_range() {
255
2
        let input = b"VERSION:3.2;3.5\r\n;";
256
2
        let (rem, prop) = prop_version::<Error>(input).unwrap();
257
2
        check_rem(rem, 1);
258
2
        assert!(prop.other_params.is_empty());
259
2
        assert_eq!(prop.min_version.unwrap(), b"3.2");
260
2
        assert_eq!(prop.max_version, b"3.5");
261
2
    }
262

            
263
    #[test]
264
2
    fn cal_scale() {
265
2
        let input = b"CALSCALE:GREGORIAN\r\n;";
266
2
        let (rem, prop) = prop_calendar_scale::<Error>(input).unwrap();
267
2
        check_rem(rem, 1);
268
2
        assert!(prop.other_params.is_empty());
269
2
        assert_eq!(prop.value, b"GREGORIAN");
270
2
    }
271

            
272
    #[test]
273
2
    fn cal_scale_with_param() {
274
2
        let input = b"CALSCALE;x-prop=val:GREGORIAN\r\n;";
275
2
        let (rem, prop) = prop_calendar_scale::<Error>(input).unwrap();
276
2
        check_rem(rem, 1);
277
2
        assert_eq!(prop.other_params.len(), 1);
278
2
        assert_eq!(
279
2
            prop.other_params[0],
280
2
            ParamValue::Other {
281
2
                name: b"x-prop",
282
2
                value: b"val"
283
2
            }
284
2
        );
285
2
        assert_eq!(prop.value, b"GREGORIAN");
286
2
    }
287

            
288
    #[test]
289
2
    fn method() {
290
2
        let input = b"METHOD:REQUEST\r\n;";
291
2
        let (rem, prop) = prop_method::<Error>(input).unwrap();
292
2
        check_rem(rem, 1);
293
2
        assert!(prop.other_params.is_empty());
294
2
        assert_eq!(prop.value, b"REQUEST");
295
2
    }
296

            
297
    #[test]
298
2
    fn method_with_param() {
299
2
        let input = b"METHOD;x-prop=val:REQUEST\r\n;";
300
2
        let (rem, prop) = prop_method::<Error>(input).unwrap();
301
2
        check_rem(rem, 1);
302
2
        assert_eq!(prop.other_params.len(), 1);
303
2
        assert_eq!(
304
2
            prop.other_params[0],
305
2
            ParamValue::Other {
306
2
                name: b"x-prop",
307
2
                value: b"val"
308
2
            }
309
2
        );
310
2
        assert_eq!(prop.value, b"REQUEST");
311
2
    }
312

            
313
    #[test]
314
2
    fn x_prop() {
315
2
        let input =
316
2
            b"X-ABC-MMSUBJ;VALUE=URI;FMTTYPE=audio/basic:http://www.example.org/mysubj.au\r\n;";
317
2
        let (rem, prop) = prop_x::<Error>(input).unwrap();
318
2
        check_rem(rem, 1);
319
2
        assert_eq!(prop.name, b"X-ABC-MMSUBJ");
320
2
        assert_eq!(prop.params.len(), 2);
321
2
        assert_eq!(prop.params[0], ParamValue::ValueType { value: Value::Uri });
322
2
        assert_eq!(
323
2
            prop.params[1],
324
2
            ParamValue::FormatType {
325
2
                type_name: "audio".to_string(),
326
2
                sub_type_name: "basic".to_string()
327
2
            }
328
2
        );
329
2
        assert_eq!(prop.value, b"http://www.example.org/mysubj.au");
330
2
    }
331

            
332
    #[test]
333
2
    fn iana_prop() {
334
2
        let input = b"DRESSCODE:CASUAL\r\n;";
335
2
        let (rem, prop) = prop_iana::<Error>(input).unwrap();
336
2
        check_rem(rem, 1);
337
2
        assert_eq!(prop.name, b"DRESSCODE");
338
2
        assert!(prop.params.is_empty());
339
2
        assert_eq!(prop.value, b"CASUAL");
340
2
    }
341

            
342
    #[test]
343
2
    fn iana_prop_with_params() {
344
2
        let input = b"NON-SMOKING;VALUE=BOOLEAN:TRUE\r\n;";
345
2
        let (rem, prop) = prop_iana::<Error>(input).unwrap();
346
2
        check_rem(rem, 1);
347
2
        assert_eq!(prop.name, b"NON-SMOKING");
348
2
        assert_eq!(prop.params.len(), 1);
349
2
        assert_eq!(
350
2
            prop.params[0],
351
2
            ParamValue::ValueType {
352
2
                value: Value::Boolean
353
2
            }
354
2
        );
355
2
        assert_eq!(prop.value, b"TRUE");
356
2
    }
357
}