diff --git a/include/gromox/mapitags.hpp b/include/gromox/mapitags.hpp index 4cb880584..5f087a6c8 100644 --- a/include/gromox/mapitags.hpp +++ b/include/gromox/mapitags.hpp @@ -1117,12 +1117,6 @@ using proptag_t = decltype(PR_NULL); } enum { - PidLidAttendeeCriticalChange = 0x0001, - PidLidGlobalObjectId = 0x0003, /* a.k.a. LID_GLOBAL_OBJID */ - PidLidIsException = 0x000A, - PidLidStartRecurrenceTime = 0x000E, - PidLidOwnerCriticalChange = 0x001A, - PidLidCleanGlobalObjectId = 0x0023, PidLidCategories = 0x2328, /* PSETID_Address */ @@ -1205,10 +1199,17 @@ enum { PidLidAppointmentTimeZoneDefinitionRecur = 0x8260, /* PSETID_Meeting */ + PidLidAttendeeCriticalChange = 0x0001, PidLidWhere = 0x0002, + PidLidGlobalObjectId = 0x0003, /* a.k.a. LID_GLOBAL_OBJID */ PidLidIsSilent = 0x0004, PidLidIsRecurring = 0x0005, /* object is associated with a recurring series object */ - PidLidTimeZone = 0x000c, + PidLidIsException = 0x000A, + PidLidTimeZone = 0x000C, + PidLidStartRecurrenceTime = 0x000E, + PidLidMonthOfYearMask = 0x0017, + PidLidOwnerCriticalChange = 0x001A, + PidLidCleanGlobalObjectId = 0x0023, PidLidMeetingType = 0x0026, /* PSETID_Common */ diff --git a/lib/mapi/oxcical.cpp b/lib/mapi/oxcical.cpp index c02cb4623..91c8c8aea 100644 --- a/lib/mapi/oxcical.cpp +++ b/lib/mapi/oxcical.cpp @@ -3411,7 +3411,6 @@ static std::string oxcical_export_internal(const char *method, const char *tzid, if (!get_propids(&pna, &propids) || propids.size() != pna.size()) return E_2201; - /* Cf. MS-OXCICAL v20240416 §2.1.3.1.1.1, "property: METHOD". */ auto num = pmsg->proplist.get(PR_MESSAGE_LOCALE_ID); auto planguage = num != nullptr ? lcid_to_ltag(*num) : nullptr; auto str = pmsg->proplist.get(PR_MESSAGE_CLASS); @@ -3459,15 +3458,6 @@ static std::string oxcical_export_internal(const char *method, const char *tzid, return fmt::format("W-2060: oxcical_export does not handle message class \"{}\"", str); } } - - /* - * Cf. MS-OXCICAL v20240416: If the METHOD property of the VCALENDAR - * component is set to "COUNTER", then apptProposedStartWhole should be - * exported as a new DTSTART property. For other values of METHOD, the - * apptStartWhole of a Calendar object should be exported as DTSTART. - * - * NOTE: b_proposal is correctly set on COUNTER (see above). - */ auto lnum = pmsg->proplist.get(PROP_TAG(PT_SYSTIME, propids[b_proposal ? l_proposedstartwhole : l_startwhole])); bool has_start_time = false; @@ -3525,18 +3515,7 @@ static std::string oxcical_export_internal(const char *method, const char *tzid, if (has_start_time && gmtime_r(&start_time, &tmp_tm) != nullptr) year = tmp_tm.tm_year + 1900; - /* - * Cf. [MS-OXCICAL] v20240416 §2.1.3.1.1.19.1 "property: TZID": - * If the tzdefRecur, tzdefStartDisplay, or tzdefEndDisplay - * property is being exported as a VTIMEZONE, then the value of - * TZID must be derived from the KeyName field of the - * tzdefRecur structure. - * - * Note: There is no need to check tzdefStartDisplay or - * tzdefEndDisplay, as it is a MUST to use the KeyName of - * tzdefRecur. - */ - tzid = nullptr; + tzid = NULL; if (b_recurrence) { bin = pmsg->proplist.get(PROP_TAG(PT_BINARY, propids[l_tzdefrecur])); if (bin != nullptr) { @@ -3555,6 +3534,26 @@ static std::string oxcical_export_internal(const char *method, const char *tzid, return "E-2208: export_timezone returned an unspecified error"; } } else { + bin = pmsg->proplist.get(PROP_TAG(PT_BINARY, propids[l_tzdefstart])); + if (bin != nullptr) + bin = pmsg->proplist.get(PROP_TAG(PT_BINARY, propids[l_tzdefend])); + if (bin != nullptr) { + EXT_PULL ext_pull; + TIMEZONEDEFINITION tz_definition; + TIMEZONESTRUCT tz_struct; + + ext_pull.init(bin->pb, bin->cb, alloc, 0); + if (ext_pull.g_tzdef(&tz_definition) != EXT_ERR_SUCCESS) + return "E-2209: PidLidAppointmentTimeZoneDefinitionEndDisplay contents not recognized"; + tzid = tz_definition.keyname; + oxcical_convert_to_tzstruct(&tz_definition, &tz_struct); + ptz_component = oxcical_export_timezone( + pical, year - 1, tzid, &tz_struct); + if (ptz_component == nullptr) + return "E-2210: export_timezone returned an unspecified error"; + } + } + if (ptz_component == nullptr) { bin = pmsg->proplist.get(PROP_TAG(PT_BINARY, propids[l_tzstruct])); tzid = pmsg->proplist.get(PROP_TAG(PT_UNICODE, propids[l_tzdesc])); if (bin != nullptr && tzid != nullptr) { @@ -3567,7 +3566,7 @@ static std::string oxcical_export_internal(const char *method, const char *tzid, ptz_component = oxcical_export_timezone( pical, year - 1, tzid, &tz_struct); if (ptz_component == nullptr) - return "E-2210: export_timezone returned an unspecified error"; + return "E-2206: export_timezone returned an unspecified error"; } } } diff --git a/tests/oxcmail_ie.cpp b/tests/oxcmail_ie.cpp index de9993b99..5e00b6674 100644 --- a/tests/oxcmail_ie.cpp +++ b/tests/oxcmail_ie.cpp @@ -378,6 +378,49 @@ static void ical_export_1() } } +static void ical_export_2() +{ + /* GXF-1819 */ + const ie_name_entry ie_map[] = { + {0x809d, {MNID_ID, PSETID_Appointment, PidLidAppointmentStartWhole}}, + {0x809e, {MNID_ID, PSETID_Appointment, PidLidAppointmentEndWhole}}, + {0x80a5, {MNID_ID, PSETID_Appointment, PidLidAppointmentSubType}}, + {0x80d2, {MNID_ID, PSETID_Appointment, PidLidAppointmentTimeZoneDefinitionStartDisplay}}, + {0x80d3, {MNID_ID, PSETID_Appointment, PidLidAppointmentTimeZoneDefinitionEndDisplay}}, + }; + auto get_propids = [&](const PROPNAME_ARRAY *a, PROPID_ARRAY *i) { + return ie_get_propids(ie_map, std::size(ie_map), a, i); + }; + static constexpr uint64_t v_start = 0x1db0f96441fb000, v_end = 0x1db105f6e897000; + static constexpr bool v_true = 1; + static const BINARY v_122 = {122, {reinterpret_cast(deconst("\x02\x01\x34\x00\x02\x00\x17\x00\x57\x00\x2e\x00\x20\x00\x45\x00\x75\x00\x72\x00\x6f\x00\x70\x00\x65\x00\x20\x00\x53\x00\x74\x00\x61\x00\x6e\x00\x64\x00\x61\x00\x72\x00\x64\x00\x20\x00\x54\x00\x69\x00\x6d\x00\x65\x00\x01\x00\x02\x01\x3e\x00\x02\x00\x41\x06\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc4\xff\xff\xff\x00\x00\x00\x00\xc4\xff\xff\xff\x00\x00\x0a\x00\x00\x00\x05\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x05\x00\x02\x00\x00\x00\x00\x00\x00\x00"))}}; + const TAGGED_PROPVAL props[] = { + {PR_MESSAGE_CLASS, deconst("IPM.Appointment")}, + {0x809d0040, deconst(&v_start)}, + {0x809e0040, deconst(&v_end)}, + {0x80a5000b, deconst(&v_true)}, + {0x80d20102, deconst(&v_122)}, + {0x80d30102, deconst(&v_122)}, + }; + fprintf(stderr, "=== ical_export_2\n"); + const MESSAGE_CONTENT msgctnt = {{std::size(props), deconst(props)}}; + ical icalout; + if (!oxcical_export(&msgctnt, icalout, "x500org", malloc, get_propids, nullptr)) { + fprintf(stderr, "oxcical_export failed\n"); + return; + } + std::string icstr; + if (icalout.serialize(icstr) != ecSuccess) { + fprintf(stderr, "ical_serialize failed\n"); + return; + } + if (strstr(icstr.c_str(), "DTSTART;VALUE=DATE;TZID=W. Europe Standard Time:20240926") == nullptr || + strstr(icstr.c_str(), "DTEND;VALUE=DATE;TZID=W. Europe Standard Time:20240927") == nullptr) { + printf("%s\n", icstr.c_str()); + fprintf(stderr, "FAILED. Substrings DTSTART/20240926 and DTEND/20240927 not found.\n"); + } +} + int main() { auto ee_get_user_ids = [](const char *, unsigned int *, unsigned int *, enum display_type *) -> BOOL { return false; }; @@ -394,5 +437,6 @@ int main() select_parts_4(); select_parts_5(); ical_export_1(); + ical_export_2(); return EXIT_SUCCESS; }