Commit b34b6caf authored by Sebastian Kummer's avatar Sebastian Kummer

Merge pull request #574 in ZP/z-push from develop to release/2.3

* commit '09493d72':
  ZP-1251 Aggregate Ping announcements about changes.
  ZP-1257 Remove statically configured shared folders from the list sent to ASDevice->SetAdditionalFolderList().
  ZP-1268 Undefined variable: name in z-push-admin.
  ZP-1259 Fixed typo in a variable.
  ZP-1259 Check if sm->data is empty for SendMail.
  ZP-1240 Also HTML-trucate bodies from IMAP.
  ZP-1255 Directly use sharedUser if it's an email address. Enables sending in multi-company environments.
  ZP-1256 Set backend2folderidCache of ASDevice to false, so it can be rebuilt.
  ZP-1256 Rebuild backend2folderidCache of ASDevice if it's not available.
  ZP-1256 Rebuild backend2folderidCache of ASDevice if it's not available.
  ZP-1250 Search for X-Push-Sender only in the headers.
  ZP-1240 HTML safe truncate streams containing HTML data.
  ZP-1240 Add HTML safe truncation options to stream wrappers.
  ZP-1240 Add parameter $htmlsafe (default false) to Utils::Utf8_truncate().
parents 958db4e5 09493d72
......@@ -1124,7 +1124,8 @@ class BackendIMAP extends BackendDiff implements ISearchProvider {
}
}
$output->asbody->data = StringStreamWrapper::Open($data);
// indicate in open that the data is HTML so it can be truncated correctly if required
$output->asbody->data = StringStreamWrapper::Open($data, ($bpReturnType == SYNC_BODYPREFERENCE_HTML));
$output->asbody->estimatedDataSize = strlen($data);
unset($data);
$output->asbody->type = $bpReturnType;
......
......@@ -433,10 +433,14 @@ class BackendKopano implements IBackend, ISearchProvider {
throw new StatusException("KopanoBackend->SendMail(): ZCP/KC version is too old, INETMAPI_IMTOMAPI is not available. Install at least ZCP version 7.0.6 or later.", SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED, null, LOGLEVEL_FATAL);
return false;
}
$mimeLength = strlen($sm->mime);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): RFC822: %d bytes forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'",
strlen($sm->mime), Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag),
$mimeLength, Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag),
Utils::PrintAsString((isset($sm->source->folderid) ? $sm->source->folderid : false)),
Utils::PrintAsString(($sm->saveinsent)), Utils::PrintAsString(isset($sm->replacemime)) ));
if ($mimeLength == 0) {
throw new StatusException("KopanoBackend->SendMail(): empty mail data", SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED);
}
// Send-As functionality - https://jira.z-hub.io/browse/ZP-908
$sendingAsSomeone = false;
......@@ -444,7 +448,7 @@ class BackendKopano implements IBackend, ISearchProvider {
$senderEmail = array();
// KOE: grep for the Sender header indicating we should send-as
// the 'X-Push-Sender-Name' header is not used
if (preg_match("/^X-Push-Sender:\s(.*?)$/im", $sm->mime, $senderEmail)) {
if (preg_match("/^X-Push-Sender:\s(.*?)$/im", substr($sm->mime, 0, strpos($sm->mime, "\r\n\r\n")), $senderEmail)) {
$sendAsEmail = trim($senderEmail[1]);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): Send-As '%s' requested by KOE", $sendAsEmail));
......@@ -474,10 +478,20 @@ class BackendKopano implements IBackend, ISearchProvider {
if ($sharedUser != false && $sharedUser != 'SYSTEM') {
$folders = ZPush::GetAdditionalSyncFolders();
if (isset($folders[$sm->source->folderid]) && ($folders[$sm->source->folderid]->Flags & DeviceManager::FLD_FLAGS_SENDASOWNER)) {
$sendAs = $this->resolveRecipientGAL($sharedUser, 1);
if (isset($sendAs[0])) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): Server side Send-As activated for shared folder. Sending as '%s'.", $sendAs[0]->emailaddress));
$sm->mime = preg_replace("/^From: .*?$/im", "From: ". $sendAs[0]->emailaddress, $sm->mime, 1);
$sendAs = false;
// for multi-company this is (mostly always) already an email address and we can just use it - ZP-1255
if (Utils::CheckEmail($sharedUser)) {
$sendAs = $sharedUser;
}
else {
$gabResult = $this->resolveRecipientGAL($sharedUser, 1);
if (isset($gabResult[0])) {
$sendAs = $gabResult[0]->emailaddress;
}
}
if ($sendAs) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): Server side Send-As activated for shared folder. Sending as '%s'.", $sendAs));
$sm->mime = preg_replace("/^From: .*?$/im", "From: ". $sendAs, $sm->mime, 1);
$sendingAsSomeone = true;
}
}
......@@ -711,7 +725,7 @@ class BackendKopano implements IBackend, ISearchProvider {
$storeprops = mapi_getprops($this->defaultstore, array(PR_IPM_WASTEBASKET_ENTRYID));
if (isset($storeprops[PR_IPM_WASTEBASKET_ENTRYID])) {
$wastebasket = mapi_msgstore_openentry($this->store, $storeprops[PR_IPM_WASTEBASKET_ENTRYID]);
$wastebasket = mapi_msgstore_openentry($this->defaultstore, $storeprops[PR_IPM_WASTEBASKET_ENTRYID]);
$wastebasketprops = mapi_getprops($wastebasket, array(PR_SOURCE_KEY));
if (isset($wastebasketprops[PR_SOURCE_KEY])) {
$this->wastebasket = bin2hex($wastebasketprops[PR_SOURCE_KEY]);
......
......@@ -2437,11 +2437,13 @@ class MAPIProvider {
* @return boolean
*/
private function setMessageBodyForType($mapimessage, $bpReturnType, &$message) {
$truncateHtmlSafe = false;
//default value is PR_BODY
$property = PR_BODY;
switch ($bpReturnType) {
case SYNC_BODYPREFERENCE_HTML:
$property = PR_HTML;
$truncateHtmlSafe = true;
break;
case SYNC_BODYPREFERENCE_RTF:
$property = PR_RTF_COMPRESSED;
......@@ -2473,11 +2475,11 @@ class MAPIProvider {
elseif (isset($message->internetcpid) && $bpReturnType == SYNC_BODYPREFERENCE_HTML) {
// if PR_HTML is UTF-8 we can stream it directly, else we have to convert to UTF-8 & wrap it
if (Utils::GetCodepageCharset($message->internetcpid) == "utf-8") {
$message->asbody->data = MAPIStreamWrapper::Open($stream);
$message->asbody->data = MAPIStreamWrapper::Open($stream, $truncateHtmlSafe);
}
else {
$body = $this->mapiReadStream($stream, $streamsize);
$message->asbody->data = StringStreamWrapper::Open(Utils::ConvertCodepageStringToUtf8($message->internetcpid, $body));
$message->asbody->data = StringStreamWrapper::Open(Utils::ConvertCodepageStringToUtf8($message->internetcpid, $body), $truncateHtmlSafe);
$message->internetcpid = INTERNET_CPID_UTF8;
}
}
......
......@@ -31,6 +31,7 @@ class MAPIStreamWrapper {
private $position;
private $streamlength;
private $toTruncate;
private $truncateHtmlSafe;
/**
* Opens the stream
......@@ -52,6 +53,7 @@ class MAPIStreamWrapper {
$this->position = 0;
$this->toTruncate = false;
$this->truncateHtmlSafe = (isset($contextOptions[self::PROTOCOL]['truncatehtmlsafe'])) ? $contextOptions[self::PROTOCOL]['truncatehtmlsafe'] : false;
// this is our stream!
$this->mapistream = $contextOptions[self::PROTOCOL]['stream'];
......@@ -65,7 +67,7 @@ class MAPIStreamWrapper {
$this->streamlength = 0;
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIStreamWrapper::stream_open(): initialized mapistream: %s streamlength: %d", $this->mapistream, $this->streamlength));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIStreamWrapper::stream_open(): initialized mapistream: %s - streamlength: %d - HTML-safe-truncate: %s", $this->mapistream, $this->streamlength, Utils::PrintAsString($this->truncateHtmlSafe)));
return true;
}
......@@ -95,7 +97,7 @@ class MAPIStreamWrapper {
// we need to truncate UTF8 compatible if ftruncate() was called
if ($this->toTruncate && $this->position >= $this->streamlength) {
$data = Utils::Utf8_truncate($data, $this->streamlength);
$data = Utils::Utf8_truncate($data, $this->streamlength, $this->truncateHtmlSafe);
}
return $data;
......@@ -175,13 +177,14 @@ class MAPIStreamWrapper {
/**
* Instantiates a MAPIStreamWrapper
*
* @param mapistream $mapistream The stream to be wrapped
* @param mapistream $mapistream The stream to be wrapped
* @param boolean $truncatehtmlsafe Indicates if a truncation should be done html-safe - default: false
*
* @access public
* @return MAPIStreamWrapper
*/
static public function Open($mapistream) {
$context = stream_context_create(array(self::PROTOCOL => array('stream' => &$mapistream)));
static public function Open($mapistream, $truncatehtmlsafe = false) {
$context = stream_context_create(array(self::PROTOCOL => array('stream' => &$mapistream, 'truncatehtmlsafe' => $truncatehtmlsafe)));
return fopen(self::PROTOCOL . "://",'r', false, $context);
}
}
......
......@@ -188,7 +188,7 @@ class ASDevice extends StateObject {
unset($this->forceSave);
unset($this->newdevice);
unset($this->ignoredMessageIds);
unset($this->backend2folderidCache);
$this->backend2folderidCache = false;
if (isset($this->ignoredmessages) && is_array($this->ignoredmessages)) {
$imessages = $this->ignoredmessages;
......
......@@ -216,16 +216,27 @@ class Ping extends RequestProcessor {
else
$changes = $fakechanges;
$announceAggregated = false;
if (count($changes) > 1) {
$announceAggregated = 0;
}
foreach ($changes as $folderid => $changecount) {
if ($changecount > 0) {
self::$encoder->startTag(SYNC_PING_FOLDER);
self::$encoder->content($folderid);
self::$encoder->endTag();
if (empty($fakechanges))
if ($announceAggregated === false) {
self::$topCollector->AnnounceInformation(sprintf("Found change in %s", $sc->GetCollection($folderid)->GetContentClass()), true);
}
else {
$announceAggregated += $changecount;
}
self::$deviceManager->AnnounceProcessStatus($folderid, SYNC_PINGSTATUS_CHANGES);
}
}
if ($announceAggregated !== false) {
self::$topCollector->AnnounceInformation(sprintf("Found %d changes in %d folders", $announceAggregated, count($changes)), true);
}
self::$encoder->endTag();
}
elseif($pingstatus == SYNC_PINGSTATUS_HBOUTOFRANGE){
......
......@@ -30,6 +30,7 @@ class StringStreamWrapper {
private $stringstream;
private $position;
private $stringlength;
private $truncateHtmlSafe;
/**
* Opens the stream
......@@ -53,9 +54,10 @@ class StringStreamWrapper {
// this is our stream!
$this->stringstream = $contextOptions[self::PROTOCOL]['string'];
$this->truncateHtmlSafe = (isset($contextOptions[self::PROTOCOL]['truncatehtmlsafe'])) ? $contextOptions[self::PROTOCOL]['truncatehtmlsafe'] : false;
$this->stringlength = strlen($this->stringstream);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("StringStreamWrapper::stream_open(): initialized stream length: %d", $this->stringlength));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("StringStreamWrapper::stream_open(): initialized stream length: %d - HTML-safe-truncate: %s", $this->stringlength, Utils::PrintAsString($this->truncateHtmlSafe)));
return true;
}
......@@ -135,7 +137,7 @@ class StringStreamWrapper {
*/
public function stream_truncate ($new_size) {
// cut the string!
$this->stringstream = Utils::Utf8_truncate($this->stringstream, $new_size);
$this->stringstream = Utils::Utf8_truncate($this->stringstream, $new_size, $this->truncateHtmlSafe);
$this->stringlength = strlen($this->stringstream);
if ($this->position > $this->stringlength) {
......@@ -161,13 +163,14 @@ class StringStreamWrapper {
/**
* Instantiates a StringStreamWrapper
*
* @param string $string The string to be wrapped
* @param string $string The string to be wrapped
* @param boolean $truncatehtmlsafe Indicates if a truncation should be done html-safe - default: false
*
* @access public
* @return StringStreamWrapper
*/
static public function Open($string) {
$context = stream_context_create(array(self::PROTOCOL => array('string' => &$string)));
static public function Open($string, $truncatehtmlsafe = false) {
$context = stream_context_create(array(self::PROTOCOL => array('string' => &$string, 'truncatehtmlsafe' => $truncatehtmlsafe)));
return fopen(self::PROTOCOL . "://",'r', false, $context);
}
}
......
......@@ -380,11 +380,13 @@ class Utils {
*
* If it's not possible to truncate properly, an empty string is returned
*
* @param string $string - the string
* @param string $length - position where string should be cut
* @param string $string the string
* @param string $length position where string should be cut
* @param boolean $htmlsafe doesn't cut html tags in half, doesn't ensure correct html - default: false
*
* @return string truncated string
*/
static public function Utf8_truncate($string, $length) {
static public function Utf8_truncate($string, $length, $htmlsafe = false) {
// make sure length is always an interger
$length = (int)$length;
......@@ -393,6 +395,16 @@ class Utils {
$length = strlen($string) - 1;
}
// The intent is not to cut HTML tags in half which causes displaying issues (see ZP-1240).
// The used method just tries to cut outside of tags, without checking tag validity and closing tags.
if ($htmlsafe) {
$offset = 0 - strlen($string) + $length;
$validPos = strrpos($string, "<", $offset);
if ($validPos > strrpos($string, ">", $offset)) {
$length = $validPos;
}
}
while($length >= 0) {
if ((ord($string[$length]) < 0x80) || (ord($string[$length]) >= 0xC0)) {
return substr($string, 0, $length);
......
......@@ -683,7 +683,18 @@ class ZPushAdmin {
return false;
}
$status = $device->SetAdditionalFolderList($set_store, $set_folders);
// check if any of the folders sent is in the statically configured list
$set_folders_checked = array();
$current_folders = ZPush::GetAdditionalSyncFolders();
foreach($set_folders as $f) {
if (isset($current_folders[$f['folderid']]) && substr($current_folders[$f['folderid']]->serverid, 0, 1) == DeviceManager::FLD_ORIGIN_CONFIG) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::AdditionalFolderSetList(): Ignoring folder '%s' id '%s' as it's a statically configured folder", $f['name'], $f['folderid']));
continue;
}
$set_folders_checked[] = $f;
}
$status = $device->SetAdditionalFolderList($set_store, $set_folders_checked);
if ($status && $device->GetData() !== false) {
ZPush::GetStateMachine()->SetState($device->GetData(), $devid, IStateMachine::DEVICEDATA);
}
......
......@@ -835,7 +835,7 @@ class ZPushAdminCLI {
$syncfolderid = $device->GetFolderIdForBackendId($df['folderid'], false, false, null);
switch($df['type']) {
case SYNC_FOLDER_TYPE_USER_APPOINTMENT:
if ($name == KOE_GAB_NAME) {
if ($df['name'] == KOE_GAB_NAME) {
$gentype = "GAB";
}
else {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment