1
use crate::common::{Encoding, Value};
2
use crate::convert::ToModel;
3
use crate::model::param::{EncodingParam, Param};
4
use crate::model::property::{
5
    AttendeeProperty, ComponentProperty, DateTimeDueProperty, DateTimeEndProperty,
6
    DateTimeStartProperty, ExceptionDateTimesProperty, OrganizerProperty,
7
    RecurrenceDateTimesProperty, RecurrenceDateTimesPropertyValue, RecurrenceIdProperty,
8
    TriggerValue,
9
};
10
use crate::parser::param_value_uri;
11
use crate::parser::prop_value_recur;
12
use crate::parser::{
13
    prop_value_binary, prop_value_date, prop_value_date_time, prop_value_duration,
14
    prop_value_float, prop_value_integer, prop_value_period, prop_value_text, prop_value_time,
15
    prop_value_utc_offset, Error,
16
};
17
use crate::serialize::WriteModel;
18
use crate::validate::recur::validate_recurrence_rule;
19
use crate::validate::{
20
    component_property_name, get_declared_value_type, validate_time, validate_utc_offset,
21
    ComponentPropertyError, ComponentPropertyLocation, ICalendarErrorSeverity, PropertyLocation,
22
    WithinPropertyLocation,
23
};
24
use anyhow::Context;
25
use nom::character::streaming::char;
26
use nom::multi::separated_list1;
27
use nom::AsBytes;
28

            
29
2122
pub(super) fn check_declared_value(
30
2122
    errors: &mut Vec<ComponentPropertyError>,
31
2122
    maybe_dt_start: Option<&DateTimeStartProperty>,
32
2122
    property: &ComponentProperty,
33
2122
    property_index: usize,
34
2122
) -> anyhow::Result<()> {
35
2122
    let declared_value_type = get_declared_value_type(property);
36
2122

            
37
2122
    let push_redundant_error_msg =
38
        |errors: &mut Vec<ComponentPropertyError>,
39
         property_index: usize,
40
14
         property: &ComponentProperty| {
41
14
            errors.push(ComponentPropertyError {
42
14
                message: "Redundant value specification which matches the default value"
43
14
                    .to_string(),
44
14
                severity: ICalendarErrorSeverity::Warning,
45
14
                location: Some(ComponentPropertyLocation {
46
14
                    index: property_index,
47
14
                    name: component_property_name(property).to_string(),
48
14
                    property_location: None,
49
14
                }),
50
14
            });
51
14
        };
52

            
53
2122
    if let Some((value_type, value_type_index)) = declared_value_type {
54
140
        match value_type {
55
            Value::Binary => {
56
18
                let mut found_encoding = None;
57
46
                for param in property.params() {
58
46
                    if let Param::Encoding(EncodingParam { encoding }) = param {
59
16
                        found_encoding = Some(encoding.clone());
60
16

            
61
16
                        if *encoding != Encoding::Base64 {
62
6
                            let mut msg = b"Property is declared to have a binary value but the encoding is set to ".to_vec();
63
6
                            encoding
64
6
                                .write_model(&mut msg)
65
6
                                .context("Failed to write encoding to model")?;
66
6
                            msg.extend_from_slice(", instead of BASE64".as_bytes());
67
6

            
68
6
                            errors.push(ComponentPropertyError {
69
6
                                message: String::from_utf8_lossy(&msg).to_string(),
70
6
                                severity: ICalendarErrorSeverity::Error,
71
6
                                location: Some(ComponentPropertyLocation {
72
6
                                    index: property_index,
73
6
                                    name: component_property_name(property).to_string(),
74
6
                                    property_location: Some(WithinPropertyLocation::Param {
75
6
                                        index: value_type_index,
76
6
                                        name: "VALUE".to_string(),
77
6
                                    }),
78
6
                                }),
79
6
                            });
80
6

            
81
6
                            return Ok(());
82
10
                        }
83
30
                    }
84
                }
85

            
86
12
                if found_encoding.is_none() {
87
2
                    errors.push(ComponentPropertyError {
88
2
                        message: "Property is declared to have a binary value but no encoding is set, must be set to BASE64".to_string(),
89
2
                        severity: ICalendarErrorSeverity::Error,
90
2
                        location: Some(ComponentPropertyLocation {
91
2
                            index: property_index,
92
2
                            name: component_property_name(property).to_string(),
93
2
                            property_location: Some(WithinPropertyLocation::Param {
94
2
                                index: value_type_index,
95
2
                                name: "VALUE".to_string(),
96
2
                            }),
97
2
                        }),
98
2
                    });
99
2
                    return Ok(());
100
10
                }
101
10

            
102
13
                let require_base64 = |v: &str, severity: ICalendarErrorSeverity| {
103
10
                    match found_encoding.expect("Always present") {
104
                        Encoding::Base64 => {
105
10
                            if !is_base64_valued(v) {
106
4
                                errors.push(ComponentPropertyError {
107
4
                                message: "Property is declared to have a binary value but the value is not base64".to_string(),
108
4
                                severity,
109
4
                                location: Some(ComponentPropertyLocation {
110
4
                                    index: property_index,
111
4
                                    name: component_property_name(property).to_string(),
112
4
                                    property_location: Some(WithinPropertyLocation::Value),
113
4
                                }),
114
4
                            });
115
10
                            }
116
                        }
117
                        _ => {
118
                            unreachable!("Encoding has already been checked to be Base64")
119
                        }
120
                    }
121
10
                };
122

            
123
10
                match property {
124
6
                    ComponentProperty::Attach(attach) => {
125
6
                        require_base64(&attach.value, ICalendarErrorSeverity::Error);
126
6
                    }
127
2
                    ComponentProperty::XProperty(x_prop) => {
128
2
                        require_base64(&x_prop.value, ICalendarErrorSeverity::Warning);
129
2
                    }
130
2
                    ComponentProperty::IanaProperty(iana_prop) => {
131
2
                        require_base64(&iana_prop.value, ICalendarErrorSeverity::Warning);
132
2
                    }
133
                    _ => {
134
                        errors.push(ComponentPropertyError {
135
                            message: "Property is declared to have a binary value but that is not valid for this property".to_string(),
136
                            severity: ICalendarErrorSeverity::Error,
137
                            location: Some(ComponentPropertyLocation {
138
                                index: property_index,
139
                                name: component_property_name(property).to_string(),
140
                                property_location: Some(WithinPropertyLocation::Value),
141
                            }),
142
                        });
143
                    }
144
                }
145
            }
146
4
            Value::Boolean => match property {
147
4
                ComponentProperty::XProperty(x_prop) if !is_boolean_valued(&x_prop.value) => {
148
4
                    errors.push(ComponentPropertyError {
149
4
                        message: "Property is declared to have a boolean value but the value is not a boolean".to_string(),
150
4
                        severity: ICalendarErrorSeverity::Warning,
151
4
                        location: Some(ComponentPropertyLocation {
152
4
                            index: property_index,
153
4
                            name: component_property_name(property).to_string(),
154
4
                            property_location: None,
155
4
                        }),
156
4
                    });
157
4
                }
158
4
                ComponentProperty::IanaProperty(iana_prop)
159
4
                    if !is_boolean_valued(&iana_prop.value) =>
160
4
                {
161
4
                    errors.push(ComponentPropertyError {
162
4
                            message: "Property is declared to have a boolean value but the value is not a boolean".to_string(),
163
4
                        severity: ICalendarErrorSeverity::Warning,
164
4
                            location: Some(ComponentPropertyLocation {
165
4
                                index: property_index,
166
4
                                name: component_property_name(property).to_string(),
167
4
                                property_location: None,
168
4
                            }),
169
4
                        });
170
4
                }
171
                _ => {
172
                    // Otherwise the value is boolean or not based on Rust typing
173
                }
174
            },
175
            Value::CalendarAddress => {
176
8
                let require_mailto =
177
                    |errors: &mut Vec<ComponentPropertyError>,
178
                     v: &str,
179
8
                     severity: ICalendarErrorSeverity| {
180
8
                        if !v.starts_with("mailto:") {
181
4
                            errors.push(ComponentPropertyError {
182
4
                            message: "Property is declared to have a calendar address value but the value is not a mailto: URI".to_string(),
183
4
                            severity,
184
4
                            location: Some(ComponentPropertyLocation {
185
4
                                index: property_index,
186
4
                                name: component_property_name(property).to_string(),
187
4
                                property_location: Some(WithinPropertyLocation::Value),
188
4
                            }),
189
4
                        });
190
4
                        }
191
8
                    };
192

            
193
8
                match property {
194
                    ComponentProperty::Attendee(AttendeeProperty { value, .. })
195
                    | ComponentProperty::Organizer(OrganizerProperty { value, .. }) => {
196
                        push_redundant_error_msg(errors, property_index, property);
197

            
198
                        require_mailto(errors, value, ICalendarErrorSeverity::Error);
199
                    }
200
4
                    ComponentProperty::XProperty(x_prop) => {
201
4
                        require_mailto(errors, &x_prop.value, ICalendarErrorSeverity::Warning);
202
4
                    }
203
4
                    ComponentProperty::IanaProperty(iana_prop) => {
204
4
                        require_mailto(errors, &iana_prop.value, ICalendarErrorSeverity::Warning);
205
4
                    }
206
                    _ => {
207
                        errors.push(ComponentPropertyError {
208
                            message: "Property is declared to have a calendar address value but that is not valid for this property".to_string(),
209
                            severity: ICalendarErrorSeverity::Error,
210
                            location: Some(ComponentPropertyLocation {
211
                                index: property_index,
212
                                name: component_property_name(property).to_string(),
213
                                property_location: None,
214
                            }),
215
                        });
216
                    }
217
                }
218
            }
219
            Value::Date => {
220
18
                let mut invalid = false;
221
                match property {
222
                    ComponentProperty::DateTimeStart(DateTimeStartProperty {
223
8
                        value: date_time,
224
                        ..
225
                    })
226
                    | ComponentProperty::DateTimeEnd(DateTimeEndProperty {
227
                        value: date_time,
228
                        ..
229
                    })
230
                    | ComponentProperty::DateTimeDue(DateTimeDueProperty {
231
                        value: date_time,
232
                        ..
233
                    })
234
                    | ComponentProperty::RecurrenceId(RecurrenceIdProperty {
235
                        value: date_time,
236
                        ..
237
                    }) => {
238
8
                        if date_time.is_date_time() {
239
                            errors.push(ComponentPropertyError {
240
                                message: "Property is declared to have a date value but the value is a date-time".to_string(),
241
                                severity: ICalendarErrorSeverity::Error,
242
                                location: Some(ComponentPropertyLocation {
243
                                    index: property_index,
244
                                    name: component_property_name(property).to_string(),
245
                                    property_location: Some(WithinPropertyLocation::Value),
246
                                }),
247
                            });
248
8
                        }
249
                    }
250
                    ComponentProperty::ExceptionDateTimes(ExceptionDateTimesProperty {
251
                        value: date_times,
252
                        ..
253
                    }) => {
254
                        if date_times.iter().any(|dt| dt.is_date_time()) {
255
                            errors.push(ComponentPropertyError {
256
                                message: "Property is declared to have date values but one of values is a date-time".to_string(),
257
                                severity: ICalendarErrorSeverity::Error,
258
                                location: Some(ComponentPropertyLocation {
259
                                    index: property_index,
260
                                    name: component_property_name(property).to_string(),
261
                                    property_location: Some(WithinPropertyLocation::Value),
262
                                }),
263
                            });
264
                        }
265
                    }
266
                    ComponentProperty::RecurrenceDateTimes(RecurrenceDateTimesProperty {
267
                        value: RecurrenceDateTimesPropertyValue::DateTimes(date_times),
268
                        ..
269
                    }) => {
270
                        if date_times.iter().any(|dt| dt.is_date_time()) {
271
                            errors.push(ComponentPropertyError {
272
                                message: "Property is declared to have date values but one of values is a date-time".to_string(),
273
                                severity: ICalendarErrorSeverity::Error,
274
                                location: Some(ComponentPropertyLocation {
275
                                    index: property_index,
276
                                    name: component_property_name(property).to_string(),
277
                                    property_location: Some(WithinPropertyLocation::Value),
278
                                }),
279
                            });
280
                        }
281
                    }
282
                    ComponentProperty::RecurrenceDateTimes(RecurrenceDateTimesProperty {
283
                        value: RecurrenceDateTimesPropertyValue::Periods(periods),
284
                        ..
285
                    }) => {
286
                        if !periods.is_empty() {
287
                            errors.push(ComponentPropertyError {
288
                                message: "Property is declared to have a date-time value contains periods".to_string(),
289
                                severity: ICalendarErrorSeverity::Error,
290
                                location: Some(ComponentPropertyLocation {
291
                                    index: property_index,
292
                                    name: component_property_name(property).to_string(),
293
                                    property_location: Some(WithinPropertyLocation::Value),
294
                                }),
295
                            });
296
                        }
297
                    }
298
8
                    ComponentProperty::XProperty(x_prop) => {
299
8
                        invalid = !is_date_valued(&x_prop.value);
300
8
                    }
301
2
                    ComponentProperty::IanaProperty(iana_prop) => {
302
2
                        invalid = !is_date_valued(&iana_prop.value);
303
2
                    }
304
                    _ => {
305
                        errors.push(ComponentPropertyError {
306
                            message: "Property is declared to have a date value but that is not valid for this property".to_string(),
307
                            severity: ICalendarErrorSeverity::Error,
308
                            location: Some(ComponentPropertyLocation {
309
                                index: property_index,
310
                                name: component_property_name(property).to_string(),
311
                                property_location: None,
312
                            }),
313
                        });
314
                    }
315
                }
316

            
317
18
                if invalid {
318
6
                    errors.push(ComponentPropertyError {
319
6
                        message:
320
6
                            "Property is declared to have a date value but the value is not a date"
321
6
                                .to_string(),
322
6
                        severity: ICalendarErrorSeverity::Warning,
323
6
                        location: Some(ComponentPropertyLocation {
324
6
                            index: property_index,
325
6
                            name: component_property_name(property).to_string(),
326
6
                            property_location: Some(WithinPropertyLocation::Value),
327
6
                        }),
328
6
                    });
329
12
                }
330
            }
331
            Value::DateTime => {
332
26
                let mut invalid = false;
333
2
                match property {
334
                    ComponentProperty::DateTimeCompleted(_)
335
                    | ComponentProperty::DateTimeCreated(_)
336
                    | ComponentProperty::DateTimeStamp(_)
337
                    | ComponentProperty::DateTimeDue(_)
338
                    | ComponentProperty::RecurrenceId(_)
339
                    | ComponentProperty::ExceptionDateTimes(_)
340
4
                    | ComponentProperty::LastModified(_) => {
341
4
                        push_redundant_error_msg(errors, property_index, property);
342
4
                    }
343
                    ComponentProperty::RecurrenceDateTimes(RecurrenceDateTimesProperty {
344
                        value: RecurrenceDateTimesPropertyValue::DateTimes(_),
345
                        ..
346
2
                    }) => {
347
2
                        push_redundant_error_msg(errors, property_index, property);
348
2
                    }
349
                    ComponentProperty::RecurrenceDateTimes(RecurrenceDateTimesProperty {
350
                        value: RecurrenceDateTimesPropertyValue::Periods(_),
351
                        ..
352
                    }) => {
353
                        errors.push(ComponentPropertyError {
354
                            message:
355
                                "Property is declared to have a date-time value contains periods"
356
                                    .to_string(),
357
                            severity: ICalendarErrorSeverity::Error,
358
                            location: Some(ComponentPropertyLocation {
359
                                index: property_index,
360
                                name: component_property_name(property).to_string(),
361
                                property_location: Some(WithinPropertyLocation::Value),
362
                            }),
363
                        });
364
                    }
365
2
                    ComponentProperty::DateTimeStart(dtstart) => {
366
2
                        push_redundant_error_msg(errors, property_index, property);
367
2
                        if dtstart.value.is_date() {
368
                            errors.push(ComponentPropertyError {
369
                                message: "Property is declared to have a date-time value but the value is a date".to_string(),
370
                                severity: ICalendarErrorSeverity::Error,
371
                                location: Some(ComponentPropertyLocation {
372
                                    index: property_index,
373
                                    name: component_property_name(property).to_string(),
374
                                    property_location: Some(WithinPropertyLocation::Value),
375
                                }),
376
                            });
377
2
                        }
378
                    }
379
2
                    ComponentProperty::DateTimeEnd(dt_end) => {
380
2
                        push_redundant_error_msg(errors, property_index, property);
381
2
                        if dt_end.value.is_date() {
382
                            errors.push(ComponentPropertyError {
383
                                message: "Property is declared to have a date-time value but the value is a date".to_string(),
384
                                severity: ICalendarErrorSeverity::Error,
385
                                location: Some(ComponentPropertyLocation {
386
                                    index: property_index,
387
                                    name: component_property_name(property).to_string(),
388
                                    property_location: Some(WithinPropertyLocation::Value),
389
                                }),
390
                            });
391
2
                        }
392
                    }
393
12
                    ComponentProperty::Trigger(trigger) => {
394
12
                        match trigger.value {
395
                            TriggerValue::Relative(_) => {
396
                                errors.push(ComponentPropertyError {
397
                                    message: "Property is declared to have a date-time value but has an absolute trigger".to_string(),
398
                                    severity: ICalendarErrorSeverity::Error,
399
                                    location: Some(ComponentPropertyLocation {
400
                                        index: property_index,
401
                                        name: component_property_name(property).to_string(),
402
                                        property_location: Some(WithinPropertyLocation::Param {
403
                                            index: value_type_index,
404
                                            name: "VALUE".to_string(),
405
                                        }),
406
                                    }),
407
                                });
408
                            }
409
12
                            TriggerValue::Absolute(_) => {
410
12
                                // Valid
411
12
                            }
412
                        }
413
                    }
414
2
                    ComponentProperty::XProperty(x_prop) => {
415
2
                        invalid = !is_date_time_valued(&x_prop.value);
416
2
                    }
417
2
                    ComponentProperty::IanaProperty(iana_prop) => {
418
2
                        invalid = !is_date_time_valued(&iana_prop.value);
419
2
                    }
420
                    _ => {
421
                        errors.push(ComponentPropertyError {
422
                            message: "Property is declared to have a date-time value but that is not valid for this property".to_string(),
423
                            severity: ICalendarErrorSeverity::Error,
424
                            location: Some(ComponentPropertyLocation {
425
                                index: property_index,
426
                                name: component_property_name(property).to_string(),
427
                                property_location: None,
428
                            }),
429
                        });
430
                    }
431
                }
432

            
433
26
                if invalid {
434
4
                    errors.push(ComponentPropertyError {
435
4
                        message:
436
4
                        "Property is declared to have a date-time value but the value is not a date-time"
437
4
                            .to_string(),
438
4
                        severity: ICalendarErrorSeverity::Warning,
439
4
                        location: Some(ComponentPropertyLocation {
440
4
                            index: property_index,
441
4
                            name: component_property_name(property).to_string(),
442
4
                            property_location: Some(WithinPropertyLocation::Value),
443
4
                        }),
444
4
                    });
445
22
                }
446
            }
447
            Value::Duration => {
448
6
                let mut invalid = false;
449
6

            
450
6
                match property {
451
                    ComponentProperty::Duration(_) => {
452
                        push_redundant_error_msg(errors, property_index, property);
453
                    }
454
2
                    ComponentProperty::Trigger(trigger) => {
455
2
                        push_redundant_error_msg(errors, property_index, property);
456
2
                        match trigger.value {
457
2
                            TriggerValue::Relative(_) => {
458
2
                                // Valid
459
2
                            }
460
                            TriggerValue::Absolute(_) => {
461
                                errors.push(ComponentPropertyError {
462
                                    message: "Property is declared to have a duration value but has an absolute trigger".to_string(),
463
                                    severity: ICalendarErrorSeverity::Error,
464
                                    location: Some(ComponentPropertyLocation {
465
                                        index: property_index,
466
                                        name: component_property_name(property).to_string(),
467
                                        property_location: Some(WithinPropertyLocation::Param {
468
                                            index: value_type_index,
469
                                            name: "VALUE".to_string(),
470
                                        }),
471
                                    }),
472
                                });
473
                            }
474
                        }
475
                    }
476
2
                    ComponentProperty::XProperty(x_prop) => {
477
2
                        invalid = !is_duration_valued(&x_prop.value);
478
2
                    }
479
2
                    ComponentProperty::IanaProperty(iana_prop) => {
480
2
                        invalid = !is_duration_valued(&iana_prop.value);
481
2
                    }
482
                    _ => {
483
                        errors.push(ComponentPropertyError {
484
                            message: "Property is declared to have a duration value but that is not valid for this property".to_string(),
485
                            severity: ICalendarErrorSeverity::Error,
486
                            location: Some(ComponentPropertyLocation {
487
                                index: property_index,
488
                                name: component_property_name(property).to_string(),
489
                                property_location: None,
490
                            }),
491
                        });
492
                    }
493
                }
494

            
495
6
                if invalid {
496
4
                    errors.push(ComponentPropertyError {
497
4
                        message:
498
4
                        "Property is declared to have a duration value but the value is not a duration"
499
4
                            .to_string(),
500
4
                        severity: ICalendarErrorSeverity::Warning,
501
4
                        location: Some(ComponentPropertyLocation {
502
4
                            index: property_index,
503
4
                            name: component_property_name(property).to_string(),
504
4
                            property_location: Some(WithinPropertyLocation::Value),
505
4
                        }),
506
4
                    });
507
4
                }
508
            }
509
            Value::Float => {
510
4
                let mut invalid = false;
511
4

            
512
4
                match property {
513
                    ComponentProperty::GeographicPosition(_) => {
514
                        push_redundant_error_msg(errors, property_index, property);
515
                    }
516
2
                    ComponentProperty::XProperty(x_prop) => {
517
2
                        invalid = !is_float_valued(&x_prop.value);
518
2
                    }
519
2
                    ComponentProperty::IanaProperty(iana_prop) => {
520
2
                        invalid = !is_float_valued(&iana_prop.value);
521
2
                    }
522
                    _ => {
523
                        errors.push(ComponentPropertyError {
524
                            message: "Property is declared to have a float value but that is not valid for this property".to_string(),
525
                            severity: ICalendarErrorSeverity::Error,
526
                            location: Some(ComponentPropertyLocation {
527
                                index: property_index,
528
                                name: component_property_name(property).to_string(),
529
                                property_location: None,
530
                            }),
531
                        });
532
                    }
533
                }
534

            
535
4
                if invalid {
536
4
                    errors.push(ComponentPropertyError {
537
4
                        message:
538
4
                        "Property is declared to have a float value but the value is not a float"
539
4
                            .to_string(),
540
4
                        severity: ICalendarErrorSeverity::Warning,
541
4
                        location: Some(ComponentPropertyLocation {
542
4
                            index: property_index,
543
4
                            name: component_property_name(property).to_string(),
544
4
                            property_location: Some(WithinPropertyLocation::Value),
545
4
                        }),
546
4
                    });
547
4
                }
548
            }
549
            Value::Integer => {
550
6
                let mut invalid = false;
551
6

            
552
6
                match property {
553
                    ComponentProperty::PercentComplete(_)
554
                    | ComponentProperty::Priority(_)
555
                    | ComponentProperty::Repeat(_) => {
556
                        push_redundant_error_msg(errors, property_index, property);
557
                    }
558
2
                    ComponentProperty::XProperty(x_prop) => {
559
2
                        invalid = !is_integer_valued(&x_prop.value);
560
2
                    }
561
2
                    ComponentProperty::IanaProperty(iana_prop) => {
562
2
                        invalid = !is_integer_valued(&iana_prop.value);
563
2
                    }
564
2
                    _ => {
565
2
                        errors.push(ComponentPropertyError {
566
2
                            message: "Property is declared to have an integer value but that is not valid for this property".to_string(),
567
2
                            severity: ICalendarErrorSeverity::Error,
568
2
                            location: Some(ComponentPropertyLocation {
569
2
                                index: property_index,
570
2
                                name: component_property_name(property).to_string(),
571
2
                                property_location: None,
572
2
                            }),
573
2
                        });
574
2
                    }
575
                }
576

            
577
6
                if invalid {
578
4
                    errors.push(ComponentPropertyError {
579
4
                        message:
580
4
                        "Property is declared to have an integer value but the value is not an integer"
581
4
                            .to_string(),
582
4
                        severity: ICalendarErrorSeverity::Warning,
583
4
                        location: Some(ComponentPropertyLocation {
584
4
                            index: property_index,
585
4
                            name: component_property_name(property).to_string(),
586
4
                            property_location: Some(WithinPropertyLocation::Value),
587
4
                        }),
588
4
                    });
589
4
                }
590
            }
591
            Value::Period => {
592
4
                let mut invalid = false;
593

            
594
                match property {
595
                    ComponentProperty::FreeBusyTime(_) => {
596
                        push_redundant_error_msg(errors, property_index, property);
597
                    }
598
                    ComponentProperty::RecurrenceDateTimes(RecurrenceDateTimesProperty {
599
                        value: RecurrenceDateTimesPropertyValue::DateTimes(_),
600
                        ..
601
                    }) => {
602
                        errors.push(ComponentPropertyError {
603
                            message:
604
                                "Property is declared to have a period value contains date-times"
605
                                    .to_string(),
606
                            severity: ICalendarErrorSeverity::Error,
607
                            location: Some(ComponentPropertyLocation {
608
                                index: property_index,
609
                                name: component_property_name(property).to_string(),
610
                                property_location: Some(WithinPropertyLocation::Value),
611
                            }),
612
                        });
613
                    }
614
2
                    ComponentProperty::XProperty(x_prop) => {
615
2
                        invalid = !is_period_valued(&x_prop.value);
616
2
                    }
617
2
                    ComponentProperty::IanaProperty(iana_prop) => {
618
2
                        invalid = !is_period_valued(&iana_prop.value);
619
2
                    }
620
                    _ => {
621
                        errors.push(ComponentPropertyError {
622
                            message: "Property is declared to have a period value but that is not valid for this property".to_string(),
623
                            severity: ICalendarErrorSeverity::Error,
624
                            location: Some(ComponentPropertyLocation {
625
                                index: property_index,
626
                                name: component_property_name(property).to_string(),
627
                                property_location: None,
628
                            }),
629
                        });
630
                    }
631
                }
632

            
633
4
                if invalid {
634
4
                    errors.push(ComponentPropertyError {
635
4
                        message:
636
4
                        "Property is declared to have a period value but the value is not a period"
637
4
                            .to_string(),
638
4
                        severity: ICalendarErrorSeverity::Warning,
639
4
                        location: Some(ComponentPropertyLocation {
640
4
                            index: property_index,
641
4
                            name: component_property_name(property).to_string(),
642
4
                            property_location: Some(WithinPropertyLocation::Value),
643
4
                        }),
644
4
                    });
645
4
                }
646
            }
647
            Value::Recurrence => {
648
4
                let mut invalid = false;
649
4

            
650
4
                match property {
651
                    ComponentProperty::RecurrenceRule(_) => {
652
                        push_redundant_error_msg(errors, property_index, property);
653
                    }
654
2
                    ComponentProperty::XProperty(x_prop) => match is_recur_valued(&x_prop.value) {
655
                        Ok(rule) => match rule.to_model() {
656
                            Ok(rule) => {
657
                                validate_recurrence_rule(
658
                                    errors,
659
                                    property,
660
                                    &rule,
661
                                    maybe_dt_start,
662
                                    PropertyLocation::Other,
663
                                    property_index,
664
                                )?;
665
                            }
666
                            Err(e) => {
667
                                errors.push(ComponentPropertyError {
668
                                    message: format!(
669
                                        "Failed to convert recurrence rule to model: {}",
670
                                        e
671
                                    ),
672
                                    severity: ICalendarErrorSeverity::Warning,
673
                                    location: Some(ComponentPropertyLocation {
674
                                        index: property_index,
675
                                        name: component_property_name(property).to_string(),
676
                                        property_location: Some(WithinPropertyLocation::Value),
677
                                    }),
678
                                });
679
                            }
680
                        },
681
2
                        Err(_) => {
682
2
                            invalid = true;
683
2
                        }
684
                    },
685
2
                    ComponentProperty::IanaProperty(iana_prop) => {
686
2
                        match is_recur_valued(&iana_prop.value) {
687
                            Ok(rule) => match rule.to_model() {
688
                                Ok(rule) => {
689
                                    validate_recurrence_rule(
690
                                        errors,
691
                                        property,
692
                                        &rule,
693
                                        maybe_dt_start,
694
                                        PropertyLocation::Other,
695
                                        property_index,
696
                                    )?;
697
                                }
698
                                Err(e) => {
699
                                    errors.push(ComponentPropertyError {
700
                                        message: format!(
701
                                            "Failed to convert recurrence rule to model: {}",
702
                                            e
703
                                        ),
704
                                        severity: ICalendarErrorSeverity::Warning,
705
                                        location: Some(ComponentPropertyLocation {
706
                                            index: property_index,
707
                                            name: component_property_name(property).to_string(),
708
                                            property_location: Some(WithinPropertyLocation::Value),
709
                                        }),
710
                                    });
711
                                }
712
                            },
713
2
                            Err(_) => {
714
2
                                invalid = true;
715
2
                            }
716
                        }
717
                    }
718
                    _ => {
719
                        errors.push(ComponentPropertyError {
720
                            message: "Property is declared to have a recurrence value but that is not valid for this property".to_string(),
721
                            severity: ICalendarErrorSeverity::Error,
722
                            location: Some(ComponentPropertyLocation {
723
                                index: property_index,
724
                                name: component_property_name(property).to_string(),
725
                                property_location: None,
726
                            }),
727
                        });
728
                    }
729
                }
730

            
731
4
                if invalid {
732
4
                    errors.push(ComponentPropertyError {
733
4
                        message:
734
4
                        "Property is declared to have a recurrence value but the value is not a recurrence"
735
4
                            .to_string(),
736
4
                        severity: ICalendarErrorSeverity::Warning,
737
4
                        location: Some(ComponentPropertyLocation {
738
4
                            index: property_index,
739
4
                            name: component_property_name(property).to_string(),
740
4
                            property_location: Some(WithinPropertyLocation::Value),
741
4
                        }),
742
4
                    });
743
4
                }
744
            }
745
            Value::Text => {
746
4
                let mut invalid = false;
747
4

            
748
4
                match property {
749
                    ComponentProperty::Categories(_)
750
                    | ComponentProperty::Classification(_)
751
                    | ComponentProperty::Comment(_)
752
                    | ComponentProperty::Description(_)
753
                    | ComponentProperty::Location(_)
754
                    | ComponentProperty::Resources(_)
755
                    | ComponentProperty::Status(_)
756
                    | ComponentProperty::Summary(_)
757
                    | ComponentProperty::TimeTransparency(_)
758
                    | ComponentProperty::TimeZoneId(_)
759
                    | ComponentProperty::TimeZoneName(_)
760
                    | ComponentProperty::Contact(_)
761
                    | ComponentProperty::UniqueIdentifier(_)
762
                    | ComponentProperty::Action(_)
763
                    | ComponentProperty::RequestStatus(_) => {
764
                        push_redundant_error_msg(errors, property_index, property);
765
                    }
766
2
                    ComponentProperty::XProperty(x_prop) => {
767
2
                        invalid = !is_text_valued(&x_prop.value);
768
2
                    }
769
2
                    ComponentProperty::IanaProperty(iana_prop) => {
770
2
                        invalid = !is_text_valued(&iana_prop.value);
771
2
                    }
772
                    _ => {
773
                        errors.push(ComponentPropertyError {
774
                            message: "Property is declared to have a text value but that is not valid for this property".to_string(),
775
                            severity: ICalendarErrorSeverity::Error,
776
                            location: Some(ComponentPropertyLocation {
777
                                index: property_index,
778
                                name: component_property_name(property).to_string(),
779
                                property_location: None,
780
                            }),
781
                        });
782
                    }
783
                }
784

            
785
4
                if invalid {
786
4
                    errors.push(ComponentPropertyError {
787
4
                        message:
788
4
                            "Property is declared to have a text value but the value is not a text"
789
4
                                .to_string(),
790
4
                        severity: ICalendarErrorSeverity::Warning,
791
4
                        location: Some(ComponentPropertyLocation {
792
4
                            index: property_index,
793
4
                            name: component_property_name(property).to_string(),
794
4
                            property_location: Some(WithinPropertyLocation::Value),
795
4
                        }),
796
4
                    });
797
4
                }
798
            }
799
            Value::Time => {
800
16
                let mut invalid = false;
801
16

            
802
16
                match property {
803
8
                    ComponentProperty::XProperty(x_prop) => match is_time_valued(&x_prop.value) {
804
8
                        Ok(times) => {
805
8
                            for (index, time) in times.iter().enumerate() {
806
8
                                if let Err(e) = validate_time(time) {
807
6
                                    errors.push(ComponentPropertyError {
808
6
                                        message: format!(
809
6
                                            "Found an invalid time at index {} - {:?}",
810
6
                                            index, e
811
6
                                        ),
812
6
                                        severity: ICalendarErrorSeverity::Warning,
813
6
                                        location: Some(ComponentPropertyLocation {
814
6
                                            index: property_index,
815
6
                                            name: component_property_name(property).to_string(),
816
6
                                            property_location: Some(WithinPropertyLocation::Value),
817
6
                                        }),
818
6
                                    });
819
6
                                }
820
                            }
821
                        }
822
                        Err(_) => {
823
                            invalid = true;
824
                        }
825
                    },
826
8
                    ComponentProperty::IanaProperty(iana_prop) => {
827
8
                        match is_time_valued(&iana_prop.value) {
828
8
                            Ok(times) => {
829
8
                                for (index, time) in times.iter().enumerate() {
830
8
                                    if let Err(e) = validate_time(time) {
831
6
                                        errors.push(ComponentPropertyError {
832
6
                                            message: format!(
833
6
                                                "Found an invalid time at index {} - {:?}",
834
6
                                                index, e
835
6
                                            ),
836
6
                                            severity: ICalendarErrorSeverity::Warning,
837
6
                                            location: Some(ComponentPropertyLocation {
838
6
                                                index: property_index,
839
6
                                                name: component_property_name(property).to_string(),
840
6
                                                property_location: Some(
841
6
                                                    WithinPropertyLocation::Value,
842
6
                                                ),
843
6
                                            }),
844
6
                                        });
845
6
                                    }
846
                                }
847
                            }
848
                            Err(_) => {
849
                                invalid = true;
850
                            }
851
                        }
852
                    }
853
                    _ => {
854
                        errors.push(ComponentPropertyError {
855
                            message: "Property is declared to have a time value but that is not valid for this property".to_string(),
856
                            severity: ICalendarErrorSeverity::Error,
857
                            location: Some(ComponentPropertyLocation {
858
                                index: property_index,
859
                                name: component_property_name(property).to_string(),
860
                                property_location: None,
861
                            }),
862
                        });
863
                    }
864
                }
865

            
866
16
                if invalid {
867
                    errors.push(ComponentPropertyError {
868
                        message:
869
                            "Property is declared to have a time value but the value is not a time"
870
                                .to_string(),
871
                        severity: ICalendarErrorSeverity::Warning,
872
                        location: Some(ComponentPropertyLocation {
873
                            index: property_index,
874
                            name: component_property_name(property).to_string(),
875
                            property_location: Some(WithinPropertyLocation::Value),
876
                        }),
877
                    });
878
16
                }
879
            }
880
            Value::Uri => {
881
6
                let require_uri =
882
                    |errors: &mut Vec<ComponentPropertyError>,
883
                     v: &str,
884
6
                     severity: ICalendarErrorSeverity| {
885
6
                        if !is_uri_valued(v) {
886
4
                            errors.push(ComponentPropertyError {
887
4
                            message: "Property is declared to have a URI value but the value is not a URI".to_string(),
888
4
                            severity,
889
4
                            location: Some(ComponentPropertyLocation {
890
4
                                index: property_index,
891
4
                                name: component_property_name(property).to_string(),
892
4
                                property_location: Some(WithinPropertyLocation::Value),
893
4
                            }),
894
4
                        });
895
4
                        }
896
6
                    };
897

            
898
6
                match property {
899
                    ComponentProperty::Url(url) => {
900
                        push_redundant_error_msg(errors, property_index, property);
901
                        require_uri(errors, &url.value, ICalendarErrorSeverity::Error);
902
                    }
903
2
                    ComponentProperty::Attach(attach) => {
904
2
                        push_redundant_error_msg(errors, property_index, property);
905
2
                        require_uri(errors, &attach.value, ICalendarErrorSeverity::Error);
906
2
                    }
907
2
                    ComponentProperty::XProperty(x_prop) => {
908
2
                        require_uri(errors, &x_prop.value, ICalendarErrorSeverity::Warning);
909
2
                    }
910
2
                    ComponentProperty::IanaProperty(iana_prop) => {
911
2
                        require_uri(errors, &iana_prop.value, ICalendarErrorSeverity::Warning);
912
2
                    }
913
                    _ => {
914
                        errors.push(ComponentPropertyError {
915
                            message: "Property is declared to have a URI value but that is not valid for this property".to_string(),
916
                            severity: ICalendarErrorSeverity::Error,
917
                            location: Some(ComponentPropertyLocation {
918
                                index: property_index,
919
                                name: component_property_name(property).to_string(),
920
                                property_location: None,
921
                            }),
922
                        });
923
                    }
924
                }
925
            }
926
            Value::UtcOffset => {
927
12
                let mut invalid = false;
928
12

            
929
12
                match property {
930
                    ComponentProperty::TimeZoneOffsetFrom(_)
931
                    | ComponentProperty::TimeZoneOffsetTo(_) => {
932
                        push_redundant_error_msg(errors, property_index, property);
933
                    }
934
6
                    ComponentProperty::XProperty(x_prop) => {
935
6
                        match is_utc_offset_valued(&x_prop.value) {
936
6
                            Ok(offset) => {
937
6
                                if let Err(e) = validate_utc_offset(&offset) {
938
4
                                    errors.push(ComponentPropertyError {
939
4
                                        message: format!("Found an invalid UTC offset - {:?}", e),
940
4
                                        severity: ICalendarErrorSeverity::Warning,
941
4
                                        location: Some(ComponentPropertyLocation {
942
4
                                            index: property_index,
943
4
                                            name: component_property_name(property).to_string(),
944
4
                                            property_location: Some(WithinPropertyLocation::Value),
945
4
                                        }),
946
4
                                    });
947
4
                                }
948
                            }
949
                            Err(_) => {
950
                                invalid = true;
951
                            }
952
                        }
953
                    }
954
6
                    ComponentProperty::IanaProperty(iana_prop) => {
955
6
                        match is_utc_offset_valued(&iana_prop.value) {
956
6
                            Ok(offset) => {
957
6
                                if let Err(e) = validate_utc_offset(&offset) {
958
4
                                    errors.push(ComponentPropertyError {
959
4
                                        message: format!("Found an invalid UTC offset - {:?}", e),
960
4
                                        severity: ICalendarErrorSeverity::Warning,
961
4
                                        location: Some(ComponentPropertyLocation {
962
4
                                            index: property_index,
963
4
                                            name: component_property_name(property).to_string(),
964
4
                                            property_location: Some(WithinPropertyLocation::Value),
965
4
                                        }),
966
4
                                    });
967
4
                                }
968
                            }
969
                            Err(_) => {
970
                                invalid = true;
971
                            }
972
                        }
973
                    }
974
                    _ => {
975
                        errors.push(ComponentPropertyError {
976
                            message: "Property is declared to have a UTC offset value but that is not valid for this property".to_string(),
977
                            severity: ICalendarErrorSeverity::Error,
978
                            location: Some(ComponentPropertyLocation {
979
                                index: property_index,
980
                                name: component_property_name(property).to_string(),
981
                                property_location: None,
982
                            }),
983
                        });
984
                    }
985
                }
986

            
987
12
                if invalid {
988
                    errors.push(ComponentPropertyError {
989
                        message:
990
                        "Property is declared to have a UTC offset value but the value is not a UTC offset"
991
                            .to_string(),
992
                        severity: ICalendarErrorSeverity::Error,
993
                        location: Some(ComponentPropertyLocation {
994
                            index: property_index,
995
                            name: component_property_name(property).to_string(),
996
                            property_location: Some(WithinPropertyLocation::Value),
997
                        }),
998
                    });
999
12
                }
            }
            Value::XName(_) | Value::IanaToken(_) => {
                // Nothing to validate, we don't know anything about the values these should take
            }
        }
1982
    }
2114
    Ok(())
2122
}
10
fn is_base64_valued(property_value: &str) -> bool {
10
    let mut content = property_value.as_bytes().to_vec();
10
    content.push(b';');
10

            
10
    let result = prop_value_binary::<Error>(content.as_bytes());
10
    match result {
10
        Ok((rest, _)) => rest.len() == 1,
        _ => false,
    }
10
}
8
fn is_boolean_valued(property_value: &str) -> bool {
8
    property_value.eq_ignore_ascii_case("TRUE") || property_value.eq_ignore_ascii_case("FALSE")
8
}
10
fn is_date_valued(property_value: &String) -> bool {
10
    let mut content = property_value.as_bytes().to_vec();
10
    content.push(b';');
10

            
10
    let result = separated_list1(char(','), prop_value_date::<Error>)(content.as_bytes());
10
    match result {
8
        Ok((rest, _)) => rest.len() == 1,
2
        _ => false,
    }
10
}
4
fn is_date_time_valued(property_value: &String) -> bool {
4
    let mut content = property_value.as_bytes().to_vec();
4
    content.push(b';');
4

            
4
    let result = separated_list1(char(','), prop_value_date_time::<Error>)(content.as_bytes());
4
    match result {
4
        Ok((rest, _)) => rest.len() == 1,
        _ => false,
    }
4
}
4
fn is_duration_valued(property_value: &String) -> bool {
4
    let mut content = property_value.as_bytes().to_vec();
4
    content.push(b';');
4

            
4
    let result = separated_list1(char(','), prop_value_duration::<Error>)(content.as_bytes());
4
    match result {
        Ok((rest, _)) => rest.len() == 1,
4
        _ => false,
    }
4
}
4
fn is_float_valued(property_value: &String) -> bool {
4
    let mut content = property_value.as_bytes().to_vec();
4
    content.push(b';');
4

            
4
    let result = separated_list1(char(','), prop_value_float::<Error>)(content.as_bytes());
4
    match result {
4
        Ok((rest, _)) => rest.len() == 1,
        _ => false,
    }
4
}
4
fn is_integer_valued(property_value: &String) -> bool {
4
    let mut content = property_value.as_bytes().to_vec();
4
    content.push(b';');
4

            
4
    let result = separated_list1(char(','), prop_value_integer::<Error>)(content.as_bytes());
4
    match result {
4
        Ok((rest, _)) => rest.len() == 1,
        _ => false,
    }
4
}
4
fn is_period_valued(property_value: &String) -> bool {
4
    let mut content = property_value.as_bytes().to_vec();
4
    content.push(b';');
4

            
4
    let result = prop_value_period::<Error>(content.as_bytes());
4
    match result {
4
        Ok((rest, _)) => rest.len() == 1,
        _ => false,
    }
4
}
4
fn is_recur_valued(
4
    property_value: &String,
4
) -> anyhow::Result<Vec<crate::parser::types::RecurRulePart>> {
4
    let mut content = property_value.as_bytes().to_vec();
4
    content.push(b'`');
4

            
4
    let result = prop_value_recur::<Error>(content.as_bytes());
    match result {
        Ok((rest, rule)) if rest.len() == 1 => Ok(rule),
4
        _ => anyhow::bail!("Not a valid recur rule"),
    }
4
}
4
fn is_text_valued(property_value: &String) -> bool {
4
    let mut content = property_value.as_bytes().to_vec();
4
    content.push(b'\r');
4
    content.push(b'\n');
4

            
4
    let result = separated_list1(char(','), prop_value_text::<Error>)(content.as_bytes());
4
    match result {
4
        Ok((rest, _)) => rest.len() == 1,
        _ => false,
    }
4
}
16
fn is_time_valued(property_value: &String) -> anyhow::Result<Vec<crate::parser::types::Time>> {
16
    let mut content = property_value.as_bytes().to_vec();
16
    content.push(b';');
16

            
16
    let result = separated_list1(char(','), prop_value_time::<Error>)(content.as_bytes());
16
    match result {
16
        Ok((rest, times)) if rest.len() == 1 => Ok(times),
        _ => anyhow::bail!("Not a valid time"),
    }
16
}
6
fn is_uri_valued(property_value: &str) -> bool {
6
    let mut content = property_value.as_bytes().to_vec();
6
    content.push(b'\n');
6

            
6
    let result = param_value_uri::<Error>(content.as_bytes());
6
    match result {
2
        Ok((rest, _)) => rest.len() == 1,
4
        _ => false,
    }
6
}
12
fn is_utc_offset_valued(
12
    property_value: &String,
12
) -> anyhow::Result<crate::parser::types::UtcOffset> {
12
    let mut content = property_value.as_bytes().to_vec();
12
    content.push(b';');
12

            
12
    let result = prop_value_utc_offset::<Error>(content.as_bytes());
12
    match result {
12
        Ok((rest, offset)) if rest.len() == 1 => Ok(offset),
        _ => anyhow::bail!("Not a valid UTC offset"),
    }
12
}