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::streaming::{char, one_of};
9
use nom::combinator::{opt, recognize};
10
use nom::error::ParseError;
11
use nom::multi::{fold_many0, many0};
12
use nom::Parser;
13
use nom::{AsChar, IResult};
14

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

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

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

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

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

            
47
974
pub fn prop_value_date<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Date, E>
48
974
where
49
974
    E: ParseError<&'a [u8]> + From<Error<'a>>,
50
974
{
51
974
    let (input, (year, month, day)) = (
52
974
        take_while_m_n(4, 4, AsChar::is_dec_digit),
53
974
        take_while_m_n(2, 2, AsChar::is_dec_digit),
54
974
        take_while_m_n(2, 2, AsChar::is_dec_digit),
55
974
    )
56
974
        .parse(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)) = (
105
892
        take_while_m_n(2, 2, AsChar::is_dec_digit),
106
892
        take_while_m_n(2, 2, AsChar::is_dec_digit),
107
892
        take_while_m_n(2, 2, AsChar::is_dec_digit),
108
892
        opt(char('Z')).map(|x| x.is_some()),
109
892
    )
110
892
        .parse(input)?;
111

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
221
142
    let (input, t) = opt(char('T')).parse(input)?;
222

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

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

            
238
46
    let (input, num) = duration_num(input)?;
239

            
240
46
    let (input, date_branch) = one_of("DW")(input)?;
241

            
242
46
    match date_branch {
243
        'D' => {
244
16
            let (input, time) = opt((char('T'), duration_time))
245
16
                .map(|opt| opt.map(|(_, t)| t))
246
16
                .parse(input)?;
247

            
248
16
            let (hours, minutes, seconds) = time.unwrap_or((None, None, None));
249
16

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

            
275
52
pub fn prop_value_float<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], f64, E>
276
52
where
277
52
    E: ParseError<&'a [u8]> + From<Error<'a>>,
278
52
{
279
52
    let (input, (sign, num)) = (
280
52
        opt_sign,
281
52
        recognize((
282
52
            take_while1(AsChar::is_dec_digit),
283
52
            opt((char('.'), take_while1(AsChar::is_dec_digit))),
284
52
        )),
285
52
    )
286
52
        .parse(input)?;
287

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

            
301
52
    Ok((input, sign as f64 * num))
302
52
}
303

            
304
126
pub fn prop_value_integer<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], i32, E>
305
126
where
306
126
    E: ParseError<&'a [u8]> + From<Error<'a>>,
307
126
{
308
126
    let (input, (sign, num)) = (opt_sign, take_while1(AsChar::is_dec_digit)).parse(input)?;
309

            
310
126
    let num: i32 = read_int(num)?;
311

            
312
126
    Ok((input, sign as i32 * num))
313
126
}
314

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

            
329
42
    Ok((input, Period { start, end }))
330
116
}
331

            
332
#[inline]
333
14240
const fn is_text_safe_char(c: u8) -> bool {
334
14240
    matches!(c, b' ' | b'\t' | b'\x21' | b'\x23'..=b'\x2B' | b'\x2D'..=b'\x39' | b'\x3C'..=b'\x5B' | b'\x5D'..=b'\x7E')
335
14240
}
336

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

            
366
994
    Ok((input, r))
367
994
}
368

            
369
172
pub fn prop_value_utc_offset<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], UtcOffset, E>
370
172
where
371
172
    E: ParseError<&'a [u8]> + From<Error<'a>>,
372
172
{
373
172
    let (input, (sign, h, m, s)) = (
374
172
        one_of("+-"),
375
172
        take_while_m_n(2, 2, AsChar::is_dec_digit),
376
172
        take_while_m_n(2, 2, AsChar::is_dec_digit),
377
172
        opt(take_while_m_n(2, 2, AsChar::is_dec_digit)),
378
172
    )
379
172
        .parse(input)?;
380

            
381
    Ok((
382
172
        input,
383
172
        UtcOffset {
384
172
            sign: if sign == '+' { 1 } else { -1 },
385
172
            hours: std::str::from_utf8(h).unwrap().parse().unwrap(),
386
172
            minutes: std::str::from_utf8(m).unwrap().parse().unwrap(),
387
172
            seconds: s.map(|s| std::str::from_utf8(s).unwrap().parse().unwrap()),
388
172
        },
389
172
    ))
390
172
}
391

            
392
#[cfg(test)]
393
mod tests {
394
    use super::*;
395

            
396
    use crate::test_utils::check_rem;
397
    use base64::Engine;
398

            
399
    #[test]
400
2
    fn base64() {
401
2
        let (rem, value) =
402
2
            prop_value_binary::<Error>(b"VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGluZyB0ZXh0;").unwrap();
403
2
        check_rem(rem, 1);
404
2
        let r = base64::prelude::BASE64_STANDARD.decode(value).unwrap();
405
2
        assert_eq!(b"This is a base64 encoding text", r.as_slice());
406
2
    }
407

            
408
    #[test]
409
2
    fn calendar_user_address() {
410
2
        let (rem, value) =
411
2
            prop_value_calendar_user_address::<Error>(b"mailto:jane_doe@example.com`").unwrap();
412
2
        check_rem(rem, 1);
413
2
        assert_eq!(value.scheme, b"mailto");
414
2
        assert_eq!(value.path, b"jane_doe@example.com")
415
2
    }
416

            
417
    #[test]
418
2
    fn date() {
419
2
        let (rem, value) = prop_value_date::<Error>(b"19970714;").unwrap();
420
2
        check_rem(rem, 1);
421
2
        assert_eq!(
422
2
            Date {
423
2
                year: 1997,
424
2
                month: 7,
425
2
                day: 14
426
2
            },
427
2
            value
428
2
        );
429
2
    }
430

            
431
    #[test]
432
2
    fn time() {
433
2
        let (rem, value) = prop_value_time::<Error>(b"230000;").unwrap();
434
2
        check_rem(rem, 1);
435
2
        assert_eq!(
436
2
            Time {
437
2
                hour: 23,
438
2
                minute: 0,
439
2
                second: 0,
440
2
                is_utc: false
441
2
            },
442
2
            value
443
2
        );
444
2
    }
445

            
446
    #[test]
447
2
    fn time_utc() {
448
2
        let (rem, value) = prop_value_time::<Error>(b"133000Z;").unwrap();
449
2
        check_rem(rem, 1);
450
2
        assert_eq!(
451
2
            Time {
452
2
                hour: 13,
453
2
                minute: 30,
454
2
                second: 0,
455
2
                is_utc: true
456
2
            },
457
2
            value
458
2
        );
459
2
    }
460

            
461
    #[test]
462
2
    fn date_time() {
463
2
        let (rem, value) = prop_value_date_time::<Error>(b"19980118T230000;").unwrap();
464
2
        check_rem(rem, 1);
465
2
        assert_eq!(
466
2
            DateTime {
467
2
                date: Date {
468
2
                    year: 1998,
469
2
                    month: 1,
470
2
                    day: 18
471
2
                },
472
2
                time: Time {
473
2
                    hour: 23,
474
2
                    minute: 0,
475
2
                    second: 0,
476
2
                    is_utc: false
477
2
                }
478
2
            },
479
2
            value
480
2
        );
481
2
    }
482

            
483
    #[test]
484
2
    fn duration_seven_weeks() {
485
2
        let (rem, value) = prop_value_duration::<Error>(b"P7W;").unwrap();
486
2
        check_rem(rem, 1);
487
2
        assert_eq!(
488
2
            Duration {
489
2
                weeks: Some(7),
490
2
                ..Default::default()
491
2
            },
492
2
            value
493
2
        );
494
2
    }
495

            
496
    #[test]
497
2
    fn duration_date_and_time() {
498
2
        let (rem, value) = prop_value_duration::<Error>(b"P15DT5H0M20S;").unwrap();
499
2
        check_rem(rem, 1);
500
2
        assert_eq!(
501
2
            Duration {
502
2
                days: Some(15),
503
2
                hours: Some(5),
504
2
                minutes: Some(0),
505
2
                seconds: Some(20),
506
2
                ..Default::default()
507
2
            },
508
2
            value
509
2
        );
510
2
    }
511

            
512
    #[test]
513
2
    fn duration_signed_time() {
514
2
        let (rem, value) = prop_value_duration::<Error>(b"-PT10M20S;").unwrap();
515
2
        check_rem(rem, 1);
516
2
        assert_eq!(
517
2
            Duration {
518
2
                sign: -1,
519
2
                minutes: Some(10),
520
2
                seconds: Some(20),
521
2
                ..Default::default()
522
2
            },
523
2
            value
524
2
        );
525
2
    }
526

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

            
534
    #[test]
535
2
    fn float_negative() {
536
2
        let (rem, value) = prop_value_float::<Error>(b"-1.333;").unwrap();
537
2
        check_rem(rem, 1);
538
2
        assert_eq!(-1.333, value);
539
2
    }
540

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

            
548
    #[test]
549
2
    fn integer_negative() {
550
2
        let (rem, value) = prop_value_integer::<Error>(b"-1234567890;").unwrap();
551
2
        check_rem(rem, 1);
552
2
        assert_eq!(-1234567890, value);
553
2
    }
554

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

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

            
622
    #[test]
623
2
    fn text() {
624
2
        let (rem, value) = prop_value_text::<Error>(
625
2
            br#"Project XYZ Final Review\nConference Room - 3B\nCome Prepared.;"#,
626
2
        )
627
2
        .unwrap();
628
2
        check_rem(rem, 1);
629
2
        assert_eq!(
630
2
            br#"Project XYZ Final Review
631
2
Conference Room - 3B
632
2
Come Prepared."#,
633
2
            value.as_slice()
634
2
        );
635
2
    }
636

            
637
    #[test]
638
2
    fn text_with_quoted_value() {
639
2
        let (rem, value) = prop_value_text::<Error>(br#"Hello\, "World";"#).unwrap();
640
2
        println!("{:?}", String::from_utf8(value.clone()).unwrap());
641
2
        check_rem(rem, 1);
642
2
        assert_eq!(br#"Hello, "World""#, value.as_slice());
643
2
    }
644

            
645
    #[test]
646
2
    fn utc_offset_negative() {
647
2
        let (rem, value) = prop_value_utc_offset::<Error>(b"-0500;").unwrap();
648
2
        check_rem(rem, 1);
649
2
        assert_eq!(
650
2
            UtcOffset {
651
2
                sign: -1,
652
2
                hours: 5,
653
2
                minutes: 0,
654
2
                seconds: None
655
2
            },
656
2
            value
657
2
        );
658
2
    }
659

            
660
    #[test]
661
2
    fn utc_offset_positive() {
662
2
        let (rem, value) = prop_value_utc_offset::<Error>(b"+0130;").unwrap();
663
2
        check_rem(rem, 1);
664
2
        assert_eq!(
665
2
            UtcOffset {
666
2
                sign: 1,
667
2
                hours: 1,
668
2
                minutes: 30,
669
2
                seconds: None
670
2
            },
671
2
            value
672
2
        );
673
2
    }
674
}