Coverage Report

Created: 2026-09-14 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
    {true, true, true}, /*VCARD_RESTRICTION_NONE */
59
    {true, false, false}, /*VCARD_RESTRICTION_ZERO */
60
    {false, true, false}, /*VCARD_RESTRICTION_ONE */
61
    {true, true, true}, /*VCARD_RESTRICTION_ZEROPLUS */
62
    {false, true, true}, /*VCARD_RESTRICTION_ONEPLUS */
63
    {true, true, false}, /*VCARD_RESTRICTION_ZEROORONE */
64
    {true, true, false}, /*VCARD_RESTRICTION_ONEEXCLUSIVE */
65
    {true, true, false}, /*VCARD_RESTRICTION_ONEMUTUAL */
66
    {true, true, true}  /*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) //TODO: 5.0 return bool
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 (int)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 bool _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
    bool 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 = (bool)vcardrestriction_compare(restr, count);
178
179
0
    if (!compare) {
180
0
        char temp[TMP_BUF_SIZE];
181
0
        vcardproperty *errProp;
182
0
        vcardparameter *errParam;
183
0
        const char *type, *kind;
184
185
0
        if (record->subcomponent != VCARD_NO_COMPONENT) {
186
0
            type = "component";
187
0
            kind = vcardcomponent_kind_to_string(record->subcomponent);
188
0
        } else {
189
0
            type = "property";
190
0
            kind = vcardproperty_kind_to_string(record->property);
191
0
        }
192
193
0
        snprintf(temp, TMP_BUF_SIZE,
194
0
                 "Failed restrictions for %s %s. "
195
0
                 "Expected %s instances of the %s and got %d",
196
0
                 kind, type, restr_string_map[restr], type, count);
197
0
        errParam =
198
0
            vcardparameter_new_xlicerrortype(VCARD_XLICERRORTYPE_RESTRICTIONCHECK);
199
0
        errProp = vcardproperty_vanew_xlicerror(temp, errParam, (void *) 0);
200
0
        vcardcomponent_add_property(comp, errProp);
201
0
        vcardproperty_free(errProp);
202
0
    }
203
204
0
    if (record->function != NULL) {
205
0
        funcr = record->function(record, comp, prop);
206
0
    }
207
208
0
    if (funcr != 0) {
209
0
        vcardproperty *errProp;
210
0
        vcardparameter *errParam;
211
212
0
        errParam =
213
0
            vcardparameter_new_xlicerrortype(VCARD_XLICERRORTYPE_RESTRICTIONCHECK);
214
0
        errProp = vcardproperty_vanew_xlicerror(funcr, errParam, (void *) 0);
215
0
        vcardcomponent_add_property(comp, errProp);
216
0
        vcardproperty_free(errProp);
217
218
0
        compare = false;
219
0
    }
220
221
0
    return compare;
222
0
}
223
224
static bool vcardrestriction_check_component(vcardcomponent *comp)
225
0
{
226
0
    vcardcomponent_kind comp_kind, inner_kind;
227
0
    vcardproperty_version version;
228
0
    vcardproperty_kind prop_kind;
229
0
    const vcardrestriction_record *start_record;
230
0
    vcardproperty *version_prop = NULL;
231
0
    vcardcomponent *inner_comp;
232
0
    int count;
233
0
    bool compare;
234
0
    bool valid = true;
235
236
0
    comp_kind = vcardcomponent_isa(comp);
237
238
0
    switch (comp_kind) {
239
0
    case VCARD_VCARD_COMPONENT:
240
        /* Get the VERSION property from the component */
241
0
        version_prop =
242
0
            vcardcomponent_get_first_property(comp, VCARD_VERSION_PROPERTY);
243
0
        break;
244
245
0
    default:
246
0
        break;
247
0
    }
248
249
0
    if (version_prop == 0) {
250
0
        version = VCARD_VERSION_NONE;
251
0
    } else {
252
0
        version = vcardproperty_get_version(version_prop);
253
0
    }
254
255
    /* Check all of the properties in this component */
256
257
0
    start_record = vcardrestriction_get_restriction(NULL, VCARD_VERSION_NONE,
258
0
                                                    comp_kind,
259
0
                                                    VCARD_ANY_PROPERTY,
260
0
                                                    VCARD_NO_COMPONENT);
261
0
    if (start_record != &null_restriction_record) {
262
263
0
        for (prop_kind = VCARD_ANY_PROPERTY + 1;
264
0
             prop_kind != VCARD_NO_PROPERTY; prop_kind++) {
265
266
0
            const vcardrestriction_record *record =
267
0
                vcardrestriction_get_restriction(start_record, version, comp_kind,
268
0
                                                 prop_kind, VCARD_NO_COMPONENT);
269
270
0
            vcardproperty *prop =
271
0
                vcardcomponent_get_first_property(comp, prop_kind);
272
273
0
            count = vcardcomponent_count_properties(comp, prop_kind, 1);
274
275
0
            compare = _check_restriction(comp, record, count, prop);
276
277
0
            valid = valid && compare; //NOLINT(readability-implicit-bool-conversion)
278
0
        }
279
0
    }
280
281
    /* Now check the inner components */
282
283
0
    start_record = vcardrestriction_get_restriction(NULL, VCARD_VERSION_NONE,
284
0
                                                    comp_kind,
285
0
                                                    VCARD_NO_PROPERTY,
286
0
                                                    VCARD_ANY_COMPONENT);
287
288
0
    if (start_record != &null_restriction_record) {
289
290
0
        for (inner_kind = VCARD_NO_COMPONENT + 3;
291
0
             inner_kind != VCARD_NUM_COMPONENT_TYPES; inner_kind++) {
292
293
0
            const vcardrestriction_record *record =
294
0
                vcardrestriction_get_restriction(start_record, version, comp_kind,
295
0
                                                VCARD_NO_PROPERTY, inner_kind);
296
297
0
            count = vcardcomponent_count_components(comp, inner_kind);
298
299
0
            compare = _check_restriction(comp, record, count, NULL);
300
301
0
            valid = valid && compare; //NOLINT(readability-implicit-bool-conversion)
302
0
        }
303
0
    }
304
305
0
    for (inner_comp = vcardcomponent_get_first_component(comp, VCARD_ANY_COMPONENT);
306
0
         inner_comp != 0;
307
0
         inner_comp = vcardcomponent_get_next_component(comp, VCARD_ANY_COMPONENT)) {
308
309
0
        compare = vcardrestriction_check_component(inner_comp);
310
311
0
        valid = valid && compare;//NOLINT(readability-implicit-bool-conversion)
312
0
    }
313
314
0
    return valid;
315
0
}
316
317
bool vcardrestriction_check(vcardcomponent *outer_comp)
318
0
{
319
0
    vcardcomponent_kind comp_kind;
320
0
    bool valid;
321
322
0
    icalerror_check_arg_rz((outer_comp != 0), "outer comp");
323
324
0
    comp_kind = vcardcomponent_isa(outer_comp);
325
326
0
    if (comp_kind != VCARD_VCARD_COMPONENT &&
327
0
        comp_kind != VCARD_XROOT_COMPONENT) {
328
0
        icalerror_set_errno(ICAL_BADARG_ERROR);
329
0
        return false;
330
0
    }
331
332
0
    valid = vcardrestriction_check_component(outer_comp);
333
334
0
    return valid;
335
0
}
336
337
static const vcardrestriction_record vcardrestriction_records[] = {
338
    {VCARD_VERSION_21, VCARD_VCARD_COMPONENT, VCARD_FN_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROPLUS, NULL},
339
    {VCARD_VERSION_21, VCARD_VCARD_COMPONENT, VCARD_N_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ONE, NULL},
340
    {VCARD_VERSION_30, VCARD_VCARD_COMPONENT, VCARD_FN_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ONEPLUS, NULL},
341
    {VCARD_VERSION_30, VCARD_VCARD_COMPONENT, VCARD_N_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ONE, NULL},
342
    {VCARD_VERSION_40, VCARD_VCARD_COMPONENT, VCARD_FN_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ONEPLUS, NULL},
343
    {VCARD_VERSION_40, VCARD_VCARD_COMPONENT, VCARD_N_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
344
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_ANNIVERSARY_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, vcardrestriction_validate_datetime_value},
345
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_BDAY_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, vcardrestriction_validate_datetime_value},
346
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_BIRTHPLACE_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
347
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_CREATED_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, vcardrestriction_validate_timestamp_value},
348
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_DEATHDATE_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, vcardrestriction_validate_datetime_value},
349
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_DEATHPLACE_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
350
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_GENDER_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
351
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_KIND_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
352
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_LANGUAGE_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
353
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_PRODID_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
354
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_REV_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, vcardrestriction_validate_timestamp_value},
355
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_UID_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ZEROORONE, NULL},
356
    {VCARD_VERSION_NONE, VCARD_VCARD_COMPONENT, VCARD_VERSION_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_ONE, NULL},
357
    {VCARD_VERSION_NONE, VCARD_NO_COMPONENT, VCARD_NO_PROPERTY, VCARD_NO_COMPONENT, VCARD_RESTRICTION_NONE, NULL}
358
};
359
360
static const vcardrestriction_record *vcardrestriction_get_restriction(
361
    const vcardrestriction_record *start,
362
    vcardproperty_version version, vcardcomponent_kind component,
363
    vcardproperty_kind property, vcardcomponent_kind subcomp)
364
0
{
365
0
    const vcardrestriction_record *rec;
366
367
0
    if (!start) {
368
0
        start = &vcardrestriction_records[0];
369
0
    }
370
371
0
    for (rec = start; rec && rec->restriction != VCARD_RESTRICTION_NONE; rec++) {
372
373
0
        if ((version == VCARD_VERSION_NONE ||
374
0
             rec->version == VCARD_VERSION_NONE || version == rec->version) &&
375
0
            (component == VCARD_ANY_COMPONENT ||
376
0
             (component == rec->component &&
377
0
              (property == VCARD_ANY_PROPERTY || property == rec->property) &&
378
0
              (subcomp == VCARD_ANY_COMPONENT || subcomp == rec->subcomponent)))) {
379
0
            return rec;
380
0
        }
381
0
    }
382
383
0
    return &null_restriction_record;
384
0
}