1
use crate::parser::property::uri::param_value_uri;
2
use crate::parser::types::{Date, DateTime, Duration, Period, PeriodEnd, Time, Uri, UtcOffset};
3
use crate::parser::{read_int, Error, InnerError};
4
use crate::utf8_seq;
5
use nom::branch::alt;
6
use nom::bytes::complete::take_while1;
7
use nom::bytes::streaming::{tag, tag_no_case, take_while_m_n};
8
use nom::character::is_digit;
9
use nom::character::streaming::{char, one_of};
10
use nom::combinator::{opt, recognize};
11
use nom::error::ParseError;
12
use nom::multi::{fold_many0, many0};
13
use nom::sequence::tuple;
14
use nom::IResult;
15
use nom::Parser;
16

            
17
#[inline]
18
684
const fn is_base64(c: u8) -> bool {
19
684
    matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'+' | b'/')
20
684
}
21

            
22
24
pub fn prop_value_binary<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8], E>
23
24
where
24
24
    E: ParseError<&'a [u8]> + From<Error<'a>>,
25
24
{
26
24
    let (input, content) = recognize(tuple((
27
24
        many0(take_while_m_n(4, 4, is_base64)),
28
24
        opt(alt((
29
24
            tuple((take_while_m_n(2, 2, is_base64), tag("=="))),
30
24
            tuple((take_while_m_n(3, 3, is_base64), tag("="))),
31
24
        ))),
32
24
    )))(input)?;
33

            
34
24
    Ok((input, content))
35
24
}
36

            
37
72
pub fn prop_value_calendar_user_address<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Uri<'a>, E>
38
72
where
39
72
    E: ParseError<&'a [u8]>
40
72
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
41
72
        + From<Error<'a>>,
42
72
{
43
72
    let (input, uri) = param_value_uri(input)?;
44

            
45
72
    Ok((input, uri))
46
72
}
47

            
48
974
pub fn prop_value_date<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Date, E>
49
974
where
50
974
    E: ParseError<&'a [u8]> + From<Error<'a>>,
51
974
{
52
974
    let (input, (year, month, day)) = tuple((
53
974
        take_while_m_n(4, 4, is_digit),
54
974
        take_while_m_n(2, 2, is_digit),
55
974
        take_while_m_n(2, 2, is_digit),
56
974
    ))(input)?;
57

            
58
972
    let year = std::str::from_utf8(year)
59
972
        .map_err(|e| {
60
            nom::Err::Error(
61
                Error::new(
62
                    input,
63
                    InnerError::EncodingError("Invalid date year text".to_string(), e),
64
                )
65
                .into(),
66
            )
67
972
        })?
68
972
        .parse()
69
972
        .map_err(|_| nom::Err::Error(Error::new(input, InnerError::InvalidDateNum).into()))?;
70

            
71
972
    let month = std::str::from_utf8(month)
72
972
        .map_err(|e| {
73
            nom::Err::Error(
74
                Error::new(
75
                    input,
76
                    InnerError::EncodingError("Invalid date month text".to_string(), e),
77
                )
78
                .into(),
79
            )
80
972
        })?
81
972
        .parse()
82
972
        .map_err(|_| nom::Err::Error(Error::new(input, InnerError::InvalidDateNum).into()))?;
83

            
84
972
    let day = std::str::from_utf8(day)
85
972
        .map_err(|e| {
86
            nom::Err::Error(
87
                Error::new(
88
                    input,
89
                    InnerError::EncodingError("Invalid date day text".to_string(), e),
90
                )
91
                .into(),
92
            )
93
972
        })?
94
972
        .parse()
95
972
        .map_err(|_| nom::Err::Error(Error::new(input, InnerError::InvalidDateNum).into()))?;
96

            
97
972
    Ok((input, Date { year, month, day }))
98
974
}
99

            
100
892
pub fn prop_value_time<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Time, E>
101
892
where
102
892
    E: ParseError<&'a [u8]> + From<Error<'a>>,
103
892
{
104
892
    let (input, (h, m, s, is_utc)) = tuple((
105
892
        take_while_m_n(2, 2, is_digit),
106
892
        take_while_m_n(2, 2, is_digit),
107
892
        take_while_m_n(2, 2, is_digit),
108
892
        opt(char('Z')).map(|x| x.is_some()),
109
892
    ))(input)?;
110

            
111
2676
    let read_time = |s: &[u8]| -> Result<u8, Error> {
112
2676
        std::str::from_utf8(s)
113
2676
            .map_err(|e| {
114
                Error::new(
115
                    input,
116
                    InnerError::EncodingError("Invalid time text".to_string(), e),
117
                )
118
2676
            })?
119
2676
            .parse()
120
2676
            .map_err(|_| Error::new(input, InnerError::InvalidTimeNum))
121
2676
    };
122

            
123
    Ok((
124
892
        input,
125
892
        Time {
126
892
            hour: read_time(h).map_err(|e| nom::Err::Error(e.into()))?,
127
892
            minute: read_time(m).map_err(|e| nom::Err::Error(e.into()))?,
128
892
            second: read_time(s).map_err(|e| nom::Err::Error(e.into()))?,
129
892
            is_utc,
130
        },
131
    ))
132
892
}
133

            
134
858
pub fn prop_value_date_time<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], DateTime, E>
135
858
where
136
858
    E: ParseError<&'a [u8]> + From<Error<'a>>,
137
858
{
138
858
    let (input, (date, _, time)) = tuple((prop_value_date, char('T'), prop_value_time))(input)?;
139

            
140
812
    Ok((input, DateTime { date, time }))
141
858
}
142

            
143
306
fn duration_num<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], u64, E>
144
306
where
145
306
    E: ParseError<&'a [u8]> + From<Error<'a>>,
146
306
{
147
306
    let (input, v) = take_while1(is_digit)(input)?;
148

            
149
164
    let s = std::str::from_utf8(v).map_err(|e| {
150
        nom::Err::Error(
151
            Error::new(
152
                input,
153
                InnerError::EncodingError("Invalid duration number text".to_string(), e),
154
            )
155
            .into(),
156
        )
157
164
    })?;
158

            
159
    Ok((
160
164
        input,
161
164
        s.parse().map_err(|_| {
162
            nom::Err::Error(Error::new(input, InnerError::InvalidDurationNum).into())
163
164
        })?,
164
    ))
165
306
}
166

            
167
type HoursMinutesSeconds = (Option<u64>, Option<u64>, Option<u64>);
168

            
169
98
fn duration_time<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], HoursMinutesSeconds, E>
170
98
where
171
98
    E: ParseError<&'a [u8]> + From<Error<'a>>,
172
98
{
173
98
    let (input, num) = duration_num(input)?;
174

            
175
98
    let (input, time_branch) = one_of("HMS")(input)?;
176

            
177
98
    match time_branch {
178
        'H' => {
179
64
            let (input, (min, sec)) = tuple((
180
64
                opt(tuple((duration_num, char('M'))).map(|(min, _)| min)),
181
64
                opt(tuple((duration_num, char('S'))).map(|(sec, _)| sec)),
182
64
            ))(input)?;
183

            
184
64
            Ok((input, (Some(num), min, sec)))
185
        }
186
        'M' => {
187
34
            let (input, sec) = opt(tuple((duration_num, char('S'))).map(|(sec, _)| sec))(input)?;
188

            
189
34
            Ok((input, (None, Some(num), sec)))
190
        }
191
        'S' => Ok((input, (None, None, Some(num)))),
192
        // This is unreachable because of the one_of combinator
193
        _ => unreachable!(),
194
    }
195
98
}
196

            
197
344
fn opt_sign<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], i8, E>
198
344
where
199
344
    E: ParseError<&'a [u8]> + From<Error<'a>>,
200
344
{
201
344
    opt(alt((char('+'), char('-'))))
202
344
        .map(|x| {
203
344
            match x {
204
14
                Some('-') => -1,
205
330
                None | Some('+') => 1,
206
                // This is unreachable because of the alt combinator
207
                _ => unreachable!(),
208
            }
209
344
        })
210
344
        .parse(input)
211
344
}
212

            
213
166
pub fn prop_value_duration<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Duration, E>
214
166
where
215
166
    E: ParseError<&'a [u8]> + From<Error<'a>>,
216
166
{
217
166
    let (input, (sign, _)) = tuple((opt_sign, char('P')))(input)?;
218

            
219
142
    let (input, t) = opt(char('T'))(input)?;
220

            
221
142
    if t.is_some() {
222
96
        let (input, (hours, minutes, seconds)) = duration_time(input)?;
223

            
224
96
        return Ok((
225
96
            input,
226
96
            Duration {
227
96
                sign,
228
96
                hours,
229
96
                minutes,
230
96
                seconds,
231
96
                ..Default::default()
232
96
            },
233
96
        ));
234
46
    };
235

            
236
46
    let (input, num) = duration_num(input)?;
237

            
238
46
    let (input, date_branch) = one_of("DW")(input)?;
239

            
240
46
    match date_branch {
241
        'D' => {
242
16
            let (input, time) = opt(tuple((char('T'), duration_time)).map(|(_, t)| t))(input)?;
243

            
244
16
            let (hours, minutes, seconds) = time.unwrap_or((None, None, None));
245
16

            
246
16
            Ok((
247
16
                input,
248
16
                Duration {
249
16
                    sign,
250
16
                    days: Some(num),
251
16
                    hours,
252
16
                    minutes,
253
16
                    seconds,
254
16
                    ..Default::default()
255
16
                },
256
16
            ))
257
        }
258
30
        'W' => Ok((
259
30
            input,
260
30
            Duration {
261
30
                sign,
262
30
                weeks: Some(num),
263
30
                ..Default::default()
264
30
            },
265
30
        )),
266
        // This is unreachable because of the one_of combinator
267
        _ => unreachable!(),
268
    }
269
166
}
270

            
271
52
pub fn prop_value_float<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], f64, E>
272
52
where
273
52
    E: ParseError<&'a [u8]> + From<Error<'a>>,
274
52
{
275
52
    let (input, (sign, num)) = tuple((
276
52
        opt_sign,
277
52
        recognize(tuple((
278
52
            take_while1(is_digit),
279
52
            opt(tuple((char('.'), take_while1(is_digit)))),
280
52
        ))),
281
52
    ))(input)?;
282

            
283
52
    let num: f64 = std::str::from_utf8(num)
284
52
        .map_err(|e| {
285
            nom::Err::Error(
286
                Error::new(
287
                    input,
288
                    InnerError::EncodingError("Invalid float number text".to_string(), e),
289
                )
290
                .into(),
291
            )
292
52
        })?
293
52
        .parse()
294
52
        .map_err(|_| nom::Err::Error(Error::new(input, InnerError::InvalidFloatNum).into()))?;
295

            
296
52
    Ok((input, sign as f64 * num))
297
52
}
298

            
299
126
pub fn prop_value_integer<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], i32, E>
300
126
where
301
126
    E: ParseError<&'a [u8]> + From<Error<'a>>,
302
126
{
303
126
    let (input, (sign, num)) = tuple((opt_sign, take_while1(is_digit)))(input)?;
304

            
305
126
    let num: i32 = read_int(num)?;
306

            
307
126
    Ok((input, sign as i32 * num))
308
126
}
309

            
310
116
pub fn prop_value_period<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Period, E>
311
116
where
312
116
    E: ParseError<&'a [u8]> + From<Error<'a>>,
313
116
{
314
116
    let (input, (start, _, end)) = tuple((
315
116
        prop_value_date_time,
316
116
        char('/'),
317
116
        alt((
318
116
            prop_value_duration.map(PeriodEnd::Duration),
319
116
            prop_value_date_time.map(PeriodEnd::DateTime),
320
116
        )),
321
116
    ))(input)?;
322

            
323
42
    Ok((input, Period { start, end }))
324
116
}
325

            
326
#[inline]
327
14240
const fn is_text_safe_char(c: u8) -> bool {
328
14240
    matches!(c, b' ' | b'\t' | b'\x21' | b'\x23'..=b'\x2B' | b'\x2D'..=b'\x39' | b'\x3C'..=b'\x5B' | b'\x5D'..=b'\x7E')
329
14240
}
330

            
331
994
pub fn prop_value_text<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Vec<u8>, E>
332
994
where
333
994
    E: ParseError<&'a [u8]> + From<Error<'a>>,
334
994
{
335
994
    let (input, r) = fold_many0(
336
994
        alt((
337
994
            // Escaped characters
338
994
            tuple((
339
994
                char('\\'),
340
994
                alt((tag_no_case("n").map(|_| b'\n' as char), one_of(r#"\;,"#))),
341
994
            ))
342
994
            .map(|(_, c)| vec![c as u8]),
343
994
            // Allowed raw characters
344
994
            one_of(r#":""#).map(|c: char| vec![c as u8]),
345
994
            // Text split over multiple lines
346
994
            tuple((tag("\r\n"), alt((char(' '), char('\t'))))).map(|_| Vec::with_capacity(0)),
347
994
            // UTF-8 sequence
348
994
            utf8_seq.map(|seq| seq.to_vec()),
349
994
            // Other text safe characters
350
1098
            take_while1(is_text_safe_char).map(|section: &[u8]| section.to_vec()),
351
994
        )),
352
994
        Vec::new,
353
1216
        |mut acc, item| {
354
1216
            acc.extend_from_slice(&item);
355
1216
            acc
356
1216
        },
357
994
    )(input)?;
358

            
359
994
    Ok((input, r))
360
994
}
361

            
362
172
pub fn prop_value_utc_offset<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], UtcOffset, E>
363
172
where
364
172
    E: ParseError<&'a [u8]> + From<Error<'a>>,
365
172
{
366
172
    let (input, (sign, h, m, s)) = tuple((
367
172
        one_of("+-"),
368
172
        take_while_m_n(2, 2, is_digit),
369
172
        take_while_m_n(2, 2, is_digit),
370
172
        opt(take_while_m_n(2, 2, is_digit)),
371
172
    ))(input)?;
372

            
373
    Ok((
374
172
        input,
375
172
        UtcOffset {
376
172
            sign: if sign == '+' { 1 } else { -1 },
377
172
            hours: std::str::from_utf8(h).unwrap().parse().unwrap(),
378
172
            minutes: std::str::from_utf8(m).unwrap().parse().unwrap(),
379
172
            seconds: s.map(|s| std::str::from_utf8(s).unwrap().parse().unwrap()),
380
172
        },
381
172
    ))
382
172
}
383

            
384
#[cfg(test)]
385
mod tests {
386
    use super::*;
387

            
388
    use crate::test_utils::check_rem;
389
    use base64::Engine;
390

            
391
    #[test]
392
2
    fn base64() {
393
2
        let (rem, value) =
394
2
            prop_value_binary::<Error>(b"VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGluZyB0ZXh0;").unwrap();
395
2
        check_rem(rem, 1);
396
2
        let r = base64::prelude::BASE64_STANDARD.decode(value).unwrap();
397
2
        assert_eq!(b"This is a base64 encoding text", r.as_slice());
398
2
    }
399

            
400
    #[test]
401
2
    fn calendar_user_address() {
402
2
        let (rem, value) =
403
2
            prop_value_calendar_user_address::<Error>(b"mailto:jane_doe@example.com`").unwrap();
404
2
        check_rem(rem, 1);
405
2
        assert_eq!(value.scheme, b"mailto");
406
2
        assert_eq!(value.path, b"jane_doe@example.com")
407
2
    }
408

            
409
    #[test]
410
2
    fn date() {
411
2
        let (rem, value) = prop_value_date::<Error>(b"19970714;").unwrap();
412
2
        check_rem(rem, 1);
413
2
        assert_eq!(
414
2
            Date {
415
2
                year: 1997,
416
2
                month: 7,
417
2
                day: 14
418
2
            },
419
2
            value
420
2
        );
421
2
    }
422

            
423
    #[test]
424
2
    fn time() {
425
2
        let (rem, value) = prop_value_time::<Error>(b"230000;").unwrap();
426
2
        check_rem(rem, 1);
427
2
        assert_eq!(
428
2
            Time {
429
2
                hour: 23,
430
2
                minute: 0,
431
2
                second: 0,
432
2
                is_utc: false
433
2
            },
434
2
            value
435
2
        );
436
2
    }
437

            
438
    #[test]
439
2
    fn time_utc() {
440
2
        let (rem, value) = prop_value_time::<Error>(b"133000Z;").unwrap();
441
2
        check_rem(rem, 1);
442
2
        assert_eq!(
443
2
            Time {
444
2
                hour: 13,
445
2
                minute: 30,
446
2
                second: 0,
447
2
                is_utc: true
448
2
            },
449
2
            value
450
2
        );
451
2
    }
452

            
453
    #[test]
454
2
    fn date_time() {
455
2
        let (rem, value) = prop_value_date_time::<Error>(b"19980118T230000;").unwrap();
456
2
        check_rem(rem, 1);
457
2
        assert_eq!(
458
2
            DateTime {
459
2
                date: Date {
460
2
                    year: 1998,
461
2
                    month: 1,
462
2
                    day: 18
463
2
                },
464
2
                time: Time {
465
2
                    hour: 23,
466
2
                    minute: 0,
467
2
                    second: 0,
468
2
                    is_utc: false
469
2
                }
470
2
            },
471
2
            value
472
2
        );
473
2
    }
474

            
475
    #[test]
476
2
    fn duration_seven_weeks() {
477
2
        let (rem, value) = prop_value_duration::<Error>(b"P7W;").unwrap();
478
2
        check_rem(rem, 1);
479
2
        assert_eq!(
480
2
            Duration {
481
2
                weeks: Some(7),
482
2
                ..Default::default()
483
2
            },
484
2
            value
485
2
        );
486
2
    }
487

            
488
    #[test]
489
2
    fn duration_date_and_time() {
490
2
        let (rem, value) = prop_value_duration::<Error>(b"P15DT5H0M20S;").unwrap();
491
2
        check_rem(rem, 1);
492
2
        assert_eq!(
493
2
            Duration {
494
2
                days: Some(15),
495
2
                hours: Some(5),
496
2
                minutes: Some(0),
497
2
                seconds: Some(20),
498
2
                ..Default::default()
499
2
            },
500
2
            value
501
2
        );
502
2
    }
503

            
504
    #[test]
505
2
    fn duration_signed_time() {
506
2
        let (rem, value) = prop_value_duration::<Error>(b"-PT10M20S;").unwrap();
507
2
        check_rem(rem, 1);
508
2
        assert_eq!(
509
2
            Duration {
510
2
                sign: -1,
511
2
                minutes: Some(10),
512
2
                seconds: Some(20),
513
2
                ..Default::default()
514
2
            },
515
2
            value
516
2
        );
517
2
    }
518

            
519
    #[test]
520
2
    fn float() {
521
2
        let (rem, value) = prop_value_float::<Error>(b"1000000.0000001;").unwrap();
522
2
        check_rem(rem, 1);
523
2
        assert_eq!(1000000.0000001f64, value);
524
2
    }
525

            
526
    #[test]
527
2
    fn float_negative() {
528
2
        let (rem, value) = prop_value_float::<Error>(b"-1.333;").unwrap();
529
2
        check_rem(rem, 1);
530
2
        assert_eq!(-1.333, value);
531
2
    }
532

            
533
    #[test]
534
2
    fn integer() {
535
2
        let (rem, value) = prop_value_integer::<Error>(b"1234567890;").unwrap();
536
2
        check_rem(rem, 1);
537
2
        assert_eq!(1234567890, value);
538
2
    }
539

            
540
    #[test]
541
2
    fn integer_negative() {
542
2
        let (rem, value) = prop_value_integer::<Error>(b"-1234567890;").unwrap();
543
2
        check_rem(rem, 1);
544
2
        assert_eq!(-1234567890, value);
545
2
    }
546

            
547
    #[test]
548
2
    fn period() {
549
2
        let (rem, value) =
550
2
            prop_value_period::<Error>(b"19970101T180000Z/19970102T070000Z;").unwrap();
551
2
        check_rem(rem, 1);
552
2
        assert_eq!(
553
2
            Period {
554
2
                start: DateTime {
555
2
                    date: Date {
556
2
                        year: 1997,
557
2
                        month: 1,
558
2
                        day: 1
559
2
                    },
560
2
                    time: Time {
561
2
                        hour: 18,
562
2
                        minute: 0,
563
2
                        second: 0,
564
2
                        is_utc: true
565
2
                    }
566
2
                },
567
2
                end: PeriodEnd::DateTime(DateTime {
568
2
                    date: Date {
569
2
                        year: 1997,
570
2
                        month: 1,
571
2
                        day: 2
572
2
                    },
573
2
                    time: Time {
574
2
                        hour: 7,
575
2
                        minute: 0,
576
2
                        second: 0,
577
2
                        is_utc: true
578
2
                    }
579
2
                })
580
2
            },
581
2
            value
582
2
        );
583
2
    }
584

            
585
    #[test]
586
2
    fn period_duration() {
587
2
        let (rem, value) = prop_value_period::<Error>(b"19970101T180000Z/PT5H30M;").unwrap();
588
2
        check_rem(rem, 1);
589
2
        assert_eq!(
590
2
            Period {
591
2
                start: DateTime {
592
2
                    date: Date {
593
2
                        year: 1997,
594
2
                        month: 1,
595
2
                        day: 1
596
2
                    },
597
2
                    time: Time {
598
2
                        hour: 18,
599
2
                        minute: 0,
600
2
                        second: 0,
601
2
                        is_utc: true
602
2
                    }
603
2
                },
604
2
                end: PeriodEnd::Duration(Duration {
605
2
                    hours: Some(5),
606
2
                    minutes: Some(30),
607
2
                    ..Default::default()
608
2
                })
609
2
            },
610
2
            value
611
2
        );
612
2
    }
613

            
614
    #[test]
615
2
    fn text() {
616
2
        let (rem, value) = prop_value_text::<Error>(
617
2
            br#"Project XYZ Final Review\nConference Room - 3B\nCome Prepared.;"#,
618
2
        )
619
2
        .unwrap();
620
2
        check_rem(rem, 1);
621
2
        assert_eq!(
622
2
            br#"Project XYZ Final Review
623
2
Conference Room - 3B
624
2
Come Prepared."#,
625
2
            value.as_slice()
626
2
        );
627
2
    }
628

            
629
    #[test]
630
2
    fn text_with_quoted_value() {
631
2
        let (rem, value) = prop_value_text::<Error>(br#"Hello\, "World";"#).unwrap();
632
2
        println!("{:?}", String::from_utf8(value.clone()).unwrap());
633
2
        check_rem(rem, 1);
634
2
        assert_eq!(br#"Hello, "World""#, value.as_slice());
635
2
    }
636

            
637
    #[test]
638
2
    fn utc_offset_negative() {
639
2
        let (rem, value) = prop_value_utc_offset::<Error>(b"-0500;").unwrap();
640
2
        check_rem(rem, 1);
641
2
        assert_eq!(
642
2
            UtcOffset {
643
2
                sign: -1,
644
2
                hours: 5,
645
2
                minutes: 0,
646
2
                seconds: None
647
2
            },
648
2
            value
649
2
        );
650
2
    }
651

            
652
    #[test]
653
2
    fn utc_offset_positive() {
654
2
        let (rem, value) = prop_value_utc_offset::<Error>(b"+0130;").unwrap();
655
2
        check_rem(rem, 1);
656
2
        assert_eq!(
657
2
            UtcOffset {
658
2
                sign: 1,
659
2
                hours: 1,
660
2
                minutes: 30,
661
2
                seconds: None
662
2
            },
663
2
            value
664
2
        );
665
2
    }
666
}