diff --git a/design-data/vcard-restrictions.csv b/design-data/vcard-restrictions.csv index a755aa219..3c79daa04 100644 --- a/design-data/vcard-restrictions.csv +++ b/design-data/vcard-restrictions.csv @@ -1,12 +1,12 @@ # Version, Target component, Property, Sub-component, Restriction NONE,VCARD,VERSION,NONE,ONE -NONE,VCARD,ANNIVERSARY,NONE,ZEROORONE -NONE,VCARD,BDAY,NONE,ZEROORONE +NONE,VCARD,ANNIVERSARY,NONE,ZEROORONE,validate_datetime_value +NONE,VCARD,BDAY,NONE,ZEROORONE,validate_datetime_value NONE,VCARD,GENDER,NONE,ZEROORONE NONE,VCARD,KIND,NONE,ZEROORONE NONE,VCARD,PRODID,NONE,ZEROORONE -NONE,VCARD,REV,NONE,ZEROORONE +NONE,VCARD,REV,NONE,ZEROORONE,validate_timestamp_value NONE,VCARD,UID,NONE,ZEROORONE NONE,VCARD,NONE,X,ZEROPLUS @@ -21,8 +21,8 @@ NONE,VCARD,NONE,X,ZEROPLUS # Birth/Death Extensions (RFC 6474) NONE,VCARD,BIRTHPLACE,NONE,ZEROORONE NONE,VCARD,DEATHPLACE,NONE,ZEROORONE -NONE,VCARD,DEATHDATE,NONE,ZEROORONE +NONE,VCARD,DEATHDATE,NONE,ZEROORONE,validate_datetime_value # JSContact Extensions (RFC 9554) -NONE,VCARD,CREATED,NONE,ZEROORONE +NONE,VCARD,CREATED,NONE,ZEROORONE,validate_timestamp_value NONE,VCARD,LANGUAGE,NONE,ZEROORONE diff --git a/src/libicalvcard/vcardcomponent.c b/src/libicalvcard/vcardcomponent.c index 3c4b71d4c..b2f98c908 100644 --- a/src/libicalvcard/vcardcomponent.c +++ b/src/libicalvcard/vcardcomponent.c @@ -875,6 +875,28 @@ static void comp_to_v4(vcardcomponent *impl) vcardproperty_set_version(prop, VCARD_VERSION_40); break; + case VCARD_BDAY_PROPERTY: + case VCARD_DEATHDATE_PROPERTY: + case VCARD_ANNIVERSARY_PROPERTY: + for (param = vcardproperty_get_first_parameter(prop, + VCARD_X_PARAMETER); + param; + param = vcardproperty_get_next_parameter(prop, + VCARD_X_PARAMETER)) { + const char *name = vcardparameter_get_xname(param); + + /* This appears in the wild for v3 date with missing year */ + if (name && !strcasecmp(name, "X-APPLE-OMIT-YEAR")) { + vcardtimetype dt = vcardproperty_get_bday(prop); + + dt.year = -1; + vcardproperty_set_bday(prop, dt); + vcardproperty_remove_parameter_by_ref(prop, param); + break; + } + } + break; + case VCARD_GEO_PROPERTY: if (vkind != VCARD_X_VALUE) { vcardgeotype geo = vcardvalue_get_geo(value); @@ -1085,6 +1107,31 @@ static void comp_to_v3(vcardcomponent *impl) vcardproperty_set_version(prop, VCARD_VERSION_30); break; + case VCARD_BDAY_PROPERTY: + case VCARD_DEATHDATE_PROPERTY: + case VCARD_ANNIVERSARY_PROPERTY: { + vcardtimetype dt = vcardproperty_get_bday(prop); + + if (dt.year == -1) { + /* This appears in the wild for v3 date with missing year */ + dt.year = 1604; + vcardproperty_set_parameter_from_string(prop, + "X-APPLE-OMIT-YEAR", + "1604"); + } + if (dt.hour != -1) { + if (dt.second == -1) { + dt.second = 0; + if (dt.minute == -1) { + dt.minute = 0; + } + } + } + + vcardproperty_set_bday(prop, dt); + break; + } + case VCARD_GEO_PROPERTY: if (vkind != VCARD_X_VALUE) { vcardgeotype geo = vcardvalue_get_geo(value); diff --git a/src/libicalvcard/vcardrestriction.c.in b/src/libicalvcard/vcardrestriction.c.in index b5d03ca5b..eff66e0de 100644 --- a/src/libicalvcard/vcardrestriction.c.in +++ b/src/libicalvcard/vcardrestriction.c.in @@ -90,6 +90,69 @@ int vcardrestriction_compare(vcardrestriction_kind restr, int count) return compare_map[restr][count]; } +/* Special case routines */ + +#define TMP_BUF_SIZE 1024 + +static const char *vcardrestriction_validate_datetime_value( + const vcardrestriction_record *rec, + vcardcomponent *comp, vcardproperty *prop) +{ + vcardtimetype t = vcardproperty_get_bday(prop); + static char buf[TMP_BUF_SIZE]; + + if (vcardtime_is_null_datetime(t)) + return 0; + + if (comp && vcardcomponent_get_version(comp) != VCARD_VERSION_40) { + unsigned missing_time_parts = + (t.hour < 0) + (t.minute < 0) + (t.second < 0); + + if (t.year < 0 || t.month < 0 || t.day < 0 || + (missing_time_parts && missing_time_parts != 3)) { + + snprintf(buf, TMP_BUF_SIZE, + "Failed restrictions for %s property. " + "The value must be a full date or date-time", + vcardproperty_kind_to_string(rec->property)); + return buf; + } + } + + if (!vcardtime_is_valid_time(t)) { + snprintf(buf, TMP_BUF_SIZE, + "Failed restrictions for %s property. " + "The value is an invalid date-and-or-time", + vcardproperty_kind_to_string(rec->property)); + return buf; + } + + return 0; +} + +static const char *vcardrestriction_validate_timestamp_value( + const vcardrestriction_record *rec, + vcardcomponent *comp, vcardproperty *prop) +{ + _unused(comp); + + vcardtimetype t = vcardproperty_get_rev(prop); + static char buf[TMP_BUF_SIZE]; + + if (vcardtime_is_null_datetime(t)) + return 0; + + if (!vcardtime_is_timestamp(t) || !vcardtime_is_valid_time(t)) { + snprintf(buf, TMP_BUF_SIZE, + "Failed restrictions for %s property. " + "The value is an invalid timestamp", + vcardproperty_kind_to_string(rec->property)); + return buf; + } + + return 0; +} + static int _check_restriction(vcardcomponent *comp, const vcardrestriction_record *record, int count, vcardproperty *prop) @@ -112,7 +175,6 @@ static int _check_restriction(vcardcomponent *comp, assert(compare != -1); if (compare == 0) { -#define TMP_BUF_SIZE 1024 char temp[TMP_BUF_SIZE]; vcardproperty *errProp; vcardparameter *errParam; diff --git a/src/libicalvcard/vcardtime.c b/src/libicalvcard/vcardtime.c index 9c4156050..7647badbe 100644 --- a/src/libicalvcard/vcardtime.c +++ b/src/libicalvcard/vcardtime.c @@ -77,7 +77,8 @@ int vcardtime_is_datetime(const vcardtimetype t) int vcardtime_is_timestamp(const vcardtimetype t) { return (t.year != -1 && t.month != -1 && t.day != -1 && - t.hour != -1 && t.minute != -1 && t.second != -1); + t.hour != -1 && t.minute != -1 && t.second != -1 && + t.utcoffset != -1); } int vcardtime_is_utc(const vcardtimetype t) diff --git a/src/test/libicalvcard/vcard_test.c b/src/test/libicalvcard/vcard_test.c index 4ef2c2662..45780e0d0 100644 --- a/src/test/libicalvcard/vcard_test.c +++ b/src/test/libicalvcard/vcard_test.c @@ -320,7 +320,7 @@ static void test_n_restriction(vcardcomponent *card) "VERSION:3.0\r\n" "FN:Mickey Mouse\r\n" "PHOTO;ENCODING=B;TYPE=JPEG:ABCDEF\r\n" - "BDAY:00001118T030000\r\n" + "BDAY;X-APPLE-OMIT-YEAR=1604:16041118T030000\r\n" "ADR:;;123 Main Street,Disney World;Orlando;FL;32836;USA;;;;;;;;;;;\r\n" "TEL:+1-888-555-1212\r\n" "LANG;PREF=1;TYPE=PREF:en\r\n" @@ -354,7 +354,7 @@ static void test_v3_to_v4(vcardcomponent *card) "VERSION:4.0\r\n" "FN:Mickey Mouse\r\n" "PHOTO:data:image/jpeg;base64,ABCDEF\r\n" - "BDAY:--1118T03\r\n" + "BDAY:--1118T030000\r\n" "ADR:;;123 Main Street,Disney World;Orlando;FL;32836;USA;;;;;;;;;;;\r\n" "TEL:+1-888-555-1212\r\n" "LANG;PREF=1:en\r\n"