diff -urN ../z-push-2.6.0.alpha1-210/src/backend/caldav/caldav.php ./src/backend/caldav/caldav.php --- ../z-push-2.6.0.alpha1-210/src/backend/caldav/caldav.php 2023-03-17 22:44:44.870027000 +0200 +++ ./src/backend/caldav/caldav.php 2023-03-17 22:49:29.305359000 +0200 @@ -125,7 +125,6 @@ $folders = array(); $calendars = $this->_caldav->FindCalendars(); foreach ($calendars as $val) { - $folder = array(); $fpath = explode("/", $val->url, -1); if (is_array($fpath)) { $folderid = array_pop($fpath); @@ -216,7 +215,7 @@ $msgs = $this->_caldav->GetEventsList($begin, $finish, $path); } else { - $msgs = $this->_caldav->GetTodosList($begin, $finish, false, false, $path); + $msgs = $this->_caldav->GetTodosList(null, null, null, null, $path); } $messages = array(); @@ -335,11 +334,51 @@ } /** - * Move a message is not supported by CalDAV. + * Move a message by fetching it, trying to create a copy + * into another collection and deleting the original. * @see BackendDiff::MoveMessage() */ public function MoveMessage($folderid, $id, $newfolderid, $contentParameters) { - return false; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->MoveMessage('%s','%s','%s')", $folderid, $id, $newfolderid)); + + if ($folderid == $newfolderid) { + throw new StatusException(sprintf("BackendCalDAV->MoveMessage('%s','%s','%s'): Error, destination folder is source folder. Canceling the move.", $folderid, $id, $newfolderid), SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST); + } + + // get source message + $path = $this->_caldav_path . substr($folderid, 1) . "/"; + $href = $path . $id; + + $messages = $this->_caldav->CalendarMultiget( array( $href ), $path ); + $data = $messages[$href]; + + if (!isset($data) || $data == '') { + throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to retrieve source message.", $folderid, $id, $newfolderid), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); + } + unset($messages, $href); + + // create copy in destination folder with new id + $newId = sprintf("%s-%s.ics", gmdate("Ymd\THis\Z"), hash("md5", microtime())); + $path = sprintf("%s%s/%s", $this->_caldav_path, substr($newfolderid, 1), $newId); + $etag = "*"; + + $etag_new = $this->_caldav->DoPUTRequest($path, $data, $etag); + unset($data); + + if ($etag_new != null) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->MoveMessage(): Created message copy '%s' in destination folder.", $newId)); + + if($this->DeleteMessage($folderid, $id, $contentParameters)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->MoveMessage(): Deleted source message '%s', MoveMessage() complete.", $id)); + return $newId; + } + else { + throw new StatusException(sprintf("BackendCalDAV->MoveMessage('%s','%s','%s'): Error, deletion of source message failed, duplicates may exist.", $folderid, $id, $newfolderid), SYNC_MOVEITEMSSTATUS_SOURCEORDESTLOCKED); + } + } + else { + throw new StatusException(sprintf("BackendCalDAV->MoveMessage('%s','%s','%s'): Error, copy to destination folder failed.", $folderid, $id, $newfolderid), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); + } } /** @@ -484,14 +523,12 @@ if (CALDAV_SUPPORTS_SYNC) { if (count($response) > 0) { $changed = true; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->ChangesSink - Changes detected")); } } else { // If the numbers of events are different, we know for sure, there are changes if (count($response) != count($v)) { $changed = true; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->ChangesSink - Changes detected")); } else { // If the numbers of events are equals, we compare the biggest date @@ -510,14 +547,11 @@ $changed = true; } } - - if ($changed) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->ChangesSink - Changes detected")); - } } } if ($changed) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->ChangesSink - Changes detected")); $notifications[] = $k; } } @@ -601,9 +635,14 @@ * @param int $truncsize */ private function _ParseVEventToSyncObject($event, $message, $truncsize) { - //Defaults - $message->busystatus = "2"; + // get user details + $userDetails = ZPush::GetBackend()->GetCurrentUsername(); + + // track vevent involvement + $userIsAttendee = false; + $userIsOrganizer = false; + $properties = $event->GetProperties(); foreach ($properties as $property) { switch ($property->Name()) { @@ -624,12 +663,21 @@ break; case "ORGANIZER": - $org_mail = str_ireplace("MAILTO:", "", $property->Value()); - $message->organizeremail = $org_mail; - $org_cn = $property->GetParameterValue("CN"); - if ($org_cn) { - $message->organizername = $org_cn; + $organizerEMail = str_ireplace("MAILTO:", "", $property->Value()); + if ($organizerEMail) { + $message->organizeremail = $organizerEMail; + if (strcasecmp($organizerEMail, $userDetails['emailaddress']) == 0) { + $userIsOrganizer = true; + } } + + $organizerName = $property->GetParameterValue("CN"); + if ($organizerName) { + $message->organizername = $organizerName; + if (strcasecmp($organizerName, $userDetails['fullname']) == 0) { + $userIsOrganizer = true; + } + } break; case "LOCATION": @@ -680,6 +728,7 @@ } break; + case "X-MICROSOFT-CDO-BUSYSTATUS": case "X-MICROSOFT-CDO-INTENDEDSTATUS": switch ($property->Value()) { case "FREE": @@ -714,25 +763,73 @@ case "STATUS": switch ($property->Value()) { case "TENTATIVE": - $message->meetingstatus = "3"; // was 1 - break; case "CONFIRMED": - $message->meetingstatus = "1"; // was 3 + $message->meetingstatus = "1"; break; case "CANCELLED": - $message->meetingstatus = "5"; // could also be 7 + $message->meetingstatus = "5"; break; } break; case "ATTENDEE": $attendee = new SyncAttendee(); - $att_email = str_ireplace("MAILTO:", "", $property->Value()); - $attendee->email = $att_email; - $att_cn = $property->GetParameterValue("CN"); - if ($att_cn) { - $attendee->name = $att_cn; + + $attendeeEMail = str_ireplace("MAILTO:", "", $property->Value()); + if ($attendeeEMail) { + $attendee->email = $attendeeEMail; + if (strcasecmp($attendeeEMail, $userDetails['emailaddress']) == 0) { + $userIsAttendee = true; + } } + + $attendeeName = $property->GetParameterValue("CN"); + if ($attendeeName) { + $attendee->name = $attendeeName; + if (strcasecmp($attendeeName, $userDetails['fullname']) == 0) { + $userIsAttendee = true; + } + } + + if (Request::GetProtocolVersion() >= 12.0) { + // attendeestatus: 0 = response unknown, 2 = tentative, 3 = accept, 4 = decline, 5 = not responded + $attendeeStatus = $property->GetParameterValue("PARTSTAT"); + if ($attendeeStatus) { + switch ($attendeeStatus) { + case "TENTATIVE": + $attendee->attendeestatus = "2"; + break; + case "ACCEPTED": + $attendee->attendeestatus = "3"; + break; + case "DECLINED": + $attendee->attendeestatus = "4"; + break; + case "NEEDS-ACTION": + $attendee->attendeestatus = "5"; + break; + } + } + + // attendeetype: 1 = required, 2 = optional, 3 = resource + $attendeeType = $property->GetParameterValue("ROLE"); + if ($attendeeType) { + switch ($attendeeType) { + case "REQ-PARTICIPANT": + $attendee->attendeetype = "1"; + break; + case "OPT-PARTICIPANT": + $attendee->attendeetype = "2"; + break; + case "NON-PARTICIPANT": + $attendee->attendeetype = "3"; + break; + case "CHAIR": + break; + } + } + } + if (isset($message->attendees) && is_array($message->attendees)) { $message->attendees[] = $attendee; } @@ -746,7 +843,7 @@ $message->asbody = new SyncBaseBody(); // the DESCRIPTION component is specified to be plain text (RFC5545), for HTML use X-ALT-DESC - $data = str_replace("\n","\r\n", str_replace("\r","",Utils::ConvertHtmlToText($property->Value()))); + $data = str_replace("\n", "\r\n", str_replace("\r", "", $property->Value())); // truncate body, if requested if (strlen($data) > $truncsize) { @@ -779,22 +876,46 @@ break; case "CATEGORIES": - $categories = explode(",", $property->Value()); - $message->categories = $categories; + $categories = $this->explodeUnescapedDelimiter(",", $property->Value()); + if (!isset($message->categories)) { + $message->categories = $this->unescape($categories); + } + else { + foreach($categories as $category) { + $message->categories[] = $this->unescape($category); + } + } break; case "EXDATE": - $exception = new SyncAppointmentException(); - $exception->deleted = "1"; - $exception->exceptionstarttime = TimezoneUtil::MakeUTCDate($property->Value()); + $exceptionDateTimes = explode(",", $property->Value()); if (!isset($message->exceptions)) { $message->exceptions = array(); } - $message->exceptions[] = $exception; + foreach($exceptionDateTimes as $exceptionDateTime) { + $exception = new SyncAppointmentException(); + $exception->deleted = "1"; + $exception->exceptionstarttime = TimezoneUtil::MakeUTCDate($exceptionDateTime); + $message->exceptions[] = $exception; + } break; + case "X-MICROSOFT-DISALLOW-COUNTER": + if (Request::GetProtocolVersion() >= 14.0) { + switch ($property->Value()) { + case "TRUE": + $message->disallownewtimeproposal = true; + break; + case "FALSE": + $message->disallownewtimeproposal = false; + break; + } + } + break; + //We can ignore the following case "PRIORITY": + case "X-MICROSOFT-CDO-IMPORTANCE": case "SEQUENCE": case "CREATED": case "DTSTAMP": @@ -809,14 +930,33 @@ } } - if ($message->meetingstatus > 0) { - // No organizer was set for the meeting, assume it is the user + // default busystatus + if (!isset($message->busystatus)) { + $message->busystatus = "2"; + } + + // meeting without attendee is an appointment + if (!isset($message->attendees)) { + $message->meetingstatus = 0; + } + + // meeting with attendee + if ($message->meetingstatus > 0 || !isset($message->meetingstatus)) { + // set meetingstatus if not already set + $message->meetingstatus = isset($message->meetingstatus) ? $message->meetingstatus : 1; + + // check if meeting has an organizer set, otherwise fallback to current user if (!isset($message->organizeremail)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_ParseVEventToSyncObject(): No organizeremail defined, using user details")); - $userDetails = ZPush::GetBackend()->GetCurrentUsername(); $message->organizeremail = $userDetails['emailaddress']; $message->organizername = $userDetails['fullname']; } + // check if user is not organizer but attendee to detect received meeting + elseif ($userIsAttendee && !$userIsOrganizer) { + // apply received flag + $message->meetingstatus |= 0x2; + } + // Ensure the organizer name is set if (!isset($message->organizername)) { $message->organizername = Utils::GetLocalPartFromEmail($message->organizeremail); @@ -833,12 +973,15 @@ $trigger = date_create("@" . TimezoneUtil::MakeUTCDate($property->Value())); $begin = date_create("@" . $message->starttime); $interval = date_diff($begin, $trigger); - $message->reminder = $interval->format("%i") + $interval->format("%h") * 60 + $interval->format("%a") * 60 * 24; + $message->reminder = $interval->format("%i") + $interval->format("%h") * 60 + intval($interval->format("%a")) * 60 * 24; } elseif (!array_key_exists("VALUE", $parameters) || $parameters["VALUE"] == "DURATION") { - $val = str_replace("-", "", $property->Value()); - $interval = new DateInterval($val); - $message->reminder = $interval->format("%i") + $interval->format("%h") * 60 + $interval->format("%a") * 60 * 24; + $val = $property->Value(); + // only use negative values, because reminder = minutes before calendar item start + if ($val[0] == '-') { + $interval = new DateInterval(substr($val, 1)); + $message->reminder = $interval->format("%i") + $interval->format("%h") * 60 + $interval->format("%d") * 60 * 24; + } } } } @@ -1010,39 +1153,73 @@ $vevent->AddProperty("UID", $id); $ical->AddComponent($vevent); if (isset($data->exceptions) && is_array($data->exceptions)) { - foreach ($data->exceptions as $ex) { - if (isset($ex->deleted) && $ex->deleted == "1") { + foreach ($data->exceptions as $exception) { + if (isset($exception->deleted) && $exception->deleted == "1") { if ($exdate = $vevent->GetPValue("EXDATE")) { if ($data->alldayevent == 1) { - $vevent->SetPValue("EXDATE", $exdate.",".$this->_GetDateFromUTC("Ymd", $ex->exceptionstarttime, $data->timezone), array("VALUE" => "DATE")); + $vevent->SetPValue("EXDATE", $exdate.",".$this->_GetDateFromUTC("Ymd", $exception->exceptionstarttime, $data->timezone), array("VALUE" => "DATE")); } else { - $vevent->SetPValue("EXDATE", $exdate.",".$this->DAVDateTimeInTimezone($ex->exceptionstarttime, $tzid), $tzpar); + $vevent->SetPValue("EXDATE", $exdate.",".$this->DAVDateTimeInTimezone($exception->exceptionstarttime, $tzid), $tzpar); } } else { if ($data->alldayevent == 1) { - $vevent->AddProperty("EXDATE", $this->_GetDateFromUTC("Ymd", $ex->exceptionstarttime, $data->timezone), array("VALUE" => "DATE")); + $vevent->AddProperty("EXDATE", $this->_GetDateFromUTC("Ymd", $exception->exceptionstarttime, $data->timezone), array("VALUE" => "DATE")); } else { - $vevent->AddProperty("EXDATE", $this->DAVDateTimeInTimezone($ex->exceptionstarttime, $tzid), $tzpar); + $vevent->AddProperty("EXDATE", $this->DAVDateTimeInTimezone($exception->exceptionstarttime, $tzid), $tzpar); } } continue; } - $exception = $this->_ParseASEventToVEvent($ex, $id, $tzid, $tzpar); + // If an element is not specified in the exception element, the element inherits from the top-level element. + if (isset($data->busystatus) && !isset($exception->busystatus)) { + $exception->busystatus = $data->busystatus; + } + if (isset($data->meetingstatus) && !isset($exception->meetingstatus)) { + $exception->meetingstatus = $data->meetingstatus; + } + if (isset($data->dtstamp) && !isset($exception->dtstamp)) { + $exception->dtstamp = $data->dtstamp; + } + if (isset($data->starttime) && !isset($exception->starttime)) { + $exception->starttime = $data->starttime; + } + if (isset($data->endtime) && !isset($exception->endtime)) { + $exception->endtime = $data->endtime; + } + if (isset($data->location) && !isset($exception->location)) { + $exception->location = $data->location; + } + if (isset($data->subject) && !isset($exception->subject)) { + $exception->subject = $data->subject; + } + if (isset($data->sensitivity) && !isset($exception->sensitivity)) { + $exception->sensitivity = $data->sensitivity; + } + if (isset($data->reminder) && !isset($exception->reminder)) { + $exception->reminder = $data->reminder; + } + if (isset($data->attendees) && !isset($exception->attendees)) { + $exception->attendees = $data->attendees; + } + + $exceptionVEvent = $this->_ParseASEventToVEvent($exception, $id, $tzid, $tzpar); + if ($data->alldayevent == 1) { - $exception->AddProperty("RECURRENCE-ID", $this->_GetDateFromUTC("Ymd", $ex->exceptionstarttime, $data->timezone), array("VALUE" => "DATE")); + $exceptionVEvent->AddProperty("RECURRENCE-ID", $this->_GetDateFromUTC("Ymd", $exception->exceptionstarttime, $data->timezone), array("VALUE" => "DATE")); } else { - $exception->AddProperty("RECURRENCE-ID", $this->DAVDateTimeInTimezone($ex->exceptionstarttime, $tzid), $tzpar); + $exceptionVEvent->AddProperty("RECURRENCE-ID", $this->DAVDateTimeInTimezone($exception->exceptionstarttime, $tzid), $tzpar); } - $exception->AddProperty("UID", $id); - $ical->AddComponent($exception); + $exceptionVEvent->AddProperty("UID", $id); + $ical->AddComponent($exceptionVEvent); } } } + if ($folderid[0] == "T") { $vtodo = $this->_ParseASTaskToVTodo($data, $id); $vtodo->AddProperty("UID", $id); @@ -1112,30 +1289,6 @@ break; } } - if (isset($data->busystatus)) { - switch ($data->busystatus) { - case "0": //Free - $vevent->AddProperty("TRANSP", "TRANSPARENT"); - $vevent->AddProperty("X-MICROSOFT-CDO-INTENDEDSTATUS", "FREE"); - break; - case "1": //Tentative - $vevent->AddProperty("TRANSP", "OPAQUE"); - $vevent->AddProperty("X-MICROSOFT-CDO-INTENDEDSTATUS", "TENTATIVE"); - break; - case "2": //Busy - $vevent->AddProperty("TRANSP", "OPAQUE"); - $vevent->AddProperty("X-MICROSOFT-CDO-INTENDEDSTATUS", "BUSY"); - break; - case "3": //Out of office - $vevent->AddProperty("TRANSP", "TRANSPARENT"); - $vevent->AddProperty("X-MICROSOFT-CDO-INTENDEDSTATUS", "OOF"); - break; - case "4": //Working elsewhere (not yet in Android) - $vevent->AddProperty("TRANSP", "TRANSPARENT"); - $vevent->AddProperty("X-MICROSOFT-CDO-INTENDEDSTATUS", "WORKINGELSEWHERE"); - break; - } - } if (isset($data->reminder)) { $valarm = new iCalComponent(); $valarm->SetType("VALARM"); @@ -1151,25 +1304,75 @@ $rtfparser->parse(); $vevent->AddProperty("DESCRIPTION", $rtfparser->out); } - if (isset($data->meetingstatus) && $data->meetingstatus > 0) { - switch ($data->meetingstatus) { - case "1": - $vevent->AddProperty("STATUS", "TENTATIVE"); + if (isset($data->busystatus) && ((isset($data->meetingstatus) && $data->meetingstatus == 0) || !isset($data->meetingstatus))) { + switch ($data->busystatus) { + case "0": //Free + $vevent->AddProperty("TRANSP", "TRANSPARENT"); + $vevent->AddProperty("X-MICROSOFT-CDO-BUSYSTATUS", "FREE"); + break; + case "1": //Tentative + $vevent->AddProperty("TRANSP", "OPAQUE"); $vevent->AddProperty("X-MICROSOFT-CDO-BUSYSTATUS", "TENTATIVE"); - $vevent->AddProperty("X-MICROSOFT-DISALLOW-COUNTER", "FALSE"); break; - case "3": - $vevent->AddProperty("STATUS", "CONFIRMED"); - $vevent->AddProperty("X-MICROSOFT-CDO-BUSYSTATUS", "CONFIRMED"); - $vevent->AddProperty("X-MICROSOFT-DISALLOW-COUNTER", "FALSE"); + case "2": //Busy + $vevent->AddProperty("TRANSP", "OPAQUE"); + $vevent->AddProperty("X-MICROSOFT-CDO-BUSYSTATUS", "BUSY"); break; - case "5": - case "7": - $vevent->AddProperty("STATUS", "CANCELLED"); - $vevent->AddProperty("X-MICROSOFT-CDO-BUSYSTATUS", "CANCELLED"); - $vevent->AddProperty("X-MICROSOFT-DISALLOW-COUNTER", "TRUE"); + case "3": //Out of office + $vevent->AddProperty("TRANSP", "OPAQUE"); + $vevent->AddProperty("X-MICROSOFT-CDO-BUSYSTATUS", "OOF"); break; + case "4": //Working elsewhere (not yet in Android) but not defined in [MS-OXCICAL] + $vevent->AddProperty("TRANSP", "TRANSPARENT"); + $vevent->AddProperty("X-MICROSOFT-CDO-BUSYSTATUS", "WORKINGELSEWHERE"); + break; } + } + elseif (isset($data->meetingstatus) && $data->meetingstatus > 0) { + if (isset($data->busystatus)) { + switch ($data->busystatus) { + case "0": //Free + $vevent->AddProperty("TRANSP", "TRANSPARENT"); + $vevent->AddProperty("X-MICROSOFT-CDO-INTENDEDSTATUS", "FREE"); + break; + case "1": //Tentative + $vevent->AddProperty("TRANSP", "OPAQUE"); + $vevent->AddProperty("X-MICROSOFT-CDO-INTENDEDSTATUS", "TENTATIVE"); + break; + case "2": //Busy + $vevent->AddProperty("TRANSP", "OPAQUE"); + $vevent->AddProperty("X-MICROSOFT-CDO-INTENDEDSTATUS", "BUSY"); + break; + case "3": //Out of office + $vevent->AddProperty("TRANSP", "OPAQUE"); + $vevent->AddProperty("X-MICROSOFT-CDO-INTENDEDSTATUS", "OOF"); + break; + case "4": //Working elsewhere (not yet in Android) but not defined in [MS-OXCICAL] + $vevent->AddProperty("TRANSP", "TRANSPARENT"); + $vevent->AddProperty("X-MICROSOFT-CDO-INTENDEDSTATUS", "WORKINGELSEWHERE"); + break; + } + } + if (isset($data->disallownewtimeproposal) && $data->disallownewtimeproposal == true) { + $vevent->AddProperty("X-MICROSOFT-DISALLOW-COUNTER", "TRUE"); + } + else { + $vevent->AddProperty("X-MICROSOFT-DISALLOW-COUNTER", "FALSE"); + } + if ($data->meetingstatus == 1 || $data->meetingstatus == 3) { + if (isset($data->busystatus)) { + if ($data->busystatus == 1) { + $vevent->AddProperty("STATUS", "TENTATIVE"); + } + elseif ($data->busystatus == 2 || $data->busystatus == 3) { + $vevent->AddProperty("STATUS", "CONFIRMED"); + } + } + } + elseif ($data->meetingstatus == 5 || $data->meetingstatus == 7) { + $vevent->AddProperty("STATUS", "CANCELLED"); + $vevent->AddProperty("X-MICROSOFT-DISALLOW-COUNTER", "TRUE"); + } if (isset($data->organizeremail) && isset($data->organizername)) { $vevent->AddProperty("ORGANIZER", sprintf("MAILTO:%s", $data->organizeremail), array("CN" => $data->organizername)); } @@ -1182,12 +1385,48 @@ $vevent->AddProperty("ORGANIZER", sprintf("MAILTO:%s", $userDetails['emailaddress']), array("CN" => $userDetails['fullname'])); } if (isset($data->attendees) && is_array($data->attendees)) { - foreach ($data->attendees as $att) { - if (isset($att->name)) { - $vevent->AddProperty("ATTENDEE", sprintf("MAILTO:%s", $att->email), array("CN" => $att->name)); + foreach ($data->attendees as $attendee) { + $attendeeParameters = array(); + if (isset($attendee->name)) { + $attendeeParameters += array("CN" => $attendee->name); } + if (isset($attendee->attendeetype)) { + switch($attendee->attendeetype) { + case "1": + $attendeeParameters += array("ROLE" => "REQ-PARTICIPANT"); + break; + case "2": + $attendeeParameters += array("ROLE" => "OPT-PARTICIPANT"); + break; + case "3": + $attendeeParameters += array("ROLE" => "NON-PARTICIPANT"); + break; + } + } + if (isset($attendee->attendeestatus)) { + switch($attendee->attendeestatus) { + case "0": + $attendeeParameters += array("PARTSTAT" => "NEEDS-ACTION"); + break; + case "2": + $attendeeParameters += array("PARTSTAT" => "TENTATIVE"); + break; + case "3": + $attendeeParameters += array("PARTSTAT" => "ACCEPTED"); + break; + case "4": + $attendeeParameters += array("PARTSTAT" => "DECLINED"); + break; + case "5": + $attendeeParameters += array("PARTSTAT" => "NEEDS-ACTION"); + break; + } + } + if (!empty($attendeeParameters)) { + $vevent->AddProperty("ATTENDEE", sprintf("MAILTO:%s", $attendee->email), $attendeeParameters); + } else { - $vevent->AddProperty("ATTENDEE", sprintf("MAILTO:%s", $att->email)); + $vevent->AddProperty("ATTENDEE", sprintf("MAILTO:%s", $attendee->email)); } } } @@ -1202,13 +1441,13 @@ } } if (isset($data->categories) && is_array($data->categories)) { - $vevent->AddProperty("CATEGORIES", implode(",", $data->categories)); + $vevent->AddProperty("CATEGORIES", implode(",", $this->escape($data->categories))); } -// X-MICROSOFT-CDO-APPT-SEQUENCE:0 -// X-MICROSOFT-CDO-OWNERAPPTID:2113393086 -// X-MICROSOFT-CDO-IMPORTANCE:1 -// X-MICROSOFT-CDO-INSTTYPE:0 + // X-MICROSOFT-CDO-APPT-SEQUENCE:0 + // X-MICROSOFT-CDO-OWNERAPPTID:2113393086 + // X-MICROSOFT-CDO-IMPORTANCE:1 + // X-MICROSOFT-CDO-INSTTYPE:0 return $vevent; @@ -1389,12 +1628,18 @@ case "PRIORITY": $priority = $property->Value(); - if ($priority <= 3) - $message->importance = "0"; - if ($priority <= 6) + // MS-ASTASK: 0 = low, 1 = normal (default), 2 = high + // RFC5545: 0 = undefined, 1-4 = high, 5 = normal, 6-9 = low + // or 0 = undefined, 1-3 = high, 4-6 = normal, 7-9 = low + if ($priority == 0 || $priority == 5) { $message->importance = "1"; - if ($priority > 6) + } + elseif ($priority > 0 && $priority < 5) { $message->importance = "2"; + } + elseif ($priority > 5 && $priority <= 9) { + $message->importance = "0"; + } break; case "RRULE": @@ -1419,13 +1664,53 @@ $message->utcstartdate = TimezoneUtil::MakeUTCDate($property->Value()); break; - case "SUMMARY": - $message->subject = $property->Value(); + case "DESCRIPTION": + if (Request::GetProtocolVersion() >= 12.0) { + $message->asbody = new SyncBaseBody(); + + // the DESCRIPTION component is specified to be plain text (RFC5545), for HTML use X-ALT-DESC + $data = str_replace("\n", "\r\n", str_replace("\r", "", $property->Value())); + + // truncate body, if requested + if (strlen($data) > $truncsize) { + $message->asbody->truncated = 1; + $data = Utils::Utf8_truncate($data, $truncsize); + } + else { + $message->asbody->truncated = 0; + } + $message->asbody->data = StringStreamWrapper::Open($data); + $message->asbody->estimatedDataSize = strlen($data); + unset($data); + + // set body type accordingly + $message->asbody->type = SYNC_BODYPREFERENCE_PLAIN; + $message->nativebodytype = SYNC_BODYPREFERENCE_PLAIN; + } + else { + $body = $property->Value(); + // truncate body, if requested + if(strlen($body) > $truncsize) { + $body = Utils::Utf8_truncate($body, $truncsize); + $message->bodytruncated = 1; + } else { + $message->bodytruncated = 0; + } + $body = str_replace("\n","\r\n", str_replace("\r","",$body)); + $message->body = $body; + } break; case "CATEGORIES": - $categories = explode(",", $property->Value()); - $message->categories = $categories; + $categories = $this->explodeUnescapedDelimiter(",", $property->Value()); + if (!isset($message->categories)) { + $message->categories = $this->unescape($categories); + } + else { + foreach($categories as $category) { + $message->categories[] = $this->unescape($category); + } + } break; } } @@ -1497,16 +1782,21 @@ $vtodo->AddProperty("DUE", gmdate("Ymd\THis\Z", $data->utcduedate)); } if (isset($data->importance)) { - if ($data->importance == "1") { - $vtodo->AddProperty("PRIORITY", 6); + switch ($data->importance) { + case "0": + $vtodo->AddProperty("PRIORITY", 9); + break; + case "1": + $vtodo->AddProperty("PRIORITY", 5); + break; + case "2": + $vtodo->AddProperty("PRIORITY", 1); + break; } - elseif ($data->importance == "2") { - $vtodo->AddProperty("PRIORITY", 9); - } - else { - $vtodo->AddProperty("PRIORITY", 1); - } } + else { + $vtodo->AddProperty("PRIORITY", 0); + } if (isset($data->recurrence)) { $vtodo->AddProperty("RRULE", $this->_GenerateRecurrence($data->recurrence, false)); } @@ -1546,7 +1836,7 @@ $vtodo->AddProperty("DESCRIPTION", $rtfparser->out); } if (isset($data->categories) && is_array($data->categories)) { - $vtodo->AddProperty("CATEGORIES", implode(",", $data->categories)); + $vtodo->AddProperty("CATEGORIES", implode(",", $this->escape($data->categories))); } return $vtodo; @@ -1838,6 +2128,76 @@ $offset = abs($phpoffset); $hours = floor($offset / 3600); return sprintf("$prefix%'.02d%'.02d", $hours, ($offset - ($hours * 3600)) / 60); + } + + /** + * Escape string according to RFC5545 (3.3.11. TEXT) + * @param string|array $data string or array of strings to be escaped + * @access private + * @return string|array + */ + private function escape($data) { + if (is_array($data)) { + foreach ($data as $key => $value) { + $data[$key] = $this->escape($value); + } + return $data; + } + + $data = str_replace(array('\\', ';', ',', "\n", "\N", "\r"), array('\\\\', '\\;', '\\,', '\\n', '\\n'), $data); + return $data; + } + + /** + * Un-escape string according to RFC5545 (3.3.11. TEXT) + * @param string $data string to be un-escaped + * @access private + * @return string + */ + private function unescape($data) { + $data = str_replace(array('\\\\', '\\;', '\\,', '\\n','\\N'),array('\\', ';', ',', "\n", "\n"),$data); + return $data; + } + + /** + * Explode string only on unescaped delimiter, this function does not regard quoted parts + * @param string $delimiter delimiter + * @param string $data string to be exploded + * @access private + * @return array + */ + private function explodeUnescapedDelimiter($delimiter, $data, $escapeCharacter = '\\') { + $length = strlen($data); + $escaped = false; + $result = array(); + $temp = ''; + + for ($position = 0; $position < $length; $position++) { + // use boolean switching to detect escaped delimiters + if ($data[$position] == $escapeCharacter) { + $escaped = !$escaped; + } + elseif ($escaped == false && $data[$position] == $delimiter) { + // only add non empty strings to result + if (strlen($temp) > 0) { + $result[] = $temp; + } + $temp = ''; + continue; + } + else { + $escaped = false; + } + + $temp .= $data[$position]; + } + + //append last part if available + if (strlen($temp) > 0) { + $result[] = $temp; + } + + return $result; } }; diff -urN ../z-push-2.6.0.alpha1-210/src/backend/combined/combined.php ./src/backend/combined/combined.php --- ../z-push-2.6.0.alpha1-210/src/backend/combined/combined.php 2023-03-17 22:44:44.871492000 +0200 +++ ./src/backend/combined/combined.php 2023-03-17 22:49:29.306420000 +0200 @@ -438,6 +438,7 @@ * @access public * @return array */ + public function ChangesSink($timeout = 30) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->ChangesSink(%d)", $timeout)); @@ -445,17 +446,25 @@ if ($this->numberChangesSink == 0) { ZLog::Write(LOGLEVEL_DEBUG, "BackendCombined doesn't include any Sinkable backends"); } else { - $time_each = $timeout / $this->numberChangesSink; + $stopat = time() + $timeout - 1; + foreach ($this->backends as $i => $b) { if ($this->backends[$i]->HasChangesSink()) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->ChangesSink - Calling in '%s' with %d", get_class($b), $time_each)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->ChangesSink - Calling in '%s'", get_class($b))); - $notifications_backend = $this->backends[$i]->ChangesSink($time_each); - //preppend backend delimiter + $notifications_backend = $this->backends[$i]->ChangesSink(1); + // prepend backend delimiter for ($c = 0; $c < count($notifications_backend); $c++) { $notifications_backend[$c] = $i . $this->config['delimiter'] . $notifications_backend[$c]; } $notifications = array_merge($notifications, $notifications_backend); + } + } + + // If nothing changed, wait until timeout + if (empty($notifications)) { + while ($stopat > time()) { + sleep(1); } } } diff -urN ../z-push-2.6.0.alpha1-210/src/backend/imap/imap.php ./src/backend/imap/imap.php --- ../z-push-2.6.0.alpha1-210/src/backend/imap/imap.php 2023-03-17 22:44:44.876105000 +0200 +++ ./src/backend/imap/imap.php 2023-03-17 22:49:29.311698000 +0200 @@ -251,6 +251,10 @@ $message->headers["cc"] = Utils::CheckAndFixEncodingInHeadersOfSentMail($Mail_RFC822->parseAddressList($message->headers["cc"], null, null, false, null)); } + if (isset($message->headers["bcc"])) { + $message->headers["bcc"] = Utils::CheckAndFixEncodingInHeadersOfSentMail($Mail_RFC822->parseAddressList($message->headers["bcc"], null, null, false, null)); + } + unset($Mail_RFC822); if (isset($message->headers["subject"]) && mb_detect_encoding($message->headers["subject"], "UTF-8") != false && preg_match('/[^\x00-\x7F]/', $message->headers["subject"]) == 1) { @@ -2635,11 +2639,11 @@ if (is_array($toaddr)) { $recipients = $toaddr; } - else { + elseif ($toaddr !== "") { $recipients = array($toaddr); } - // Cc and Bcc headers are sent, but we need to make sure that the recipient list contains them + // add Bcc and Cc header fields to recipients foreach (array("CC", "cc", "Cc", "BCC", "Bcc", "bcc") as $key) { if (!empty($headers[$key])) { if (is_array($headers[$key])) { @@ -2647,6 +2651,11 @@ } else { $recipients[] = $headers[$key]; + } + + // remove BCC header field from message + if (strcasecmp($key, "BCC") == 0) { + unset($headers[$key]); } } } diff -urN ../z-push-2.6.0.alpha1-210/src/backend/imap/mime_calendar.php ./src/backend/imap/mime_calendar.php --- ../z-push-2.6.0.alpha1-210/src/backend/imap/mime_calendar.php 2023-03-17 22:44:44.876607000 +0200 +++ ./src/backend/imap/mime_calendar.php 2023-03-17 22:49:29.312270000 +0200 @@ -64,7 +64,7 @@ $caldav->Logoff(); } else { - ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->delete_calendar_dav(): event not found, we will end with zombie events"); + ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->delete_calendar_dav(): event not found, we may have zombie events"); } } else { @@ -167,6 +167,17 @@ * @param $is_sent_folder boolean */ function parse_meeting_calendar($part, &$output, $is_sent_folder) { + $connected = false; + if (defined('IMAP_MEETING_USE_CALDAV') && IMAP_MEETING_USE_CALDAV) { + $caldav = new BackendCalDAV(); + if ($caldav->Logon(Request::GetAuthUser(), Request::GetAuthDomain(), Request::GetAuthPassword())) { + $connected = true; + } + else { + ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->parse_meeting_calendar(): Error connecting with BackendCalDAV"); + } + } + $ical = new iCalComponent(); $ical->ParseFrom($part->body); ZLog::Write(LOGLEVEL_WBXML, sprintf("BackendIMAP->parse_meeting_calendar(): %s", $part->body)); @@ -200,12 +211,17 @@ case "cancel": $output->messageclass = "IPM.Schedule.Meeting.Canceled"; ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Event canceled, removing calendar object"); - delete_calendar_dav($uid); + + // don't delete the recurring event on receiving cancelled exception + if (count($ical->GetPropertiesByPath("VEVENT/RECURRENCE-ID")) == 0) { + delete_calendar_dav($uid); + } break; + case "declinecounter": + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Declining a counter is not implemented."); case "counter": ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Counter received"); $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; - $output->meetingrequest->disallownewtimeproposal = 0; break; case "reply": ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Reply received"); @@ -248,18 +264,24 @@ } } } - $output->meetingrequest->disallownewtimeproposal = 1; + $output->meetingrequest->disallownewtimeproposal = "1"; break; case "request": $output->messageclass = "IPM.Schedule.Meeting.Request"; - $output->meetingrequest->disallownewtimeproposal = 0; ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): New request"); // New meeting, we don't create it now, because we need to confirm it first, but if we don't create it we won't see it in the calendar break; + case "add": + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Add method is not implemented."); + $output->messageclass = "IPM.Appointment"; + break; + case "publish": + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Publish method is not a meeting request."); + $output->messageclass = "IPM.Appointment"; + break; default: - ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->parse_meeting_calendar() - Unknown method <%s>, please report it to the developers", strtolower($part->headers["method"]))); + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->parse_meeting_calendar() - Unknown method <%s>, please report it to the developers", $method)); $output->messageclass = "IPM.Appointment"; - $output->meetingrequest->disallownewtimeproposal = 0; break; } } @@ -318,7 +340,7 @@ } // Get $tz from first timezone - $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID"); + $props = $ical->GetPropertiesByPath('VTIMEZONE/TZID'); if (count($props) > 0) { // TimeZones shouldn't have dots $tzname = str_replace(".", "", $props[0]->Value()); @@ -329,13 +351,87 @@ } $output->meetingrequest->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz)); - // Fixed values - $output->meetingrequest->instancetype = 0; + // guess instancetype by checking for recurrence rules or ids + if (count($ical->GetPropertiesByPath('VEVENT/RRULE')) == 1) { + $output->meetingrequest->instancetype = 1; + } + elseif (count($ical->GetPropertiesByPath('VEVENT/RECURRENCE-ID')) == 1) { + if ($connected && count($caldav->FindCalendar($uid)) == 1) { + $output->meetingrequest->instancetype = 3; + } + else { + $output->meetingrequest->instancetype = 2; + } + } + else { + $output->meetingrequest->instancetype = 0; + } + $output->meetingrequest->responserequested = 1; - $output->meetingrequest->busystatus = 2; - // TODO: reminder - $output->meetingrequest->reminder = ""; + // get intended busystatus + $props = $ical->GetPropertiesByPath('VEVENT/X-MICROSOFT-CDO-INTENDEDSTATUS'); + if (count($props) == 1) { + switch ($props[0]->Value()) { + case "FREE": + $output->meetingrequest->busystatus = "0"; + break; + case "TENTATIVE": + $output->meetingrequest->busystatus = "1"; + break; + case "BUSY": + $output->meetingrequest->busystatus = "2"; + break; + case "OOF": + $output->meetingrequest->busystatus = "3"; + break; + } + } + elseif (count($props = $ical->GetPropertiesByPath('VEVENT/TRANSP')) == 1) { + switch ($props[0]->Value()) { + case "TRANSPARENT": + $output->meetingrequest->busystatus = "0"; + break; + case "OPAQUE": + $output->meetingrequest->busystatus = "2"; + break; + } + } + else { + $output->meetingrequest->busystatus = 2; + } + + // is counter allowed + $props = $ical->GetPropertiesByPath('VEVENT/X-MICROSOFT-DISALLOW-COUNTER'); + if (count($props) > 0) { + switch ($props[0]->Value()) { + case "TRUE": + $output->meetingrequest->disallownewtimeproposal = "1"; + break; + case "FALSE": + $output->meetingrequest->disallownewtimeproposal = "0"; + break; + } + } + + // use reminder with smallest interval + $props = $ical->GetPropertiesByPath('VEVENT/VALARM/TRIGGER'); + if (count($props) > 0) { + foreach ($props as $vAlarmTrigger) { + $vAlarmTriggerValue = $vAlarmTrigger->Value(); + if ($vAlarmTriggerValue[0] == "-") { + $reminderSeconds = new DateInterval(substr($vAlarmTriggerValue, 1)); + $reminderSeconds = $reminderSeconds->format("%s") + $reminderSeconds->format("%i") * 60 + $reminderSeconds->format("%h") * 3600 + $reminderSeconds->format("%d") * 86400; + if (!isset($output->meetingrequest->reminderSeconds) || $output->meetingrequest->reminder > $reminderSeconds) { + $output->meetingrequest->reminder = $reminderSeconds; + } + } + } + } + + if ($connected) { + $caldav->Logoff(); + } } diff -urN ../z-push-2.6.0.alpha1-210/src/backend/imap/user_identity.php ./src/backend/imap/user_identity.php --- ../z-push-2.6.0.alpha1-210/src/backend/imap/user_identity.php 2023-03-17 22:44:44.876885000 +0200 +++ ./src/backend/imap/user_identity.php 2023-03-17 22:49:29.312593000 +0200 @@ -136,23 +136,31 @@ if ($ldap_conn) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromLdap() - Connected to LDAP")); + ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option($ldap_conn, LDAP_OPT_REFERRALS, 0); $ldap_bind = ldap_bind($ldap_conn, IMAP_FROM_LDAP_USER, IMAP_FROM_LDAP_PASSWORD); if ($ldap_bind) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromLdap() - Authenticated in LDAP")); + $filter = str_replace('#username', $username, str_replace('#domain', $domain, IMAP_FROM_LDAP_QUERY)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromLdap() - Searching From with filter: %s", $filter)); + $search = ldap_search($ldap_conn, IMAP_FROM_LDAP_BASE, $filter, unserialize(IMAP_FROM_LDAP_FIELDS)); $items = ldap_get_entries($ldap_conn, $search); if ($items['count'] > 0) { $ret_value = $identity; ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromLdap() - Found entry in LDAP. Generating From")); - // We get the first object. It's your responsability to make the query unique + + // We get the first object. It's your responsibility to make the query unique. foreach (unserialize(IMAP_FROM_LDAP_FIELDS) as $field) { - $ret_value = str_replace('#'.$field, $items[0][$field][0], $ret_value); + if (isset($items[0][$field][0])) { + $ret_value = str_replace('#' . $field, $items[0][$field][0], $ret_value); + } } + $ret_value = trim($ret_value); + if ($encode) { $ret_value = encodeFrom($ret_value); } diff -urN ../z-push-2.6.0.alpha1-210/src/backend/ipcsharedmemory/ipcsharedmemoryprovider.php ./src/backend/ipcsharedmemory/ipcsharedmemoryprovider.php --- ../z-push-2.6.0.alpha1-210/src/backend/ipcsharedmemory/ipcsharedmemoryprovider.php 2023-03-17 22:44:44.877276000 +0200 +++ ./src/backend/ipcsharedmemory/ipcsharedmemoryprovider.php 2023-03-17 22:49:29.313087000 +0200 @@ -41,8 +41,8 @@ $this->type = $type; $this->allocate = $allocate; - if ($this->initSharedMem()) - ZLog::Write(LOGLEVEL_DEBUG, sprintf("%s(): Initialized mutexid %s and memid %s.", $class, $this->mutexid, $this->memid)); + if ($this->initSharedMem()) + ZLog::Write(LOGLEVEL_DEBUG, sprintf("%s(): Initialized.", $class)); } /** diff -urN ../z-push-2.6.0.alpha1-210/src/backend/kopano/mapi/class.meetingrequest.php ./src/backend/kopano/mapi/class.meetingrequest.php --- ../z-push-2.6.0.alpha1-210/src/backend/kopano/mapi/class.meetingrequest.php 2023-03-17 22:44:44.882818000 +0200 +++ ./src/backend/kopano/mapi/class.meetingrequest.php 2023-03-17 22:49:29.319623000 +0200 @@ -690,9 +690,9 @@ // instead of tentative, and accept the same as per the intended busystatus. $senderEntryId = isset($messageprops[PR_SENT_REPRESENTING_ENTRYID]) ? $messageprops[PR_SENT_REPRESENTING_ENTRYID] : $messageprops[PR_SENDER_ENTRYID]; if(isset($messageprops[PR_RECEIVED_BY_ENTRYID]) && MAPIUtils::CompareEntryIds($senderEntryId, $messageprops[PR_RECEIVED_BY_ENTRYID])) { - $entryid = $this->accept(false, $sendresponse, $move, $proposeNewTimeProps, $body, true, $store, $calFolder, $basedate); + $entryid = $this->accept(false, $sendresponse, $move, $proposeNewTimeProps, $store, $calFolder, $body, true, $basedate); } else { - $entryid = $this->accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $body, $userAction, $store, $calFolder, $basedate); + $entryid = $this->accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $store, $calFolder, $body, $userAction, $basedate); } // if we have first time processed this meeting then set PR_PROCESSED property @@ -707,13 +707,13 @@ return $entryid; } - function accept($tentative, $sendresponse, $move, $proposeNewTimeProps = array(), $body = false, $userAction = false, $store, $calFolder, $basedate = false) + function accept($tentative, $sendresponse, $move, $proposeNewTimeProps, $store, $calFolder, $body = false, $userAction = false, $basedate = false) { $messageprops = mapi_getprops($this->message); $isDelegate = isset($messageprops[PR_RCVD_REPRESENTING_NAME]); if ($sendresponse) { - $this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $proposeNewTimeProps, $body, $store, $basedate, $calFolder); + $this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $proposeNewTimeProps, $store, $calFolder, $body, $basedate); } /** @@ -875,7 +875,7 @@ // Main recurring item is found, so now update exception if ($calendarItem) { - $this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate); + $this->acceptException($calendarItem, $this->message, $basedate, $store, $tentative, $userAction, $move, $isDelegate); $calendarItemProps = mapi_getprops($calendarItem, array(PR_ENTRYID)); $entryid = $calendarItemProps[PR_ENTRYID]; } @@ -1141,7 +1141,7 @@ } if($sendresponse) { - $this->createResponse(olResponseDeclined, array(), $body, $store, $basedate, $calFolder); + $this->createResponse(olResponseDeclined, $store, $calFolder, $body, $basedate); } if ($basedate) { @@ -1856,7 +1856,7 @@ * @param Array $proposeNewTimeProps properties of attendee's proposal * @param integer $basedate date of occurrence which attendee has responded */ - function createResponse($status, $proposeNewTimeProps = array(), $body = false, $store, $basedate = false, $calFolder) { + function createResponse($status, $store, $calFolder, $proposeNewTimeProps = array(), $body = false, $basedate = false) { $messageprops = mapi_getprops($this->message, Array(PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, @@ -2691,7 +2691,8 @@ * @param resource $store user store * @param boolean $isDelegate true if delegate is processing this meeting request */ - function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false) + + function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $store, $tentative, $userAction = false, $move = false, $isDelegate = false) { $recurr = new Recurrence($store, $recurringItem); diff -urN ../z-push-2.6.0.alpha1-210/src/backend/kopano/mapiprovider.php ./src/backend/kopano/mapiprovider.php --- ../z-push-2.6.0.alpha1-210/src/backend/kopano/mapiprovider.php 2023-03-17 22:44:44.886493000 +0200 +++ ./src/backend/kopano/mapiprovider.php 2023-03-17 22:49:29.323536000 +0200 @@ -2907,7 +2907,14 @@ if (strlen($persistData) == 4 && $persistData == PERSIST_SENTINEL) { break; } - $unpackedData = unpack("vdataSize/velementID/velDataSize", substr($persistData, 2, 6)); + // incase of empty $persistData: Suppress error 'unpack(): Type v: not enough input, need 2, have 0' + $part = substr($persistData, 2, 6); + if(strlen($part)==6) + $unpackedData = unpack("vdataSize/velementID/velDataSize", $part); + else{ + ZLog::Write(LOGLEVEL_INFO, "MAPIProvider->getSpecialFoldersData(): Not enough data"); + $unpackedData = array(); + } if (isset($unpackedData['dataSize']) && isset($unpackedData['elementID']) && $unpackedData['elementID'] == RSF_ELID_ENTRYID && isset($unpackedData['elDataSize'])) { $this->specialFoldersData[] = substr($persistData, 8, $unpackedData['elDataSize']); // Add PersistId and DataElementsSize lenghts to the data size as they're not part of it diff -urN ../z-push-2.6.0.alpha1-210/src/backend/kopano/replybackimexporter.php ./src/backend/kopano/replybackimexporter.php --- ../z-push-2.6.0.alpha1-210/src/backend/kopano/replybackimexporter.php 2023-03-17 22:44:44.887001000 +0200 +++ ./src/backend/kopano/replybackimexporter.php 2023-03-17 22:49:29.324623000 +0200 @@ -557,7 +557,7 @@ $data = substr(get_class($oldmessage), 4) . "\r\n"; // get the suppported fields as we need them to determine the ghosted properties $supportedFields = ZPush::GetDeviceManager()->GetSupportedFields(ZPush::GetDeviceManager()->GetFolderIdForBackendId($folderid)); - $dataarray = $oldmessage->EvaluateAndCompare($message, @constant('READ_ONLY_NOTIFY_YOURDATA'), $supportedFields); + $dataarray = $oldmessage->EvaluateAndCompare($message, $supportedFields, @constant('READ_ONLY_NOTIFY_YOURDATA')); foreach($dataarray as $key => $value) { $value = str_replace("\r", "", $value); diff -urN ../z-push-2.6.0.alpha1-210/src/backend/maildir/maildir.php ./src/backend/maildir/maildir.php --- ../z-push-2.6.0.alpha1-210/src/backend/maildir/maildir.php 2023-03-17 22:44:44.887851000 +0200 +++ ./src/backend/maildir/maildir.php 2023-03-17 22:49:29.325562000 +0200 @@ -279,7 +279,7 @@ return false; while($entry = readdir($dir)) { - if($entry{0} == ".") + if($entry[0] == ".") continue; $message = array(); @@ -383,16 +383,37 @@ } } - // convert mime-importance to AS-importance - if (isset($message->headers["x-priority"])) { - $mimeImportance = preg_replace("/\D+/", "", $message->headers["x-priority"]); - if ($mimeImportance > 3) + // convert mime-importance to AS-importance using RFC4021, X-Priority or default to "normal" (ZP-320) + //AS: 0 - low, 1 - normal, 2 - important + if (isset($message->headers["importance"])) { + //Importance: high, normal, low + $mimeImportance = strtolower($message->headers["importance"]); + if ($mimeImportance == "normal") { + $output->importance = 1; + } + elseif ($mimeImportance == "high") { + $output->importance = 2; + } + elseif ($mimeImportance == "low") { $output->importance = 0; - if ($mimeImportance == 3) + } + } + elseif (isset($message->headers["x-priority"])) { + //X-Priority: 1 - highest, 2 - high, 3 - normal, 4 - low, 5 - lowest + $mimeImportance = preg_replace("/\D+/", "", $message->headers["x-priority"]); + if ($mimeImportance == 3) { $output->importance = 1; - if ($mimeImportance < 3) + } + elseif ($mimeImportance < 3) { $output->importance = 2; + } + elseif ($mimeImportance > 3) { + $output->importance = 0; + } } + else { + $output->importance = 1; + } // Attachments are only searched in the top-level part $n = 0; @@ -679,7 +700,7 @@ $newdir = opendir($newdirname); while($newentry = readdir($newdir)) { - if($newentry{0} == ".") + if($newentry[0] == ".") continue; // link/unlink == move. This is the way to move the message according to cr.yp.to diff -urN ../z-push-2.6.0.alpha1-210/src/backend/stickynote/stickynote.php ./src/backend/stickynote/stickynote.php --- ../z-push-2.6.0.alpha1-210/src/backend/stickynote/stickynote.php 2023-03-17 22:44:44.889117000 +0200 +++ ./src/backend/stickynote/stickynote.php 2023-03-17 22:49:29.327040000 +0200 @@ -309,115 +309,122 @@ ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendStickyNote->ChangeMessage('%s','%s')", $folderid, $id)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendStickyNote->ChangeMessage(Message '%s')", $message)); - // If we have a null ID then it's a new note; allocate an ordinal for - // it. Then insert into the database and return the stat pointer for it. - // If we get an ID then it's an update; perform it and return stat - // pointer. - // - $_contents = stream_get_contents($message->asbody->data, 1024000); - if (!$id) { - $this->_result = pg_query($this->_dbconn, "select nextval('ordinal')"); - if (pg_result_status($this->_result) != PGSQL_TUPLES_OK) { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Cannot get new sequence number for item')")); - return false; - } - $id = pg_fetch_result($this->_result, 0, 0); - pg_free_result($this->_result); + if(ZPush::GetDeviceManager()->IsKoe() && KOE_CAPABILITY_NOTES && $id && !isset($message->asbody)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendStickyNote->ChangeMessage(): KOE patch item update. Ignoring incoming update.")); + } + else { + $_contents = stream_get_contents($message->asbody->data); - $this->_result = pg_query($this->_dbconn, "Begin"); - if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Transaction start failure!')")); + // If we have a null ID then it's a new note; allocate an ordinal for + // it. Then insert into the database and return the stat pointer for it. + // If we get an ID then it's an update; perform it and return stat + // pointer. + // + $_contents = stream_get_contents($message->asbody->data, 1024000); + if (!$id) { + $this->_result = pg_query($this->_dbconn, "select nextval('ordinal')"); + if (pg_result_status($this->_result) != PGSQL_TUPLES_OK) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Cannot get new sequence number for item')")); + return false; + } + $id = pg_fetch_result($this->_result, 0, 0); pg_free_result($this->_result); - return false; - } - pg_free_result($this->_result); - $_params = array(); - array_push($_params, $id, $message->subject, $_contents, $this->_user, $this->_domain); - $this->_result = pg_query_params($this->_dbconn, "insert into note (ordinal, subject, content, login, domain) values ($1, $2, $3, $4, $5)", $_params); - if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Cannot insert new item; fail!')")); + $this->_result = pg_query($this->_dbconn, "Begin"); + if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Transaction start failure!')")); + pg_free_result($this->_result); + return false; + } pg_free_result($this->_result); - $this->_result = pg_query($this->_dbconn, "Rollback"); + + $_params = array(); + array_push($_params, $id, $message->subject, $_contents, $this->_user, $this->_domain); + $this->_result = pg_query_params($this->_dbconn, "insert into note (ordinal, subject, content, login, domain) values ($1, $2, $3, $4, $5)", $_params); + if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Cannot insert new item; fail!')")); + pg_free_result($this->_result); + $this->_result = pg_query($this->_dbconn, "Rollback"); + pg_free_result($this->_result); + return false; + } + if (pg_affected_rows($this->_result) == 1) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendStickyNote->ChangeMessage('Insert of item %s (subj '%s') succeded')", $id, $message->subject)); + } else { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Insert of item %s (subj '%s') failed')", $id, $message->subject)); + pg_free_result($this->_result); + $this->_result = pg_query($this->_dbconn, "Rollback"); + pg_free_result($this->_result); + return false; + } + unset ($_params); pg_free_result($this->_result); - return false; - } - if (pg_affected_rows($this->_result) == 1) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendStickyNote->ChangeMessage('Insert of item %s (subj '%s') succeded')", $id, $message->subject)); - } else { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Insert of item %s (subj '%s') failed')", $id, $message->subject)); - pg_free_result($this->_result); - $this->_result = pg_query($this->_dbconn, "Rollback"); - pg_free_result($this->_result); - return false; - } - unset ($_params); - pg_free_result($this->_result); - if ($message->categories) { - foreach ($message->categories as $_category) { - $_params = array(); - array_push($_params, $id, $_category); - $this->_result = pg_query_params($this->_dbconn, "insert into categories (ordinal, tag) values ($1, $2)", $_params); - if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Cannot insert category for item; fail!')")); + if ($message->categories) { + foreach ($message->categories as $_category) { + $_params = array(); + array_push($_params, $id, $_category); + $this->_result = pg_query_params($this->_dbconn, "insert into categories (ordinal, tag) values ($1, $2)", $_params); + if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Cannot insert category for item; fail!')")); + pg_free_result($this->_result); + $this->_result = pg_query($this->_dbconn, "Rollback"); + pg_free_result($this->_result); + return(false); + } pg_free_result($this->_result); - $this->_result = pg_query($this->_dbconn, "Rollback"); - pg_free_result($this->_result); - return(false); } - pg_free_result($this->_result); + unset ($_category); } - unset ($_category); - } - } else { - $_params = array(); - array_push($_params, $message->subject, $_contents, $id, $this->_user, $this->_domain); - $this->_result = pg_query_params($this->_dbconn, "update note set subject=$1, content=$2, modified=now() where ordinal=$3 and login=$4 and domain=$5", $_params); - if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Update of item %s failed!')", $id)); - } - if (pg_affected_rows($this->_result) == 1) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendStickyNote->ChangeMessage('Update of item %s (subj '%s') succeded')", $id, $message->subject)); } else { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Update of item %s (subj '%s') failed (credential mismatch)')", $id, $message->subject)); - } - pg_free_result($this->_result); - unset ($_params); - if ($message->categories) { $_params = array(); - array_push($_params, $id); - $this->_result = pg_query_params($this->_dbconn, "delete from categories where ordinal=$1", $_params); + array_push($_params, $message->subject, $_contents, $id, $this->_user, $this->_domain); + $this->_result = pg_query_params($this->_dbconn, "update note set subject=$1, content=$2, modified=now() where ordinal=$3 and login=$4 and domain=$5", $_params); if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Cannot clear category for item; fail!')")); - pg_free_result($this->_result); - $this->_result = pg_query($this->_dbconn, "Rollback"); - pg_free_result($this->_result); - return(false); + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Update of item %s failed!')", $id)); } + if (pg_affected_rows($this->_result) == 1) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendStickyNote->ChangeMessage('Update of item %s (subj '%s') succeded')", $id, $message->subject)); + } else { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Update of item %s (subj '%s') failed (credential mismatch)')", $id, $message->subject)); + } + pg_free_result($this->_result); unset ($_params); - foreach ($message->categories as $_category) { + if ($message->categories) { $_params = array(); - array_push($_params, $id, $_category); - $this->_result = pg_query_params($this->_dbconn, "insert into categories (ordinal, tag) values ($1, $2)", $_params); + array_push($_params, $id); + $this->_result = pg_query_params($this->_dbconn, "delete from categories where ordinal=$1", $_params); if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Cannot insert category for item; fail!')")); + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Cannot clear category for item; fail!')")); pg_free_result($this->_result); $this->_result = pg_query($this->_dbconn, "Rollback"); pg_free_result($this->_result); return(false); } - pg_free_result($this->_result); + unset ($_params); + foreach ($message->categories as $_category) { + $_params = array(); + array_push($_params, $id, $_category); + $this->_result = pg_query_params($this->_dbconn, "insert into categories (ordinal, tag) values ($1, $2)", $_params); + if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Cannot insert category for item; fail!')")); + pg_free_result($this->_result); + $this->_result = pg_query($this->_dbconn, "Rollback"); + pg_free_result($this->_result); + return(false); + } + pg_free_result($this->_result); + } + unset ($_category); } - unset ($_category); + } + $this->_result = pg_query($this->_dbconn, "COMMIT"); + if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Transaction commit FAIL!')")); + pg_free_result($this->_result); + return false; } - } - $this->_result = pg_query($this->_dbconn, "COMMIT"); - if (pg_result_status($this->_result) != PGSQL_COMMAND_OK) { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendStickyNote->ChangeMessage('Transaction commit FAIL!')")); pg_free_result($this->_result); - return false; } - pg_free_result($this->_result); return $this->StatMessage($folderid, $id); } diff -urN ../z-push-2.6.0.alpha1-210/src/include/iCalendar.php ./src/include/iCalendar.php --- ../z-push-2.6.0.alpha1-210/src/include/iCalendar.php 2023-03-17 22:44:44.890317000 +0200 +++ ./src/include/iCalendar.php 2023-03-17 22:49:29.328150000 +0200 @@ -104,19 +104,29 @@ } list($prop, $value) = $split; - // Unescape ESCAPED-CHAR - $this->content = preg_replace( "/\\\\([,;:\"\\\\])/", '$1', $value); - // Split property name and parameters $parameters = $this->SplitQuoted($prop, ';'); $this->name = array_shift($parameters); $this->parameters = array(); + + // Unescape ESCAPED-CHAR + switch( $this->name ) { + case 'CATEGORIES': + case 'RESOURCES': + $this->content = $value; + break; + default: + $this->content = preg_replace( "/\\\\([,;:\"\\\\])/", '$1', $value); + } + + // Split parameters foreach ($parameters AS $k => $v) { $pos = strpos($v, '='); $name = substr($v, 0, $pos); $value = substr($v, $pos + 1); $this->parameters[$name] = preg_replace('/^"(.+)"$/', '$1', $value); // Removes DQUOTE on demand } + ZLog::Write(LOGLEVEL_DEBUG, sprintf("iCalendar->ParseFrom(): found '%s' = '%s' with %d parameters", $this->name, substr($this->content,0,200), count($this->parameters))); } @@ -282,7 +292,8 @@ case 'DURATION': case 'FREEBUSY': case 'TZOFFSETFROM': case 'TZOFFSETTO': case 'TZURL': case 'ATTENDEE': case 'ORGANIZER': case 'RECURRENCE-ID': case 'URL': case 'EXRULE': case 'SEQUENCE': case 'CREATED': - case 'RRULE': case 'REPEAT': case 'TRIGGER': + case 'RRULE': case 'REPEAT': case 'TRIGGER': case 'CATEGORIES': + case 'RESOURCES': break; case 'COMPLETED': case 'DTEND': diff -urN ../z-push-2.6.0.alpha1-210/src/include/mimeDecode.php ./src/include/mimeDecode.php --- ../z-push-2.6.0.alpha1-210/src/include/mimeDecode.php 2023-03-17 22:44:44.890696000 +0200 +++ ./src/include/mimeDecode.php 2023-03-17 22:49:29.328544000 +0200 @@ -154,7 +154,7 @@ /** * Flag to determine whether to decode headers - * (set to UTF8 to iconv convert headers) + * (set to UTF8 to convert headers) * @var mixed * @access private */ @@ -234,20 +234,15 @@ // Called via an object } else { - $this->_include_bodies = isset($params['include_bodies']) ? - $params['include_bodies'] : false; - $this->_decode_bodies = isset($params['decode_bodies']) ? - $params['decode_bodies'] : false; - $this->_decode_headers = isset($params['decode_headers']) ? - $params['decode_headers'] : false; - $this->_rfc822_bodies = isset($params['rfc_822bodies']) ? - $params['rfc_822bodies'] : false; - $this->_charset = isset($params['charset']) ? - strtolower($params['charset']) : 'utf-8'; + $this->_include_bodies = isset($params['include_bodies']) ? $params['include_bodies'] : false; + $this->_decode_bodies = isset($params['decode_bodies']) ? $params['decode_bodies'] : false; + $this->_decode_headers = isset($params['decode_headers']) ? $params['decode_headers'] : false; + $this->_rfc822_bodies = isset($params['rfc_822bodies']) ? $params['rfc_822bodies'] : false; + $this->_charset = isset($params['charset']) ? strtolower($params['charset']) : 'utf-8'; if (is_string($this->_decode_headers)) { - if (!function_exists('iconv')) { - $this->raiseError('header decode conversion requested, however iconv is missing'); + if (!function_exists('mb_convert_encoding')) { + $this->raiseError('header decode conversion requested, however mbstring is missing'); } $this->_decode_headers = strtolower($this->_decode_headers); } @@ -527,7 +522,7 @@ if (!$got_start) { // munge headers for mbox style from if ($value[0] == '>') { - $value = substring($value, 1); // remove mbox > + $value = substr($value, 1); // remove mbox > } if (substr($value,0,5) == 'From ') { $value = 'Return-Path: ' . substr($value, 5); @@ -825,15 +820,32 @@ break; } if (is_string($this->_decode_headers) && $charset != $this->_decode_headers) { - $conv = @iconv($charset, $this->_decode_headers, $text); - $text = ($conv === false) ? $text : $conv; + if (@mb_check_encoding($text, $charset) == false) { + // list of encodings, sorted by priority to assist mb_detect_encoding() + $encodingPriority = array('UTF-8', 'SJIS', 'GB18030', 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4', + 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-13', + 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16', 'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', + 'KOI8-R', 'BIG-5', 'ISO-2022-KR', 'ISO-2022-JP-MS'); + + // only use encodings supported by the system + $encodings = array_unique(array_merge($encodingPriority, mb_list_encodings())); + + // detect suitable encoding + if (@mb_check_encoding($text, ($encoding = mb_detect_encoding($text, $encodings)))) { + ZLog::Write(LOGLEVEL_WARN, sprintf("mimeDecode::_decodeHeader(): invalid encoding in header: using '%s' instead of '%s'", $encoding, $charset)); + $charset = $encoding; + } + else { + ZLog::Write(LOGLEVEL_WARN, sprintf("mimeDecode::_decodeHeader(): invalid encoding '%s' used in header, no substitution found", $charset)); + } + } + $text = @mb_convert_encoding($text, $this->_decode_headers, $charset); } $input = str_replace($encoded, $text, $input); } - if ($default_charset && is_string($this->_decode_headers) && $charset != $this->_decode_headers) { - $conv = @iconv($charset, $this->_decode_headers, $input); // TODO: shouldn't this be $default_charset ? - $input = ($conv === false) ? $input : $conv; + if ($default_charset && is_string($this->_decode_headers) && $default_charset != $this->_decode_headers) { + $input = mb_convert_encoding($input, $this->_decode_headers, $default_charset); } return $input; @@ -861,9 +873,28 @@ $input = base64_decode($input); break; } + if ($detectCharset && strtolower($charset) != $this->_charset) { - $conv = @iconv($charset, $this->_charset, $input); - $input = ($conv === false) ? $input : $conv; + if (@mb_check_encoding($input, $charset) == false) { + // list of encodings, sorted by priority to assist mb_detect_encoding() + $encodingPriority = array('UTF-8', 'SJIS', 'GB18030', 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4', + 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-13', + 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16', 'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', + 'KOI8-R', 'BIG-5', 'ISO-2022-KR', 'ISO-2022-JP-MS'); + + // only use encodings supported by the system + $encodings = array_unique(array_merge($encodingPriority, mb_list_encodings())); + + // detect suitable encoding + if (@mb_check_encoding($input, ($encoding = mb_detect_encoding($input, $encodings)))) { + ZLog::Write(LOGLEVEL_WARN, sprintf("mimeDecode::_decodeBody(): invalid encoding in body: using '%s' instead of '%s'", $encoding, $charset)); + $charset = $encoding; + } + else { + ZLog::Write(LOGLEVEL_WARN, sprintf("mimeDecode::_decodeBody(): invalid encoding '%s' used in body, no substitution found", $charset)); + } + } + $input = @mb_convert_encoding($input, $this->_decode_headers, $charset); } return $input; @@ -1175,4 +1206,4 @@ return false; } -} // End of class \ No newline at end of file +} // End of class diff -urN ../z-push-2.6.0.alpha1-210/src/include/z_RTF.php ./src/include/z_RTF.php --- ../z-push-2.6.0.alpha1-210/src/include/z_RTF.php 2023-03-17 22:44:44.891369000 +0200 +++ ./src/include/z_RTF.php 2023-03-17 22:49:29.329228000 +0200 @@ -172,7 +172,7 @@ $c=0; $end = $off + $len; for($i=$off;$i < $end;$i++) { - $c=$this->CRC32_TABLE[($c ^ ord($buf{$i})) & 0xFF] ^ (($c >> 8) & 0x00ffffff); + $c=$this->CRC32_TABLE[($c ^ ord($buf[$i])) & 0xFF] ^ (($c >> 8) & 0x00ffffff); } return $c; } diff -urN ../z-push-2.6.0.alpha1-210/src/include/z_caldav.php ./src/include/z_caldav.php --- ../z-push-2.6.0.alpha1-210/src/include/z_caldav.php 2023-03-17 22:44:44.891719000 +0200 +++ ./src/include/z_caldav.php 2023-03-17 22:49:29.329601000 +0200 @@ -96,15 +96,15 @@ */ private $curl = false; - private $synctoken = array(); + private $synctoken = array(); /** - * Constructor, initialises the class - * - * @param string $caldav_url The URL for the calendar server - * @param string $user The name of the user logging in - * @param string $pass The password for that user - */ + * Constructor, initialises the class + * + * @param string $caldav_url The URL for the calendar server + * @param string $user The name of the user logging in + * @param string $pass The password for that user + */ function __construct( $caldav_url, $user, $pass ) { $this->url = $caldav_url; $this->user = $user; @@ -115,86 +115,91 @@ $parsed_url = parse_url($caldav_url); if ($parsed_url === false) { ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendCalDAV->caldav_backend(): Couldn't parse URL: %s", $caldav_url)); - return; + return; } $this->server = $parsed_url['scheme'] . '://' . $parsed_url['host'] . ':' . $parsed_url['port']; $this->base_url = $parsed_url['path']; - //ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->caldav_backend(): base_url '%s'", $this->base_url)); - //$this->base_url .= !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; - //$this->base_url .= !empty($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->caldav_backend(): base_url '%s'", $this->base_url)); - if (substr($this->base_url, -1) !== '/') { + //$this->base_url .= !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; + //$this->base_url .= !empty($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + + if (substr($this->base_url, -1) !== '/') { $this->base_url = $this->base_url . '/'; } } + /** - * Checks if the CalDAV server is reachable - * - * @return boolean - */ - public function CheckConnection() { - $result = $this->DoRequest($this->url, 'OPTIONS'); + * Checks if the CalDAV server is reachable + * + * @return boolean + */ + public function CheckConnection() { + $result = $this->DoRequest($this->url, 'OPTIONS'); - switch ($this->httpResponseCode) { - case 200: - case 207: - case 401: - $status = true; - break; - default: - $status = false; - } + switch ($this->httpResponseCode) { + case 200: + case 207: + case 401: + $status = true; + break; + default: + $status = false; + } + return $status; + } - return $status; - } - /** - * Disconnect curl connection - * - */ - public function Disconnect() { - if ($this->curl !== false) { - curl_close($this->curl); - $this->curl = false; - } - } + /** + * Disconnect curl connection + * + */ + public function Disconnect() { + if ($this->curl !== false) { + curl_close($this->curl); + $this->curl = false; + } + } /** - * Adds an If-Match or If-None-Match header - * - * @param bool $match to Match or Not to Match, that is the question! - * @param string $etag The etag to match / not match against. - */ + * Adds an If-Match or If-None-Match header + * + * @param bool $match to Match or Not to Match, that is the question! + * @param string $etag The etag to match / not match against. + */ function SetMatch( $match, $etag = '*' ) { $this->headers['match'] = sprintf( "%s-Match: \"%s\"", ($match ? "If" : "If-None"), trim($etag,'"')); } + /** - * Add a Depth: header. Valid values are 0, 1 or infinity - * - * @param int $depth The depth, default to infinity - */ + * Add a Depth: header. Valid values are 0, 1 or infinity + * + * @param int $depth The depth, default to infinity + */ function SetDepth( $depth = '0' ) { $this->headers['depth'] = 'Depth: '. ($depth == '1' ? "1" : ($depth == 'infinity' ? $depth : "0") ); } + /** - * Set the calendar_url we will be using for a while. - * - * @param string $url The calendar_url - */ + * Set the calendar_url we will be using for a while. + * + * @param string $url The calendar_url + */ function SetCalendar( $url ) { $this->calendar_url = $url; } + /** - * Split response into httpResponse and xmlResponse - * - * @param string Response from server - */ + * Split response into httpResponse and xmlResponse + * + * @param string Response from server + */ function ParseResponse( $response ) { $pos = strpos($response, 'curl_init(); @@ -289,12 +294,12 @@ /** - * Send an OPTIONS request to the server - * - * @param string $url The URL to make the request to - * - * @return array The allowed options - */ + * Send an OPTIONS request to the server + * + * @param string $url The URL to make the request to + * + * @return array The allowed options + */ function DoOptionsRequest( $url = null ) { $headers = $this->DoRequest($url === null ? $this->url : $url, "OPTIONS"); $options_header = preg_replace( '/^.*Allow: ([a-z, ]+)\r?\n.*/is', '$1', $headers ); @@ -304,48 +309,48 @@ /** - * Send an XML request to the server (e.g. PROPFIND, REPORT, MKCALENDAR) - * - * @param string $method The method (PROPFIND, REPORT, etc) to use with the request - * @param string $xml The XML to send along with the request - * @param string $url The URL to make the request to - * - * @return array An array of the allowed methods - */ + * Send an XML request to the server (e.g. PROPFIND, REPORT, MKCALENDAR) + * + * @param string $method The method (PROPFIND, REPORT, etc) to use with the request + * @param string $xml The XML to send along with the request + * @param string $url The URL to make the request to + * + * @return array An array of the allowed methods + */ function DoXMLRequest( $request_method, $xml, $url = null ) { return $this->DoRequest($url, $request_method, $xml, "text/xml"); } /** - * Get a single item from the server. - * - * @param string $url The URL to GET - */ + * Get a single item from the server. + * + * @param string $url The URL to GET + */ function DoGETRequest( $url ) { return $this->DoRequest($url, "GET"); } /** - * Get the HEAD of a single item from the server. - * - * @param string $url The URL to HEAD - */ + * Get the HEAD of a single item from the server. + * + * @param string $url The URL to HEAD + */ function DoHEADRequest( $url ) { return $this->DoRequest($url, "HEAD"); } /** - * PUT a text/icalendar resource, returning the etag - * - * @param string $url The URL to make the request to - * @param string $icalendar The iCalendar resource to send to the server - * @param string $etag The etag of an existing resource to be overwritten, or '*' for a new resource. - * - * @return string The content of the response from the server - */ + * PUT a text/icalendar resource, returning the etag + * + * @param string $url The URL to make the request to + * @param string $icalendar The iCalendar resource to send to the server + * @param string $etag The etag of an existing resource to be overwritten, or '*' for a new resource. + * + * @return string The content of the response from the server + */ function DoPUTRequest( $url, $icalendar, $etag = null ) { if ( $etag != null ) { $this->SetMatch( ($etag != '*'), $etag ); @@ -373,13 +378,13 @@ /** - * DELETE a text/icalendar resource - * - * @param string $url The URL to make the request to - * @param string $etag The etag of an existing resource to be deleted, or '*' for any resource at that URL. - * - * @return int The HTTP Result Code for the DELETE - */ + * DELETE a text/icalendar resource + * + * @param string $url The URL to make the request to + * @param string $etag The etag of an existing resource to be deleted, or '*' for any resource at that URL. + * + * @return int The HTTP Result Code for the DELETE + */ function DoDELETERequest( $url, $etag = null ) { if ( $etag != null ) { $this->SetMatch( true, $etag ); @@ -390,10 +395,10 @@ /** - * Get a single item from the server. - * - * @param string $url The URL to PROPFIND on - */ + * Get a single item from the server. + * + * @param string $url The URL to PROPFIND on + */ function DoPROPFINDRequest( $url, $props, $depth = 0 ) { $this->SetDepth($depth); $xml = new XMLDocument( array( 'DAV:' => '', 'urn:ietf:params:xml:ns:caldav' => 'C' ) ); @@ -408,10 +413,10 @@ /** - * Get/Set the Principal URL - * - * @param $url string The Principal URL to set - */ + * Get/Set the Principal URL + * + * @param $url string The Principal URL to set + */ function PrincipalURL( $url = null ) { if ( isset($url) ) { $this->principal_url = $url; @@ -421,10 +426,10 @@ /** - * Get/Set the calendar-home-set URL - * - * @param $url array of string The calendar-home-set URLs to set - */ + * Get/Set the calendar-home-set URL + * + * @param $url array of string The calendar-home-set URLs to set + */ function CalendarHomeSet( $urls = null ) { if ( isset($urls) ) { if ( !is_array($urls) ) { @@ -437,10 +442,10 @@ /** - * Get/Set the calendar-home-set URL - * - * @param $urls array of string The calendar URLs to set - */ + * Get/Set the calendar-home-set URL + * + * @param $urls array of string The calendar URLs to set + */ function CalendarUrls( $urls = null ) { if ( isset($urls) ) { if ( !is_array($urls) ) { @@ -453,10 +458,10 @@ /** - * Return the first occurrence of an href inside the named tag. - * - * @param string $tagname The tag name to find the href inside of - */ + * Return the first occurrence of an href inside the named tag. + * + * @param string $tagname The tag name to find the href inside of + */ function HrefValueInside( $tagname ) { foreach( $this->xmltags[$tagname] AS $k => $v ) { $j = $v + 1; @@ -469,11 +474,11 @@ /** - * Return the href containing this property. Except only if it's inside a status != 200 - * - * @param string $tagname The tag name of the property to find the href for - * @param integer $which Which instance of the tag should we use - */ + * Return the href containing this property. Except only if it's inside a status != 200 + * + * @param string $tagname The tag name of the property to find the href for + * @param integer $which Which instance of the tag should we use + */ function HrefForProp( $tagname, $i = 0 ) { if ( isset($this->xmltags[$tagname]) && isset($this->xmltags[$tagname][$i]) ) { $j = $this->xmltags[$tagname][$i]; @@ -497,11 +502,11 @@ /** - * Return the href which has a resourcetype of the specified type - * - * @param string $tagname The tag name of the resourcetype to find the href for - * @param integer $which Which instance of the tag should we use - */ + * Return the href which has a resourcetype of the specified type + * + * @param string $tagname The tag name of the resourcetype to find the href for + * @param integer $which Which instance of the tag should we use + */ function HrefForResourcetype( $tagname, $i = 0 ) { if ( isset($this->xmltags[$tagname]) && isset($this->xmltags[$tagname][$i]) ) { $j = $this->xmltags[$tagname][$i]; @@ -518,10 +523,10 @@ /** - * Return the ... of a propstat where the status is OK - * - * @param string $nodenum The node number in the xmlnodes which is the href - */ + * Return the ... of a propstat where the status is OK + * + * @param string $nodenum The node number in the xmlnodes which is the href + */ function GetOKProps( $nodenum ) { $props = null; $level = $this->xmlnodes[$nodenum]['level']; @@ -549,10 +554,10 @@ /** - * Attack the given URL in an attempt to find a principal URL - * - * @param string $url The URL to find the principal-URL from - */ + * Attack the given URL in an attempt to find a principal URL + * + * @param string $url The URL to find the principal-URL from + */ function FindPrincipal( $url=null ) { $xml = $this->DoPROPFINDRequest( $url, array('resourcetype', 'current-user-principal', 'owner', 'principal-URL', 'urn:ietf:params:xml:ns:caldav:calendar-home-set'), 1); @@ -571,10 +576,10 @@ /** - * Attack the given URL in an attempt to find a principal URL - * - * @param string $url The URL to find the calendar-home-set from - */ + * Attack the given URL in an attempt to find a principal URL + * + * @param string $url The URL to find the calendar-home-set from + */ function FindCalendarHome( $recursed=false ) { if ( !isset($this->principal_url) ) { $this->FindPrincipal(); @@ -607,8 +612,8 @@ /** - * Find the calendars, from the calendar_home_set - */ + * Find the calendars, from the calendar_home_set + */ function FindCalendars( $recursed=false ) { if ( !isset($this->calendar_home_set[0]) ) { $this->FindCalendarHome(); @@ -654,8 +659,8 @@ /** - * Find the calendars, from the calendar_home_set - */ + * Find the calendars, from the calendar_home_set + */ function GetCalendarDetails( $url = null ) { if ( isset($url) ) { $this->SetCalendar($url); @@ -687,8 +692,8 @@ /** - * Get all etags for a calendar - */ + * Get all etags for a calendar + */ function GetCollectionETags( $url = null ) { if ( isset($url) ) { $this->SetCalendar($url); @@ -711,8 +716,8 @@ /** - * Get a bunch of events for a calendar with a calendar-multiget report - */ + * Get a bunch of events for a calendar with a calendar-multiget report + */ function CalendarMultiget( $event_hrefs, $url = null ) { if ( isset($url) ) { $this->SetCalendar($url); @@ -752,20 +757,20 @@ /** - * Given XML for a calendar query, return an array of the events (/todos) in the - * response. Each event in the array will have a 'href', 'etag' and an optional '$response_type' - * part (depending on the flag '$include_data'), where the 'href' is relative to the calendar and - * the '$response_type' contains the - * definition of the calendar data in iCalendar format. - * - * @param string $filter XML fragment which is the element of a calendar-query - * @param string $url The URL of the calendar, or empty/null to use the 'current' calendar_url - * @param boolean $include_data Flags whether to request the content-data (the icalendar data) in the response - * - * @return array An array of the relative URLs, etags, and events from the server. Each element of the array will - * be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied - * etag (which only varies when the data changes) and the calendar data in iCalendar format. - */ + * Given XML for a calendar query, return an array of the events (/todos) in the + * response. Each event in the array will have a 'href', 'etag' and an optional '$response_type' + * part (depending on the flag '$include_data'), where the 'href' is relative to the calendar and + * the '$response_type' contains the + * definition of the calendar data in iCalendar format. + * + * @param string $filter XML fragment which is the element of a calendar-query + * @param string $url The URL of the calendar, or empty/null to use the 'current' calendar_url + * @param boolean $include_data Flags whether to request the content-data (the icalendar data) in the response + * + * @return array An array of the relative URLs, etags, and events from the server. Each element of the array will + * be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied + * etag (which only varies when the data changes) and the calendar data in iCalendar format. + */ function DoCalendarQuery( $filter, $url = null, $include_data = true ) { if ( !empty($url) ) { $this->SetCalendar($url); @@ -820,25 +825,25 @@ } /** - * Get a list of events in a range from $start to $finish. The dates should be in the - * format yyyymmddThhmmssZ and should be in GMT. The events are returned as an - * array of event parameter arrays. Unlike the original GetEvents, each event array will only contain the 'href' and 'etag' - * parts, where the 'href' is relative to the calendar. - * - * @param timestamp $start The start time for the period - * @param timestamp $finish The finish time for the period - * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''. - * - * @return array An array of the relative URLs and etags as [['href'], ['etag']] - * - * This function has been modified from the original GetEvents function; the function has been renamed to prevent regression errors - */ + * Get a list of events in a range from $start to $finish. The dates should be in the + * format yyyymmddThhmmssZ and should be in GMT. The events are returned as an + * array of event parameter arrays. Unlike the original GetEvents, each event array will only contain the 'href' and 'etag' + * parts, where the 'href' is relative to the calendar. + * + * @param timestamp $start The start time for the period + * @param timestamp $finish The finish time for the period + * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''. + * + * @return array An array of the relative URLs and etags as [['href'], ['etag']] + * + * This function has been modified from the original GetEvents function; the function has been renamed to prevent regression errors + */ function GetEventsList( $start = null, $finish = null, $relative_url = null ) { - $filter = ""; - if ( isset($start) && isset($finish) ) { + + if ( isset($start, $finish) ) { $range = ""; } else { - $range = ''; + $range = ""; } $filter = << -EOTIME; - } else { - $time_range = ""; - } + $range_filter = ""; + if(isset($start)) { + $range_filter .= ' start="' . $start . '"'; + } + if(isset($finish)) { + $range_filter .= ' end="' . $finish . '"'; + } + if($range_filter !== "") { + $range_filter = ""; + } // Warning! May contain traces of double negatives... - $neg_cancelled = ( $cancelled === true ? "no" : "yes" ); - $neg_completed = ( $cancelled === true ? "no" : "yes" ); + $completed_filter = ""; + if(isset($completed)) { + $completed_filter = ' - - - COMPLETED - - - CANCELLED - $time_range - + + $completed_filter + $cancelled_filter + $range_filter + EOFILTER; @@ -907,14 +933,14 @@ /** - * Get the calendar entry by UID - * - * @param uid - * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''. - * @param string $component_type The component type inside the VCALENDAR. Default 'VEVENT'. - * - * @return array An array of the relative URL, etag, and calendar data returned from DoCalendarQuery() @see DoCalendarQuery() - */ + * Get the calendar entry by UID + * + * @param uid + * @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''. + * @param string $component_type The component type inside the VCALENDAR. Default 'VEVENT'. + * + * @return array An array of the relative URL, etag, and calendar data returned from DoCalendarQuery() @see DoCalendarQuery() + */ function GetEntryByUid( $uid, $relative_url = null, $component_type = 'VEVENT' ) { $filter = ""; if ( $uid ) { @@ -936,37 +962,38 @@ /** - * Get the calendar entry by HREF - * - * @param string $href The href from a call to GetEvents or GetTodos etc. - * - * @return string The iCalendar of the calendar entry - */ + * Get the calendar entry by HREF + * + * @param string $href The href from a call to GetEvents or GetTodos etc. + * + * @return string The iCalendar of the calendar entry + */ function GetEntryByHref( $href ) { $href = str_replace( rawurlencode('/'),'/',rawurlencode($href)); return $this->DoGETRequest( $href ); } - /** - * Do a Sync operation. This is the fastest way to detect changes. - * - * @param string $url URL for the calendar - * @param boolean $initial It's the first synchronization - * @param boolean $support_dav_sync The CalDAV server supports sync-collection - * - * @return array of responses - */ - public function GetSync($relative_url = null, $initial = true, $support_dav_sync = false) { - if (!empty($relative_url)) { - $this->SetCalendar($relative_url); - } - $hasToken = !$initial && isset($this->synctoken[$this->calendar_url]); - if ($support_dav_sync) { - $token = ($hasToken ? $this->synctoken[$this->calendar_url] : ""); + /** + * Do a Sync operation. This is the fastest way to detect changes. + * + * @param string $url URL for the calendar + * @param boolean $initial It's the first synchronization + * @param boolean $support_dav_sync The CalDAV server supports sync-collection + * + * @return array of responses + */ + public function GetSync($relative_url = null, $initial = true, $support_dav_sync = false) { + if (!empty($relative_url)) { + $this->SetCalendar($relative_url); + } + $hasToken = !$initial && isset($this->synctoken[$this->calendar_url]); + if ($support_dav_sync) { + $token = ($hasToken ? $this->synctoken[$this->calendar_url] : ""); ZLog::Write(LOGLEVEL_WBXML, sprintf("CalDAVClient->GetSync called for %s'%s' with token '%s'", ($initial ? "initial sync of " : ""), $this->calendar_url, $token)); - $body = << $token @@ -977,9 +1004,9 @@ EOXML; - } - else { - $body = << @@ -991,50 +1018,49 @@ EOXML; - } + } - $this->SetDepth(1); - $this->DoRequest($this->calendar_url, "REPORT", $body, "text/xml"); + $this->SetDepth(1); + $this->DoRequest($this->calendar_url, "REPORT", $body, "text/xml"); - $report = array(); - foreach ($this->xmlnodes as $k => $v) { - switch ($v['tag']) { - case 'DAV::response': - if ($v['type'] == 'open') { - $response = array(); - } - elseif ($v['type'] == 'close') { - $report[] = $response; - ZLog::Write(LOGLEVEL_WBXML, sprintf("CalDAVClient->GetSync return value includes response: %s",implode("|",$response))); - } - break; - case 'DAV::href': - $response['href'] = basename( rawurldecode($v['value']) ); - break; - case 'DAV::getlastmodified': - if (isset($v['value'])) { - $response['getlastmodified'] = $v['value']; - } - else { - $response['getlastmodified'] = ''; - } - break; - case 'DAV::getetag': - $response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']); - break; - case 'DAV::sync-token': - $this->synctoken[$this->calendar_url] = $v['value']; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("CalDAVClient->GetSync: Response from '%s' includes sync token: '%s'", $this->calendar_url, $this->synctoken[$this->calendar_url])); - break; - } - } + $report = array(); + foreach ($this->xmlnodes as $k => $v) { + switch ($v['tag']) { + case 'DAV::response': + if ($v['type'] == 'open') { + $response = array(); + } + elseif ($v['type'] == 'close') { + $report[] = $response; + ZLog::Write(LOGLEVEL_WBXML, sprintf("CalDAVClient->GetSync return value includes response: %s",implode("|",$response))); + } + break; + case 'DAV::href': + $response['href'] = basename( rawurldecode($v['value']) ); + break; + case 'DAV::getlastmodified': + if (isset($v['value'])) { + $response['getlastmodified'] = $v['value']; + } + else { + $response['getlastmodified'] = ''; + } + break; + case 'DAV::getetag': + $response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']); + break; + case 'DAV::sync-token': + $this->synctoken[$this->calendar_url] = $v['value']; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("CalDAVClient->GetSync: Response from '%s' includes sync token: '%s'", $this->calendar_url, $this->synctoken[$this->calendar_url])); + break; + } + } - // Report sync-token support on initial sync - if ($initial && $support_dav_sync && !isset($this->synctoken[$this->calendar_url])) { - ZLog::Write(LOGLEVEL_WARN, 'CalDAVClient->GetSync(): no DAV::sync-token received; did you set CALDAV_SUPPORTS_SYNC correctly?'); - } + // Report sync-token support on initial sync + if ($initial && $support_dav_sync && !isset($this->synctoken[$this->calendar_url])) { + ZLog::Write(LOGLEVEL_WARN, 'CalDAVClient->GetSync(): no DAV::sync-token received; did you set CALDAV_SUPPORTS_SYNC correctly?'); + } - return $report; - } - + return $report; + } }; diff -urN ../z-push-2.6.0.alpha1-210/src/lib/core/streamer.php ./src/lib/core/streamer.php --- ../z-push-2.6.0.alpha1-210/src/lib/core/streamer.php 2023-03-17 22:44:44.895287000 +0200 +++ ./src/lib/core/streamer.php 2023-03-17 22:49:29.333286000 +0200 @@ -457,6 +457,10 @@ * @return string */ private function formatDate($ts, $type) { + if ('' === $ts) { + $ts = null; + } + if($type == self::STREAMER_TYPE_DATE) return gmstrftime("%Y%m%dT%H%M%SZ", $ts); else if($type == self::STREAMER_TYPE_DATE_DASHES) diff -urN ../z-push-2.6.0.alpha1-210/src/lib/syncobjects/syncobject.php ./src/lib/syncobjects/syncobject.php --- ../z-push-2.6.0.alpha1-210/src/lib/syncobjects/syncobject.php 2023-03-17 22:44:44.907054000 +0200 +++ ./src/lib/syncobjects/syncobject.php 2023-03-17 22:49:29.347503000 +0200 @@ -170,7 +170,7 @@ * @access public * @return array with one property per line, key being the property instance variable name */ - public function EvaluateAndCompare($odo, $odoName = "", $supportedFields, $keyprefix = "", $recCount = 0) { + public function EvaluateAndCompare($odo, $supportedFields, $odoName = "", $keyprefix = "", $recCount = 0) { if ($odo === false) return false; diff -urN ../z-push-2.6.0.alpha1-210/src/lib/utils/timezoneutil.php ./src/lib/utils/timezoneutil.php --- ../z-push-2.6.0.alpha1-210/src/lib/utils/timezoneutil.php 2023-03-17 22:44:44.909419000 +0200 +++ ./src/lib/utils/timezoneutil.php 2023-03-17 22:49:29.349867000 +0200 @@ -1329,6 +1329,9 @@ //20110930 (Append T000000Z to the date, so it starts at midnight) $date = date_create_from_format('Ymd\THis\Z', $value . "T000000Z", $tz); } + if (!$date) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("TimezoneUtil::MakeUTCDate(): failed to convert '%s' to date", $value)); + } return date_timestamp_get($date); } diff -urN ../z-push-2.6.0.alpha1-210/src/lib/wbxml/wbxmlencoder.php ./src/lib/wbxml/wbxmlencoder.php --- ../z-push-2.6.0.alpha1-210/src/lib/wbxml/wbxmlencoder.php 2023-03-17 22:44:44.911398000 +0200 +++ ./src/lib/wbxml/wbxmlencoder.php 2023-03-17 22:49:29.351432000 +0200 @@ -169,6 +169,7 @@ */ public function contentStream($stream, $asBase64 = false, $opaque = false) { // Do not append filters to opaque data as it might contain null char + $stream = $this->stringToStream($stream); if (!$asBase64 && !$opaque) { stream_filter_register('replacenullchar', 'ReplaceNullcharFilter'); $rnc_filter = stream_filter_append($stream, 'replacenullchar'); @@ -551,4 +552,24 @@ } ZLog::Write(LOGLEVEL_WBXML, "WBXML-OUT: ". $data, false); } + + /** + * converts string to stream + * + * @param string $string + * + * @access private + * @return + */ + private function stringToStream($string) { + if (!is_string($string)) { + return $string; + } + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $string); + rewind($stream); + return $stream; + } + } diff -urN ../z-push-2.6.0.alpha1-210/src/z-push-top.php ./src/z-push-top.php --- ../z-push-2.6.0.alpha1-210/src/z-push-top.php 2023-03-17 22:44:44.913830000 +0200 +++ ./src/z-push-top.php 2023-03-17 22:49:29.353889000 +0200 @@ -104,6 +104,7 @@ private $activeHosts = array(); private $activeUsers = array(); private $activeDevices = array(); + private $activeBackend; /** * Constructor @@ -127,6 +128,9 @@ $this->scrSize = array('width' => 80, 'height' => 24); $this->pingInterval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 12; + // Identify the backend that will be loaded by z-push + $this->activeBackend = get_class(ZPush::GetBackend()); + // get a TopCollector $this->topCollector = new TopCollector(); } @@ -304,8 +308,8 @@ $this->scrPrintAt($lc,0, "\033[1mZ-Push top live statistics\033[0m\t\t\t\t\t". @strftime("%d/%m/%Y %T")."\n"); $lc++; $this->scrPrintAt($lc,0, sprintf("Open connections: %d\t\t\t\tUsers:\t %d\tZ-Push: %s ",count($this->activeConn),count($this->activeUsers), $this->getVersion())); $lc++; - $this->scrPrintAt($lc,0, sprintf("Push connections: %d\t\t\t\tDevices: %d\tPHP-MAPI: %s", $this->pushConn, count($this->activeDevices),phpversion("mapi"))); $lc++; - $this->scrPrintAt($lc,0, sprintf(" Hosts:\t %d", count($this->activeHosts))); $lc++; + $this->scrPrintAt($lc,0, sprintf("Push connections: %d\t\t\t\tDevices: %d\tPHP-MAPI: %s", $this->pushConn, count($this->activeDevices), phpversion("mapi"))); $lc++; + $this->scrPrintAt($lc,0, sprintf(" Hosts:\t %d\tBackend: %s", count($this->activeHosts), $this->$activeBackend)); $lc++; $lc++; $this->scrPrintAt($lc,0, "\033[4m". $this->getLine(array('pid'=>'PID', 'ip'=>'IP', 'user'=>'USER', 'command'=>'COMMAND', 'time'=>'TIME', 'devagent'=>'AGENT', 'devid'=>'DEVID', 'addinfo'=>'Additional Information')). str_repeat(" ",20)."\033[0m"); $lc++;