diff --git a/Makefile.am b/Makefile.am index 5fe2b1d42..ae51964fe 100644 --- a/Makefile.am +++ b/Makefile.am @@ -82,7 +82,7 @@ libgromox_email_la_LIBADD = ${fmt_LIBS} ${HX_LIBS} ${jsoncpp_LIBS} ${ssl_LIBS} $ libgromox_epoll_la_CXXFLAGS = ${libgromox_common_la_CXXFLAGS} libgromox_epoll_la_SOURCES = lib/contexts_pool.cpp lib/threads_pool.cpp libgromox_epoll_la_LIBADD = -lpthread libgromox_common.la -libgromox_exrpc_la_SOURCES = lib/exmdb_client.cpp lib/exmdb_ext.cpp lib/exmdb_rpc.cpp lib/ruleproc.cpp +libgromox_exrpc_la_SOURCES = lib/exmdb_client.cpp lib/exmdb_ext.cpp lib/exmdb_rpc.cpp lib/freebusy.cpp lib/ruleproc.cpp libgromox_exrpc_la_LIBADD = libgromox_mapi.la libgromox_mapi_la_CXXFLAGS = ${libgromox_common_la_CXXFLAGS} libgromox_mapi_la_SOURCES = lib/mapi/eid_array.cpp lib/mapi/element_data.cpp lib/mapi/html.cpp lib/mapi/idset.cpp lib/mapi/lzxpress.cpp lib/mapi/msgchg_grouping.cpp lib/mapi/oxcical.cpp lib/mapi/oxcmail.cpp lib/mapi/oxvcard.cpp lib/mapi/pcl.cpp lib/mapi/proptag_array.cpp lib/mapi/propval.cpp lib/mapi/restriction.cpp lib/mapi/restriction2.cpp lib/mapi/rop_util.cpp lib/mapi/rtf.cpp lib/mapi/rtfcp.cpp lib/mapi/rule_actions.cpp lib/mapi/sortorder_set.cpp lib/mapi/tarray_set.cpp lib/mapi/tnef.cpp lib/mapi/tpropval_array.cpp @@ -327,7 +327,7 @@ tzd_files = data/AUS_Central.tzd data/AUS_Eastern.tzd data/Afghanistan.tzd data/ tzd_files += data/Haiti.tzd data/Hawaiian.tzd data/India.tzd data/Iran.tzd data/Israel.tzd data/Jordan.tzd data/Kaliningrad.tzd data/Korea.tzd data/Libya.tzd data/Line_Islands.tzd data/Lord_Howe.tzd data/Magadan.tzd data/Magallanes.tzd data/Marquesas.tzd data/Mauritius.tzd data/Middle_East.tzd data/Montevideo.tzd data/Morocco.tzd data/Mountain.tzd data/Mountain__Mexico_.tzd data/Myanmar.tzd data/N__Central_Asia.tzd data/Namibia.tzd data/Nepal.tzd data/New_Zealand.tzd data/Newfoundland.tzd data/Norfolk.tzd data/North_Asia.tzd data/North_Asia_East.tzd data/North_Korea.tzd data/Omsk.tzd data/Pacific.tzd data/Pacific_SA.tzd data/Pacific__Mexico_.tzd data/Pakistan.tzd data/Paraguay.tzd data/Qyzylorda.tzd data/Romance.tzd data/Russia_Time_Zone_10.tzd data/Russia_Time_Zone_11.tzd data/Russia_Time_Zone_3.tzd data/Russian.tzd tzd_files += data/SA_Eastern.tzd data/SA_Pacific.tzd data/SA_Western.tzd data/SE_Asia.tzd data/Saint_Pierre.tzd data/Sakhalin.tzd data/Samoa.tzd data/Sao_Tome.tzd data/Saratov.tzd data/Singapore.tzd data/South_Africa.tzd data/South_Sudan.tzd data/Sri_Lanka.tzd data/Sudan.tzd data/Syria.tzd data/Taipei.tzd data/Tasmania.tzd data/Tocantins.tzd data/Tokyo.tzd data/Tomsk.tzd data/Tonga.tzd data/Transbaikal.tzd data/Turkey.tzd data/Turks_And_Caicos.tzd data/US_Eastern.tzd data/US_Mountain.tzd data/UTC+12.tzd data/UTC+13.tzd data/UTC-02.tzd data/UTC-08.tzd data/UTC-09.tzd data/UTC-11.tzd data/UTC.tzd data/Ulaanbaatar.tzd data/Venezuela.tzd data/Vladivostok.tzd data/Volgograd.tzd data/W__Australia.tzd data/W__Central_Africa.tzd data/W__Europe.tzd data/W__Mongolia.tzd data/West_Asia.tzd data/West_Bank.tzd data/West_Pacific.tzd data/Yakutsk.tzd data/Yukon.tzd data/_GMT_+01_00_.tzd header_files = include/gromox/ab_tree.hpp include/gromox/arcfour.hpp include/gromox/atomic.hpp include/gromox/authmgr.hpp include/gromox/binrdwr.hpp include/gromox/bounce_gen.hpp include/gromox/clock.hpp include/gromox/common_types.hpp include/gromox/config_file.hpp include/gromox/contexts_pool.hpp include/gromox/cookie_parser.hpp include/gromox/cryptoutil.hpp include/gromox/database.h include/gromox/database_mysql.hpp include/gromox/dbop.h include/gromox/dcerpc.hpp include/gromox/defs.h include/gromox/double_list.hpp include/gromox/dsn.hpp include/gromox/eid_array.hpp include/gromox/element_data.hpp include/gromox/endian.hpp include/gromox/exmdb_client.hpp include/gromox/exmdb_common_util.hpp include/gromox/exmdb_ext.hpp include/gromox/exmdb_idef.hpp include/gromox/exmdb_provider_client.hpp include/gromox/exmdb_rpc.hpp include/gromox/exmdb_server.hpp include/gromox/ext_buffer.hpp -header_files += include/gromox/fileio.h include/gromox/flusher_common.h include/gromox/generic_connection.hpp include/gromox/hook_common.h include/gromox/hpm_common.h include/gromox/html.hpp include/gromox/ical.hpp include/gromox/icase.hpp include/gromox/int_hash.hpp include/gromox/json.hpp include/gromox/list_file.hpp include/gromox/lzxpress.hpp include/gromox/mail.hpp include/gromox/mail_func.hpp include/gromox/mapi_types.hpp include/gromox/mapidefs.h include/gromox/mapierr.hpp include/gromox/mapitags.hpp include/gromox/mem_file.hpp include/gromox/midb.hpp include/gromox/mime.hpp include/gromox/mime_pool.hpp include/gromox/mjson.hpp include/gromox/msg_unit.hpp include/gromox/msgchg_grouping.hpp include/gromox/mysql_adaptor.hpp include/gromox/ndr.hpp include/gromox/ntlmssp.hpp include/gromox/oxcmail.hpp include/gromox/oxoabkt.hpp +header_files += include/gromox/fileio.h include/gromox/flusher_common.h include/gromox/freebusy.hpp include/gromox/generic_connection.hpp include/gromox/hook_common.h include/gromox/hpm_common.h include/gromox/html.hpp include/gromox/ical.hpp include/gromox/icase.hpp include/gromox/int_hash.hpp include/gromox/json.hpp include/gromox/list_file.hpp include/gromox/lzxpress.hpp include/gromox/mail.hpp include/gromox/mail_func.hpp include/gromox/mapi_types.hpp include/gromox/mapidefs.h include/gromox/mapierr.hpp include/gromox/mapitags.hpp include/gromox/mem_file.hpp include/gromox/midb.hpp include/gromox/mime.hpp include/gromox/mime_pool.hpp include/gromox/mjson.hpp include/gromox/msg_unit.hpp include/gromox/msgchg_grouping.hpp include/gromox/mysql_adaptor.hpp include/gromox/ndr.hpp include/gromox/ntlmssp.hpp include/gromox/oxcmail.hpp include/gromox/oxoabkt.hpp header_files += include/gromox/paths.h.in include/gromox/pcl.hpp include/gromox/plugin.hpp include/gromox/proc_common.h include/gromox/proptag_array.hpp include/gromox/propval.hpp include/gromox/range_set.hpp include/gromox/resource_pool.hpp include/gromox/restriction.hpp include/gromox/rop_util.hpp include/gromox/rpc_types.hpp include/gromox/rtf.hpp include/gromox/rtfcp.hpp include/gromox/rule_actions.hpp include/gromox/safeint.hpp include/gromox/scope.hpp include/gromox/simple_tree.hpp include/gromox/sortorder_set.hpp include/gromox/stream.hpp include/gromox/svc_common.h include/gromox/svc_loader.hpp include/gromox/textmaps.hpp include/gromox/threads_pool.hpp include/gromox/tie.hpp include/gromox/timezone.hpp include/gromox/tnef.hpp include/gromox/util.hpp include/gromox/vcard.hpp include/gromox/xarray2.hpp include/gromox/zcore_client.hpp include/gromox/zcore_rpc.hpp include/gromox/zz_ndr_stack.hpp dist_pkgdata_DATA = ${abkt_files} ${tzd_files} toolprogs = tools/defs2php.pl tools/defs2php.sh tools/duplogid tools/enumsort tools/exmidl.pl tools/exmidl.sh tools/includesort tools/proptagsort tools/stackusage tools/warncount tools/zcidl.pl tools/zcidl.sh diff --git a/exch/zcore/names.cpp b/exch/zcore/names.cpp index 074686021..14212e9e2 100644 --- a/exch/zcore/names.cpp +++ b/exch/zcore/names.cpp @@ -100,7 +100,8 @@ static constexpr const char *zcore_rpc_names[] = { E(icaltomessage2), E(imtomessage2), E(essdn_to_username), - E(logon_token) + E(logon_token), + E(getuserfreebusy), }; #undef E #undef EXP @@ -108,7 +109,7 @@ static constexpr const char *zcore_rpc_names[] = { const char *zcore_rpc_idtoname(zcore_callid i) { auto j = static_cast(i); - static_assert(std::size(zcore_rpc_names) == static_cast(zcore_callid::logon_token) + 1); + static_assert(std::size(zcore_rpc_names) == static_cast(zcore_callid::getuserfreebusy) + 1); auto s = j < std::size(zcore_rpc_names) ? zcore_rpc_names[j] : nullptr; return znul(s); } diff --git a/exch/zcore/rpc_ext.cpp b/exch/zcore/rpc_ext.cpp index 997bd1282..e17f10ad1 100644 --- a/exch/zcore/rpc_ext.cpp +++ b/exch/zcore/rpc_ext.cpp @@ -1525,6 +1525,23 @@ static pack_result zrpc_pull(EXT_PULL &x, zcreq_vcftomessage &d) return pack_result::ok; } +static pack_result zrpc_pull(EXT_PULL &x, zcreq_getuserfreebusy &d) +{ + QRF(x.g_guid(&d.hsession)); + QRF(x.g_bin(&d.entryid)); + QRF(x.g_uint64(&d.starttime)); + QRF(x.g_uint64(&d.endtime)); + return pack_result::ok; +} + +static pack_result zrpc_push(EXT_PUSH &x, const zcresp_getuserfreebusy &d) +{ + QRF(x.p_uint32(d.fb_events.count)); + for (size_t i = 0; i < d.fb_events.count; ++i) + QRF(x.p_fbevent(d.fb_events.fb_events[i])); + return pack_result::ok; +} + static pack_result zrpc_pull(EXT_PULL &x, zcreq_getuseravailability &d) { QRF(x.g_guid(&d.hsession)); @@ -1692,6 +1709,7 @@ pack_result rpc_ext_pull_request(const BINARY *pbin_in, zcreq *&prequest) E(imtomessage2) E(essdn_to_username) E(logon_token) + E(getuserfreebusy) #undef E default: return pack_result::bad_switch; @@ -1808,6 +1826,7 @@ pack_result rpc_ext_push_response(const zcresp *presponse, BINARY *pbin_out) E(imtomessage2) E(essdn_to_username) E(logon_token) + E(getuserfreebusy) #undef E default: return pack_result::bad_switch; diff --git a/exch/zcore/zserver.cpp b/exch/zcore/zserver.cpp index 722fed009..2e197126a 100644 --- a/exch/zcore/zserver.cpp +++ b/exch/zcore/zserver.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -5080,6 +5081,34 @@ ec_error_t zs_vcftomessage(GUID hsession, return pmessage->write_message(pmsgctnt) ? ecSuccess : ecError; } +ec_error_t zs_getuserfreebusy(GUID hsession, BINARY entryid, + uint64_t starttime, uint64_t endtime, FB_ARRAY *fb_events) +{ + char maildir[256]; + char username[UADDR_SIZE]; + + auto pinfo = zs_query_session(hsession); + if (pinfo == nullptr) + return ecError; + if (!common_util_addressbook_entryid_to_username(entryid, + username, std::size(username)) || + !system_services_get_maildir(username, maildir, std::size(maildir))) + return ecSuccess; + + std::vector fb_data; + get_freebusy(pinfo->get_username(), maildir, starttime, endtime, fb_data); + pinfo.reset(); + + fb_events->count = 0; + fb_events->fb_events = cu_alloc(fb_data.size()); + for (const auto &fb_event: fb_data) { + fb_events->fb_events[fb_events->count++] = fb_event; + } + fb_data.clear(); + + return ecSuccess; +} + ec_error_t zs_getuseravailability(GUID hsession, BINARY entryid, uint64_t starttime, uint64_t endtime, char **ppresult_string) { diff --git a/exch/zcore/zserver.hpp b/exch/zcore/zserver.hpp index 669795a6d..07b656c42 100644 --- a/exch/zcore/zserver.hpp +++ b/exch/zcore/zserver.hpp @@ -137,3 +137,4 @@ extern ec_error_t zs_getuseravailability(GUID ses, BINARY entryid, uint64_t star extern ec_error_t zs_setpasswd(const char *username, const char *passwd, const char *new_passwd); extern ec_error_t zs_linkmessage(GUID ses, BINARY search_eid, BINARY msg_eid); extern ec_error_t zs_essdn_to_username(const char *essdn, char **username); +extern ec_error_t zs_getuserfreebusy(GUID ses, BINARY entryid, uint64_t starttime, uint64_t endtime, FB_ARRAY *fb_events); diff --git a/include/gromox/ext_buffer.hpp b/include/gromox/ext_buffer.hpp index 7affedf10..8be6641d3 100644 --- a/include/gromox/ext_buffer.hpp +++ b/include/gromox/ext_buffer.hpp @@ -183,6 +183,8 @@ struct EXT_PULL { pack_result g_apptrecpat(APPOINTMENT_RECUR_PAT *); pack_result g_goid(GLOBALOBJECTID *); pack_result g_msgctnt(MESSAGE_CONTENT *); + pack_result g_fb(freebusy_event *); + pack_result g_fb_a(FB_ARRAY *); template inline T *anew() { return static_cast(m_alloc(sizeof(T))); } template inline T *anew(size_t elem) { return static_cast(m_alloc(sizeof(T) * elem)); } @@ -275,6 +277,7 @@ struct EXT_PUSH { pack_result p_goid(const GLOBALOBJECTID &); pack_result p_msgctnt(const MESSAGE_CONTENT &); pack_result p_rpchdr(const RPC_HEADER_EXT &); + pack_result p_fbevent(const freebusy_event &); BOOL b_alloc = false; union { diff --git a/include/gromox/freebusy.hpp b/include/gromox/freebusy.hpp new file mode 100644 index 000000000..be6deda66 --- /dev/null +++ b/include/gromox/freebusy.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +using namespace gromox; + +template<> struct fmt::formatter +{ + auto parse(format_parse_context &ctx) { return ctx.begin(); } + format_context::iterator format(const ICAL_TIME &t, format_context &ctx) const + { + return fmt::format_to(ctx.out(), "{:04}{:02}{:02}T{:02}{:02}{:02}", + t.year, t.month, t.day, t.hour, t.minute, t.second); + } +}; + +struct event +{ + time_t start_time = 0, end_time = 0; + EXCEPTIONINFO *ei = nullptr; + EXTENDEDEXCEPTION *xe = nullptr; +}; + +struct freebusy_tags +{ + freebusy_tags(const char *); + + uint32_t apptstartwhole = 0, apptendwhole = 0, busystatus = 0, recurring = 0, + apptrecur = 0, apptsubtype = 0, private_flag = 0, apptstateflags = 0, + clipend = 0, location = 0, reminderset = 0, globalobjectid = 0, + timezonestruct = 0; +}; + +extern GX_EXPORT bool get_freebusy(const char *, const char *, uint64_t, uint64_t, + std::vector &); diff --git a/include/gromox/mapidefs.h b/include/gromox/mapidefs.h index 6e0c50707..dfbfaacb6 100644 --- a/include/gromox/mapidefs.h +++ b/include/gromox/mapidefs.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -867,6 +868,27 @@ struct DOUBLE_ARRAY { double *mval; }; +struct freebusy_event_details { + freebusy_event_details(char *, char *, char *, bool, bool, bool, bool, bool); + + char *id, *subject, *location; + bool is_meeting, is_recurring, is_exception, is_reminderset, is_private; +}; + +struct freebusy_event { + freebusy_event(time_t, time_t, uint32_t, char *, char *, char *, + bool, bool, bool, bool, bool, bool); + + time_t start_time, end_time; + uint32_t busy_status; + std::optional details; +}; + +struct FB_ARRAY { + uint32_t count; + freebusy_event *fb_events; +}; + /** * The host-endian view of struct GUID is often not needed, and so a plethora * of GUIDs exist as bytearrays/FLATUID, mostly when the consumer does not care diff --git a/include/gromox/zcore_client.hpp b/include/gromox/zcore_client.hpp index fb36d0c08..284ea70d8 100644 --- a/include/gromox/zcore_client.hpp +++ b/include/gromox/zcore_client.hpp @@ -105,5 +105,6 @@ ZCIDL(checksession, (GUID hsession)) ZCIDL(imtomessage2, (GUID session, uint32_t folder, uint32_t data_type, const char *im_data, IDLOUT LONG_ARRAY *msg_handles)) ZCIDL(essdn_to_username, (const char *essdn, IDLOUT char **username)) ZCIDL(logon_token, (const char *token, IDLOUT GUID *hsession)) +ZCIDL(getuserfreebusy, (GUID hsession, BINARY entryid, uint64_t starttime, uint64_t endtime, IDLOUT FB_ARRAY *fb_events)) #undef ZCIDL #undef IDLOUT diff --git a/include/gromox/zcore_rpc.hpp b/include/gromox/zcore_rpc.hpp index e62774fbd..c10adc3bc 100644 --- a/include/gromox/zcore_rpc.hpp +++ b/include/gromox/zcore_rpc.hpp @@ -100,6 +100,7 @@ enum class zcore_callid : uint8_t { imtomessage2 = 0x58, essdn_to_username = 0x59, logon_token = 0x5a, + getuserfreebusy = 0x5b, /* update exch/zcore/names.cpp! */ }; @@ -635,6 +636,13 @@ struct zcreq_essdn_to_username : public zcreq { char *essdn; }; +struct zcreq_getuserfreebusy : public zcreq { + GUID hsession; + BINARY entryid; + uint64_t starttime; + uint64_t endtime; +}; + struct zcresp { zcore_callid call_id; ec_error_t result; @@ -873,6 +881,10 @@ struct zcresp_essdn_to_username : public zcresp { char *username; }; +struct zcresp_getuserfreebusy : public zcresp { + FB_ARRAY fb_events; +}; + using zcresp_checksession = zcresp; using zcresp_configimport = zcresp; using zcresp_copyfolder = zcresp; diff --git a/lib/freebusy.cpp b/lib/freebusy.cpp new file mode 100644 index 000000000..31b858fb0 --- /dev/null +++ b/lib/freebusy.cpp @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 grommunio GmbH +// This file is part of Gromox. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace gromox; +namespace exmdb_client = exmdb_client_remote; + +freebusy_tags::freebusy_tags(const char *dir) +{ + static const PROPERTY_NAME propname_buff[] = { + {MNID_ID, PSETID_APPOINTMENT, PidLidAppointmentStartWhole}, + {MNID_ID, PSETID_APPOINTMENT, PidLidAppointmentEndWhole}, + {MNID_ID, PSETID_APPOINTMENT, PidLidBusyStatus}, + {MNID_ID, PSETID_APPOINTMENT, PidLidRecurring}, + {MNID_ID, PSETID_APPOINTMENT, PidLidAppointmentRecur}, + {MNID_ID, PSETID_APPOINTMENT, PidLidAppointmentSubType}, + {MNID_ID, PSETID_COMMON, PidLidPrivate}, + {MNID_ID, PSETID_APPOINTMENT, PidLidAppointmentStateFlags}, + {MNID_ID, PSETID_APPOINTMENT, PidLidClipEnd}, + {MNID_ID, PSETID_APPOINTMENT, PidLidLocation}, + {MNID_ID, PSETID_COMMON, PidLidReminderSet}, + {MNID_ID, PSETID_MEETING, PidLidGlobalObjectId}, + {MNID_ID, PSETID_APPOINTMENT, PidLidTimeZoneStruct}, + }; + + const PROPNAME_ARRAY propnames = {std::size(propname_buff), deconst(propname_buff)}; + PROPID_ARRAY ids; + if (exmdb_client::get_named_propids(dir, false, &propnames, &ids) && + ids.count == propnames.count) { + + apptstartwhole = PROP_TAG(PT_SYSTIME, ids.ppropid[0]); + apptendwhole = PROP_TAG(PT_SYSTIME, ids.ppropid[1]); + busystatus = PROP_TAG(PT_LONG, ids.ppropid[2]); + recurring = PROP_TAG(PT_BOOLEAN, ids.ppropid[3]); + apptrecur = PROP_TAG(PT_BINARY, ids.ppropid[4]); + apptsubtype = PROP_TAG(PT_BOOLEAN, ids.ppropid[5]); + private_flag = PROP_TAG(PT_BOOLEAN, ids.ppropid[6]); + apptstateflags = PROP_TAG(PT_LONG, ids.ppropid[7]); + clipend = PROP_TAG(PT_SYSTIME, ids.ppropid[8]); + location = PROP_TAG(PT_UNICODE, ids.ppropid[9]); + reminderset = PROP_TAG(PT_BOOLEAN, ids.ppropid[10]); + globalobjectid = PROP_TAG(PT_BINARY, ids.ppropid[11]); + timezonestruct = PROP_TAG(PT_BINARY, ids.ppropid[12]); + } +} + +freebusy_event_details::freebusy_event_details(char *ev_id, char *ev_subject, + char *ev_location, bool ev_meeting, bool ev_recurring, bool ev_exception, + bool ev_reminderset, bool ev_private) : + id(ev_id), subject(ev_subject), location(ev_location), + is_meeting(ev_meeting), is_recurring(ev_recurring), + is_exception(ev_exception), is_reminderset(ev_reminderset), + is_private(ev_private) {} + +freebusy_event::freebusy_event(time_t start, time_t end, uint32_t b_status, + char *ev_id, char *ev_subject, char *ev_location, + bool ev_meeting, bool ev_recurring, bool ev_exception, bool ev_reminderset, + bool ev_private, bool detailed) : + start_time(start), end_time(end), busy_status(b_status) +{ + if (!detailed) + return; + + details = std::make_optional(freebusy_event_details{ev_id, ev_subject, + ev_location, ev_meeting, ev_recurring, ev_exception, ev_reminderset, ev_private}); +} + +static bool fill_tzcom(ical_component &tzcom, const SYSTEMTIME &sys, int year, + int from_bias, int to_bias, bool dstmonth) +{ + std::string str; + if (!dstmonth) { + str = "16010101T000000"; + } else if (sys.year == 0) { + int day = ical_get_dayofmonth(year, sys.month, sys.day, sys.dayofweek); + str = fmt::format("{}", ICAL_TIME{year, sys.month, day, + sys.hour, sys.minute, sys.second}); + } else if (sys.year == 1) { + str = fmt::format("{}", ICAL_TIME{year, sys.month, sys.day, + sys.hour, sys.minute, sys.second}); + } else { + return false; + } + + int utc_offset = -from_bias; + tzcom.append_line("TZOFFSETFROM", fmt::format("{:+03}{:02}", + utc_offset / 60, abs(utc_offset) % 60)); + utc_offset = -to_bias; + tzcom.append_line("TZOFFSETTO", fmt::format("{:+03}{:02}", + utc_offset / 60, abs(utc_offset) % 60)); + tzcom.append_line("DTSTART", std::move(str)); + + if (!dstmonth) + return true; + if (sys.year == 0) { + auto &line = tzcom.append_line("RRULE"); + line.append_value("FREQ", "YEARLY"); + int order = sys.day == 5 ? -1 : sys.day; + auto dow = weekday_to_str(sys.dayofweek); + if (dow == nullptr) + return false; + line.append_value("BYDAY", fmt::format("{}{}", order, dow)); + line.append_value("BYMONTH", fmt::format("{}", sys.month)); + } else if (sys.year == 1) { + auto &line = tzcom.append_line("RRULE"); + line.append_value("FREQ", "YEARLY"); + line.append_value("BYMONTHDAY", fmt::format("{}", sys.day)); + line.append_value("BYMONTH", fmt::format("{}", sys.month)); + } + return true; +} + +static std::optional tz_to_vtimezone(int year, + const char *tzid, const TIMEZONESTRUCT &tz) +{ + std::optional com("VTIMEZONE"); + com->append_line("TZID", tzid); + + auto &stdtime = com->append_comp("STANDARD"); + int std_bias = tz.bias + tz.standardbias; + int dst_bias = tz.bias + tz.daylightbias; + if (!fill_tzcom(stdtime, tz.standarddate, year, dst_bias, std_bias, + tz.daylightdate.month)) + return std::nullopt; + if (tz.daylightdate.month == 0) + return com; + auto &dsttime = com->append_comp("DAYLIGHT"); + if (!fill_tzcom(dsttime, tz.daylightdate, year, std_bias, dst_bias, + tz.standarddate.month)) + return std::nullopt; + return com; +} + +static bool recurrencepattern_to_rrule(const ical_component *tzcom, + time_t start_whole, const APPOINTMENT_RECUR_PAT &apr, ICAL_RRULE *irrule) +{ + auto &rpat = apr.recur_pat; + ICAL_TIME itime; + ical_line line("RRULE"); + + switch (rpat.patterntype) { + case PATTERNTYPE_DAY: + line.append_value("FREQ", "DAILY"); + line.append_value("INTERVAL", fmt::format("{}", rpat.period / 1440)); + break; + case PATTERNTYPE_WEEK: { + line.append_value("FREQ", "WEEKLY"); + line.append_value("INTERVAL", fmt::format("{}", rpat.period)); + auto &val = line.append_value("BYDAY"); + for (unsigned int wd = 0; wd < 7; ++wd) + if (rpat.pts.weekrecur & (1 << wd)) + val.append_subval(weekday_to_str(wd)); + break; + } + case PATTERNTYPE_MONTH: + case PATTERNTYPE_HJMONTH: { + auto monthly = rpat.period % 12 != 0; + auto interval = rpat.period; + line.append_value("FREQ", monthly ? "MONTHLY" : "YEARLY"); + if (monthly) + interval /= 12; + line.append_value("INTERVAL", fmt::format("{}", interval)); + line.append_value("BYMONTHDAY", fmt::format("{}", + rpat.pts.dayofmonth == 31 ? -1 : rpat.pts.dayofmonth));//test what happens if making recurrence with day30/31 (rather than lastday) + if (monthly) + break; + ical_get_itime_from_yearday(1601, rpat.firstdatetime / 1440 + 1, &itime); + line.append_value("BYMONTH", fmt::format("{}", itime.month)); + break; + } + case PATTERNTYPE_MONTHNTH: + case PATTERNTYPE_HJMONTHNTH: { + auto monthly = rpat.period % 12 != 0; + auto interval = rpat.period; + line.append_value("FREQ", monthly ? "MONTHLY" : "YEARLY"); + if (monthly) + interval /= 12; + line.append_value("INTERVAL", fmt::format("{}", interval)); + auto &pts = rpat.pts; + auto &val = line.append_value("BYDAY"); + for (unsigned int wd = 0; wd < 7; ++wd) + if (pts.monthnth.weekrecur & (1 << wd)) + val.append_subval(weekday_to_str(wd)); + line.append_value("BYSETPOS", fmt::format("{}", + pts.monthnth.recurnum == 5 ? -1 : pts.monthnth.recurnum)); + if (monthly) + break; + line.append_value("BYMONTH", fmt::format("{}", rpat.firstdatetime)); + break; + } + default: + return false; + } + if (rpat.endtype == ENDTYPE_AFTER_N_OCCURRENCES) { + line.append_value("COUNT", fmt::format("{}", rpat.occurrencecount)); + } else if (rpat.endtype == ENDTYPE_AFTER_DATE) { + auto ut = rop_util_rtime_to_unix(rpat.enddate + apr.starttimeoffset); + ical_utc_to_datetime(tzcom, ut, &itime); + line.append_value("UNTIL", fmt::format("{}Z", itime)); + } + if (rpat.patterntype == PATTERNTYPE_WEEK) { + auto wd = weekday_to_str(rpat.firstdow); + if (wd == nullptr) + return false; + line.append_value("WKST", wd); + } + return ical_parse_rrule(tzcom, start_whole, &line.value_list, irrule); +} + +static bool find_recur_times(const ical_component *tzcom, + time_t start_whole, const APPOINTMENT_RECUR_PAT &apr, + time_t start_time, time_t end_time, std::vector &evlist) +{ + ICAL_RRULE irrule; + + if (!recurrencepattern_to_rrule(tzcom, start_whole, apr, &irrule)) + return false; + do { + auto itime = irrule.instance_itime; + time_t ut{}, utnz{}; + if (!ical_itime_to_utc(tzcom, itime, &ut)) + break; + if (ut < start_time) + continue; + if (!ical_itime_to_utc(nullptr, itime, &utnz)) + break; + auto &ei = apr.pexceptioninfo; + auto time_test = [&](const EXCEPTIONINFO &e) { + return rop_util_rtime_to_unix(e.originalstartdate) == utnz; + }; + if (std::any_of(&ei[0], &ei[apr.exceptioncount], time_test)) + continue; + evlist.push_back(event{ut, ut + (apr.endtimeoffset - apr.starttimeoffset) * 60}); + if (ut >= end_time) + break; + } while (irrule.iterate()); + for (unsigned int i = 0; i < apr.exceptioncount; ++i) { + auto ut = rop_util_rtime_to_unix(apr.pexceptioninfo[i].startdatetime); + ICAL_TIME itime; + if (!ical_utc_to_datetime(nullptr, ut, &itime) || + !ical_itime_to_utc(tzcom, itime, &ut) || + ut < start_time || ut > end_time) + continue; + event event = {ut}; + ut = rop_util_rtime_to_unix(apr.pexceptioninfo[i].enddatetime); + if (!ical_utc_to_datetime(nullptr, ut, &itime) || + !ical_itime_to_utc(tzcom, itime, &ut)) + continue; + event.end_time = ut; + event.ei = &apr.pexceptioninfo[i]; + event.xe = &apr.pextendedexception[i]; + evlist.push_back(std::move(event)); + } + return true; +} + +static int goid_to_icaluid2(BINARY *gobj, std::string &uid_buf) +{ + EXT_PUSH ext_push; + char guidbuf[16], ngidbuf[56]; + GLOBALOBJECTID ngid; + + if (gobj == nullptr) { + if (!ext_push.init(guidbuf, std::size(guidbuf), 0) || + ext_push.p_guid(GUID::random_new()) != EXT_ERR_SUCCESS) + return -EIO; + ngid.arrayid = EncodedGlobalId; + ngid.creationtime = rop_util_unix_to_nttime(time(nullptr)); + ngid.data.cb = 16; + ngid.data.pc = guidbuf; + uid_buf.resize(std::size(ngidbuf) * 2 + 1); + if (!ext_push.init(ngidbuf, std::size(ngidbuf), 0) || + ext_push.p_goid(ngid) != EXT_ERR_SUCCESS || + !encode_hex_binary(ngidbuf, ext_push.m_offset, + uid_buf.data(), uid_buf.size())) + return -EIO; + return 2; + } + + EXT_PULL ext_pull; + auto cl_0 = make_scope_exit([&]() { free(ngid.data.pc); }); + ext_pull.init(gobj->pb, gobj->cb, malloc, 0); + if (ext_pull.g_goid(&ngid) != EXT_ERR_SUCCESS) + return -EIO; + static_assert(sizeof(ThirdPartyGlobalId) == 12); + if (ngid.data.cb >= 12 && memcmp(ngid.data.pc, ThirdPartyGlobalId, 12) == 0) { + auto m = ngid.data.cb - 12; + if (m > 255) + m = 255;//check if this is the typical boundary(OXFB) + uid_buf.assign(&ngid.data.pc[12], m); + return 1; + } + + uid_buf.resize(56*2+1); + ngid.year = ngid.month = ngid.day = 0; + if (!ext_push.init(ngidbuf, std::size(ngidbuf), 0) || + ext_push.p_goid(ngid) != EXT_ERR_SUCCESS || + !encode_hex_binary(ngidbuf, ext_push.m_offset, uid_buf.data(), uid_buf.size())) + return -EIO; + return 2; +} + +static bool goid_to_icaluid(BINARY *gobj, std::string &uid_buf) +{ + auto ret = goid_to_icaluid2(gobj, uid_buf); + if (ret < 0) + return false; + if (ret == 2) + HX_strupper(uid_buf.data()); + return true; +} + +bool get_freebusy(const char *username, const char *dir, + uint64_t start_time, uint64_t end_time, std::vector& fb_data) +{ + uint32_t permission = 0; + auto cal_eid = rop_util_make_eid_ex(1, PRIVATE_FID_CALENDAR); + + if (username != nullptr) { + if (!exmdb_client::get_folder_perm(dir, cal_eid, username, &permission)) + return false; + if (!(permission & (frightsFreeBusySimple | frightsFreeBusyDetailed | frightsReadAny))) + return false; + } else { + permission = frightsFreeBusyDetailed | frightsReadAny; + } + + freebusy_tags ptag(dir); + auto start_nttime = rop_util_unix_to_nttime(start_time); + auto end_nttime = rop_util_unix_to_nttime(end_time); + bool detailed = permission & (frightsFreeBusyDetailed | frightsReadAny); + static constexpr uint8_t fixed_true = 1; + + /* C1: apptstartwhole >= start && apptstartwhole <= end */ + RESTRICTION_PROPERTY rst_1 = {RELOP_GE, ptag.apptstartwhole, {ptag.apptstartwhole, &start_nttime}}; + RESTRICTION_PROPERTY rst_2 = {RELOP_LE, ptag.apptstartwhole, {ptag.apptstartwhole, &end_nttime}}; + RESTRICTION rst_3[2] = {{RES_PROPERTY, {&rst_1}}, {RES_PROPERTY, {&rst_2}}}; + RESTRICTION_AND_OR rst_4 = {std::size(rst_3), rst_3}; + + /* C2: apptendwhole >= start && apptendwhole <= end */ + RESTRICTION_PROPERTY rst_5 = {RELOP_GE, ptag.apptendwhole, {ptag.apptendwhole, &start_nttime}}; + RESTRICTION_PROPERTY rst_6 = {RELOP_LE, ptag.apptendwhole, {ptag.apptendwhole, &end_nttime}}; + RESTRICTION rst_7[2] = {{RES_PROPERTY, {&rst_5}}, {RES_PROPERTY, {&rst_6}}}; + RESTRICTION_AND_OR rst_8 = {std::size(rst_7), rst_7}; + + /* C3: apptstartwhole < start && apptendwhole > end */ + RESTRICTION_PROPERTY rst_9 = {RELOP_LT, ptag.apptstartwhole, {ptag.apptstartwhole, &start_nttime}}; + RESTRICTION_PROPERTY rst_10 = {RELOP_GT, ptag.apptendwhole, {ptag.apptendwhole, &end_nttime}}; + RESTRICTION rst_11[2] = {{RES_PROPERTY, {&rst_9}}, {RES_PROPERTY, {&rst_10}}}; + RESTRICTION_AND_OR rst_12 = {std::size(rst_11), rst_11}; + + /* C4: have(clipend) && recurring && clipend >= start */ + RESTRICTION_EXIST rst_13 = {ptag.clipend}; + RESTRICTION_PROPERTY rst_14 = {RELOP_EQ, ptag.recurring, {ptag.recurring, deconst(&fixed_true)}}; + RESTRICTION_PROPERTY rst_15 = {RELOP_GE, ptag.clipend, {ptag.clipend, &start_nttime}}; + RESTRICTION rst_16[3] = {{RES_EXIST, {&rst_13}}, {RES_PROPERTY, {&rst_14}}, {RES_PROPERTY, {&rst_15}}}; + RESTRICTION_AND_OR rst_17 = {std::size(rst_16), rst_16}; + + /* C5: !have(clipend) && recurring && apptstartwhole <= end */ + RESTRICTION_EXIST rst_18 = {ptag.clipend}; + RESTRICTION rst_19 = {RES_EXIST, {&rst_18}}; + RESTRICTION_PROPERTY rst_20 = {RELOP_EQ, ptag.recurring, {ptag.recurring, deconst(&fixed_true)}}; + RESTRICTION_PROPERTY rst_21 = {RELOP_LE, ptag.apptstartwhole, {ptag.apptstartwhole, &end_nttime}}; + RESTRICTION rst_22[3] = {{RES_NOT, {&rst_19}}, {RES_PROPERTY, {&rst_20}}, {RES_PROPERTY, {&rst_21}}}; + RESTRICTION_AND_OR rst_23 = {std::size(rst_22), rst_22}; + + /* OR over C1-C5 */ + RESTRICTION rst_24[5] = {{RES_AND, {&rst_4}}, {RES_AND, {&rst_8}}, {RES_AND, {&rst_12}}, {RES_AND, {&rst_17}}, {RES_AND, {&rst_23}}}; + RESTRICTION_AND_OR rst_25 = {std::size(rst_24), rst_24}; + RESTRICTION rst_26 = {RES_OR, {&rst_25}}; + + uint32_t table_id = 0, row_count = 0; + if (!exmdb_client::load_content_table(dir, CP_ACP, cal_eid, nullptr, + TABLE_FLAG_NONOTIFICATIONS, &rst_26, nullptr, &table_id, &row_count)) + return false; + + auto cl_0 = make_scope_exit([&]() { exmdb_client::unload_table(dir, table_id);}); + + uint32_t proptag_buff[] = { + ptag.apptstartwhole, ptag.apptendwhole, ptag.busystatus, + ptag.recurring, ptag.apptrecur, ptag.apptsubtype, ptag.private_flag, + ptag.apptstateflags, ptag.location, ptag.reminderset, + ptag.globalobjectid, ptag.timezonestruct, PR_SUBJECT, + }; + const PROPTAG_ARRAY proptags = {std::size(proptag_buff), deconst(proptag_buff)}; + TARRAY_SET rows; + if (!exmdb_client::query_table(dir, nullptr, CP_ACP, table_id, + &proptags, 0, row_count, &rows)) + return false; + + for (size_t i = 0; i < rows.count; ++i) { + std::string uid_buf; + if (!goid_to_icaluid(rows.pparray[i]->get(ptag.globalobjectid), uid_buf)) + continue; + auto ts = rows.pparray[i]->get(ptag.apptstartwhole); + if (ts == nullptr) + continue; + auto start_whole = rop_util_nttime_to_unix(*ts); + ts = rows.pparray[i]->get(ptag.apptendwhole); + if (ts == nullptr) + continue; + auto end_whole = rop_util_nttime_to_unix(*ts); + auto subject = rows.pparray[i]->get(PR_SUBJECT); + auto location = rows.pparray[i]->get(ptag.location); + auto flag = rows.pparray[i]->get(ptag.reminderset); + bool is_reminder = flag != nullptr && *flag != 0; + flag = rows.pparray[i]->get(ptag.private_flag); + bool is_private = flag != nullptr && *flag != 0; + auto num = rows.pparray[i]->get(ptag.busystatus); + uint32_t busy_type = num == nullptr || *num > olWorkingElsewhere ? 0 : *num; + num = rows.pparray[i]->get(ptag.apptstateflags); + bool is_meeting = num != nullptr && *num & asfMeeting; + flag = rows.pparray[i]->get(ptag.recurring); + + // non-recurring appointments + if (flag == nullptr || *flag == 0) { + fb_data.emplace_back(start_whole, end_whole, busy_type, uid_buf.data(), + subject, location, is_meeting, false, false, is_reminder, is_private, detailed); + continue; + } + // recurring appointments + EXT_PULL ext_pull; + std::optional tzcom; + auto bin = rows.pparray[i]->get(ptag.timezonestruct); + if (bin != nullptr) { + TIMEZONESTRUCT tz; + ext_pull.init(bin->pb, bin->cb, exmdb_rpc_alloc, EXT_FLAG_UTF16); + if (ext_pull.g_tzstruct(&tz) != EXT_ERR_SUCCESS) + continue; + tzcom = tz_to_vtimezone(1600, "timezone", tz); + if (!tzcom.has_value()) + continue; + } + + bin = rows.pparray[i]->get(ptag.apptrecur); + if (bin == nullptr) + continue; + APPOINTMENT_RECUR_PAT apprecurr; + ext_pull.init(bin->pb, bin->cb, exmdb_rpc_alloc, EXT_FLAG_UTF16); + if (ext_pull.g_apptrecpat(&apprecurr) != EXT_ERR_SUCCESS) + continue; + + std::vector event_list; + if (!find_recur_times(tzcom.has_value() ? &*tzcom : nullptr, + start_whole, apprecurr, start_time, end_time, event_list)) + continue; + + for (const auto &event : event_list) { + if (event.ei == nullptr || event.xe == nullptr) { + fb_data.emplace_back(event.start_time, event.end_time, busy_type, + uid_buf.data(), subject, location, is_meeting, TRUE, false, + is_reminder, is_private, detailed); + continue; + } + + bool ov_meeting = (event.ei->overrideflags & ARO_MEETINGTYPE) ? event.ei->meetingtype & 1 : is_meeting; + bool ov_reminder = (event.ei->overrideflags & ARO_REMINDER) ? event.ei->reminderset == 0 : is_reminder; + uint32_t ov_busy = (event.ei->overrideflags & ARO_BUSYSTATUS) ? event.ei->busystatus : busy_type; + auto ov_subj = (event.ei->overrideflags & ARO_SUBJECT) ? event.xe->subject : subject; + auto ov_location = (event.ei->overrideflags & ARO_LOCATION) ? event.xe->location : location; + + fb_data.emplace_back(event.start_time, event.end_time, ov_busy, + uid_buf.data(), ov_subj, ov_location, ov_meeting, TRUE, TRUE, + ov_reminder, is_private, detailed); + } + } + + cl_0.release(); + if (!exmdb_client::unload_table(dir, table_id)) + return false; + + return true; +} \ No newline at end of file diff --git a/lib/mapi/ext_buffer.cpp b/lib/mapi/ext_buffer.cpp index d399727a5..6d0fead1b 100644 --- a/lib/mapi/ext_buffer.cpp +++ b/lib/mapi/ext_buffer.cpp @@ -2045,6 +2045,48 @@ pack_result EXT_PULL::g_msgctnt(MESSAGE_CONTENT *r) return EXT_ERR_SUCCESS; } +pack_result EXT_PULL::g_fb(freebusy_event *fb_event) +{ + TRY(g_int64(&fb_event->start_time)); + TRY(g_int64(&fb_event->end_time)); + TRY(g_uint32(&fb_event->busy_status)); + BOOL b; + TRY(g_bool(&b)); + + if (b) { + TRY(g_str(&fb_event->details->id)); + TRY(g_str(&fb_event->details->subject)); + TRY(g_bool(&b)); + if (b) + TRY(g_str(&fb_event->details->location)); + TRY(g_bool(&b)); fb_event->details->is_meeting = b; + TRY(g_bool(&b)); fb_event->details->is_recurring = b; + TRY(g_bool(&b)); fb_event->details->is_exception = b; + TRY(g_bool(&b)); fb_event->details->is_reminderset = b; + TRY(g_bool(&b)); fb_event->details->is_private = b; + } + + return EXT_ERR_SUCCESS; +} + +pack_result EXT_PULL::g_fb_a(FB_ARRAY *r) +{ + TRY(g_uint32(&r->count)); + if (r->count == 0) { + r->fb_events = NULL; + return EXT_ERR_SUCCESS; + } + r->fb_events = anew(r->count); + if (r->fb_events == nullptr) { + r->count = 0; + return EXT_ERR_ALLOC; + } + + for (size_t i = 0; i < r->count; ++i) + TRY(g_fb(&r->fb_events[i])); + return EXT_ERR_SUCCESS; +} + BOOL EXT_PUSH::init(void *pdata, uint32_t alloc_size, uint32_t flags, const EXT_BUFFER_MGT *mgt) { @@ -3432,6 +3474,27 @@ pack_result EXT_PUSH::p_msgctnt(const MESSAGE_CONTENT &r) } } +pack_result EXT_PUSH::p_fbevent(const freebusy_event &r) +{ + TRY(p_int64(r.start_time)); + TRY(p_int64(r.end_time)); + TRY(p_uint32(r.busy_status)); + TRY(p_bool(r.details.has_value())); + if (r.details.has_value()) { + TRY(p_str(r.details->id)); + TRY(p_str(r.details->subject)); + TRY(p_bool(r.details->location != nullptr)); + if (r.details->location != nullptr) + TRY(p_str(r.details->location)); + TRY(p_bool(r.details->is_meeting)); + TRY(p_bool(r.details->is_recurring)); + TRY(p_bool(r.details->is_exception)); + TRY(p_bool(r.details->is_reminderset)); + TRY(p_bool(r.details->is_private)); + } + return pack_result::ok; +} + uint8_t *EXT_PUSH::release() { auto p = this; diff --git a/php_mapi/mapi.cpp b/php_mapi/mapi.cpp index a40b2c0a7..5a301e7f2 100644 --- a/php_mapi/mapi.cpp +++ b/php_mapi/mapi.cpp @@ -3209,6 +3209,51 @@ static ZEND_FUNCTION(mapi_zarafa_setpermissionrules) RETVAL_TRUE; } +/* + This function will get user's freebusy data + + param session[in] session object + param entryid[in] user's entryid + param starttime unix time stamp + param endtime unix time stamp + return Array of user's freebusy data, + Array of events, empty array means not + found. fields: + starttime, endtime, busytype, id, subject, location, + rest are all bool(absence means false). meeting, recurring, + exception, reminderset, private +*/ +static ZEND_FUNCTION(mapi_getuserfreebusy) +{ + ZCL_MEMORY; + zend_long starttime, endtime; + BINARY entryid; + size_t eid_size = 0; + zval pzfbevents, *pzresource; + FB_ARRAY fb_events; + MAPI_RESOURCE *psession; + + ZVAL_NULL(&pzfbevents); + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rsll", + &pzresource, &entryid.pb, &eid_size, &starttime, &endtime) == FAILURE || + pzresource == nullptr || entryid.pb == nullptr || eid_size == 0) + pthrow(ecInvalidParam); + entryid.cb = eid_size; + ZEND_FETCH_RESOURCE(psession, MAPI_RESOURCE*, + &pzresource, -1, name_mapi_session, le_mapi_session); + if (psession->type != zs_objtype::session) + pthrow(ecInvalidObject); + auto result = zclient_getuserfreebusy(psession->hsession, entryid, starttime, + endtime, &fb_events); + if (result != ecSuccess) + pthrow(result); + if (auto err = fb_array_to_php(&fb_events, &pzfbevents); err != ecSuccess) + pthrow(err); + zarray_init(return_value); + add_assoc_zval(return_value, "fbevents", &pzfbevents); + MAPI_G(hr) = ecSuccess; +} + /* This function will get user's freebusy data @@ -4477,6 +4522,7 @@ static zend_function_entry mapi_functions[] = { F(mapi_decompressrtf) F(mapi_zarafa_getpermissionrules) F(mapi_zarafa_setpermissionrules) + F(mapi_getuserfreebusy) F(mapi_getuseravailability) F(mapi_exportchanges_config) F(mapi_exportchanges_synchronize) diff --git a/php_mapi/mapi.stub.php b/php_mapi/mapi.stub.php index da75a29f5..7ee25f310 100644 --- a/php_mapi/mapi.stub.php +++ b/php_mapi/mapi.stub.php @@ -81,6 +81,7 @@ function mapi_getidsfromnames(resource $store, array $names, ?array $guids = nul function mapi_decompressrtf(string $data) : string|false {} function mapi_zarafa_getpermissionrules(resource $any, int $type) : array|false {} function mapi_zarafa_setpermissionrules(resource $any, array $perms) : bool {} +function mapi_getuserfreebusy(resource $ses, string $entryid, int $start, int $end) : array|false {} function mapi_getuseravailability(resource $ses, string $entryid, int $start, int $end) : string|false {} function mapi_exportchanges_config(resource $e, resource $stream, int $flags, mixed $i, mixed $restrict, mixed $inclprop, mixed $exclprop, int $bufsize) : bool {} function mapi_exportchanges_synchronize(resource $x) : mixed {} diff --git a/php_mapi/mapi_arginfo.hpp b/php_mapi/mapi_arginfo.hpp index c934c75a5..f401f3358 100644 --- a/php_mapi/mapi_arginfo.hpp +++ b/php_mapi/mapi_arginfo.hpp @@ -416,6 +416,13 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_mapi_zarafa_setpermissionrules, ZEND_ARG_TYPE_INFO(0, perms, IS_ARRAY, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_mapi_getuserfreebusy, 0, 4, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, ses, resource, 0) + ZEND_ARG_TYPE_INFO(0, entryid, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, start, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, end, IS_LONG, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_mapi_getuseravailability, 0, 4, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_OBJ_INFO(0, ses, resource, 0) ZEND_ARG_TYPE_INFO(0, entryid, IS_STRING, 0) diff --git a/php_mapi/rpc_ext.cpp b/php_mapi/rpc_ext.cpp index 5bbe698ca..2d7a1a3eb 100644 --- a/php_mapi/rpc_ext.cpp +++ b/php_mapi/rpc_ext.cpp @@ -1064,6 +1064,21 @@ static pack_result zrpc_push(PUSH_CTX &x, const zcreq_vcftomessage &d) return pack_result::ok; } +static pack_result zrpc_push(PUSH_CTX &x, const zcreq_getuserfreebusy &d) +{ + TRY(x.p_guid(d.hsession)); + TRY(x.p_bin(d.entryid)); + TRY(x.p_uint64(d.starttime)); + TRY(x.p_uint64(d.endtime)); + return pack_result::ok; +} + +static pack_result zrpc_pull(PULL_CTX &x, zcresp_getuserfreebusy &d) +{ + TRY(x.g_fb_a(&d.fb_events)); + return pack_result::ok; +} + static pack_result zrpc_push(PUSH_CTX &x, const zcreq_getuseravailability &d) { TRY(x.p_guid(d.hsession)); @@ -1212,6 +1227,7 @@ pack_result rpc_ext_push_request(const zcreq *prequest, BINARY *pbin_out) E(imtomessage2) E(essdn_to_username) E(logon_token) + E(getuserfreebusy) #undef E default: return pack_result::bad_switch; @@ -1325,6 +1341,7 @@ pack_result rpc_ext_pull_response(const BINARY *pbin_in, zcresp *presponse) E(imtomessage2) E(essdn_to_username) E(logon_token) + E(getuserfreebusy) #undef E default: return pack_result::bad_switch; diff --git a/php_mapi/type_conversion.cpp b/php_mapi/type_conversion.cpp index 016b27159..179c1fb82 100644 --- a/php_mapi/type_conversion.cpp +++ b/php_mapi/type_conversion.cpp @@ -103,6 +103,31 @@ ec_error_t binary_array_to_php(const BINARY_ARRAY *pbins, zval *pzval) return ecSuccess; } +ec_error_t fb_array_to_php(const FB_ARRAY *pfbs, zval *pzval) +{ + zval pzvalfbevent; + zarray_init(pzval); + for (size_t i = 0; i < pfbs->count; ++i) { + zarray_init(&pzvalfbevent); + add_assoc_long(&pzvalfbevent, "start", pfbs->fb_events[i].start_time); + add_assoc_long(&pzvalfbevent, "end", pfbs->fb_events[i].end_time); + add_assoc_long(&pzvalfbevent, "busystatus", pfbs->fb_events[i].busy_status); + if (pfbs->fb_events[i].details->id != nullptr) + add_assoc_string(&pzvalfbevent, "id", pfbs->fb_events[i].details->id); + if (pfbs->fb_events[i].details->subject != nullptr) + add_assoc_string(&pzvalfbevent, "subject", pfbs->fb_events[i].details->subject); + if (pfbs->fb_events[i].details->location != nullptr) + add_assoc_string(&pzvalfbevent, "location", pfbs->fb_events[i].details->location); + add_assoc_bool(&pzvalfbevent, "meeting", pfbs->fb_events[i].details->is_meeting); + add_assoc_bool(&pzvalfbevent, "recurring", pfbs->fb_events[i].details->is_recurring); + add_assoc_bool(&pzvalfbevent, "exception", pfbs->fb_events[i].details->is_exception); + add_assoc_bool(&pzvalfbevent, "reminderset", pfbs->fb_events[i].details->is_reminderset); + add_assoc_bool(&pzvalfbevent, "private", pfbs->fb_events[i].details->is_private); + add_next_index_zval(pzval, &pzvalfbevent); + } + return ecSuccess; +} + ec_error_t php_to_sortorder_set(zval *pzval, SORTORDER_SET *pset) { unsigned long idx; @@ -300,7 +325,7 @@ static void *php_to_propval(zval *entry, uint16_t proptype) return NULL; } ZEND_HASH_FOREACH_VAL(pdata_hash, data_entry) { - xs->ps[j++] = zval_get_long(data_entry); + xs->ps[j++] = zval_get_long(entry); } ZEND_HASH_FOREACH_END(); break; } @@ -324,7 +349,7 @@ static void *php_to_propval(zval *entry, uint16_t proptype) return NULL; } ZEND_HASH_FOREACH_VAL(pdata_hash, data_entry) { - xl->pl[j++] = zval_get_long(data_entry); + xl->pl[j++] = zval_get_long(entry); } ZEND_HASH_FOREACH_END(); break; } diff --git a/php_mapi/type_conversion.hpp b/php_mapi/type_conversion.hpp index 1a30f0f6d..4579ebca2 100644 --- a/php_mapi/type_conversion.hpp +++ b/php_mapi/type_conversion.hpp @@ -27,6 +27,7 @@ extern ec_error_t state_array_to_php(const STATE_ARRAY *, zval *); extern ec_error_t php_to_state_array(zval *, STATE_ARRAY *); extern ec_error_t znotification_array_to_php(ZNOTIFICATION_ARRAY *, zval *); extern ec_error_t php_to_propname_array(zval *names, zval *guids, PROPNAME_ARRAY *); +extern ec_error_t fb_array_to_php(const FB_ARRAY *, zval *); /* Wrap this so cov-scan only complains once (hopefully) */ static inline void zarray_init(zval *x)