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::is_digit;
18
use nom::character::streaming::char;
19
use nom::combinator::{cut, recognize, verify};
20
use nom::error::ParseError;
21
use nom::sequence::tuple;
22
use nom::{IResult, Parser};
23

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

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

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

            
66
187
    Ok((
67
187
        input,
68
187
        VersionProperty {
69
187
            other_params: params,
70
187
            min_version: min_ver,
71
187
            max_version: max_ver,
72
187
        },
73
187
    ))
74
494
}
75

            
76
311
pub fn prop_calendar_scale<'a, E>(
77
311
    input: &'a [u8],
78
311
) -> IResult<&'a [u8], CalendarScaleProperty<'a>, E>
79
311
where
80
311
    E: ParseError<&'a [u8]> + From<Error<'a>>,
81
311
{
82
311
    let (input, (_, params, _, value, _)) = tuple((
83
311
        tag_no_case("CALSCALE"),
84
311
        cut(other_params),
85
311
        char(':'),
86
311
        prop_value_text,
87
311
        tag("\r\n"),
88
311
    ))(input)?;
89

            
90
6
    Ok((
91
6
        input,
92
6
        CalendarScaleProperty {
93
6
            other_params: params,
94
6
            value,
95
6
        },
96
6
    ))
97
311
}
98

            
99
309
pub fn prop_method<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], MethodProperty<'a>, E>
100
309
where
101
309
    E: ParseError<&'a [u8]> + From<Error<'a>>,
102
309
{
103
309
    let (input, (_, params, _, value, _)) = tuple((
104
309
        tag_no_case("METHOD"),
105
309
        cut(other_params),
106
309
        char(':'),
107
309
        iana_token,
108
309
        tag("\r\n"),
109
309
    ))(input)?;
110

            
111
92
    Ok((
112
92
        input,
113
92
        MethodProperty {
114
92
            other_params: params,
115
92
            value,
116
92
        },
117
92
    ))
118
309
}
119

            
120
707
pub fn prop_x<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], XProperty<'a>, E>
121
707
where
122
707
    E: ParseError<&'a [u8]>
123
707
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
124
707
        + From<Error<'a>>,
125
707
{
126
90
    let (input, (name, params, _, value, _)) =
127
707
        tuple((x_name, cut(property_params), char(':'), value, tag("\r\n")))(input)?;
128

            
129
90
    Ok((
130
90
        input,
131
90
        XProperty {
132
90
            name,
133
90
            params,
134
90
            value,
135
90
        },
136
90
    ))
137
707
}
138

            
139
621
pub fn prop_iana<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], IanaProperty<'a>, E>
140
621
where
141
621
    E: ParseError<&'a [u8]>
142
621
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
143
621
        + From<Error<'a>>,
144
621
{
145
621
    let (input, (name, params, _, value, _)) = tuple((
146
621
        verify(iana_token, |t: &[u8]| {
147
621
            // Not ideal, but in order to avoid IANA names colliding with ical structure, filter these values out
148
621
            t != b"BEGIN" && t != b"END"
149
621
        }),
150
621
        cut(property_params),
151
621
        char(':'),
152
621
        value,
153
621
        tag("\r\n"),
154
621
    ))(input)?;
155

            
156
74
    Ok((
157
74
        input,
158
74
        IanaProperty {
159
74
            name,
160
74
            params,
161
74
            value,
162
74
        },
163
74
    ))
164
621
}
165

            
166
#[cfg(test)]
167
mod tests {
168
    use super::*;
169
    use crate::common::Value;
170
    use crate::parser::types::ParamValue;
171
    use crate::test_utils::check_rem;
172

            
173
    #[test]
174
2
    fn product_id_property() {
175
2
        let (rem, prop) =
176
2
            prop_product_id::<Error>(b"PRODID:-//ABC Corporation//NONSGML My Product//EN\r\n;")
177
2
                .unwrap();
178
2
        check_rem(rem, 1);
179
2
        assert!(prop.other_params.is_empty());
180
2
        assert_eq!(prop.value, b"-//ABC Corporation//NONSGML My Product//EN");
181
2
    }
182

            
183
    #[test]
184
2
    fn product_id_property_with_params() {
185
2
        let (rem, prop) = prop_product_id::<Error>(
186
2
            b"PRODID;x-prop=val:-//ABC Corporation//NONSGML My Product//EN\r\n;",
187
2
        )
188
2
        .unwrap();
189
2
        check_rem(rem, 1);
190
2
        assert_eq!(prop.other_params.len(), 1);
191
2
        assert_eq!(
192
2
            prop.other_params[0],
193
2
            ParamValue::Other {
194
2
                name: b"x-prop",
195
2
                value: b"val"
196
2
            }
197
2
        );
198
2
        assert_eq!(prop.value, b"-//ABC Corporation//NONSGML My Product//EN");
199
2
    }
200

            
201
    #[test]
202
2
    fn version_property() {
203
2
        let input = b"VERSION:2.0\r\n;";
204
2
        let (rem, prop) = prop_version::<Error>(input).unwrap();
205
2
        check_rem(rem, 1);
206
2
        assert!(prop.other_params.is_empty());
207
2
        assert_eq!(prop.min_version, None);
208
2
        assert_eq!(prop.max_version, b"2.0");
209
2
    }
210

            
211
    #[test]
212
2
    fn version_property_with_param() {
213
2
        let input = b"VERSION;x-prop=val:2.0\r\n;";
214
2
        let (rem, prop) = prop_version::<Error>(input).unwrap();
215
2
        check_rem(rem, 1);
216
2
        assert_eq!(prop.other_params.len(), 1);
217
2
        assert_eq!(
218
2
            prop.other_params[0],
219
2
            ParamValue::Other {
220
2
                name: b"x-prop",
221
2
                value: b"val"
222
2
            }
223
2
        );
224
2
        assert_eq!(prop.min_version, None);
225
2
        assert_eq!(prop.max_version, b"2.0");
226
2
    }
227

            
228
    #[test]
229
2
    fn version_property_with_newer_version() {
230
2
        let input = b"VERSION:3.1\r\n;";
231
2
        let (rem, prop) = prop_version::<Error>(input).unwrap();
232
2
        check_rem(rem, 1);
233
2
        assert!(prop.other_params.is_empty());
234
2
        assert_eq!(prop.min_version, None);
235
2
        assert_eq!(prop.max_version, b"3.1");
236
2
    }
237

            
238
    #[test]
239
2
    fn version_property_with_version_range() {
240
2
        let input = b"VERSION:3.2;3.5\r\n;";
241
2
        let (rem, prop) = prop_version::<Error>(input).unwrap();
242
2
        check_rem(rem, 1);
243
2
        assert!(prop.other_params.is_empty());
244
2
        assert_eq!(prop.min_version.unwrap(), b"3.2");
245
2
        assert_eq!(prop.max_version, b"3.5");
246
2
    }
247

            
248
    #[test]
249
2
    fn cal_scale() {
250
2
        let input = b"CALSCALE:GREGORIAN\r\n;";
251
2
        let (rem, prop) = prop_calendar_scale::<Error>(input).unwrap();
252
2
        check_rem(rem, 1);
253
2
        assert!(prop.other_params.is_empty());
254
2
        assert_eq!(prop.value, b"GREGORIAN");
255
2
    }
256

            
257
    #[test]
258
2
    fn cal_scale_with_param() {
259
2
        let input = b"CALSCALE;x-prop=val:GREGORIAN\r\n;";
260
2
        let (rem, prop) = prop_calendar_scale::<Error>(input).unwrap();
261
2
        check_rem(rem, 1);
262
2
        assert_eq!(prop.other_params.len(), 1);
263
2
        assert_eq!(
264
2
            prop.other_params[0],
265
2
            ParamValue::Other {
266
2
                name: b"x-prop",
267
2
                value: b"val"
268
2
            }
269
2
        );
270
2
        assert_eq!(prop.value, b"GREGORIAN");
271
2
    }
272

            
273
    #[test]
274
2
    fn method() {
275
2
        let input = b"METHOD:REQUEST\r\n;";
276
2
        let (rem, prop) = prop_method::<Error>(input).unwrap();
277
2
        check_rem(rem, 1);
278
2
        assert!(prop.other_params.is_empty());
279
2
        assert_eq!(prop.value, b"REQUEST");
280
2
    }
281

            
282
    #[test]
283
2
    fn method_with_param() {
284
2
        let input = b"METHOD;x-prop=val:REQUEST\r\n;";
285
2
        let (rem, prop) = prop_method::<Error>(input).unwrap();
286
2
        check_rem(rem, 1);
287
2
        assert_eq!(prop.other_params.len(), 1);
288
2
        assert_eq!(
289
2
            prop.other_params[0],
290
2
            ParamValue::Other {
291
2
                name: b"x-prop",
292
2
                value: b"val"
293
2
            }
294
2
        );
295
2
        assert_eq!(prop.value, b"REQUEST");
296
2
    }
297

            
298
    #[test]
299
2
    fn x_prop() {
300
2
        let input =
301
2
            b"X-ABC-MMSUBJ;VALUE=URI;FMTTYPE=audio/basic:http://www.example.org/mysubj.au\r\n;";
302
2
        let (rem, prop) = prop_x::<Error>(input).unwrap();
303
2
        check_rem(rem, 1);
304
2
        assert_eq!(prop.name, b"X-ABC-MMSUBJ");
305
2
        assert_eq!(prop.params.len(), 2);
306
2
        assert_eq!(prop.params[0], ParamValue::ValueType { value: Value::Uri });
307
2
        assert_eq!(
308
2
            prop.params[1],
309
2
            ParamValue::FormatType {
310
2
                type_name: "audio".to_string(),
311
2
                sub_type_name: "basic".to_string()
312
2
            }
313
2
        );
314
2
        assert_eq!(prop.value, b"http://www.example.org/mysubj.au");
315
2
    }
316

            
317
    #[test]
318
2
    fn iana_prop() {
319
2
        let input = b"DRESSCODE:CASUAL\r\n;";
320
2
        let (rem, prop) = prop_iana::<Error>(input).unwrap();
321
2
        check_rem(rem, 1);
322
2
        assert_eq!(prop.name, b"DRESSCODE");
323
2
        assert!(prop.params.is_empty());
324
2
        assert_eq!(prop.value, b"CASUAL");
325
2
    }
326

            
327
    #[test]
328
2
    fn iana_prop_with_params() {
329
2
        let input = b"NON-SMOKING;VALUE=BOOLEAN:TRUE\r\n;";
330
2
        let (rem, prop) = prop_iana::<Error>(input).unwrap();
331
2
        check_rem(rem, 1);
332
2
        assert_eq!(prop.name, b"NON-SMOKING");
333
2
        assert_eq!(prop.params.len(), 1);
334
2
        assert_eq!(
335
2
            prop.params[0],
336
2
            ParamValue::ValueType {
337
2
                value: Value::Boolean
338
2
            }
339
2
        );
340
2
        assert_eq!(prop.value, b"TRUE");
341
2
    }
342
}