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

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

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

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

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

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

            
84
6
                            return Ok(());
85
10
                        }
86
30
                    }
87
                }
88

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
990
12
                if invalid {
991
                    errors.push(ComponentPropertyError {
992
                        message:
993
                        "Property is declared to have a UTC offset value but the value is not a UTC offset"
994
                            .to_string(),
995
                        severity: ICalendarErrorSeverity::Error,
996
                        location: Some(ComponentPropertyLocation {
997
                            index: property_index,
998
                            name: component_property_name(property).to_string(),
999
                            property_location: Some(WithinPropertyLocation::Value),
                        }),
                    });
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>).parse(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 =
4
        separated_list1(char(','), prop_value_date_time::<Error>).parse(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>).parse(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>).parse(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>).parse(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
) -> AetoliaResult<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
        _ => Err(AetoliaError::other("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>).parse(content.as_bytes());
4
    match result {
4
        Ok((rest, _)) => rest.len() == 1,
        _ => false,
    }
4
}
16
fn is_time_valued(property_value: &String) -> AetoliaResult<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>).parse(content.as_bytes());
16
    match result {
16
        Ok((rest, times)) if rest.len() == 1 => Ok(times),
        _ => Err(AetoliaError::other("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(property_value: &String) -> AetoliaResult<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),
        _ => Err(AetoliaError::other("Not a valid UTC offset")),
    }
12
}