Coverage Report

Created: 2026-04-15 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libical/src/libicalvcard/vcardrestriction.c
Line
Count
Source
1
/*======================================================================
2
 FILE: vcardrestriction.c
3
 CREATOR: Ken Murchison 24 Aug 2022
4
5
 SPDX-FileCopyrightText: 2022, Fastmail Pty. Ltd. (https://fastmail.com)
6
 SPDX-License-Identifier: LGPL-2.1-only OR MPL-2.0
7
 ======================================================================*/
8
9
/**
10
 * @file vcardrestriction.c
11
 * @brief Functions to check if a vcardcomponent meets the restrictions
12
 * imposed by the standard
13
 */
14
15
#ifdef HAVE_CONFIG_H
16
#include <config.h>
17
#endif
18
19
#include "vcardrestriction.h"
20
#include "icalerror_p.h"
21
22
#include <assert.h>
23
24
/* Define the structs for the restrictions. these data are filled out
25
in machine generated code below */
26
27
struct vcardrestriction_record;
28
29
typedef const char *(*restriction_func) (const struct vcardrestriction_record * rec,
30
                                         vcardcomponent *comp, vcardproperty *prop);
31
32
typedef struct vcardrestriction_record
33
{
34
    vcardproperty_version version;
35
    vcardcomponent_kind component;
36
    vcardproperty_kind property;
37
    vcardcomponent_kind subcomponent;
38
    vcardrestriction_kind restriction;
39
    restriction_func function;
40
} vcardrestriction_record;
41
42
static const vcardrestriction_record *vcardrestriction_get_restriction(
43
    const vcardrestriction_record *start,
44
    vcardproperty_version version, vcardcomponent_kind component,
45
    vcardproperty_kind property, vcardcomponent_kind subcomp);
46
47
static const vcardrestriction_record null_restriction_record =
48
    { VCARD_VERSION_NONE, VCARD_NO_COMPONENT,
49
      VCARD_NO_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_UNKNOWN, NULL };
50
51
/** Each row gives the result of comparing a restriction against a count.
52
   The columns in each row represent 0,1,2+. '-1' indicates
53
   'invalid, 'don't care' or 'needs more analysis' So, for
54
   VCARD_RESTRICTION_ONE, if there is 1 of a property with that
55
   restriction, it passes, but if there are 0 or 2+, it fails. */
56
57
static const bool compare_map[VCARD_RESTRICTION_UNKNOWN + 1][3] = {
58
    {1, 1, 1}, /*VCARD_RESTRICTION_NONE */
59
    {1, 0, 0}, /*VCARD_RESTRICTION_ZERO */
60
    {0, 1, 0}, /*VCARD_RESTRICTION_ONE */
61
    {1, 1, 1}, /*VCARD_RESTRICTION_ZEROPLUS */
62
    {0, 1, 1}, /*VCARD_RESTRICTION_ONEPLUS */
63
    {1, 1, 0}, /*VCARD_RESTRICTION_ZEROORONE */
64
    {1, 1, 0}, /*VCARD_RESTRICTION_ONEEXCLUSIVE */
65
    {1, 1, 0}, /*VCARD_RESTRICTION_ONEMUTUAL */
66
    {1, 1, 1}  /*VCARD_RESTRICTION_UNKNOWN */
67
};
68
69
static const char restr_string_map[VCARD_RESTRICTION_UNKNOWN + 1][60] = {
70
    "unknown number", /*VCARD_RESTRICTION_NONE */
71
    "0", /*VCARD_RESTRICTION_ZERO */
72
    "1", /*VCARD_RESTRICTION_ONE */
73
    "zero or more", /*VCARD_RESTRICTION_ZEROPLUS */
74
    "one or more", /*VCARD_RESTRICTION_ONEPLUS */
75
    "zero or one", /*VCARD_RESTRICTION_ZEROORONE */
76
    "zero or one, exclusive with another property", /*VCARD_RESTRICTION_ONEEXCLUSIVE */
77
    "zero or one, mutual with another property", /*VCARD_RESTRICTION_ONEMUTUAL */
78
    "unknown number"    /*VCARD_RESTRICTION_UNKNOWN */
79
};
80
81
int vcardrestriction_compare(vcardrestriction_kind restr, int count)
82
0
{
83
    /* restr is an unsigned int and VCARD_RESTRICTION_NONE == 0,
84
       so no need to check if restr < VCARD_RESTRICTION_NONE */
85
0
    if (restr > VCARD_RESTRICTION_UNKNOWN || count < 0) {
86
0
        return -1;
87
0
    }
88
89
0
    if (count > 2) {
90
0
        count = 2;
91
0
    }
92
93
0
    return compare_map[restr][count];
94
0
}
95
96
/* Special case routines */
97
98
#define TMP_BUF_SIZE 1024
99
100
static const char *vcardrestriction_validate_datetime_value(
101
    const vcardrestriction_record *rec,
102
    vcardcomponent *comp, vcardproperty *prop)
103
0
{
104
0
    vcardtimetype t = vcardproperty_get_bday(prop);
105
0
    static char buf[TMP_BUF_SIZE];
106
107
0
    if (vcardtime_is_null_datetime(t)) {
108
0
        return 0;
109
0
    }
110
111
0
    if (comp && vcardcomponent_get_version(comp) != VCARD_VERSION_40) {
112
0
        unsigned missing_time_parts = (unsigned)((t.hour < 0) + (t.minute < 0) + (t.second < 0));
113
114
0
        if (t.year < 0 || t.month < 0 || t.day < 0 ||
115
0
            (missing_time_parts && missing_time_parts != 3)) {
116
117
0
            snprintf(buf, TMP_BUF_SIZE,
118
0
                     "Failed restrictions for %s property. "
119
0
                     "The value must be a full date or date-time",
120
0
                     vcardproperty_kind_to_string(rec->property));
121
0
            return buf;
122
0
        }
123
0
    }
124
125
0
    if (!vcardtime_is_valid_time(t)) {
126
0
        snprintf(buf, TMP_BUF_SIZE,
127
0
                 "Failed restrictions for %s property. "
128
0
                 "The value is an invalid date-and-or-time",
129
0
                 vcardproperty_kind_to_string(rec->property));
130
0
        return buf;
131
0
    }
132
133
0
    return 0;
134
0
}
135
136
static const char *vcardrestriction_validate_timestamp_value(
137
    const vcardrestriction_record *rec,
138
    vcardcomponent *comp, vcardproperty *prop)
139
0
{
140
0
    _unused(comp);
141
142
0
    vcardtimetype t = vcardproperty_get_rev(prop);
143
0
    static char buf[TMP_BUF_SIZE];
144
145
0
    if (vcardtime_is_null_datetime(t)) {
146
0
        return 0;
147
0
    }
148
149
0
    if (!vcardtime_is_timestamp(t) || !vcardtime_is_valid_time(t)) {
150
0
        snprintf(buf, TMP_BUF_SIZE,
151
0
                 "Failed restrictions for %s property. "
152
0
                 "The value is an invalid timestamp",
153
0
                 vcardproperty_kind_to_string(rec->property));
154
0
        return buf;
155
0
    }
156
157
0
    return 0;
158
0
}
159
160
static int _check_restriction(vcardcomponent *comp,
161
                              const vcardrestriction_record *record,
162
                              int count, vcardproperty *prop)
163
0
{
164
0
    vcardrestriction_kind restr;
165
0
    const char *funcr = 0;
166
0
    int compare;
167
168
0
    restr = record->restriction;
169
170
0
    if (restr == VCARD_RESTRICTION_ONEEXCLUSIVE ||
171
0
        restr == VCARD_RESTRICTION_ONEMUTUAL) {
172
173
        /* First treat it as a 0/1 restriction */
174
0
        restr = VCARD_RESTRICTION_ZEROORONE;
175
0
    }
176
177
0
    compare = vcardrestriction_compare(restr, count);
178
179
0
    assert(compare != -1);
180
181
0
    if (compare == 0) {
182
0
        char temp[TMP_BUF_SIZE];
183
0
        vcardproperty *errProp;
184
0
        vcardparameter *errParam;
185
0
        const char *type, *kind;
186
187
0
        if (record->subcomponent != VCARD_NO_COMPONENT) {
188
0
            type = "component";
189
0
            kind = vcardcomponent_kind_to_string(record->subcomponent);
190
0
        } else {
191
0
            type = "property";
192
0
            kind = vcardproperty_kind_to_string(record->property);
193
0
        }
194
195
0
        snprintf(temp, TMP_BUF_SIZE,
196
0
                 "Failed restrictions for %s %s. "
197
0
                 "Expected %s instances of the %s and got %d",
198
0
                 kind, type, restr_string_map[restr], type, count);
199
0
        errParam =
200
0
            vcardparameter_new_xlicerrortype(VCARD_XLICERRORTYPE_RESTRICTIONCHECK);
201
0
        errProp = vcardproperty_vanew_xlicerror(temp, errParam, (void *) 0);
202
0
        vcardcomponent_add_property(comp, errProp);
203
0
        vcardproperty_free(errProp);
204
0
    }
205
206
0
    if (record->function != NULL) {
207
0
        funcr = record->function(record, comp, prop);
208
0
    }
209
210
0
    if (funcr != 0) {
211
0
        vcardproperty *errProp;
212
0
        vcardparameter *errParam;
213
214
0
        errParam =
215
0
            vcardparameter_new_xlicerrortype(VCARD_XLICERRORTYPE_RESTRICTIONCHECK);
216
0
        errProp = vcardproperty_vanew_xlicerror(funcr, errParam, (void *) 0);
217
0
        vcardcomponent_add_property(comp, errProp);
218
0
        vcardproperty_free(errProp);
219
220
0
        compare = 0;
221
0
    }
222
223
0
    return compare;
224
0
}
225
226
static bool vcardrestriction_check_component(vcardcomponent *comp)
227
0
{
228
0
    vcardcomponent_kind comp_kind, inner_kind;
229
0
    vcardproperty_version version;
230
0
    vcardproperty_kind prop_kind;
231
0
    const vcardrestriction_record *start_record;
232
0
    vcardproperty *version_prop = NULL;
233
0
    vcardcomponent *inner_comp;
234
0
    int count;
235
0
    int compare;
236
0
    bool valid = true;
237
238
0
    comp_kind = vcardcomponent_isa(comp);
239
240
0
    switch (comp_kind) {
241
0
    case VCARD_VCARD_COMPONENT:
242
        /* Get the VERSION property from the component */
243
0
        version_prop =
244
0
            vcardcomponent_get_first_property(comp, VCARD_VERSION_PROPERTY);
245
0
        break;
246
247
0
    default:
248
0
        break;
249
0
    }
250
251
0
    if (version_prop == 0) {
252
0
        version = VCARD_VERSION_NONE;
253
0
    } else {
254
0
        version = vcardproperty_get_version(version_prop);
255
0
    }
256
257
    /* Check all of the properties in this component */
258
259
0
    start_record = vcardrestriction_get_restriction(NULL, VCARD_VERSION_NONE,
260
0
                                                    comp_kind,
261
0
                                                    VCARD_ANY_PROPERTY,
262
0
                                                    VCARD_NO_COMPONENT);
263
0
    if (start_record != &null_restriction_record) {
264
265
0
        for (prop_kind = VCARD_ANY_PROPERTY + 1;
266
0
             prop_kind != VCARD_NO_PROPERTY; prop_kind++) {
267
268
0
            const vcardrestriction_record *record =
269
0
                vcardrestriction_get_restriction(start_record, version, comp_kind,
270
0
                                                 prop_kind, VCARD_NO_COMPONENT);
271
272
0
            vcardproperty *prop =
273
0
                vcardcomponent_get_first_property(comp, prop_kind);
274
275
0
            count = vcardcomponent_count_properties(comp, prop_kind, 1);
276
277
0
            compare = _check_restriction(comp, record, count, prop);
278
279
0
            valid = valid && compare;
280
0
        }
281
0
    }
282
283
    /* Now check the inner components */
284
285
0
    start_record = vcardrestriction_get_restriction(NULL, VCARD_VERSION_NONE,
286
0
                                                    comp_kind,
287
0
                                                    VCARD_NO_PROPERTY,
288
0
                                                    VCARD_ANY_COMPONENT);
289
290
0
    if (start_record != &null_restriction_record) {
291
292
0
        for (inner_kind = VCARD_NO_COMPONENT + 3;
293
0
             inner_kind != VCARD_NUM_COMPONENT_TYPES; inner_kind++) {
294
295
0
            const vcardrestriction_record *record =
296
0
                vcardrestriction_get_restriction(start_record, version, comp_kind,
297
0
                                                VCARD_NO_PROPERTY, inner_kind);
298
299
0
            count = vcardcomponent_count_components(comp, inner_kind);
300
301
0
            compare = _check_restriction(comp, record, count, NULL);
302
303
0
            valid = valid && compare;
304
0
        }
305
0
    }
306
307
0
    for (inner_comp = vcardcomponent_get_first_component(comp, VCARD_ANY_COMPONENT);
308
0
         inner_comp != 0;
309
0
         inner_comp = vcardcomponent_get_next_component(comp, VCARD_ANY_COMPONENT)) {
310
311
0
        compare = vcardrestriction_check_component(inner_comp);
312
313
0
        valid = valid && compare;
314
0
    }
315
316
0
    return valid;
317
0
}
318
319
bool vcardrestriction_check(vcardcomponent *outer_comp)
320
0
{
321
0
    vcardcomponent_kind comp_kind;
322
0
    bool valid;
323
324
0
    icalerror_check_arg_rz((outer_comp != 0), "outer comp");
325
326
0
    comp_kind = vcardcomponent_isa(outer_comp);
327
328
0
    if (comp_kind != VCARD_VCARD_COMPONENT &&
329
0
        comp_kind != VCARD_XROOT_COMPONENT) {
330
0
        icalerror_set_errno(ICAL_BADARG_ERROR);
331
0
        return 0;
332
0
    }
333
334
0
    valid = vcardrestriction_check_component(outer_comp);
335
336
0
    return valid;
337
0
}
338
339
static const vcardrestriction_record vcardrestriction_records[] = {
340
    {VCARD_VERSION_21, VCARD_VCARD_COMPONENT, VCARD_FN_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROPLUS, NULL},
341
    {VCARD_VERSION_21, VCARD_VCARD_COMPONENT, VCARD_N_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ONE, NULL},
342
    {VCARD_VERSION_30, VCARD_VCARD_COMPONENT, VCARD_FN_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ONEPLUS, NULL},
343
    {VCARD_VERSION_30, VCARD_VCARD_COMPONENT, VCARD_N_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ONE, NULL},
344
    {VCARD_VERSION_40, VCARD_VCARD_COMPONENT, VCARD_FN_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ONEPLUS, NULL},
345
    {VCARD_VERSION_40, VCARD_VCARD_COMPONENT, VCARD_N_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
346
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_ANNIVERSARY_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, vcardrestriction_validate_datetime_value},
347
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_BDAY_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, vcardrestriction_validate_datetime_value},
348
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_BIRTHPLACE_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
349
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_CREATED_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, vcardrestriction_validate_timestamp_value},
350
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_DEATHDATE_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, vcardrestriction_validate_datetime_value},
351
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_DEATHPLACE_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
352
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_GENDER_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
353
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_KIND_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
354
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_LANGUAGE_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
355
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_NO_PROPERTY, VCARD_X_COMPONENT, VCARD_RESTRICTION_ZEROPLUS, NULL},
356
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_PRODID_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
357
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_REV_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, vcardrestriction_validate_timestamp_value},
358
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_UID_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
359
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_VERSION_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ONE, NULL},
360
    {VCARD_VERSION_NONE, VCARD_NO_COMPONENT, VCARD_NO_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_NONE, NULL}
361
};
362
363
static const vcardrestriction_record *vcardrestriction_get_restriction(
364
    const vcardrestriction_record *start,
365
    vcardproperty_version version, vcardcomponent_kind component,
366
    vcardproperty_kind property, vcardcomponent_kind subcomp)
367
0
{
368
0
    const vcardrestriction_record *rec;
369
370
0
    if (!start) {
371
0
        start = &vcardrestriction_records[0];
372
0
    }
373
374
0
    for (rec = start; rec && rec->restriction != VCARD_RESTRICTION_NONE; rec++) {
375
376
0
        if ((version == VCARD_VERSION_NONE ||
377
0
             rec->version == VCARD_VERSION_NONE || version == rec->version) &&
378
0
            (component == VCARD_ANY_COMPONENT ||
379
0
             (component == rec->component &&
380
0
              (property == VCARD_ANY_PROPERTY || property == rec->property) &&
381
0
              (subcomp == VCARD_ANY_COMPONENT || subcomp == rec->subcomponent)))) {
382
0
            return rec;
383
0
        }
384
0
    }
385
386
0
    return &null_restriction_record;
387
0
}