diff --git a/sources/INSTALL b/sources/INSTALL index a989fc5..f870974 100644 --- a/sources/INSTALL +++ b/sources/INSTALL @@ -4,21 +4,19 @@ Installing Z-Push Requirements ------------ -Z-Push 2 runs only on PHP 5.1 or later +Z-Push 2 runs only on PHP 5.3 or later A PEAR dependency as in previous versions does not exist in Z-Push 2. The PHP version requirement is met in these distributions and versions (or later). -Debian 4.0 (etch) -Ubuntu 8.04 (hardy heron) -RHEL/CentOS 5.5 -Fedora 5 (bordeaux) -OpenSuse 10.1 -Slackware 12.0 -Gentoo 2006.1 -FreeBSD 6.1 -OpenBSD 4.0 -Mandriva 2007 +Debian 6.0 (squeeze) +Ubuntu 10.04 (LTS lucid) +RHEL/CentOS 6 (You can use SCL for newer version without overwriting system files: https://www.softwarecollections.org) +Fedora 12 (constantine) +OpenSuse 11.2 +Slackware 13.37 +FreeBSD 7.4 +OpenBSD 5.0 If your distribution is not listed here, you can check which PHP version is default for it at http://distrowatch.com/. @@ -34,22 +32,26 @@ additional php packages are required. These provide SOAP support, access to process control and shared memory. These packages vary in names between the distributions. -- Generally install the packages: php-cli php-soap +- Generally install the packages: php-cli php-soap - On Suse (SLES & OpenSuse) install the packages: php53 php53-soap php53-pcntl php53-sysvshm php53-sysvsem php53-posix - On RHEL based systems install the package: php-cli php-soap php-process In order to install these packages you need to add an extra channel subscription from the RHEL Server Optional channel. +Be aware that each backend can have their own requirements. Take a look to the +REQUIREMENTS file inside its folder for more information. + + How to install -------------- To install Z-Push, simply untar the z-push archive, e.g. with: tar -xzvf z-push-[version]-{buildnr}.tar.gz - + The tar contains a folder which has the following structure: z-push-[version]-{buildnr} - + The contents of this folder should be copied to /usr/share/z-push. In a case that /usr/share/z-push does not exist yet, create it with: mkdir -p /usr/share/z-push @@ -70,7 +72,7 @@ your apache process or make the directories world writeable: chmod 755 /var/lib/z-push /var/log/z-push chown apache:apache /var/lib/z-push /var/log/z-push - + For the default webserver user please refer to your distribution's manual. Now, you must configure Apache to redirect the URL @@ -122,7 +124,7 @@ FileStateMachine (which is default). In order to make this possible, you either need to disable the php-safe-mode in php.ini or .htaccess with php_admin_flag safe_mode off or configure it accordingly, so Z-Push is allowed to write to the -log and state directories. +log and state directories. After doing this, you should be able to synchronize with your mobile device. @@ -217,7 +219,7 @@ property. It should be mapped to 0x3A220102 in ldap.propmap.cfg (0x3A220102 = userCertificate). Make sure it looks like this in LDAP: userCertificate;binary - MIIFGjCCBAKgAwIBAgIQbRnqpxlPa… + MIIFGjCCBAKgAwIBAgIQbRnqpxlPa… *Important* It is strongly recommended to use MS AD or LDAP to manage certificates. @@ -273,11 +275,11 @@ synchronisation. *every* user on the system. You can set a different log level for particular users by adding them comma separated to $specialLogUsers in the config.php e.g. $specialLogUsers = array("user1", "user2", "user3"); - - *NOTE* Be aware that if you are using LOGLEVEL_DEBUG and LOGLEVEL_WBXML + +*NOTE* Be aware that if you are using LOGLEVEL_DEBUG and LOGLEVEL_WBXML Z-Push will be quite talkative, so it is advisable to use log-rotate on the log file. - + *Repeated incorrect password messages* If a password contains characters which are encoded differently in ISO-8859-1 and Windows-1252 encodings (e.g. "§") the login might fail with Z-Push but diff --git a/sources/NOTES b/sources/NOTES new file mode 100644 index 0000000..cab93eb --- /dev/null +++ b/sources/NOTES @@ -0,0 +1,4 @@ +Run composer to update autoinclude +================================== +curl -sS https://getcomposer.org/installer | php +php composer.phar dump-autoload -o diff --git a/sources/README.md b/sources/README.md index aca9208..b10db1a 100644 --- a/sources/README.md +++ b/sources/README.md @@ -1,6 +1,8 @@ Z-Push-contrib ============== +[![Join the chat at https://gitter.im/fmbiete/Z-Push-contrib](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/fmbiete/Z-Push-contrib?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + This is a Z-Push fork with changes that I will try to put into the contrib branch, so they can get into the official Z-Push IMPORTANT: @@ -31,9 +33,9 @@ You can find some configuration guidelines in the Wiki https://github.com/fmbiet Requisites ========== -- PHP 5.5 (5.3 should also work, 5.4 it's fine, but 5.5 is better) +- PHP 5.x (5.3 it's the minimum supported) using PHP-FPM or MOD_PHP +- HHVM 3.6 or newer, instead of PHP - NGINX or APACHE -- PHP-FPM or MOD_PHP Configuration ============= diff --git a/sources/autodiscover/INSTALL b/sources/autodiscover/INSTALL index 5f334d5..1128816 100644 --- a/sources/autodiscover/INSTALL +++ b/sources/autodiscover/INSTALL @@ -1,208 +1,177 @@ -Z-Push AutoDiscover manual --------------------------- -This manual gives an introduction to the Z-Push AutoDiscover service, discusses -technical details and explains the installation. - -Introduction ------------- -AutoDiscover is the service used to simplify the configuration of collaboration -accounts for clients, especially for mobile phones. -While in the past the user was required to enter the server name, user name and -password manually into his mobile phone in order to connect, with AutoDiscover -the user is only required to fill in his email address and the password. -AutoDiscover will try several methods to reach the correct server automatically. - - -How does it work? ------------------ -When speaking about AutoDiscover, this includes two distinct realms: -- AutoDiscover is a specification which defines the steps a client should take - in order to contact a service to request additional data. -- The AutoDiscover service is piece of software which accepts requests from the - clients, authenticates them, requests some additional data from the - collaboration server and sends this data back to the client. -The specification suggests several ways for client to contact the responsible -server to receive additional information. Tests have shown, that basically all -mobile phones tested support only the most basic ways. These are sufficient for -almost all types of scenarios and are the ones implemented by Z-Push AutoDiscover. -Please refer to the Mobile Compatibility List (http://z-push.sf.net/compatibility) -for an overview of supported and tested devices. -The used email address is the key for the process. The client splits it up into -the local and domain part (before and after the @-sign). The client then tries -to connect to this domain in order to get in contact with the AutoDiscover -service. The local part of the email address is used as "login" to the -AutoDiscover service. There is also an option, to use the full email address as -login name (see "Configuration" section below for details). - - - --------------- - | Client | - | e.g. mobile | - --------------- - / \ - 1. Searches for / \ 2. Data access - information / \ - / \ - V V - ---------------- -------------- - | AutoDiscover | redirects to | Z-Push | - | | --------------------> | ActiveSync | - ---------------- -------------- - \ / - Authen- \ / Synchronizes - ticates \ / - via Z-Push \ / - Backend V V - ----------------- - | Collaboration | - | Platform | - ----------------- - -Requirements ------------- -As described in the previous chapter, the local part of the email address or -the email address is used in order to log in. -Your configuration requires that this type of login is possible: -- either the user name is used to login and must be used in the email address - entered on the mobile, or -- the entire email address is used to login. - -Which option is used has to be configured in the AutoDiscover configuration and -in the underlying platform (e.g. ZCP (hosting mode)). -Most companies use the user name as local part of the email by default. From the -AutoDiscover point of view, it is not required that user is able to receive -emails at the used email address. It is recommended allowing that in order not -to confuse end users. - -AutoDiscover also requires a valid SSL certificate to work as expected. A very -little percentage of mobiles support self-signed certificates (showing a -pop-up alerting the user). Most mobiles silently ignore self-signed certificates -and just declare the AutoDiscover process as failed in such cases. -If AutoDiscover fails, the user is generally redirected to the -"manual configuration" of the client. - -If you do not plan to acquire an official certificate, you will probably not be -able to use the AutoDiscover service. -Depending on your setup, it could be necessary to add new DNS entries for your -mail domain. - - -Domain setup ------------- -There are two general ways the AutoDiscover process can be configured: -1. Directly with "yourdomain.com" website ("www.yourdomain.com" will most - probably not work) -2. With the sub-domain "autodiscover.yourdomain.com" -In both cases, an official SSL certificate is required. If you already have a -certificate for your domain, the webserver answering for that domain could be -reconfigured to allow AutoDiscover requests as well. In the case that you do -not have direct access to this type of configuration (e.g. hosting provider), -it's recommended to acquire a dedicated certificate for -"autodiscover.yourdomain.com". Please note, that this sub-domain can NOT be -renamed. In general, "wildcard" certificates can be used, as long they are -valid for the required domain. - - -Software requirements ---------------------- -Like Z-Push, AutoDiscover is written in PHP, where PHP 5.1 or newer is required. -Please consult the Z-Push INSTALL file for further information about PHP versions. -If only AutoDiscover is to be executed on a host, the Z-Push PHP dependencies do -NOT need to be installed. -AutoDiscover has one direct dependency, the php-xml parser library. - -These packages vary in names between the distributions. -- Generally install the packages: php-xml -- On Suse (SLES & OpenSuse) install the packages: php53-xml -- On RHEL based systems install the package: php-xml - - -Installation ------------- -AutoDiscover is part of the Z-Push package and uses some of the functionality -available in Z-Push. -It is possible to install AutoDiscover on the same host as Z-Push, or to -install them on different hosts. - -Currently, independently from the setup, it's recommended to extract the entire -z-push tarball and configure the services as required. -Please follow the install instructions from the Z-Push INSTALL file (section -"How to install") to copy the files to your server. -If you do not want to setup Z-Push on the host, do not add the "Alias" for -ActiveSync. - -To setup the SSL certificate, please refer to one of the many setup guides -available on the internet, like that one: -http://www.apache.com/resources/how-to-setup-an-ssl-certificate-on-apache/ - -The mobiles requests these URLs (where "yourdomain.com" corresponds to the -domain part of the email used in the client): - https://yourdomain.com/Autodiscover/Autodiscover.xml and/or - https://autodiscover.yourdomain.com/Autodiscover/Autodiscover.xml - -Add the following line to the apache site configuration file. - AliasMatch (?i)/Autodiscover/Autodiscover.xml "/usr/share/z-push/autodiscover/autodiscover.php" - -This line assumes that Z-Push is installed in /usr/share/z-push. If the path -is different, please adjust it accordingly. - -Note: some mobiles use different casings, like "AutoDiscover" in the URL. The -above statement is valid for these as well. - -Please restart Apache afterwards. - - -Configuration -------------- -There are several parameters in the configuration file, which allow to customize -the behaviour of the AutoDiscover Service. -The configuration, generally is located in the z-push/autodiscover directory and -is called "config.php". - -The parameters: -BASE_PATH This property specifies where the AutoDiscover files are - located. Normally there is no need to adjust this parameter. -SERVERURL This is the full URL where the Z-Push server is available. - You should adjust it to the domain/server where Z-Push is - installed. - -USE_FULLEMAIL_FOR_LOGIN If this is set to "true", AutoDiscover will attempt to - login on the collaboration server with the full email - address sent by the client. If disabled (default), the - local part of the email address is used. - -LOGFILEDIR The directory where logfiles are created. - -LOGFILE The default AutoDiscover log file. - -LOGERRORFILE The default AutoDiscover error log file. - -LOGLEVEL The loglevel, set it to WBXML to see the data received - and sent from/to clients. - -LOGAUTHFAIL Set to true, to explicitly log failed login attempts. - -BACKEND_PROVIDER The backend to be used. If empty (default) the code - will auto detect which backend to use. - -Please note: the desired backend also needs to be configured, in the -"backends//config.php" file. - - -Test installation ------------------ -If everything is correct, accessing with a browser the URL for your setup, you -should see: - 1. a pop-up asking for your username + password. Always use the email - address which you would also enter on the mobile (independently from - the configuration). - 2. if the authentication was successful, you will see a Z-Push informational - page (like when accessing the Z-Push location). - -Note: The same test can also be performed in the mobiles web browser to check -if the access works correctly from the mobile network. - -If the authentication fails, please check the configuration options of AutoDiscover. -Also check the logfiles for possible failures. - -If the manual method works, try setting up your mobile phone! :) +Z-Push AutoDiscover manual +-------------------------- +This manual gives an introduction to the Z-Push AutoDiscover service, discusses +technical details and explains the installation. + +Introduction +------------ +AutoDiscover is the service used to simplify the configuration of collaboration +accounts for clients, especially for mobile phones. +While in the past the user was required to enter the server name, user name and +password manually into his mobile phone in order to connect, with AutoDiscover +the user is only required to fill in his email address and the password. +AutoDiscover will try several methods to reach the correct server automatically. + + +How does it work? +----------------- +When speaking about AutoDiscover, this includes two distinct realms: +- AutoDiscover is a specification which defines the steps a client should take + in order to contact a service to request additional data. +- The AutoDiscover service is piece of software which accepts requests from the + clients, authenticates them, requests some additional data from the + collaboration server and sends this data back to the client. +The specification suggests several ways for client to contact the responsible +server to receive additional information. Tests have shown, that basically all +mobile phones tested support only the most basic ways. These are sufficient for +almost all types of scenarios and are the ones implemented by Z-Push AutoDiscover. +Please refer to the Mobile Compatibility List (http://z-push.sf.net/compatibility) +for an overview of supported and tested devices. +The used email address is the key for the process. The client splits it up into +the local and domain part (before and after the @-sign). The client then tries +to connect to this domain in order to get in contact with the AutoDiscover +service. The local part of the email address is used as "login" to the +AutoDiscover service. There is also an option, to use the full email address as +login name (see "Configuration" section below for details). + + + --------------- + | Client | + | e.g. mobile | + --------------- + / \ + 1. Searches for / \ 2. Data access + information / \ + / \ + V V + ---------------- -------------- + | AutoDiscover | redirects to | Z-Push | + | | --------------------> | ActiveSync | + ---------------- -------------- + \ / + Authen- \ / Synchronizes + ticates \ / + via Z-Push \ / + Backend V V + ----------------- + | Collaboration | + | Platform | + ----------------- + +Requirements +------------ +As described in the previous chapter, the local part of the email address or +the email address is used in order to log in. +Your configuration requires that this type of login is possible: +- either the user name is used to login and must be used in the email address + entered on the mobile, or +- the entire email address is used to login. + +Which option is used has to be configured in the AutoDiscover configuration and +in the underlying platform (e.g. ZCP (hosting mode)). +Most companies use the user name as local part of the email by default. From the +AutoDiscover point of view, it is not required that user is able to receive +emails at the used email address. It is recommended allowing that in order not +to confuse end users. + +AutoDiscover also requires a valid SSL certificate to work as expected. A very +little percentage of mobiles support self-signed certificates (showing a +pop-up alerting the user). Most mobiles silently ignore self-signed certificates +and just declare the AutoDiscover process as failed in such cases. +If AutoDiscover fails, the user is generally redirected to the +"manual configuration" of the client. + +If you do not plan to acquire an official certificate, you will probably not be +able to use the AutoDiscover service. +Depending on your setup, it could be necessary to add new DNS entries for your +mail domain. + + +Domain setup +------------ +There are two general ways the AutoDiscover process can be configured: +1. Directly with "yourdomain.com" website ("www.yourdomain.com" will most + probably not work) +2. With the sub-domain "autodiscover.yourdomain.com" +In both cases, an official SSL certificate is required. If you already have a +certificate for your domain, the webserver answering for that domain could be +reconfigured to allow AutoDiscover requests as well. In the case that you do +not have direct access to this type of configuration (e.g. hosting provider), +it's recommended to acquire a dedicated certificate for +"autodiscover.yourdomain.com". Please note, that this sub-domain can NOT be +renamed. In general, "wildcard" certificates can be used, as long they are +valid for the required domain. + + +Software requirements +--------------------- +Like Z-Push, AutoDiscover is written in PHP, where PHP 5.3 or newer is required. +Please consult the Z-Push INSTALL file for further information about PHP versions. +If only AutoDiscover is to be executed on a host, the Z-Push PHP dependencies do +NOT need to be installed. +AutoDiscover has one direct dependency, the php-xml parser library. + +These packages vary in names between the distributions. +- Generally install the packages: php-xml +- On Suse (SLES & OpenSuse) install the packages: php53-xml +- On RHEL based systems install the package: php-xml + + +Installation +------------ +AutoDiscover is part of the Z-Push package and uses some of the functionality +available in Z-Push. +It is possible to install AutoDiscover on the same host as Z-Push, or to +install them on different hosts. + +Currently, independently from the setup, it's recommended to extract the entire +z-push tarball and configure the services as required. +Please follow the install instructions from the Z-Push INSTALL file (section +"How to install") to copy the files to your server. +If you do not want to setup Z-Push on the host, do not add the "Alias" for +ActiveSync. + +To setup the SSL certificate, please refer to one of the many setup guides +available on the internet, like that one: +http://www.apache.com/resources/how-to-setup-an-ssl-certificate-on-apache/ + +The mobiles requests these URLs (where "yourdomain.com" corresponds to the +domain part of the email used in the client): + https://yourdomain.com/Autodiscover/Autodiscover.xml and/or + https://autodiscover.yourdomain.com/Autodiscover/Autodiscover.xml + +Add the following line to the apache site configuration file. + AliasMatch (?i)/Autodiscover/Autodiscover.xml$ "/usr/share/z-push/autodiscover/autodiscover.php" + +This line assumes that Z-Push is installed in /usr/share/z-push. If the path +is different, please adjust it accordingly. + +Note: some mobiles use different casings, like "AutoDiscover" in the URL. The +above statement is valid for these as well. + +Please restart Apache afterwards. + + +Configuration +------------- +You don't need extra configuration for the AutoDiscover Service. It will use +the configuration already defined for the main Z-Push Service. + + +Test installation +----------------- +If everything is correct, accessing with a browser the URL for your setup, you +should see: + 1. a pop-up asking for your username + password. Always use the email + address which you would also enter on the mobile (independently from + the configuration). + 2. if the authentication was successful, you will see a Z-Push informational + page (like when accessing the Z-Push location). + +Note: The same test can also be performed in the mobiles web browser to check +if the access works correctly from the mobile network. + +If the authentication fails, please check the configuration options of AutoDiscover. +Also check the logfiles for possible failures. + +If the manual method works, try setting up your mobile phone! :) diff --git a/sources/autodiscover/autodiscover.php b/sources/autodiscover/autodiscover.php index 301ebc7..a22cb71 100644 --- a/sources/autodiscover/autodiscover.php +++ b/sources/autodiscover/autodiscover.php @@ -41,19 +41,8 @@ * Consult LICENSE file for details ************************************************/ -include_once('../lib/core/zpushdefs.php'); -include_once('../lib/exceptions/exceptions.php'); -include_once('../lib/utils/utils.php'); -include_once('../lib/core/zpush.php'); -include_once('../lib/core/zlog.php'); -include_once('../lib/interface/ibackend.php'); -include_once('../lib/interface/ichanges.php'); -include_once('../lib/interface/iexportchanges.php'); -include_once('../lib/interface/iimportchanges.php'); -include_once('../lib/interface/isearchprovider.php'); -include_once('../lib/interface/istatemachine.php'); -include_once('../version.php'); -include_once('config.php'); +require_once '../vendor/autoload.php'; +require_once '../config.php'; class ZPushAutodiscover { const ACCEPTABLERESPONSESCHEMA = 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006'; @@ -120,9 +109,8 @@ class ZPushAutodiscover { else { ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover incorrect request: '%s'", $ex->getMessage())); } - header('HTTP/1.1 401 Unauthorized'); - header('WWW-Authenticate: Basic realm="ZPush"'); http_response_code(401); + header('WWW-Authenticate: Basic realm="ZPush"'); } catch (ZPushException $ex) { ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover because of ZPushException. Error: %s", $ex->getMessage())); @@ -220,12 +208,13 @@ class ZPushAutodiscover { * @return string */ private function createResponse($email, $userFullname) { + $server_url = 'https://' . $_SERVER['SERVER_NAME'] . '/Microsoft-Server-ActiveSync'; $xml = file_get_contents('response.xml'); $response = new SimpleXMLElement($xml); $response->Response->User->DisplayName = $userFullname; $response->Response->User->EMailAddress = $email; - $response->Response->Action->Settings->Server->Url = SERVERURL; - $response->Response->Action->Settings->Server->Name = SERVERURL; + $response->Response->Action->Settings->Server->Url = $server_url; + $response->Response->Action->Settings->Server->Name = $server_url; $response = $response->asXML(); ZLog::Write(LOGLEVEL_WBXML, sprintf("ZPushAutodiscover->createResponse() XML response:%s%s", PHP_EOL, $response)); return $response; @@ -259,10 +248,9 @@ class ZPushAutodiscover { if (isset($userDetails[$attrib]) && $userDetails[$attrib]) { return $userDetails[$attrib]; } - ZLog::Write(LOGLEVEL_WARN, sprintf("The backend was not able to find attribute '%s' of the user. Fall back to the default value.")); + ZLog::Write(LOGLEVEL_WARN, sprintf("The backend was not able to find attribute '%s' of the user. Fall back to the default value.", $attrib)); return false; } } ZPushAutodiscover::DoZPushAutodiscover(); -?> \ No newline at end of file diff --git a/sources/autodiscover/config.php b/sources/autodiscover/config.php deleted file mode 100644 index 5403b59..0000000 --- a/sources/autodiscover/config.php +++ /dev/null @@ -1,89 +0,0 @@ -. -* -* Consult LICENSE file for details -************************************************/ - -/********************************************************************************** - * Default settings - */ - // Defines the base path on the server - define('BASE_PATH', dirname($_SERVER['SCRIPT_FILENAME']). '/'); - - // The Z-Push server location for the autodiscover response - define('SERVERURL', 'https://localhost/Microsoft-Server-ActiveSync'); - - /* - * Whether to use the complete email address as a login name - * (e.g. user@company.com) or the username only (user). - * Possible values: - * false - use the username only (default). - * true - use the complete email address. - */ - define('USE_FULLEMAIL_FOR_LOGIN', false); - -/********************************************************************************** - * Logging settings - * Possible LOGLEVEL and LOGUSERLEVEL values are: - * LOGLEVEL_OFF - no logging - * LOGLEVEL_FATAL - log only critical errors - * LOGLEVEL_ERROR - logs events which might require corrective actions - * LOGLEVEL_WARN - might lead to an error or require corrective actions in the future - * LOGLEVEL_INFO - usually completed actions - * LOGLEVEL_DEBUG - debugging information, typically only meaningful to developers - * LOGLEVEL_WBXML - also prints the WBXML sent to/from the device - * LOGLEVEL_DEVICEID - also prints the device id for every log entry - * LOGLEVEL_WBXMLSTACK - also prints the contents of WBXML stack - * - * The verbosity increases from top to bottom. More verbose levels include less verbose - * ones, e.g. setting to LOGLEVEL_DEBUG will also output LOGLEVEL_FATAL, LOGLEVEL_ERROR, - * LOGLEVEL_WARN and LOGLEVEL_INFO level entries. - */ - define('LOGFILEDIR', '/var/log/z-push/'); - define('LOGFILE', LOGFILEDIR . 'autodiscover.log'); - define('LOGERRORFILE', LOGFILEDIR . 'autodiscover-error.log'); - define('LOGLEVEL', LOGLEVEL_INFO); - define('LOGUSERLEVEL', LOGLEVEL); -/********************************************************************************** - * Backend settings - */ - // the backend data provider - define('BACKEND_PROVIDER', ''); -?> \ No newline at end of file diff --git a/sources/backend/caldav/caldav.php b/sources/backend/caldav/caldav.php index c8d3b9a..a937381 100644 --- a/sources/backend/caldav/caldav.php +++ b/sources/backend/caldav/caldav.php @@ -46,25 +46,20 @@ // config file require_once("backend/caldav/config.php"); -include_once('lib/default/diffbackend/diffbackend.php'); -include_once('include/z_caldav.php'); -include_once('include/z_RTF.php'); -include_once('include/iCalendar.php'); - class BackendCalDAV extends BackendDiff { + /** + * @var CalDAVClient + */ private $_caldav; private $_caldav_path; private $_collection = array(); - private $_username; private $changessinkinit; private $sinkdata; private $sinkmax; - /** * Constructor - * */ public function BackendCalDAV() { if (!function_exists("curl_init")) { @@ -81,18 +76,17 @@ class BackendCalDAV extends BackendDiff { * @see IBackend::Logon() */ public function Logon($username, $domain, $password) { - $this->_username = $username; $this->_caldav_path = str_replace('%u', $username, CALDAV_PATH); - $this->_caldav = new CalDAVClient(CALDAV_SERVER . ":" . CALDAV_PORT . $this->_caldav_path, $username, $password); - $options = $this->_caldav->DoOptionsRequest(); - if (isset($options["PROPFIND"])) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->Logon(): User '%s' is authenticated on CalDAV", $username)); - return true; + $url = sprintf("%s://%s:%d%s", CALDAV_PROTOCOL, CALDAV_SERVER, CALDAV_PORT, $this->_caldav_path); + $this->_caldav = new CalDAVClient($url, $username, $password); + if ($connected = $this->_caldav->CheckConnection()) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->Logon(): User '%s' is authenticated on CalDAV '%s'", $username, $url)); } else { - ZLog::Write(LOGLEVEL_WARN, sprintf("BackendCalDAV->Logon(): User '%s' is not authenticated on CalDAV", $username)); - return false; + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendCalDAV->Logon(): User '%s' is not authenticated on CalDAV '%s'", $username, $url)); } + + return $connected; } /** @@ -100,14 +94,18 @@ class BackendCalDAV extends BackendDiff { * @see IBackend::Logoff() */ public function Logoff() { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->Logoff()")); - $this->_caldav = null; + if ($this->_caldav != null) { + $this->_caldav->Disconnect(); + unset($this->_caldav); + } $this->SaveStorages(); unset($this->sinkdata); unset($this->sinkmax); + ZLog::Write(LOGLEVEL_DEBUG, "BackendCalDAV->Logoff(): disconnected from CALDAV server"); + return true; } @@ -306,15 +304,14 @@ class BackendCalDAV extends BackendDiff { } else { $etag = "*"; - $date = gmdate("Ymd\THis\Z"); - $random = hash("md5", microtime()); - $id = $date . "-" . $random . ".ics"; + $id = sprintf("%s-%s.ics", gmdate("Ymd\THis\Z"), hash("md5", microtime())); } - $data = $this->_ParseASToVCalendar($message, $folderid, substr($id, 0, strlen($id)-4)); - $url = $this->_caldav_path . substr($folderid, 1) . "/" . $id; - $etag_new = $this->_caldav->DoPUTRequest($url, $data, $etag); + + $data = $this->_ParseASToVCalendar($message, $folderid, substr($id, 0, strlen($id) - 4)); + + $etag_new = $this->CreateUpdateCalendar($data, $url, $etag); $item = array(); $item['href'] = $id; @@ -333,21 +330,6 @@ class BackendCalDAV extends BackendDiff { return false; } - /** - * Changes the 'star' flag of a message on disk - * - * @param string $folderid id of the folder - * @param string $id id of the message - * @param int $flags star flag of the message - * - * @access public - * @return boolean status of the operation - * @throws StatusException could throw specific SYNC_STATUS_* exceptions - */ - public function SetStarFlag($folderid, $id, $flags, $contentParameters) { - return false; - } - /** * Delete a message from the CalDAV server. * @see BackendDiff::DeleteMessage() @@ -356,10 +338,7 @@ class BackendCalDAV extends BackendDiff { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->DeleteMessage('%s','%s')", $folderid, $id)); $url = $this->_caldav_path . substr($folderid, 1) . "/" . $id; $http_status_code = $this->_caldav->DoDELETERequest($url); - if ($http_status_code == "204") { - return true; - } - return false; + return $http_status_code == "204"; } /** @@ -370,6 +349,51 @@ class BackendCalDAV extends BackendDiff { return false; } + /** + * Create or Update one event + * + * @access public + * @param $data string VCALENDAR text + * @param $url string URL for the calendar, if false a new calendar object is created + * @param $etag string ETAG for the calendar, if '*' is a new object + * @return array + */ + public function CreateUpdateCalendar($data, $url = false, $etag = "*") { + if ($url === false) { + $url = sprintf("%s%s/%s-%s.ics", $this->_caldav_path, CALDAV_PERSONAL, gmdate("Ymd\THis\Z"), hash("md5", microtime())); + $etag = "*"; + } + + return $this->_caldav->DoPUTRequest($url, $data, $etag); + } + + /** + * Deletes one VCALENDAR + * + * @access public + * @param $id string ID of the VCALENDAR + * @return boolean + */ + public function DeleteCalendar($id) { + $http_status_code = $this->_caldav->DoDELETERequest(sprintf("%s%s/%s", $this->_caldav_path, CALDAV_PERSONAL, $id)); + return $http_status_code == "204"; + } + + /** + * Finds one VCALENDAR + * + * @access public + * @param $uid string UID attribute + * @return array + */ + public function FindCalendar($uid) { + $filter = sprintf("%s", $uid); + + $events = $this->_caldav->DoCalendarQuery($filter, sprintf("%s%s", $this->_caldav_path, CALDAV_PERSONAL)); + + return $events; + } + /** * Indicates which AS version is supported by the backend. * @@ -444,57 +468,59 @@ class BackendCalDAV extends BackendDiff { return $notifications; } - while($stopat > time() && empty($notifications)) { + // only check once to reduce pressure in the DAV server + foreach ($this->sinkdata as $k => $v) { + $changed = false; - foreach ($this->sinkdata as $k => $v) { - $changed = false; + $url = $this->_caldav_path . substr($k, 1) . "/"; + $response = $this->_caldav->GetSync($url, false, CALDAV_SUPPORTS_SYNC); - $url = $this->_caldav_path . substr($k, 1) . "/"; - $response = $this->_caldav->GetSync($url, false, CALDAV_SUPPORTS_SYNC); - - if (CALDAV_SUPPORTS_SYNC) { - if (count($response) > 0) { - $changed = true; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->ChangesSink - Changes detected")); - } + 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 different, we know for sure, there are changes - if (count($response) != count($v)) { - $changed = true; + // If the numbers of events are equals, we compare the biggest date + // FIXME: we are comparing strings no dates + if (!isset($this->sinkmax[$k])) { + $this->sinkmax[$k] = ''; + for ($i = 0; $i < count($v); $i++) { + if ($v[$i]['getlastmodified'] > $this->sinkmax[$k]) { + $this->sinkmax[$k] = $v[$i]['getlastmodified']; + } + } + } + + for ($i = 0; $i < count($response); $i++) { + if ($response[$i]['getlastmodified'] > $this->sinkmax[$k]) { + $changed = true; + } + } + + if ($changed) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->ChangesSink - Changes detected")); } - else { - // If the numbers of events are equals, we compare the biggest date - // FIXME: we are comparing strings no dates - if (!isset($this->sinkmax[$k])) { - $this->sinkmax[$k] = ''; - for ($i = 0; $i < count($v); $i++) { - if ($v[$i]['getlastmodified'] > $this->sinkmax[$k]) { - $this->sinkmax[$k] = $v[$i]['getlastmodified']; - } - } - } - - for ($i = 0; $i < count($response); $i++) { - if ($response[$i]['getlastmodified'] > $this->sinkmax[$k]) { - $changed = true; - } - } - - if ($changed) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->ChangesSink - Changes detected")); - } - } - } - - if ($changed) { - $notifications[] = $k; } } - if (empty($notifications)) - sleep(5); + if ($changed) { + $notifications[] = $k; + } + } + + // Wait to timeout + if (empty($notifications)) { + while ($stopat > time()) { + sleep(1); + } } return $notifications; @@ -508,7 +534,8 @@ class BackendCalDAV extends BackendDiff { * @return SyncAppointment */ private function _ParseVEventToAS($data, $contentparameters) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_ParseVEventToAS(): Parsing VEvent")); + ZLog::Write(LOGLEVEL_DEBUG, "BackendCalDAV->_ParseVEventToAS(): Parsing VEvent"); + $truncsize = Utils::GetTruncSize($contentparameters->GetTruncation()); $message = new SyncAppointment(); @@ -700,7 +727,6 @@ class BackendCalDAV extends BackendDiff { $body = Utils::Utf8_truncate($body, $truncsize); $message->bodytruncated = 1; } else { - $body = $body; $message->bodytruncated = 0; } $body = str_replace("\n","\r\n", str_replace("\r","",$body)); @@ -739,6 +765,12 @@ class BackendCalDAV extends BackendDiff { } } + // Workaround #127 - No organizeremail defined + if (!isset($message->organizeremail)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_ParseVEventToSyncObject(): No organizeremail defined, using username")); + $message->organizeremail = $this->originalUsername; + } + $valarm = current($event->GetComponents("VALARM")); if ($valarm) { $properties = $valarm->GetProperties(); @@ -880,7 +912,7 @@ class BackendCalDAV extends BackendDiff { $ical = new iCalComponent(); $ical->SetType("VCALENDAR"); $ical->AddProperty("VERSION", "2.0"); - $ical->AddProperty("PRODID", "-//php-push//NONSGML PHP-Push Calendar//EN"); + $ical->AddProperty("PRODID", "-//z-push-contrib//NONSGML Z-Push-contrib Calendar//EN"); $ical->AddProperty("CALSCALE", "GREGORIAN"); if ($folderid[0] == "C") { @@ -950,9 +982,11 @@ class BackendCalDAV extends BackendDiff { if (isset($data->endtime)) { if ($data->alldayevent == 1) { $vevent->AddProperty("DTEND", $this->_GetDateFromUTC("Ymd", $data->endtime, $data->timezone), array("VALUE" => "DATE")); + $vevent->AddProperty("X-MICROSOFT-CDO-ALLDAYEVENT", "TRUE"); } else { $vevent->AddProperty("DTEND", gmdate("Ymd\THis\Z", $data->endtime)); + $vevent->AddProperty("X-MICROSOFT-CDO-ALLDAYEVENT", "FALSE"); } } if (isset($data->recurrence)) { @@ -1002,13 +1036,19 @@ class BackendCalDAV extends BackendDiff { switch ($data->meetingstatus) { case "1": $vevent->AddProperty("STATUS", "TENTATIVE"); + $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"); break; case "5": case "7": $vevent->AddProperty("STATUS", "CANCELLED"); + $vevent->AddProperty("X-MICROSOFT-CDO-BUSYSTATUS", "CANCELLED"); + $vevent->AddProperty("X-MICROSOFT-DISALLOW-COUNTER", "TRUE"); break; } } @@ -1017,11 +1057,15 @@ class BackendCalDAV extends BackendDiff { //Some phones doesn't send the organizeremail, so we gotto get it somewhere else. //Lets use the login here ($username) if (!isset($data->organizeremail)) { - $vevent->AddProperty("ORGANIZER", sprintf("MAILTO:%s", $this->_username)); + $vevent->AddProperty("ORGANIZER", sprintf("MAILTO:%s", $this->originalUsername)); } foreach ($data->attendees as $att) { - $att_str = sprintf("MAILTO:%s", $att->email); - $vevent->AddProperty("ATTENDEE", $att_str, array("CN" => $att->name)); + if (isset($att->name)) { + $vevent->AddProperty("ATTENDEE", sprintf("MAILTO:%s", $att->email), array("CN" => $att->name)); + } + else { + $vevent->AddProperty("ATTENDEE", sprintf("MAILTO:%s", $att->email)); + } } } if (isset($data->body)) { @@ -1034,6 +1078,13 @@ class BackendCalDAV extends BackendDiff { $vevent->AddProperty("CATEGORIES", implode(",", $data->categories)); } +// X-MICROSOFT-CDO-APPT-SEQUENCE:0 +// X-MICROSOFT-CDO-OWNERAPPTID:2113393086 +// X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +// X-MICROSOFT-CDO-IMPORTANCE:1 +// X-MICROSOFT-CDO-INSTTYPE:0 + + return $vevent; } @@ -1461,6 +1512,4 @@ class BackendCalDAV extends BackendDiff { } return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0)); } -} - -?> +} \ No newline at end of file diff --git a/sources/backend/caldav/config.php b/sources/backend/caldav/config.php index 3be2478..c42419c 100644 --- a/sources/backend/caldav/config.php +++ b/sources/backend/caldav/config.php @@ -45,23 +45,23 @@ // BackendCalDAV settings // ************************ -// Server address -define('CALDAV_SERVER', 'http://calendar.domain.com'); +// Server protocol: http or https +define('CALDAV_PROTOCOL', 'https'); -// Port -define('CALDAV_PORT', '80'); +// Server name +define('CALDAV_SERVER', 'caldavserver.domain.com'); + +// Server port +define('CALDAV_PORT', '443'); // Path define('CALDAV_PATH', '/caldav.php/%u/'); // Default CalDAV folder (calendar folder/principal). This will be marked as the default calendar in the mobile -define('CALDAV_PERSONAL', 'home'); +define('CALDAV_PERSONAL', 'PRINCIPAL'); // If the CalDAV server supports the sync-collection operation // DAViCal, SOGo and SabreDav support it // SabreDav version must be at least 1.9.0, otherwise set this to false // Setting this to false will work with most servers, but it will be slower -define('CALDAV_SUPPORTS_SYNC', false); - - -?> \ No newline at end of file +define('CALDAV_SUPPORTS_SYNC', false); \ No newline at end of file diff --git a/sources/backend/carddav/carddav.php b/sources/backend/carddav/carddav.php index afe15a0..653502d 100644 --- a/sources/backend/carddav/carddav.php +++ b/sources/backend/carddav/carddav.php @@ -44,14 +44,14 @@ // config file require_once("backend/carddav/config.php"); -include_once('lib/default/diffbackend/diffbackend.php'); -include_once('include/z_carddav.php'); - class BackendCardDAV extends BackendDiff implements ISearchProvider { private $domain = ''; private $username = ''; private $url = null; + /** + * @var carddav_backend + */ private $server = null; private $default_url = null; private $gal_url = null; @@ -131,8 +131,10 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { * @return boolean */ public function Logoff() { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->Logoff()")); - $this->server = null; + if ($this->server != null) { + $this->server->disconnect(); + unset($this->server); + } $this->SaveStorages(); @@ -140,6 +142,8 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { unset($this->sinkdata); unset($this->addressbooks); + ZLog::Write(LOGLEVEL_DEBUG, "BackendCardDAV->Logoff(): disconnected from CARDDAV server"); + return true; } @@ -207,8 +211,6 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { public function ChangesSinkInitialize($folderid) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->ChangesSinkInitialize(): folderid '%s'", $folderid)); - - // We don't need the actual cards, we only need to get the changes since this moment $init_ok = true; foreach ($this->addressbooks as $addressbook) { @@ -261,58 +263,61 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { return $notifications; } - while($stopat > time() && empty($notifications)) { - foreach ($this->addressbooks as $addressbook) { - $vcards = false; - try { - $this->server->set_url($addressbook); - $vcards = $this->server->do_sync(false, false, CARDDAV_SUPPORTS_SYNC); - } - catch (Exception $ex) { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendCardDAV->ChangesSink - Error resyncing vcards: %s", $ex->getMessage())); - } - - if ($vcards === false) { - ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendCardDAV->ChangesSink - Error getting the changes")); - return false; - } - else { - $xml_vcards = new SimpleXMLElement($vcards); - - if (CARDDAV_SUPPORTS_SYNC) { - if (count($xml_vcards->element) > 0) { - $changed = true; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->ChangesSink - Changes detected")); - } - } - else { - $xml_sinkdata = new SimpleXMLElement($this->sinkdata[$addressbook]); - if (count($xml_vcards->element) != count($xml_sinkdata->element)) { - // If the number of cards is different, we know for sure, there are changes - $changed = true; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->ChangesSink - Changes detected")); - } - else { - // If it's the same we need to check vcard to vcard, or the original strings - if (strcmp($this->sinkdata[$addressbook], $vcards) != 0) { - $changed = true; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->ChangesSink - Changes detected")); - } - } - unset($xml_sinkdata); - } - - unset($vcards); - unset($xml_vcards); - } - - if ($changed) { - $notifications[] = $this->foldername; - } + // only check once to reduce pressure in the DAV server + foreach ($this->addressbooks as $addressbook) { + $vcards = false; + try { + $this->server->set_url($addressbook); + $vcards = $this->server->do_sync(false, false, CARDDAV_SUPPORTS_SYNC); + } + catch (Exception $ex) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendCardDAV->ChangesSink - Error resyncing vcards: %s", $ex->getMessage())); } - if (empty($notifications)) - sleep(5); + if ($vcards === false) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendCardDAV->ChangesSink - Error getting the changes")); + return false; + } + else { + $xml_vcards = new SimpleXMLElement($vcards); + + if (CARDDAV_SUPPORTS_SYNC) { + if (count($xml_vcards->element) > 0) { + $changed = true; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->ChangesSink - Changes detected")); + } + } + else { + $xml_sinkdata = new SimpleXMLElement($this->sinkdata[$addressbook]); + if (count($xml_vcards->element) != count($xml_sinkdata->element)) { + // If the number of cards is different, we know for sure, there are changes + $changed = true; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->ChangesSink - Changes detected")); + } + else { + // If it's the same we need to check vcard to vcard, or the original strings + if (strcmp($this->sinkdata[$addressbook], $vcards) != 0) { + $changed = true; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->ChangesSink - Changes detected")); + } + } + unset($xml_sinkdata); + } + + unset($vcards); + unset($xml_vcards); + } + + if ($changed) { + $notifications[] = $this->foldername; + } + } + + // Wait to timeout + if (empty($notifications)) { + while ($stopat > time()) { + sleep(1); + } } return $notifications; @@ -545,7 +550,6 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { $message["mod"] = $this->contactsetag[$id]; $message["id"] = $id; $message["flags"] = 1; - $message["star"] = 0; return $message; } @@ -641,23 +645,6 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { return false; } - /** - * Changes the 'star' flag of a message on disk - * Not implemented here - * - * @param string $folderid id of the folder - * @param string $id id of the message - * @param int $flags star flag of the message - * @param ContentParameters $contentParameters - * - * @access public - * @return boolean status of the operation - * @throws StatusException could throw specific SYNC_STATUS_* exceptions - */ - public function SetStarFlag($folderid, $id, $flags, $contentParameters) { - return false; - } - /** * Called when the user has requested to delete (really delete) a message * @@ -959,7 +946,8 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { } /** - * Converts the vCard into SyncContact + * Converts the vCard into SyncContact. + * See RFC 6350 for vCard format details. * * @param string $data string with the vcard * @param int $truncsize truncate size requested @@ -1001,45 +989,49 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { continue; $field = trim(substr($line, 0, $pos)); - $value = trim(substr($line, $pos+1)); + $value = trim(substr($line, $pos + 1)); $fieldparts = preg_split('/(? $v) { $val[$i] = $this->unescape($v); } @@ -1125,6 +1116,7 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { } } } + //;;street;city;state;postalcode;country if (isset($vcard['adr'])) { foreach ($vcard['adr'] as $adr) { @@ -1212,8 +1204,13 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { $message->bodysize = strlen($message->body); } } + + // Support both ROLE and TITLE (RFC 6350 § 6.6.1 / § 6.6.2) as mapped to JobTitle if (!empty($vcard['role'][0]['val'][0])) - $message->jobtitle = $vcard['role'][0]['val'][0];//$vcard['title'][0]['val'][0] + $message->jobtitle = $vcard['role'][0]['val'][0]; + if (!empty($vcard['title'][0]['val'][0])) + $message->jobtitle = $vcard['title'][0]['val'][0]; + if (!empty($vcard['url'][0]['val'][0])) $message->webpage = $vcard['url'][0]['val'][0]; if (!empty($vcard['categories'][0]['val'])) @@ -1448,5 +1445,4 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider { return $addressbookId; } -}; -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/backend/combined/combined.php b/sources/backend/combined/combined.php index b0bf385..79e5605 100644 --- a/sources/backend/combined/combined.php +++ b/sources/backend/combined/combined.php @@ -49,9 +49,6 @@ * Consult LICENSE file for details ************************************************/ -// default backend -include_once('lib/default/backend.php'); - //include the CombinedBackend's own config file require_once("backend/combined/config.php"); require_once("backend/combined/importer.php"); @@ -59,11 +56,19 @@ require_once("backend/combined/exporter.php"); class BackendCombined extends Backend implements ISearchProvider { public $config; + /** + * @var IBackend[] + */ public $backends; + /** + * @var IBackend + */ private $activeBackend; private $activeBackendID; private $numberChangesSink; + private $logon_done = false; + /** * Constructor of the combined backend * @@ -100,6 +105,8 @@ class BackendCombined extends Backend implements ISearchProvider { $u = $username; $d = $domain; $p = $password; + + // Apply mapping from configuration if(isset($this->config['backends'][$i]['users'])){ if(!isset($this->config['backends'][$i]['users'][$username])){ unset($this->backends[$i]); @@ -112,12 +119,23 @@ class BackendCombined extends Backend implements ISearchProvider { if(isset($this->config['backends'][$i]['users'][$username]['domain'])) $d = $this->config['backends'][$i]['users'][$username]['domain']; } - if($this->backends[$i]->Logon($u, $d, $p) == false){ + + // Apply username mapping from state backend + if (isset($this->config['usemapping']) && $this->config['usemapping']) { + $mappedUsername = ZPush::GetStateMachine()->GetMappedUsername($u, strtolower($this->config['backends'][$i]['name'])); + if ($mappedUsername !== null) { + $u = $mappedUsername; + } + } + + if ($this->backends[$i]->Logon($u, $d, $p) == false) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Logon() failed on %s ", $this->config['backends'][$i]['name'])); return false; } + $this->backends[$i]->SetOriginalUsername($username); } + $this->logon_done = true; ZLog::Write(LOGLEVEL_DEBUG, "Combined->Logon() success"); return true; } @@ -167,6 +185,10 @@ class BackendCombined extends Backend implements ISearchProvider { * @return boolean */ public function Logoff() { + // If no Logon in done, omit Logoff + if (!$this->logon_done) + return true; + ZLog::Write(LOGLEVEL_DEBUG, "Combined->Logoff()"); foreach ($this->backends as $i => $b){ $this->backends[$i]->Logoff(); @@ -459,27 +481,21 @@ class BackendCombined extends Backend implements ISearchProvider { $notifications = array(); if ($this->numberChangesSink == 0) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined doesn't include any Sinkable backends")); + ZLog::Write(LOGLEVEL_DEBUG, "BackendCombined doesn't include any Sinkable backends"); } else { - $stopat = time() + $timeout - 1; - //we will spend 2 seconds at least in each backend that support changessink - // why 2 seconds? because it's the minimum to ensure we run at least once the changessink - // I think it's fairer than run for 10 continuos seconds the same backend (run backend1, run backend2, run backend1, run backend2... vs run backend1, run backend1, run backend2, run backend2) - do { - 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), 2)); + $time_each = $timeout / $this->numberChangesSink; + 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)); - // 2 seconds hardcoded timeout!!! - $notifications_backend = $this->backends[$i]->ChangesSink(2); - //preppend 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); + $notifications_backend = $this->backends[$i]->ChangesSink($time_each); + //preppend 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); } - } while($stopat > time() && empty($notifications)); + } } return $notifications; @@ -688,4 +704,3 @@ class BackendCombined extends Backend implements ISearchProvider { return false; } } -?> \ No newline at end of file diff --git a/sources/backend/combined/config.php b/sources/backend/combined/config.php index 32e61f1..ca6bd16 100644 --- a/sources/backend/combined/config.php +++ b/sources/backend/combined/config.php @@ -109,7 +109,8 @@ class BackendCombinedConfig { ), //creating a new folder in the root folder should create a folder in one backend 'rootcreatefolderbackend' => 'i', + //enable to use username mapping for the different backends + 'usemapping' => false, ); } } -?> \ No newline at end of file diff --git a/sources/backend/combined/exporter.php b/sources/backend/combined/exporter.php index 1ddbdad..eb1abbd 100644 --- a/sources/backend/combined/exporter.php +++ b/sources/backend/combined/exporter.php @@ -47,8 +47,14 @@ */ class ExportChangesCombined implements IExportChanges { + /** + * @var BackendCombined + */ private $backend; private $syncstates; + /** + * @var IExportChanges[] + */ private $exporters; private $importer; private $importwraps; @@ -181,4 +187,3 @@ class ExportChangesCombined implements IExportChanges { ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->InitializeExporter(...) success"); } } -?> \ No newline at end of file diff --git a/sources/backend/combined/importer.php b/sources/backend/combined/importer.php index 24497e7..1c01826 100644 --- a/sources/backend/combined/importer.php +++ b/sources/backend/combined/importer.php @@ -131,23 +131,6 @@ class ImportChangesCombined implements IImportChanges { return $this->icc->ImportMessageReadFlag($id, $flags); } - /** - * Imports a change in 'star' flag - * This can never conflict - * - * @param string $id - * @param int $flags - * - * @access public - * @return boolean - */ - public function ImportMessageStarFlag($id, $flags) { - if (!$this->icc) { - ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageReadFlag() icc not configured"); - return false; - } - return $this->icc->ImportMessageStarFlag($id, $flags); - } /** * Imports a move of a message. This occurs when a user moves an item to another folder @@ -168,7 +151,15 @@ class ImportChangesCombined implements IImportChanges { ZLog::Write(LOGLEVEL_WARN, "ImportChangesCombined->ImportMessageMove() cannot move message between two backends"); return false; } - return $this->icc->ImportMessageMove($id, $this->backend->GetBackendFolder($newfolder)); + $res = $this->icc->ImportMessageMove($id, $this->backend->GetBackendFolder($newfolder)); + + if ($res) { + //TODO: we should add newid to new folder, instead of a full folder resync + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesCombined->ImportMessageMove(): Force resync of dest folder (%s)", $newfolder)); + ZPushAdmin::ResyncFolder(Request::GetAuthUser(), Request::GetDeviceID(), $newfolder); + } + + return $res; } @@ -187,7 +178,7 @@ class ImportChangesCombined implements IImportChanges { public function ImportFolderChange($folder) { $id = $folder->serverid; $parent = $folder->parentid; - ZLog::Write(LOGLEVEL_DEBUG, "ImportChangesCombined->ImportFolderChange() ".print_r($folder, 1)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesCombined->ImportFolderChange() id: '%s', parent: '%s'", $id, $parent)); if($parent == '0') { if($id) { $backendid = $this->backend->GetBackendId($id); @@ -367,5 +358,3 @@ class ImportHierarchyChangesCombinedWrap { return $this->ihc->ImportFolderDeletion($this->backendid.$this->backend->config['delimiter'].$id); } } - -?> \ No newline at end of file diff --git a/sources/backend/imap/THANKS b/sources/backend/imap/THANKS new file mode 100644 index 0000000..b56e45c --- /dev/null +++ b/sources/backend/imap/THANKS @@ -0,0 +1,4 @@ +*Drenalina SRL (www.drenalina.com)* sponsored the development of the following features in the BackendIMAP, any existing bug it's my fault not theirs ;-) +Thank you very much for helping to improve it!! + + - Meeting invitations and attendees \ No newline at end of file diff --git a/sources/backend/imap/config.php b/sources/backend/imap/config.php index 17975ce..8f00310 100644 --- a/sources/backend/imap/config.php +++ b/sources/backend/imap/config.php @@ -54,6 +54,84 @@ define('IMAP_PORT', 143); // best cross-platform compatibility (see http://php.net/imap_open for options) define('IMAP_OPTIONS', '/notls/norsh'); + +// Mark messages as read when moving to Trash. +// BE AWARE that you will lose the unread flag, but some mail clients do this so the Trash folder doesn't get boldened +define('IMAP_AUTOSEEN_ON_DELETE', false); + + +// IMPORTANT: BASIC IMAP FOLDERS [ask your mail admin] + // We can have diferent cases (case insensitive): + // 1. + // inbox + // sent + // drafts + // trash + // 2. + // inbox + // common.sent + // common.drafts + // common.trash + // 3. + // common.inbox + // common.sent + // common.drafts + // common.trash + // 4. + // common + // common.sent + // common.drafts + // common.trash + // + // gmail is a special case, where the default folders are under the [gmail] prefix and the folders defined by the user are under INBOX. + // This configuration seems to work: + // define('IMAP_FOLDER_PREFIX', ''); + // define('IMAP_FOLDER_INBOX', 'INBOX'); + // define('IMAP_FOLDER_SENT', '[Gmail]/Sent'); + // define('IMAP_FOLDER_DRAFTS', '[Gmail]/Drafts'); + // define('IMAP_FOLDER_TRASH', '[Gmail]/Trash'); + // define('IMAP_FOLDER_SPAM', '[Gmail]/Spam'); + // define('IMAP_FOLDER_ARCHIVE', '[Gmail]/All Mail'); + +// Since I know you won't configure this, I will raise an error unless you do. +// When configured set this to true to remove the error +define('IMAP_FOLDER_CONFIGURED', false); + +// Folder prefix is the common part in your names (3, 4) +define('IMAP_FOLDER_PREFIX', ''); + +// Inbox will have the preffix preppend (3 & 4 to true) +define('IMAP_FOLDER_PREFIX_IN_INBOX', false); + +// Inbox folder name (case doesn't matter) - (empty in 4) +define('IMAP_FOLDER_INBOX', 'INBOX'); + +// Sent folder name (case doesn't matter) +define('IMAP_FOLDER_SENT', 'SENT'); + +// Draft folder name (case doesn't matter) +define('IMAP_FOLDER_DRAFT', 'DRAFTS'); + +// Trash folder name (case doesn't matter) +define('IMAP_FOLDER_TRASH', 'TRASH'); + +// Spam folder name (case doesn't matter). Only showed as special by iOS devices +define('IMAP_FOLDER_SPAM', 'SPAM'); + +// Archive folder name (case doesn't matter). Only showed as special by iOS devices +define('IMAP_FOLDER_ARCHIVE', 'ARCHIVE'); + + + +// forward messages inline (default true - inlined) +define('IMAP_INLINE_FORWARD', true); + +// list of folders we want to exclude from sync. Names, or part of it, separated by | +// example: dovecot.sieve|archive|spam +define('IMAP_EXCLUDED_FOLDERS', ''); + + + // overwrite the "from" header with some value // options: // '' - do nothing, use the From header @@ -99,29 +177,6 @@ define('IMAP_FROM_LDAP_FROM', '#givenname #sn <#mail>'); define('IMAP_FROM_LDAP_FULLNAME', '#givenname #sn'); -// Root folder or prefix in your IMAP server (without the separator). For example, with courier it will be INBOX, and your folder will be INBOX.Sent -// You can use the real case -define('IMAP_FOLDER_ROOT', 'INBOX'); - -// copy outgoing mail to this folder. If not set z-push will try the default folders -// You can use the real case and the full path (INBOX.Sent) -define('IMAP_FOLDER_SENT', ''); - -// Draft folder -// You can use the real case and the full path (INBOX.Draft) -define('IMAP_FOLDER_DRAFT', ''); - -// Trash folder -// You can use the real case and the full path (INBOX.Trash) -define('IMAP_FOLDER_TRASH', ''); - -// forward messages inline (default true - inlined) -define('IMAP_INLINE_FORWARD', true); - -// list of folders we want to exclude from sync. Names, or part of it, separated by | -// example: dovecot.sieve|archive|spam -define('IMAP_EXCLUDED_FOLDERS', ''); - // Method used for sending mail // mail => mail() php function @@ -136,25 +191,37 @@ $imap_smtp_params = array(); // sendmail //$imap_smtp_params = array('sendmail_path' => '/usr/bin/sendmail', 'sendmail_args' => '-i'); // smtp -// "host" - The server to connect. Default is localhost. -// "port" - The port to connect. Default is 25. -// "auth" - Whether or not to use SMTP authentication. Default is FALSE. -// "username" - The username to use for SMTP authentication. "imap_username" for using the same username as the imap server -// "password" - The password to use for SMTP authentication. "imap_password" for using the same password as the imap server -// "localhost" - The value to give when sending EHLO or HELO. Default is localhost -// "timeout" - The SMTP connection timeout. Default is NULL (no timeout). -// "verp" - Whether to use VERP or not. Default is FALSE. -// "debug" - Whether to enable SMTP debug mode or not. Default is FALSE. -// "persist" - Indicates whether or not the SMTP connection should persist over multiple calls to the send() method. -// "pipelining" - Indicates whether or not the SMTP commands pipelining should be used. +// "host" - The server to connect. Default is localhost. +// "port" - The port to connect. Default is 25. +// "auth" - Whether or not to use SMTP authentication. Default is FALSE. +// "username" - The username to use for SMTP authentication. "imap_username" for using the same username as the imap server +// "password" - The password to use for SMTP authentication. "imap_password" for using the same password as the imap server +// "localhost" - The value to give when sending EHLO or HELO. Default is localhost +// "timeout" - The SMTP connection timeout. Default is NULL (no timeout). +// "verp" - Whether to use VERP or not. Default is FALSE. +// "debug" - Whether to enable SMTP debug mode or not. Default is FALSE. +// "persist" - Indicates whether or not the SMTP connection should persist over multiple calls to the send() method. +// "pipelining" - Indicates whether or not the SMTP commands pipelining should be used. +// "verify_peer" - Require verification of SSL certificate used. Default is TRUE. +// "verify_peer_name" - Require verification of peer name. Default is TRUE. +// "allow_self_signed" - Allow self-signed certificates. Requires verify_peer. Default is FALSE. //$imap_smtp_params = array('host' => 'localhost', 'port' => 25, 'auth' => false); // If you want to use SSL with port 25 or port 465 you must preppend "ssl://" before the hostname or IP of your SMTP server // IMPORTANT: To use SSL you must use PHP 5.1 or later, install openssl libs and use ssl:// within the host variable +// IMPORTANT: To use SSL with PHP 5.6 you should set verify_peer, verify_peer_name and allow_self_signed //$imap_smtp_params = array('host' => 'ssl://localhost', 'port' => 465, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password'); + // If you are using IMAP_SMTP_METHOD = mail or sendmail and your sent messages are not correctly displayed you can change this to "\n". // BUT, it doesn't comply with RFC 2822 and will break if using smtp method define('MAIL_MIMEPART_CRLF', "\r\n"); -?> \ No newline at end of file + +// A file containing file mime types->extension mappings. +// SELINUX users: make sure the file has a security context accesible by your apache/php-fpm process +define('SYSTEM_MIME_TYPES_MAPPING', '/etc/mime.types'); + + +// Use BackendCalDAV for Meetings. You cannot hope to get that functionality working without a caldav backend. +define('IMAP_MEETING_USE_CALDAV', false); \ No newline at end of file diff --git a/sources/backend/imap/imap.php b/sources/backend/imap/imap.php index bfa68f9..5f2a08e 100644 --- a/sources/backend/imap/imap.php +++ b/sources/backend/imap/imap.php @@ -45,15 +45,10 @@ // config file require_once("backend/imap/config.php"); + +require_once("backend/imap/mime_calendar.php"); require_once("backend/imap/mime_encode.php"); - -include_once('lib/default/diffbackend/diffbackend.php'); -include_once('include/Mail.php'); -include_once('include/mimeDecode.php'); -include_once('include/mimePart.php'); -include_once('include/z_RFC822.php'); -include_once('include/iCalendar.php'); - +require_once("backend/imap/user_identity.php"); class BackendIMAP extends BackendDiff implements ISearchProvider { private $wasteID; @@ -64,10 +59,10 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { private $username; private $password; private $domain; - private $serverdelimiter; private $sinkfolders = array(); private $sinkstates = array(); private $changessinkinit = false; + private $folderhierarchy; private $excludedFolders; private static $mimeTypes = false; @@ -111,6 +106,9 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { if (!function_exists("imap_open")) throw new FatalException("BackendIMAP(): php-imap module is not installed", 0, null, LOGLEVEL_FATAL); + if (defined('IMAP_FOLDER_CONFIGURED') && IMAP_FOLDER_CONFIGURED == false) + throw new FatalException("BackendIMAP(): You didn't configure your IMAP folder names. Do it before!", 0, null, LOGLEVEL_FATAL); + /* BEGIN fmbiete's contribution r1527, ZP-319 */ $this->excludedFolders = array(); if (defined('IMAP_EXCLUDED_FOLDERS') && strlen(IMAP_EXCLUDED_FOLDERS) > 0) { @@ -128,8 +126,6 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $this->username = $username; $this->password = $password; $this->domain = $domain; - // set serverdelimiter - $this->serverdelimiter = $this->getServerDelimiter(); return true; } else { @@ -147,23 +143,7 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * @return boolean */ public function Logoff() { - if ($this->mbox) { - // list all errors - $errors = imap_errors(); - if (is_array($errors)) { - foreach ($errors as $e) { - if (stripos($e, "fail") !== false) { - $level = LOGLEVEL_WARN; - } - else { - $level = LOGLEVEL_DEBUG; - } - ZLog::Write($level, "BackendIMAP->Logoff(): IMAP said: " . $e); - } - } - @imap_close($this->mbox); - ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->Logoff(): IMAP connection closed"); - } + $this->close_connection(); $this->SaveStorages(); } @@ -201,7 +181,7 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $this->imap_reopen_folder($parent); $sourceMail = @imap_fetchheader($this->mbox, $sm->source->itemid, FT_UID) . @imap_body($this->mbox, $sm->source->itemid, FT_PEEK | FT_UID); $mobj = new Mail_mimeDecode($sourceMail); - $sourceMessage = $mobj->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); + $sourceMessage = $mobj->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'rfc_822bodies' => true, 'charset' => 'utf-8')); unset($mobj); //We will need $sourceMail if the message is forwarded and not inlined @@ -223,7 +203,7 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->SendMail(): We get the new message")); $mobj = new Mail_mimeDecode($sm->mime); - $message = $mobj->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); + $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'rfc_822bodies' => true, 'charset' => 'utf-8')); unset($mobj); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->SendMail(): We get the From and To")); @@ -239,13 +219,21 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } unset($Mail_RFC822); + // overwrite CC and BCC with the decoded versions, because we will parse/validate the address in the sending method + if (isset($message->headers["cc"])) { + $message->headers["cc"] = $message->headers["cc"]; + } + if (isset($message->headers["bcc"])) { + $message->headers["bcc"] = $message->headers["bcc"]; + } + $this->setReturnPathValue($message->headers, $fromaddr); $finalBody = ""; $finalHeaders = array(); - // if it's a S/MIME message I don't do anything with it - if (is_smime($message)) { + // if it's a S/MIME message or has VCALENDAR objects I don't do anything with it + if (is_smime($message) || has_calendar_object($message)) { $mobj = new Mail_mimeDecode($sm->mime); $parts = $mobj->getSendArray(); unset($mobj); @@ -253,7 +241,7 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { throw new StatusException(sprintf("BackendIMAP->SendMail(): Could not getSendArray for SMIME messages"), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED); } else { - list($recipents, $finalHeaders, $finalBody) = $parts; + list($recipients, $finalHeaders, $finalBody) = $parts; $this->setFromHeaderValue($finalHeaders); $this->setReturnPathValue($finalHeaders, $fromaddr); @@ -340,11 +328,13 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $send = $this->sendMessage($fromaddr, $toaddr, $finalHeaders, $finalBody); - if (isset($sm->saveinsent)) { - $this->saveSentMessage($finalHeaders, $finalBody); - } - else { - ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): Not saving in SentFolder"); + if ($send) { + if (isset($sm->saveinsent)) { + $this->saveSentMessage($finalHeaders, $finalBody); + } + else { + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): Not saving in SentFolder"); + } } unset($finalHeaders); @@ -432,20 +422,17 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * @return void */ private function addTextPartsMessage(&$email, &$message) { - $htmlBody = $plainBody = ''; - Mail_mimeDecode::getBodyRecursive($message, "html", $htmlBody); - Mail_mimeDecode::getBodyRecursive($message, "plain", $plainBody); - $altEmail = new Mail_mimePart('', array('content_type' => 'multipart/alternative')); - if (strlen($htmlBody) > 0) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->addTextPartsMessage(): The message has HTML body")); - $altEmail->addSubPart($htmlBody, array('content_type' => 'text/html; charset=utf-8', 'encoding' => 'base64')); - } - if (strlen($plainBody) > 0) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->addTextPartsMessage(): The message has PLAIN body")); - $altEmail->addSubPart($plainBody, array('content_type' => 'text/plain; charset=utf-8', 'encoding' => 'base64')); + foreach (array("plain", "html", "calendar") as $type) { + $body = ''; + Mail_mimeDecode::getBodyRecursive($message, $type, $body); + if (strlen($body) > 0) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->addTextPartsMessage(): The message has %s body", $type)); + $altEmail->addSubPart($body, array('content_type' => sprintf("text/%s; charset=utf-8", $type), 'encoding' => 'base64')); + } } + unset($body); $boundary = '=_' . md5(rand() . microtime()); $altEmail = $altEmail->encode($boundary); @@ -453,9 +440,6 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $email->addSubPart($altEmail['body'], array('content_type' => 'multipart/alternative;'."\n".' boundary="'.$boundary.'"')); unset($altEmail); - - unset($htmlBody); - unset($plainBody); } /** @@ -468,7 +452,10 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { // TODO this could be retrieved from the DeviceFolderCache if ($this->wasteID == false) { //try to get the waste basket without doing complete hierarchy sync - $wastebaskt = @imap_getmailboxes($this->mbox, $this->server, "Trash"); + $folder_name = IMAP_FOLDER_TRASH; + if (defined('IMAP_FOLDER_PREFIX') && strlen(IMAP_FOLDER_PREFIX) > 0) + $folder_name = IMAP_FOLDER_PREFIX . $this->getServerDelimiter() . $folder_name; + $wastebaskt = @imap_getmailboxes($this->mbox, $this->server, $folder_name); if (isset($wastebaskt[0])) { $this->wasteID = $this->convertImapId(substr($wastebaskt[0]->name, strlen($this->server))); return $this->wasteID; @@ -483,7 +470,6 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * Returns the content of the named attachment as stream. The passed attachment identifier is * the exact string that is returned in the 'AttName' property of an SyncAttachment. * Any information necessary to find the attachment must be encoded in that 'attname' property. - * Data is written directly (with print $data;) * * @param string $attname * @@ -510,7 +496,7 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } $mobj = new Mail_mimeDecode($mail); - $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); + $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'rfc_822bodies' => true, 'charset' => 'utf-8')); if (!isset($message->parts)) { throw new StatusException(sprintf("BackendIMAP->GetAttachmentData('%s'): Error, message without parts. Requesting part key: '%d'", $attname, $part), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); @@ -536,7 +522,6 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { unset($mobj); unset($mail); - include_once('include/stringstreamwrapper.php'); $attachment = new SyncItemOperationsAttachment(); /* BEGIN fmbiete's contribution r1528, ZP-320 */ $attachment->data = StringStreamWrapper::Open($mparts[$part]->body); @@ -552,6 +537,67 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { return $attachment; } + + /** + * Deletes all contents of the specified folder. + * This is generally used to empty the trash (wastebasked), but could also be used on any + * other folder. + * + * @param string $folderid + * @param boolean $includeSubfolders (opt) also delete sub folders, default true + * + * @access public + * @return boolean + * @throws StatusException + */ + public function EmptyFolder($folderid, $includeSubfolders = true) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->EmptyFolder('%s', '%s')", $folderid, Utils::PrintAsString($includeSubfolders))); + + $folderImapid = $this->getImapIdFromFolderId($folderid); + if ($folderImapid === false) { + throw new StatusException(sprintf("BackendIMAP->EmptyFolder('%s','%s'): Error, unable to open folder (no entry id)", $folderid, Utils::PrintAsString($includeSubfolders)), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR); + } + + if (!$this->imap_reopen_folder($folderImapid)) { + throw new StatusException(sprintf("BackendIMAP->EmptyFolder('%s','%s'): Error, unable to open parent folder (open entry)", $folderid, Utils::PrintAsString($includeSubfolders)), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR); + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->EmptyFolder('%s','%s'): emptying folder", $folderid, Utils::PrintAsString($includeSubfolders))); + + // TODO: make transactional all these deletes: see comment bellow + if (@imap_delete($this->mbox, "1:*")) { + @imap_expunge($this->mbox); + + + // An error erasing any subfolder won't return an error to the device, because we should undelete the already expunged messages, and we cannot undelete a folder + if ($includeSubfolders) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->EmptyFolder('%s','%s'): deleting subfolders", $folderid, Utils::PrintAsString($includeSubfolders))); + + // Find subfolders + $subfolders = @imap_getmailboxes($this->mbox, $this->server . $folderImapid, "*"); + if (is_array($subfolders)) { + + // delete mailbox and its content + foreach ($subfolders as $val) { + $subname = substr($val->name, strlen($this->server)); + if (!@imap_deletemailbox($this->mbox, $val->name)) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendIMAP->EmptyFolder('%s','%s'): Error deleting subfolder %s", $folderid, Utils::PrintAsString($includeSubfolders), $subname)); + } + } + } + else { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendIMAP->EmptyFolder('%s','%s'): Error getting subfolder list", $folderid, Utils::PrintAsString($includeSubfolders))); + } + } + } + else { + throw new StatusException(sprintf("BackendIMAP->EmptyFolder('%s','%s'): Error, imap_delete() failed, the error will show at the logout", $folderid, Utils::PrintAsString($includeSubfolders)), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR); + } + + return true; + } + + /** * Indicates if the backend has a ChangesSink. * A sink is an active notification mechanism which does not need polling. @@ -579,6 +625,11 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $imapid = $this->getImapIdFromFolderId($folderid); + if (!$this->changessinkinit) { + // First folder, store the actual folder structure + $this->folderhierarchy = $this->get_folder_list(); + } + if ($imapid !== false) { $this->sinkfolders[] = $imapid; $this->changessinkinit = true; @@ -610,34 +661,48 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { return $notifications; } - while($stopat > time() && empty($notifications)) { - foreach ($this->sinkfolders as $i => $imapid) { - $this->imap_reopen_folder($imapid); + // Reconnect IMAP server + $this->imap_reconnect(); - // courier-imap only cleares the status cache after checking - @imap_check($this->mbox); + // Check folder hierarchy and create change + if (count(array_diff($this->folderhierarchy, $this->get_folder_list())) > 0) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->ChangesSink(): Changes in folder hierarchy detected!!"); + throw new StatusException("BackendIMAP->ChangesSink(): HierarchySync required.", SyncCollections::HIERARCHY_CHANGED); + } - $status = @imap_status($this->mbox, $this->server . $imapid, SA_ALL); - if (!$status) { - ZLog::Write(LOGLEVEL_WARN, sprintf("ChangesSink: could not stat folder '%s': %s ", $this->getFolderIdFromImapId($imapid), imap_last_error())); + // only check once to reduce pressure in the IMAP server + foreach ($this->sinkfolders as $i => $imapid) { + $this->imap_reopen_folder($imapid); + + // courier-imap only clears the status cache after checking + @imap_check($this->mbox); + + $status = @imap_status($this->mbox, $this->server . $imapid, SA_ALL); + if (!$status) { + ZLog::Write(LOGLEVEL_WARN, sprintf("ChangesSink: could not stat folder '%s': %s ", $this->getFolderIdFromImapId($imapid), imap_last_error())); + } + else { + $newstate = "M:". $status->messages ."-R:". $status->recent ."-U:". $status->unseen; + + if (! isset($this->sinkstates[$imapid]) ) { + $this->sinkstates[$imapid] = $newstate; } - else { - $newstate = "M:". $status->messages ."-R:". $status->recent ."-U:". $status->unseen; - if (! isset($this->sinkstates[$imapid]) ) { - $this->sinkstates[$imapid] = $newstate; - } - - if ($this->sinkstates[$imapid] != $newstate) { - $notifications[] = $this->getFolderIdFromImapId($imapid); - $this->sinkstates[$imapid] = $newstate; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->ChangesSink(): ChangesSink detected!!")); - } + if ($this->sinkstates[$imapid] != $newstate) { + $notifications[] = $this->getFolderIdFromImapId($imapid); + $this->sinkstates[$imapid] = $newstate; + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->ChangesSink(): ChangesSink detected!!"); } } + } + // Close IMAP connection, we will reconnect in the next execution. This will reduce IMAP pressure + $this->close_connection(); - if (empty($notifications)) - sleep(5); + // Wait to timeout + if (empty($notifications)) { + while ($stopat > time()) { + sleep(1); + } } return $notifications; @@ -658,46 +723,26 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { public function GetFolderList() { $folders = array(); - $list = @imap_getmailboxes($this->mbox, $this->server, "*"); - if (is_array($list)) { - // reverse list to obtain folders in right order - $list = array_reverse($list); - - foreach ($list as $val) { - /* BEGIN fmbiete's contribution r1527, ZP-319 */ - // don't return the excluded folders - $notExcluded = true; - for ($i = 0, $cnt = count($this->excludedFolders); $notExcluded && $i < $cnt; $i++) { // expr1, expr2 modified by mku ZP-329 - // fix exclude folders with special chars by mku ZP-329 - if (strpos(strtolower($val->name), strtolower(Utils::Utf7_iconv_encode(Utils::Utf8_to_utf7($this->excludedFolders[$i])))) !== false) { - $notExcluded = false; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("Pattern: <%s> found, excluding folder: '%s'", $this->excludedFolders[$i], $val->name)); // sprintf added by mku ZP-329 - } - } - - if ($notExcluded) { - $box = array(); - // cut off serverstring - $imapid = substr($val->name, strlen($this->server)); - $box["id"] = $this->convertImapId($imapid); - - $fhir = explode($val->delimiter, $imapid); - if (count($fhir) > 1) { - $this->getModAndParentNames($fhir, $box["mod"], $imapparent); - $box["parent"] = $this->convertImapId($imapparent); - } - else { - $box["mod"] = $imapid; - $box["parent"] = "0"; - } - $folders[]=$box; - /* END fmbiete's contribution r1527, ZP-319 */ + $list = $this->get_folder_list(); + foreach ($list as $val) { + // don't return the excluded folders + $notExcluded = true; + for ($i = 0, $cnt = count($this->excludedFolders); $notExcluded && $i < $cnt; $i++) { // expr1, expr2 modified by mku ZP-329 + // fix exclude folders with special chars by mku ZP-329 + if (strpos(strtolower($val), strtolower(Utils::Utf7_iconv_encode(Utils::Utf8_to_utf7($this->excludedFolders[$i])))) !== false) { + $notExcluded = false; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Pattern: <%s> found, excluding folder: '%s'", $this->excludedFolders[$i], $val)); // sprintf added by mku ZP-329 } } - } - else { - ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->GetFolderList(): imap_list failed: " . imap_last_error()); - return false; + + if ($notExcluded) { + $box = array(); + // cut off serverstring + $imapid = substr($val, strlen($this->server)); + $box["id"] = $this->convertImapId($imapid); + + $folders[] = $box; + } } return $folders; @@ -719,62 +764,66 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $imapid = $this->getImapIdFromFolderId($id); // explode hierarchy - $fhir = explode($this->serverdelimiter, $imapid); + $fhir = explode($this->getServerDelimiter(), $imapid); - // compare on lowercase strings - $lid = strtolower($imapid); -// TODO WasteID or SentID could be saved for later ussage - if($lid == strtolower(IMAP_FOLDER_ROOT)) { - $folder->parentid = "0"; // Root + // TODO WasteID or SentID could be saved for later ussage + if (strcasecmp($imapid, $this->create_name_folder(IMAP_FOLDER_INBOX)) == 0) { + $folder->parentid = "0"; $folder->displayname = "Inbox"; $folder->type = SYNC_FOLDER_TYPE_INBOX; } - // Zarafa IMAP-Gateway outputs - else if($lid == "drafts" || $lid == strtolower(IMAP_FOLDER_DRAFT)) { + else if (strcasecmp($imapid, $this->create_name_folder(IMAP_FOLDER_DRAFT)) == 0) { $folder->parentid = "0"; $folder->displayname = "Drafts"; $folder->type = SYNC_FOLDER_TYPE_DRAFTS; } - else if(($lid == "trash" || $lid == "deleted messages" || $lid == strtolower(IMAP_FOLDER_TRASH)) && ($this->wasteID === false || $this->wasteID == $id)) { - $folder->parentid = "0"; - $folder->displayname = "Trash"; - $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET; - $this->wasteID = $id; - } - else if($lid == "sent" || $lid == "sent items" || $lid == "sent messages" || $lid == strtolower(IMAP_FOLDER_SENT)) { + else if (strcasecmp($imapid, $this->create_name_folder(IMAP_FOLDER_SENT)) == 0) { $folder->parentid = "0"; $folder->displayname = "Sent"; $folder->type = SYNC_FOLDER_TYPE_SENTMAIL; $this->sentID = $id; } - else if($lid == strtolower(IMAP_FOLDER_ROOT) . $this->serverdelimiter . "drafts" || $lid == strtolower(IMAP_FOLDER_DRAFT)) { - $folder->parentid = $this->convertImapId($fhir[0]); - $folder->displayname = "Drafts"; - $folder->type = SYNC_FOLDER_TYPE_DRAFTS; - } - else if(($lid == strtolower(IMAP_FOLDER_ROOT) . $this->serverdelimiter . "trash" || $lid == strtolower(IMAP_FOLDER_ROOT) . $this->serverdelimiter . "deleted messages" || $lid == strtolower(IMAP_FOLDER_TRASH)) && ($this->wasteID === false || $this->wasteID == $id)) { - $folder->parentid = $this->convertImapId($fhir[0]); + else if (strcasecmp($imapid, $this->create_name_folder(IMAP_FOLDER_TRASH)) == 0) { + $folder->parentid = "0"; $folder->displayname = "Trash"; $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET; $this->wasteID = $id; } - else if($lid == strtolower(IMAP_FOLDER_ROOT) . $this->serverdelimiter . "sent" || $lid == strtolower(IMAP_FOLDER_ROOT) . $this->serverdelimiter . "sent messages" || $lid == strtolower(IMAP_FOLDER_SENT)) { - $folder->parentid = $this->convertImapId($fhir[0]); - $folder->displayname = "Sent"; - $folder->type = SYNC_FOLDER_TYPE_SENTMAIL; - $this->sentID = $id; + else if (strcasecmp($imapid, $this->create_name_folder(IMAP_FOLDER_SPAM)) == 0) { + $folder->parentid = "0"; + $folder->displayname = "Junk"; + $folder->type = SYNC_FOLDER_TYPE_USER_MAIL; + } + else if (strcasecmp($imapid, $this->create_name_folder(IMAP_FOLDER_ARCHIVE)) == 0) { + $folder->parentid = "0"; + $folder->displayname = "Archive"; + $folder->type = SYNC_FOLDER_TYPE_USER_MAIL; } - - // define the rest as other-folders else { - if (count($fhir) > 1) { - $this->getModAndParentNames($fhir, $folder->displayname, $imapparent); - $folder->parentid = $this->convertImapId($imapparent); - $folder->displayname = Utils::Utf7_to_utf8(Utils::Utf7_iconv_decode($folder->displayname)); + if (defined('IMAP_FOLDER_PREFIX') && strlen(IMAP_FOLDER_PREFIX) > 0) { + if (strcasecmp($fhir[0], IMAP_FOLDER_PREFIX) == 0) { + // Discard prefix + array_shift($fhir); + } + else { + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->GetFolder('%s'): '%s'; using server delimiter '%s', first part '%s' is not equal to the prefix defined '%s'. Something is wrong with your config.", $id, $imapid, $this->getServerDelimiter(), $fhir[0], IMAP_FOLDER_PREFIX)); + } + } + + if (count($fhir) == 1) { + $folder->displayname = Utils::Utf7_to_utf8(Utils::Utf7_iconv_decode($fhir[0])); + $folder->parentid = "0"; } else { - $folder->displayname = Utils::Utf7_to_utf8(Utils::Utf7_iconv_decode($imapid)); - $folder->parentid = "0"; + $this->getModAndParentNames($fhir, $folder->displayname, $imapparent); + $folder->displayname = Utils::Utf7_to_utf8(Utils::Utf7_iconv_decode($folder->displayname)); + if ($imapparent === null) { + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->GetFolder('%s'): '%s'; we didn't found a valid parent name for the folder, but we should... contact the developers for further info", $id, $imapid)); + $folder->parentid = "0"; // We put the folder as root folder, so we see it + } + else { + $folder->parentid = $this->convertImapId($imapparent); + } } $folder->type = SYNC_FOLDER_TYPE_USER_MAIL; } @@ -821,29 +870,38 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { public function ChangeFolder($folderid, $oldid, $displayname, $type){ ZLog::Write(LOGLEVEL_INFO, sprintf("BackendIMAP->ChangeFolder('%s','%s','%s','%s')", $folderid, $oldid, $displayname, $type)); - // go to parent mailbox - $this->imap_reopen_folder($folderid); - - // build name for new mailboxBackendMaildir - $displayname = Utils::Utf7_iconv_encode(Utils::Utf8_to_utf7($displayname)); - $newname = $this->server . $this->getImapIdFromFolderId($folderid) . $this->serverdelimiter . $displayname; - - $csts = false; // if $id is set => rename mailbox, otherwise create if ($oldid) { // rename doesn't work properly with IMAP // the activesync client doesn't support a 'changing ID' // TODO this would be solved by implementing hex ids (Mantis #459) - //$csts = imap_renamemailbox($this->mbox, $this->server . imap_utf7_encode(str_replace(".", $this->serverdelimiter, $oldid)), $newname); + //$csts = imap_renamemailbox($this->mbox, $this->server . imap_utf7_encode(str_replace(".", $this->getServerDelimiter(), $oldid)), $newname); + ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->ChangeFolder() : we do not support rename for now"); + return false; } else { - $csts = @imap_createmailbox($this->mbox, $newname); + + // build name for new mailboxBackendMaildir + $displayname = Utils::Utf7_iconv_encode(Utils::Utf8_to_utf7($displayname)); + + if ($folderid == "0") { + $newimapid = $displayname; + } + else { + $imapid = $this->getImapIdFromFolderId($folderid); + $newimapid = $imapid . $this->getServerDelimiter() . $displayname; + } + + $csts = imap_createmailbox($this->mbox, $this->server . $newimapid); + if ($csts) { + imap_subscribe($this->mbox, $this->server . $newimapid); + return $this->StatFolder($folderid . $this->getServerDelimiter() . $displayname); + } + else { + ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->ChangeFolder() : mailbox creation failed"); + return false; + } } - if ($csts) { - return $this->StatFolder($folderid . $this->serverdelimiter . $displayname); - } - else - return false; } /** @@ -858,7 +916,11 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * */ public function DeleteFolder($id, $parentid){ - // TODO implement + $imapid = $this->getImapIdFromFolderId($id); + if ($imapid) { + return imap_deletemailbox($this->mbox, $this->server.$imapid); + } + return false; } @@ -885,13 +947,17 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $sequence = "1:*"; if ($cutoffdate > 0) { $search = @imap_search($this->mbox, "SINCE ". date("d-M-Y", $cutoffdate)); - if ($search !== false) - $sequence = implode(",", $search); + if ($search === false) { + ZLog::Write(LOGLEVEL_INFO, sprintf("BackendIMAP->GetMessageList('%s','%s'): 0 result for the search or error: %s", $folderid, $cutoffdate, imap_last_error())); + return $messages; + } + + $sequence = implode(",", $search); } ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessageList(): searching with sequence '%s'", $sequence)); $overviews = @imap_fetch_overview($this->mbox, $sequence); - if (!$overviews || !is_array($overviews)) { + if (!is_array($overviews)) { $error = imap_last_error(); if (strlen($error) > 0 && imap_num_msg($this->mbox) > 0) { ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->GetMessageList('%s','%s'): Failed to retrieve overview: %s", $folderid, $cutoffdate, imap_last_error())); @@ -899,26 +965,31 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { return $messages; } - foreach($overviews as $overview) { - $date = ""; - $vars = get_object_vars($overview); - if (array_key_exists( "date", $vars)) { - // message is out of range for cutoffdate, ignore it - if ($this->cleanupDate($overview->date) < $cutoffdate) continue; - $date = $overview->date; + foreach ($overviews as $overview) { + // Determine the message's date and apply the cutoff; if the overview's ->udate property is + // not available, fall back to the "Date:" header as it appears in the email. + $date = 0; + if (isset($overview->udate)) { + $date = $overview->udate; + } else if (isset($overview->date)) { + $date = $this->cleanupDate($overview->date); + } + if ($date < $cutoffdate) { + // Message is out of range; ignore it + continue; } // cut of deleted messages - if (array_key_exists("deleted", $vars) && $overview->deleted) + if (isset($overview->deleted) && $overview->deleted) continue; - if (array_key_exists("uid", $vars)) { + if (isset($overview->uid)) { $message = array(); $message["mod"] = $date; $message["id"] = $overview->uid; // 'seen' aka 'read' - if(array_key_exists("seen", $vars) && $overview->seen) { + if (isset($overview->seen) && $overview->seen) { $message["flags"] = 1; } else { @@ -926,14 +997,14 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } // 'flagged' aka 'FollowUp' aka 'starred' - if (array_key_exists("flagged", $vars) && $overview->flagged) { + if (isset($overview->flagged) && $overview->flagged) { $message["star"] = 1; } else { $message["star"] = 0; } - array_push($messages, $message); + $messages[] = $message; } } return $messages; @@ -953,90 +1024,118 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $truncsize = Utils::GetTruncSize($contentparameters->GetTruncation()); $mimesupport = $contentparameters->GetMimeSupport(); $bodypreference = $contentparameters->GetBodyPreference(); /* fmbiete's contribution r1528, ZP-320 */ - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage('%s','%s')", $folderid, $id)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage('%s', '%s', '%s')", $folderid, $id, implode(",", $bodypreference))); $folderImapid = $this->getImapIdFromFolderId($folderid); + $is_sent_folder = strcasecmp($folderImapid, $this->create_name_folder(IMAP_FOLDER_SENT)) == 0; + // Get flags, etc $stat = $this->StatMessage($folderid, $id); if ($stat) { $this->imap_reopen_folder($folderImapid); - $mail = @imap_fetchheader($this->mbox, $id, FT_UID) . @imap_body($this->mbox, $id, FT_PEEK | FT_UID); + $mail_headers = @imap_fetchheader($this->mbox, $id, FT_UID); + $mail = $mail_headers . @imap_body($this->mbox, $id, FT_PEEK | FT_UID); if (empty($mail)) { throw new StatusException(sprintf("BackendIMAP->GetMessage(): Error, message not found, maybe was moved"), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); } $mobj = new Mail_mimeDecode($mail); - $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); + $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'rfc_822bodies' => true, 'charset' => 'utf-8')); - /* BEGIN fmbiete's contribution r1528, ZP-320 */ - $output = new SyncMail(); + $is_multipart = is_multipart($message); + $is_smime = is_smime($message); + $is_encrypted = $is_smime ? is_encrypted($message) : false; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage(): Message is multipart: %d, smime: %d, smime encrypted: %d", $is_multipart, $is_smime, $is_encrypted)); //Select body type preference $bpReturnType = SYNC_BODYPREFERENCE_PLAIN; if ($bodypreference !== false) { $bpReturnType = Utils::GetBodyPreferenceBestMatch($bodypreference); // changed by mku ZP-330 } - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage - getBodyPreferenceBestMatch: %d", $bpReturnType)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage(): getBodyPreferenceBestMatch: %d", $bpReturnType)); - if (is_smime($message)) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage - Message is SMIME, forcing to work with MIME")); + // Prefered format is MIME -OR- message is SMIME -OR- the device supports MIME (iPhone) and doesn't really understand HTML + if ($bpReturnType == SYNC_BODYPREFERENCE_MIME || $is_smime || in_array(SYNC_BODYPREFERENCE_MIME, $bodypreference)) { $bpReturnType = SYNC_BODYPREFERENCE_MIME; } - //Get body data - Mail_mimeDecode::getBodyRecursive($message, "plain", $plainBody); - Mail_mimeDecode::getBodyRecursive($message, "html", $htmlBody); - if ($plainBody == "") { - $plainBody = Utils::ConvertHtmlToText($htmlBody); + // We need the text body even though MIME is used, for the preview + $textBody = ""; + Mail_mimeDecode::getBodyRecursive($message, "html", $textBody, true); + if (strlen($textBody) > 0) { + if ($bpReturnType != SYNC_BODYPREFERENCE_MIME) { + $bpReturnType = SYNC_BODYPREFERENCE_HTML; + } } - $htmlBody = str_replace("\n","\r\n", str_replace("\r","",$htmlBody)); - $plainBody = str_replace("\n","\r\n", str_replace("\r","",$plainBody)); + else { + Mail_mimeDecode::getBodyRecursive($message, "plain", $textBody, true); + if ($bpReturnType != SYNC_BODYPREFERENCE_MIME) { + $bpReturnType = SYNC_BODYPREFERENCE_PLAIN; + } + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage(): after thinking a bit we will use: %d", $bpReturnType)); + + + $output = new SyncMail(); if (Request::GetProtocolVersion() >= 12.0) { $output->asbody = new SyncBaseBody(); switch($bpReturnType) { case SYNC_BODYPREFERENCE_PLAIN: - $output->asbody->data = $plainBody; + $output->asbody->data = $textBody; break; case SYNC_BODYPREFERENCE_HTML: - if ($htmlBody == "") { - $output->asbody->data = $plainBody; - $bpReturnType = SYNC_BODYPREFERENCE_PLAIN; - } - else { - $output->asbody->data = $htmlBody; - } + $output->asbody->data = $textBody; break; case SYNC_BODYPREFERENCE_MIME: - if (is_smime($message)) { - $output->asbody->data = $mail; + if ($is_smime) { + if ($is_encrypted) { + // #190, KD 2015-06-04 - If message body is encrypted only send the headers, as data should only be in the attachment + $output->asbody->data = $mail_headers; + } + else { + $output->asbody->data = $mail; + } } else { $output->asbody->data = build_mime_message($message); } break; case SYNC_BODYPREFERENCE_RTF: - ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->GetMessage RTF Format NOT CHECKED"); - $output->asbody->data = base64_encode($plainBody); + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->GetMessage(): RTF Format NOT CHECKED"); + $output->asbody->data = base64_encode($textBody); break; } - // truncate body, if requested, but never truncate MIME messages - if($bpReturnType !== SYNC_BODYPREFERENCE_MIME && strlen($output->asbody->data) > $truncsize) { - $output->asbody->data = Utils::Utf8_truncate($output->asbody->data, $truncsize); - $output->asbody->truncated = 1; + + // truncate body, if requested. + // MIME should not be truncated, but encrypted messages are truncated always to the headers size + if ($bpReturnType == SYNC_BODYPREFERENCE_MIME) { + if ($is_encrypted) { + $output->asbody->truncated = 1; + } + else { + $output->asbody->truncated = 0; + } } else { - $output->asbody->truncated = 0; + if (strlen($output->asbody->data) > $truncsize) { + $output->asbody->data = Utils::Utf8_truncate($output->asbody->data, $truncsize); + $output->asbody->truncated = 1; + } + else { + $output->asbody->truncated = 0; + } } $output->asbody->type = $bpReturnType; if ($bpReturnType == SYNC_BODYPREFERENCE_MIME) { + // NativeBodyType can be only (1 => PLAIN, 2 => HTML, 3 => RTF). MIME uses 1 $output->nativebodytype = SYNC_BODYPREFERENCE_PLAIN; - // http://msdn.microsoft.com/en-us/library/ee220018%28v=exchg.80%29.aspx } else { $output->nativebodytype = $bpReturnType; @@ -1045,11 +1144,13 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $bpo = $contentparameters->BodyPreference($output->asbody->type); if (Request::GetProtocolVersion() >= 14.0 && $bpo->GetPreview()) { - $output->asbody->preview = Utils::Utf8_truncate(Utils::ConvertHtmlToText($plainBody), $bpo->GetPreview()); + $output->asbody->preview = Utils::Utf8_truncate(Utils::ConvertHtmlToText($textBody), $bpo->GetPreview()); } } /* END fmbiete's contribution r1528, ZP-320 */ else { // ASV_2.5 + //DEPRECATED : very old devices, and incomplete code + $output->bodytruncated = 0; /* BEGIN fmbiete's contribution r1528, ZP-320 */ if ($bpReturnType == SYNC_BODYPREFERENCE_MIME) { @@ -1060,12 +1161,12 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } else { // truncate body, if requested - if (strlen($plainBody) > $truncsize) { - $output->body = Utils::Utf8_truncate($plainBody, $truncsize); + if (strlen($textBody) > $truncsize) { + $output->body = Utils::Utf8_truncate($textBody, $truncsize); $output->bodytruncated = 1; } else { - $output->body = $plainBody; + $output->body = $textBody; $output->bodytruncated = 0; } $output->bodysize = strlen($output->body); @@ -1073,9 +1174,19 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { /* END fmbiete's contribution r1528, ZP-320 */ } + unset($textBody); + unset($mail_headers); + $output->datereceived = isset($message->headers["date"]) ? $this->cleanupDate($message->headers["date"]) : null; - if (is_smime($message)) { - $output->messageclass = "IPM.Note.SMIME.MultipartSigned"; + + if ($is_smime) { + // #190, KD 2015-06-04 - Add Encrypted (and possibly signed) to the classifications emitted + if ($is_encrypted) { + $output->messageclass = "IPM.Note.SMIME"; + } + else { + $output->messageclass = "IPM.Note.SMIME.MultipartSigned"; + } } else { $output->messageclass = "IPM.Note"; @@ -1143,25 +1254,30 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } else { foreach($addrlist as $addr) { - if (isset($addr->mailbox) && isset($addr->host) && isset($addr->personal)) { - $address = $addr->mailbox . "@" . $addr->host; - $name = $addr->personal; - - if (!isset($output->displayto) && $name != "") - $output->displayto = $name; - - if($name == "" || $name == $address) - $fulladdr = $address; - else { - if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') { - $fulladdr = "\"" . $name ."\" <" . $address . ">"; - } - else { - $fulladdr = $name ." <" . $address . ">"; + // If the address was a group we have "groupname" and "addresses" atributes + if (isset($addr->addresses)) { + if (count($addr->addresses) == 0) { + // readd the empty group delimiter + array_push($output->$type, sprintf("%s:;", $addr->groupname)); + if (!isset($output->displayto) && strlen($addr->groupname) > 0) { + $output->displayto = $addr->groupname; } } - - array_push($output->$type, $fulladdr); + else { + foreach($addr->addresses as $addr_group) { + $name = $this->add_address_to_list($output->$type, $addr_group); + if (!isset($output->displayto) && strlen($name) > 0) { + $output->displayto = $name; + } + } + } + } + else { + // Not a group + $name = $this->add_address_to_list($output->$type, $addr); + if (!isset($output->displayto) && strlen($name) > 0) { + $output->displayto = $name; + } } } } @@ -1186,18 +1302,23 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { // Attachments are also needed for MIME messages if(isset($message->parts)) { $mparts = $message->parts; - for ($i=0; $ictype_primary) && $part->ctype_primary == "multipart") && (isset($part->ctype_secondary) && ($part->ctype_secondary == "mixed" || $part->ctype_secondary == "alternative" || $part->ctype_secondary == "related"))) { - foreach($part->parts as $spart) - $mparts[] = $spart; + if (isset($part->parts)) { + foreach($part->parts as $spart) + $mparts[] = $spart; + } + // Go to the for again continue; } + if (is_calendar($part)) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage - text/calendar part found, trying to convert")); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage(): text/calendar part found, trying to convert")); $output->meetingrequest = new SyncMeetingRequest(); - $this->parseMeetingCalendar($part, $output); + parse_meeting_calendar($part, $output, $is_sent_folder); } else { //add part as attachment if it's disposition indicates so or if it is not a text part @@ -1227,21 +1348,24 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $attachment->contentid = isset($part->headers['content-id']) ? str_replace("<", "", str_replace(">", "", $part->headers['content-id'])) : ""; if (isset($part->disposition) && $part->disposition == "inline") { $attachment->isinline = 1; - // We try to fix the name for the inline file. - // FIXME: This is a dirty hack as the used in the Zarafa backend, if you have a better method let me know! - if (isset($part->ctype_primary) && isset($part->ctype_secondary)) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage - Guessing extension for inline attachment [primary_type %s secondary_type %s]", $part->ctype_primary, $part->ctype_secondary)); - if (isset(BackendIMAP::$mimeTypes[$part->ctype_primary.'/'.$part->ctype_secondary])) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage - primary_type %s secondary_type %s", $part->ctype_primary, $part->ctype_secondary)); - $attachment->displayname = "inline_".$i.".".BackendIMAP::$mimeTypes[$part->ctype_primary.'/'.$part->ctype_secondary]; + // #209 - KD 2015-06-16 If we got a filename use it, otherwise guess + if (!isset($part->filename)) { + // We try to fix the name for the inline file. + // FIXME: This is a dirty hack as the used in the Zarafa backend, if you have a better method let me know! + if (isset($part->ctype_primary) && isset($part->ctype_secondary)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage(): Guessing extension for inline attachment [primary_type %s secondary_type %s]", $part->ctype_primary, $part->ctype_secondary)); + if (isset(BackendIMAP::$mimeTypes[$part->ctype_primary.'/'.$part->ctype_secondary])) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage(): primary_type %s secondary_type %s", $part->ctype_primary, $part->ctype_secondary)); + $attachment->displayname = "inline_".$i.".".BackendIMAP::$mimeTypes[$part->ctype_primary.'/'.$part->ctype_secondary]; + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage(): no extension found in '%s'!!", SYSTEM_MIME_TYPES_MAPPING)); + } } else { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage - no extension found in /etc/mime.types'!!")); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage(): no primary_type or secondary_type")); } } - else { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage - no primary_type or secondary_type")); - } } else { $attachment->isinline = 0; @@ -1269,9 +1393,11 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } } } - // unset mimedecoder & mail + + unset($message); unset($mobj); unset($mail); + return $output; } @@ -1288,29 +1414,32 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * @return array/boolean */ public function StatMessage($folderid, $id) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->StatMessage('%s','%s')", $folderid, $id)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->StatMessage('%s','%s')", $folderid, $id)); $folderImapid = $this->getImapIdFromFolderId($folderid); $this->imap_reopen_folder($folderImapid); - $overview = @imap_fetch_overview( $this->mbox , $id , FT_UID); + $overview = @imap_fetch_overview($this->mbox, $id, FT_UID); if (!$overview) { - ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->StatMessage('%s','%s'): Failed to retrieve overview: %s", $folderid, $id, imap_last_error())); + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->StatMessage('%s','%s'): Failed to retrieve overview: %s", $folderid, $id, imap_last_error())); return false; } - // check if variables for this overview object are available - $vars = get_object_vars($overview[0]); - // without uid it's not a valid message - if (! array_key_exists( "uid", $vars)) return false; + if (empty($overview[0]->uid)) return false; $entry = array(); - $entry["mod"] = (array_key_exists( "date", $vars)) ? $overview[0]->date : ""; + if (isset($overview[0]->udate)) { + $entry["mod"] = $overview[0]->udate; + } else if (isset($overview[0]->date)) { + $entry["mod"] = $this->cleanupDate($overview[0]->date); + } else { + $entry["mod"] = 0; + } $entry["id"] = $overview[0]->uid; // 'seen' aka 'read' - if (array_key_exists("seen", $vars) && $overview[0]->seen) { + if (isset($overview[0]->seen) && $overview[0]->seen) { $entry["flags"] = 1; } else { @@ -1318,7 +1447,7 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } // 'flagged' aka 'FollowUp' aka 'starred' - if (array_key_exists("flagged", $vars) && $overview[0]->flagged) { + if (isset($overview[0]->flagged) && $overview[0]->flagged) { $entry["star"] = 1; } else { @@ -1410,40 +1539,6 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { return $status; } - /** - * Changes the 'star' flag of a message on disk - * - * @param string $folderid id of the folder - * @param string $id id of the message - * @param int $flags read flag of the message - * @param ContentParameters $contentparameters - * - * @access public - * @return boolean status of the operation - * @throws StatusException could throw specific SYNC_STATUS_* exceptions - */ - public function SetStarFlag($folderid, $id, $flags, $contentparameters) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->SetStarFlag('%s','%s','%s')", $folderid, $id, $flags)); - - $folderImapid = $this->getImapIdFromFolderId($folderid); - $this->imap_reopen_folder($folderImapid); - - if ($this->imap_inside_cutoffdate(Utils::GetCutOffDate($contentparameters->GetFilterType()), $id)) { - if ($flags == 0) { - // set as "UnFlagged" (unstarred) - $status = @imap_clearflag_full($this->mbox, $id, "\\Flagged", ST_UID); - } else { - // set as "Flagged" (starred) - $status = @imap_setflag_full($this->mbox, $id, "\\Flagged", ST_UID); - } - } - else { - throw new StatusException(sprintf("BackendIMAP->SetStarFlag(): Message is outside the sync range"), SYNC_STATUS_OBJECTNOTFOUND); - } - - return $status; - } - /** * Called when the user has requested to delete (really delete) a message * @@ -1471,7 +1566,6 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { throw new StatusException(sprintf("BackendIMAP->DeleteMessage(): Message is outside the sync range"), SYNC_STATUS_OBJECTNOTFOUND); } - return ($s1 && $s2 && $s11); } @@ -1528,14 +1622,16 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { // open new folder $stat = $this->imap_reopen_folder($newfolderImapid); - if (! $s1) + if (!$stat) throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, opening the destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); // remove all flags $s3 = @imap_clearflag_full($this->mbox, $newid, "\\Seen \\Answered \\Flagged \\Deleted \\Draft", FT_UID); $newflags = ""; - if ($overview[0]->seen) + $move_to_trash = strcasecmp($newfolderImapid, $this->create_name_folder(IMAP_FOLDER_TRASH)) == 0; + + if ($overview[0]->seen || ($move_to_trash && defined('IMAP_AUTOSEEN_ON_DELETE') && IMAP_AUTOSEEN_ON_DELETE == true)) $newflags .= "\\Seen"; if ($overview[0]->flagged) $newflags .= " \\Flagged"; @@ -1574,52 +1670,32 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $mail = @imap_fetchheader($this->mbox, $requestid, FT_UID) . @imap_body($this->mbox, $requestid, FT_PEEK | FT_UID); if (empty($mail)) { - throw new StatusException(sprintf("BackendIMAP->MeetingResponse(): Error, message not found, maybe was moved"), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); + throw new StatusException("BackendIMAP->MeetingResponse(): Error, message not found, maybe was moved", SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); } + // Get the original calendar request, so we don't need to create it from scratch $mobj = new Mail_mimeDecode($mail); unset($mail); - $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); + $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'rfc_822bodies' => true, 'charset' => 'utf-8')); unset($mobj); - $Mail_RFC822 = new Mail_RFC822(); - $from_header = $this->getDefaultFromValue(); - $fromaddr = $this->parseAddr($Mail_RFC822->parseAddressList($from_header)); - $to_header = ""; - if (isset($message->headers["from"])) { - $to_header = $message->headers["from"]; - } - else { - if (isset($message->headers["return-path"])) { - $to_header = $message->headers["return-path"]; - } - else { - throw new StatusException(sprintf("BackendIMAP->MeetingResponse(): Error, no reply address"), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); - } - } - $toaddr = $this->parseAddr($Mail_RFC822->parseAddressList($to_header)); - if (isset($message->headers["subject"])) { - $subject_header = $message->headers["subject"]; - } - else { - $subject_header = ""; - } - $body_part = null; if(isset($message->parts)) { $mparts = $message->parts; for ($i=0; $i < count($mparts); $i++) { $part = $mparts[$i]; //recursively add parts - if ((isset($part->ctype_primary) && $part->ctype_primary == "multipart") && (isset($part->ctype_secondary) && ($part->ctype_secondary == "mixed" || $part->ctype_secondary == "alternative" || $part->ctype_secondary == "related"))) { + if ((isset($part->ctype_primary) && $part->ctype_primary == "multipart") + && (isset($part->ctype_secondary) && ($part->ctype_secondary == "mixed" || $part->ctype_secondary == "alternative" || $part->ctype_secondary == "related"))) { foreach($part->parts as $spart) $mparts[] = $spart; continue; } if (is_calendar($part)) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MeetingResponse - text/calendar part found, trying to reply")); - $body_part = $this->replyMeetingCalendar($part, $response); + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->MeetingResponse - text/calendar part found, trying to reply"); + // FIXME: here we should use the user email address, that could not be username + $body_part = reply_meeting_calendar($part, $response, $this->username); } } unset($mparts); @@ -1627,37 +1703,30 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { unset($message); if ($body_part === null) { - throw new StatusException(sprintf("BackendIMAP->MeetingResponse(): Error, no calendar part modified"), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); + throw new StatusException("BackendIMAP->MeetingResponse(): Error, no calendar part modified", SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); } - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MeetingResponse - Creating response message")); - $mail = new Mail_mimepart(); - $headers = array("MIME-version" => "1.0", - "From" => $mail->encodeHeader("from", $from_header, "UTF-8"), - "To" => $mail->encodeHeader("to", $to_header, "UTF-8"), - "Date" => gmdate("D, d M Y H:i:s", time())." GMT", - "Subject" => $mail->encodeHeader("subject", $subject_header, "UTF-8"), - "Content-class" => "urn:content-classes:calendarmessage", - "Content-transfer-encoding" => "8BIT"); - unset($mail); - $mail = new Mail_mimepart($body_part, array("content_type" => "text/calendar; method=REPLY; charset=UTF-8", "headers" => $headers)); - - $encoded_mail = $mail->encode(); - unset($mail); - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MeetingResponse - Response message")); - foreach ($encoded_mail["headers"] as $k => $v) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("%s: %s", $k, $v)); + $uuid_calendar = ""; + switch($response) { + case 1: // ACCEPTED + case 2: // TENTATIVE + $uuid_calendar = create_calendar_dav($body_part); + break; + case 3: // DECLINED + // Do nothing + break; } - ZLog::Write(LOGLEVEL_DEBUG, sprintf("%s", $encoded_mail["body"])); - $send = $this->sendMessage($fromaddr, $toaddr, $encoded_mail["headers"], $encoded_mail["body"]); + // We don't need to send a reply, because the client will do it - if ($send) { - $this->saveSentMessage($encoded_mail["headers"], $encoded_mail["body"]); - } - unset($encoded_mail); + // Remove message: answered invitation + // Roundcube client doesn't remove the original message, but Zarafa backend does + $s1 = @imap_delete ($this->mbox, $requestid, FT_UID); + $s11 = @imap_setflag_full($this->mbox, $requestid, "\\Deleted", FT_UID); + $s2 = @imap_expunge($this->mbox); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MeetingResponse('%s','%s'): removing message result: s-delete: '%s' s-expunge: '%s' setflag: '%s'", $folderid, $requestid, $s1, $s2, $s11)); - return $send; + return $uuid_calendar; } @@ -1675,7 +1744,38 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { if (strpos($username, "@") === false && strlen($this->domain) > 0) { $email .= "@" . $this->domain; } - return array('emailaddress' => $email, 'fullname' => $this->getDefaultFullNameValue($username)); + return array('emailaddress' => $email, 'fullname' => getDefaultFullNameValue($username, $this->domain)); + } + + + /** + * Applies settings to and gets informations from the device + * + * @param SyncObject $settings (SyncOOF or SyncUserInformation possible) + * + * @access public + * @return SyncObject $settings + */ + public function Settings($settings) { + if ($settings instanceof SyncOOF) { + $this->settingsOOF($settings); + } + else if ($settings instanceof SyncUserInformation) { + $this->settingsUserInformation($settings); + } + + return $settings; + } + + + /** + * Indicates which AS version is supported by the backend. + * + * @access public + * @return string AS version constant + */ + public function GetSupportedASVersion() { + return ZPush::ASV_14; } @@ -2017,13 +2117,21 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * @return string delimiter */ protected function getServerDelimiter() { - $list = @imap_getmailboxes($this->mbox, $this->server, "*"); - if (is_array($list)) { - $val = $list[0]; - - return $val->delimiter; + $this->InitializePermanentStorage(); + if (isset($this->permanentStorage->serverdelimiter)) { + return $this->permanentStorage->serverdelimiter; } - return "."; // default "." + + $list = @imap_getmailboxes($this->mbox, $this->server, "*"); + if (is_array($list) && count($list) > 0) { + // get the delimiter from the first folder + $delimiter = $list[0]->delimiter; + $this->permanentStorage->serverdelimiter = $delimiter; + } else { + // default + $delimiter = "."; + } + return $delimiter; } /** @@ -2037,11 +2145,8 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * @return boolean if folder is opened */ protected function imap_reopen_folder($folderid, $force = false) { - // if the stream is not alive, we open it again - if (!@imap_ping($this->mbox)) { - $this->mbox = @imap_open($this->server, $this->username, $this->password, OP_HALFOPEN); - $this->mboxFolder = ""; - } + // Reconnect + $this->imap_reconnect(); // to see changes, the folder has to be reopened! if ($this->mboxFolder != $folderid || $force) { @@ -2057,6 +2162,20 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { return true; } + /** + * Reconnect IMAP connection if needed + * + * @access private + */ + private function imap_reconnect() { + if ($this->mbox) { + imap_ping($this->mbox); + } + else { + $this->mbox = @imap_open($this->server, $this->username, $this->password, OP_HALFOPEN); + $this->mboxFolder = ""; + } + } /** * Creates a new IMAP folder. @@ -2168,10 +2287,9 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * @return */ protected function getModAndParentNames($fhir, &$displayname, &$parent) { - // if mod is already set add the previous part to it as it might be a folder which has - // delimiter in its name - $displayname = (isset($displayname) && strlen($displayname) > 0) ? $displayname = array_pop($fhir).$this->serverdelimiter.$displayname : array_pop($fhir); - $parent = implode($this->serverdelimiter, $fhir); + // if mod is already set add the previous part to it as it might be a folder which has delimiter in its name + $displayname = (isset($displayname) && strlen($displayname) > 0) ? $displayname = array_pop($fhir) . $this->getServerDelimiter() . $displayname : array_pop($fhir); + $parent = implode($this->getServerDelimiter(), $fhir); if (count($fhir) == 1 || $this->checkIfIMAPFolder($parent)) { return; @@ -2180,6 +2298,26 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $this->getModAndParentNames($fhir, $displayname, $parent); } + /** + * Prepare the folder name to get the type and parent. + * + * @param string $folder_name + * @return string + * @access private + */ + private function create_name_folder($folder_name) { + $foldername = $folder_name; + // If we have defined a folder prefix, and it's not empty + if (defined('IMAP_FOLDER_PREFIX') && IMAP_FOLDER_PREFIX != "") { + // If inbox uses prefix or we are not evaluating inbox + if (IMAP_FOLDER_PREFIX_IN_INBOX == true || strcasecmp($foldername, IMAP_FOLDER_INBOX) != 0) { + $foldername = IMAP_FOLDER_PREFIX . $this->getServerDelimiter() . $foldername; + } + } + + return $foldername; + } + /** * Checks if a specified name is a folder in the IMAP store * @@ -2189,9 +2327,13 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * @return boolean */ protected function checkIfIMAPFolder($folderName) { - $parent = imap_list($this->mbox, $this->server, $folderName); - if ($parent === false) return false; - return true; + $folder_name = $folderName; + if (defined(IMAP_FOLDER_PREFIX) && strlen(IMAP_FOLDER_PREFIX) > 0) { + // TODO: We don't care about the inbox exception with the prefix, because we won't check inbox + $folder_name = IMAP_FOLDER_PREFIX . $this->getServerDelimiter() . $folder_name; + } + $list_subfolders = @imap_list($this->mbox, $this->server, $folder_name); + return is_array($list_subfolders); } /** @@ -2201,244 +2343,20 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * @param string $receiveddate a date as a string * * @access protected - * @return string + * @return integer */ protected function cleanupDate($receiveddate) { if (is_array($receiveddate)) { // Header Date could be repeated in the message, we only check the first $receiveddate = $receiveddate[0]; } - $receiveddate = strtotime(preg_replace("/\(.*\)/", "", $receiveddate)); - if ($receiveddate == false || $receiveddate == -1) { - ZLog::Write(LOGLEVEL_DEBUG, "cleanupDate() : Received date is false. Message might be broken."); + $receivedtime = strtotime(preg_replace('/\(.*\)/', "", $receiveddate)); + if ($receivedtime === false || $receivedtime == -1) { + ZLog::Write(LOGLEVEL_WARN, sprintf("cleanupDate('%s'): strtotime() failed - message might be broken.", $receiveddate)); return null; } - return $receiveddate; - } - - /** - * Returns the default value for "From" - * - * @access private - * @return string - */ - private function getDefaultFromValue() { - $v = ""; - switch (IMAP_DEFAULTFROM) { - case 'username': - $v = $this->username; - break; - case 'domain': - $v = $this->domain; - break; - case 'ldap': - $v = $this->getIdentityFromLdap($this->username, $this->domain, IMAP_FROM_LDAP_FROM, true); - break; - case 'sql': - $v = $this->getIdentityFromSql($this->username, $this->domain, IMAP_FROM_SQL_FROM, true); - break; - case 'passwd': - $v = $this->getIdentityFromPasswd($this->username, $this->domain, 'FROM', true); - break; - default: - $v = $this->username . IMAP_DEFAULTFROM; - break; - } - - return $v; - } - - /** - * Return the default value for "FullName" - * - * @access private - * @param string $username Username - * @return string - */ - private function getDefaultFullNameValue($username) { - $v = $this->username; - switch (IMAP_DEFAULTFROM) { - case 'ldap': - $v = $this->getIdentityFromSql($username, $this->domain, IMAP_FROM_LDAP_FULLNAME, false); - break; - case 'sql': - $v = $this->getIdentityFromSql($username, $this->domain, IMAP_FROM_SQL_FULLNAME, false); - break; - case 'passwd': - $v = $this->getIdentityFromPasswd($username, $this->domain, 'FULLNAME', false); - break; - } - - return $v; - } - - /** - * Generate the "From"/"FullName" value stored in a LDAP server - * - * @access private - * @params string $username username value - * @params string $domain domain value - * @params string $identity pattern to fill with ldap values - * @params boolean $encode if the result should be encoded as a header - * @return string - */ - private function getIdentityFromLdap($username, $domain, $identity, $encode = true) { - $ret_value = $username; - - $ldap_conn = null; - try { - $ldap_conn = ldap_connect(IMAP_FROM_LDAP_SERVER, IMAP_FROM_LDAP_SERVER_PORT); - 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 - foreach (unserialize(IMAP_FROM_LDAP_FIELDS) as $field) { - $ret_value = str_replace('#'.$field, $items[0][$field][0], $ret_value); - } - if ($encode) { - $ret_value = $this->encodeFrom($ret_value); - } - } - else { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromLdap() - No entry found in LDAP")); - } - } - else { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromLdap() - Not authenticated in LDAP server")); - } - } - else { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromLdap() - Not connected to LDAP server")); - } - } - catch(Exception $ex) { - ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getIdentityFromLdap() - Error getting From value from LDAP server: %s", $ex)); - } - - ldap_close($ldap_conn); - - return $ret_value; - } - - - /** - * Generate the "From" value stored in a SQL Database - * - * @access private - * @params string $username username value - * @params string $domain domain value - * @return string - */ - private function getIdentityFromSql($username, $domain, $identity, $encode = true) { - $ret_value = $username; - - $dbh = $sth = $record = null; - try { - $dbh = new PDO(IMAP_FROM_SQL_DSN, IMAP_FROM_SQL_USER, IMAP_FROM_SQL_PASSWORD, unserialize(IMAP_FROM_SQL_OPTIONS)); - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromSql() - Connected to SQL Database")); - - $sql = str_replace('#username', $username, str_replace('#domain', $domain, IMAP_FROM_SQL_QUERY)); - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromSql() - Searching From with filter: %s", $sql)); - $sth = $dbh->prepare($sql); - $sth->execute(); - $record = $sth->fetch(PDO::FETCH_ASSOC); - if ($record) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromSql() - Found entry in SQL Database. Generating From")); - $ret_value = $identity; - foreach (unserialize(IMAP_FROM_SQL_FIELDS) as $field) { - $ret_value = str_replace('#'.$field, $record[$field], $ret_value); - } - if ($encode) { - $ret_value = $this->encodeFrom($ret_value); - } - } - else { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromSql() - No entry found in SQL Database")); - } - } - catch(PDOException $ex) { - ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getIdentityFromSql() - Error getting From value from SQL Database: %s", $ex)); - } - - $dbh = $sth = $record = null; - - return $ret_value; - } - - /** - * Generate the "From" value from the local posix passwd database - * - * @access private - * @params string $username username value - * @params string $domain domain value - * @return string - */ - private function getIdentityFromPasswd($username, $domain, $identity, $encode = true) { - $ret_value = $username; - - try { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromPasswd() - Fetching info for user %s", $username)); - - $local_user = posix_getpwnam($username); - if ($local_user) { - $tmp = $local_user['gecos']; - $tmp = explode(',', $tmp); - $name = $tmp[0]; - unset($tmp); - - switch ($identity) { - case 'FROM': - if (strlen($domain) > 0) { - $ret_value = sprintf("%s <%s@%s>", $name, $username, $domain); - } else { - ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getIdentityFromPasswd() - No domain passed. Cannot construct From address.")); - } - break; - case 'FULLNAME': - $ret_value = sprintf("%s", $name); - break; - } - if ($encode) { - $ret_value = $this->encodeFrom($ret_value); - } - } else { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromPasswd() - No entry found in Password database")); - - } - - } - catch(Exception $ex) { - ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getIdentityFromPasswd() - Error getting From value from passwd database: %s", $ex)); - } - - return $ret_value; - } - - - /** - * Encode the From value as Base64 - * - * @access private - * @param string $from From value - * @return string - */ - private function encodeFrom($from) { - $items = explode("<", $from); - $name = trim($items[0]); - return "=?UTF-8?B?" . base64_encode($name) . "?= <" . $items[1]; + return $receivedtime; } @@ -2450,9 +2368,8 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { */ private function SystemExtensionMimeTypes() { $out = array(); - $mime_file = '/etc/mime.types'; - if (file_exists($mime_file)) { - $file = fopen($mime_file, 'r'); + if (file_exists(SYSTEM_MIME_TYPES_MAPPING)) { + $file = fopen(SYSTEM_MIME_TYPES_MAPPING, 'r'); while(($line = fgets($file)) !== false) { $line = trim(preg_replace('/#.*/', '', $line)); if(!$line) @@ -2474,173 +2391,6 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } - /** - * Modify a text/calendar part to transform it in a reply - * - * @access private - * @param $part MIME part - * @param $response Response numeric value - * @return string MIME text/calendar - */ - private function replyMeetingCalendar($part, $response) { - $response_text = "ACCEPTED"; // 1 or default is ACCEPTED - switch ($response) { - case 1: - $response_text = "ACCEPTED"; - break; - case 2: - $response_text = "TENTATIVE"; - break; - case 3: - $response_text = "DECLINED"; - break; - } - - $ical = new iCalComponent(); - $ical->ParseFrom($part->body); - - $ical->SetPValue("METHOD", "REPLY"); - $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", $response_text); - - return $ical->Render(); - } - - - /** - * Converts a text/calendar part into SyncMeetingRequest - * - * @access private - * @param $part MIME part - * @param $output SyncMail object - */ - private function parseMeetingCalendar($part, &$output) { - $ical = new iCalComponent(); - $ical->ParseFrom($part->body); - - if (isset($part->ctype_parameters["method"])) { - switch (strtolower($part->ctype_parameters["method"])) { - case "cancel": - $output->messageclass = "IPM.Schedule.Meeting.Canceled"; - break; - case "counter": - $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; - break; - case "reply": - $props = $ical->GetPropertiesByPath('!VTIMEZONE/ATTENDEE'); - if (count($props) == 1) { - $props_params = $props[0]->Parameters(); - if (isset($props_params["PARTSTAT"])) { - switch (strtolower($props_params["PARTSTAT"])) { - case "accepted": - $output->messageclass = "IPM.Schedule.Meeting.Resp.Pos"; - break; - case "needs-action": - case "tentative": - $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; - break; - case "declined": - $output->messageclass = "IPM.Schedule.Meeting.Resp.Neg"; - break; - default: - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - Unknown reply status %s", strtolower($props_params["PARTSTAT"]))); - $output->messageclass = "IPM.Appointment"; - break; - } - } - else { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - No reply status found")); - $output->messageclass = "IPM.Appointment"; - } - } - else { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - There are not attendees")); - $output->messageclass = "IPM.Appointment"; - } - break; - case "request": - $output->messageclass = "IPM.Schedule.Meeting.Request"; - break; - default: - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - Unknown method %s", strtolower($part->headers["method"]))); - $output->messageclass = "IPM.Appointment"; - break; - } - } - else { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - No method header")); - $output->messageclass = "IPM.Appointment"; - } - - $props = $ical->GetPropertiesByPath('VEVENT/DTSTAMP'); - if (count($props) == 1) { - $output->meetingrequest->dtstamp = Utils::MakeUTCDate($props[0]->Value()); - } - $props = $ical->GetPropertiesByPath('VEVENT/UID'); - if (count($props) == 1) { - $output->meetingrequest->globalobjid = $props[0]->Value(); - } - $props = $ical->GetPropertiesByPath('VEVENT/DTSTART'); - if (count($props) == 1) { - $output->meetingrequest->starttime = Utils::MakeUTCDate($props[0]->Value()); - if (strlen($props[0]->Value()) == 8) { - $output->meetingrequest->alldayevent = 1; - } - } - $props = $ical->GetPropertiesByPath('VEVENT/DTEND'); - if (count($props) == 1) { - $output->meetingrequest->endtime = Utils::MakeUTCDate($props[0]->Value()); - if (strlen($props[0]->Value()) == 8) { - $output->meetingrequest->alldayevent = 1; - } - } - $props = $ical->GetPropertiesByPath('VEVENT/ORGANIZER'); - if (count($props) == 1) { - $output->meetingrequest->organizer = str_ireplace("MAILTO:", "", $props[0]->Value()); - } - $props = $ical->GetPropertiesByPath('VEVENT/LOCATION'); - if (count($props) == 1) { - $output->meetingrequest->location = $props[0]->Value(); - } - $props = $ical->GetPropertiesByPath('VEVENT/CLASS'); - if (count($props) == 1) { - switch ($props[0]->Value()) { - case "PUBLIC": - $output->meetingrequest->sensitivity = "0"; - break; - case "PRIVATE": - $output->meetingrequest->sensitivity = "2"; - break; - case "CONFIDENTIAL": - $output->meetingrequest->sensitivity = "3"; - break; - default: - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - No sensitivity class. Using 2")); - $output->meetingrequest->sensitivity = "2"; - break; - } - } - - // Get $tz from first timezone - $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID"); - if (count($props) > 0) { - $tzname = $props[0]->Value(); - $tz = TimezoneUtil::GetFullTZFromTZName($tzname); - } - else { - $tz = TimezoneUtil::GetFullTZ(); - } - $output->meetingrequest->timezone = base64_encode(TimezoneUtil::getSyncBlobFromTZ($tz)); - - // Fixed values - $output->meetingrequest->instancetype = 0; - $output->meetingrequest->responserequested = 1; - $output->meetingrequest->busystatus = 2; - - // TODO: reminder - $output->meetingrequest->reminder = ""; - } - - /** * Sends a message * @@ -2667,9 +2417,29 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } } } + + if (is_array($toaddr)) { + $recipients = $toaddr; + } + else { + $recipients = array($toaddr); + } + + // Cc and Bcc headers are sent, but we need to make sure that the recipient list contains them + foreach (array("CC", "cc", "Cc", "BCC", "Bcc", "bcc") as $key) { + if (!empty($headers[$key])) { + if (is_array($headers[$key])) { + $recipients = array_merge($recipients, $headers[$key]); + } + else { + $recipients[] = $headers[$key]; + } + } + } + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->sendMessage(): SendingMail with %s", $sendingMethod)); - $mail =& Mail::factory($sendingMethod, $sendingMethod == 'mail' ? '-f '.$fromaddr : $imap_smtp_params); - $send = $mail->send($toaddr, $headers, $body); + $mail =& Mail::factory($sendingMethod, $sendingMethod == "mail" ? "-f " . $fromaddr : $imap_smtp_params); + $send = $mail->send($recipients, $headers, $body); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->sendMessage(): send return value %s", $send)); if ($send !== true) { @@ -2699,41 +2469,20 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { $headers .= "$k: $v"; } + if ($this->sentID === false) { + $this->sentID = $this->getFolderIdFromImapId($this->create_name_folder(IMAP_FOLDER_SENT)); + } + $saved = false; if ($this->sentID) { - $saved = $this->addSentMessage($this->sentID, $headers, $finalBody); + $imapid = $this->getImapIdFromFolderId($this->sentID); + $saved = $this->addSentMessage($imapid, $headers, $finalBody); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->saveSentMessage(): Outgoing mail saved in 'Sent' folder '%s' ['%s']", $imapid, $this->sentID)); } - else if (strlen(IMAP_FOLDER_SENT) > 0) { - // try to open the sentfolder - if (!$this->imap_reopen_folder(IMAP_FOLDER_SENT, false)) { - // if we cannot open it, it mustn't exist, we try to create it. - $this->imap_create_folder($this->server . IMAP_FOLDER_SENT); - } - $saved = $this->addSentMessage(IMAP_FOLDER_SENT, $headers, $finalBody); - ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->saveSentMessage(): Outgoing mail saved in configured 'Sent' folder '%s'", IMAP_FOLDER_SENT)); - } - // No Sent folder set, try defaults else { - ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->saveSentMessage(): No Sent mailbox set"); - if($this->addSentMessage("INBOX.Sent", $headers, $finalBody)) { - ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->saveSentMessage(): Outgoing mail saved in 'INBOX.Sent'"); - $saved = true; - } - else if ($this->addSentMessage("Sent", $headers, $finalBody)) { - ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->saveSentMessage(): Outgoing mail saved in 'Sent'"); - $saved = true; - } - else if ($this->addSentMessage("Sent Items", $headers, $finalBody)) { - ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->saveSentMessage(): Outgoing mail saved in 'Sent Items'"); - $saved = true; - } - } - - unset($headers); - - if (!$saved) { ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->saveSentMessage(): The email could not be saved to Sent Items folder. Check your configuration."); } + unset($headers); return $saved; } @@ -2747,9 +2496,8 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { * @access private */ private function setFromHeaderValue(&$headers) { - $from = $this->getDefaultFromValue(); + $from = getDefaultFromValue($this->username, $this->domain); - // If the message is not s/mime if (isset($headers["from"])) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getFromHeaderValue(): from defined: %s", $headers["from"])); if (strlen(IMAP_DEFAULTFROM) > 0) { @@ -2758,7 +2506,6 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } } elseif (isset($headers["From"])) { - // if the message is s/mime ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getFromHeaderValue(): From defined: %s", $headers["From"])); if (strlen(IMAP_DEFAULTFROM) > 0) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getFromHeaderValue(): Overwriting From: %s", $from)); @@ -2772,6 +2519,7 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } } + /** * Set the Return-Path header value if not set * @@ -2788,17 +2536,120 @@ class BackendIMAP extends BackendDiff implements ISearchProvider { } - /* BEGIN fmbiete's contribution r1528, ZP-320 */ /** - * Indicates which AS version is supported by the backend. + * The meta function for out of office settings. * - * @access public - * @return string AS version constant + * @param SyncObject $oof + * + * @access private + * @return void */ - public function GetSupportedASVersion() { - return ZPush::ASV_14; - } - /* END fmbiete's contribution r1528, ZP-320 */ -}; + private function settingsOOF(&$oof) { + //if oof state is set it must be set of oof and get otherwise + if (!isset($oof->oofstate)) { + $oof->oofstate = SYNC_SETTINGSOOF_DISABLED; + $oof->Status = SYNC_SETTINGSSTATUS_SUCCESS; -?> \ No newline at end of file + //unset body type for oof in order not to stream it + unset($oof->bodytype); + return true; + } + else { + return false; + } + } + + + /** + * Gets the user's email address from server + * + * @param SyncObject $userinformation + * + * @access private + * @return void + */ + private function settingsUserInformation(&$userinformation) { + $userinformation->Status = SYNC_SETTINGSSTATUS_USERINFO_SUCCESS; + $userinformation->emailaddresses[] = $this->username; + return true; + } + + + /** + * Gets the folder list + * + * @access private + * @return array + */ + private function get_folder_list() { + $folders = array(); + $list = @imap_getmailboxes($this->mbox, $this->server, "*"); + if (is_array($list)) { + $list = array_reverse($list); + foreach ($list as $l) { + $folders[] = $l->name; + } + } + + return $folders; + } + + /** + * Add one address to the list + * + * @access private + * @param array $addresses + * @param RFC822 address object $addr + * @return string + */ + private function add_address_to_list(&$addresses, $addr) { + $name = ""; + + if (isset($addr->mailbox) && isset($addr->host) && isset($addr->personal)) { + $address = sprintf("%s@%s", $addr->mailbox, $addr->host); + $name = $addr->personal; + + if(strlen($name) == 0 || $name == $address) { + $fulladdr = $address; + } + else { + if (preg_match('/^\".*\"$/', $name)) { + $fulladdr = sprintf("%s <%s>", $name, $address); + } + else { + $fulladdr = sprintf("\"%s\" <%s>", $name, $address); + } + } + + array_push($addresses, $fulladdr); + } + + return $name; + } + + /** + * Close the IMAP connection. + * + * @access private + */ + private function close_connection() { + if ($this->mbox) { + // list all errors + $errors = imap_errors(); + if (is_array($errors)) { + foreach ($errors as $e) { + if (stripos($e, "fail") !== false) { + $level = LOGLEVEL_WARN; + } + else { + $level = LOGLEVEL_DEBUG; + } + ZLog::Write($level, "BackendIMAP->close_connection(): IMAP said: " . $e); + } + } + @imap_close($this->mbox); + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->close_connection(): disconnected from IMAP server"); + $this->mbox = false; + } + } +}; diff --git a/sources/backend/imap/mime_calendar.php b/sources/backend/imap/mime_calendar.php new file mode 100644 index 0000000..c3e9689 --- /dev/null +++ b/sources/backend/imap/mime_calendar.php @@ -0,0 +1,344 @@ +create_calendar_dav(): Creating calendar event"); + + if (defined('IMAP_MEETING_USE_CALDAV') && IMAP_MEETING_USE_CALDAV) { + $caldav = new BackendCalDAV(); + if ($caldav->Logon(Request::GetAuthUser(), Request::GetAuthDomain(), Request::GetAuthPassword())) { + $etag = $caldav->CreateUpdateCalendar($data); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->create_calendar_dav(): Calendar created with etag '%s' and data <%s>", $etag, $data)); + $caldav->Logoff(); + } + else { + ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->create_calendar_dav(): Error connecting with BackendCalDAV"); + } + } +} + +function delete_calendar_dav($uid) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->delete_calendar_dav('%s'): Deleting calendar event", $uid)); + + if ($uid === false) { + ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->delete_calendar_dav(): UID not found; report the full calendar object to developers"); + } + else { + if (defined('IMAP_MEETING_USE_CALDAV') && IMAP_MEETING_USE_CALDAV) { + $caldav = new BackendCalDAV(); + if ($caldav->Logon(Request::GetAuthUser(), Request::GetAuthDomain(), Request::GetAuthPassword())) { + $events = $caldav->FindCalendar($uid); + if (count($events) == 1) { + $href = $events[0]["href"]; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->delete_calendar_dav(): found event with href '%s', deleting", $href)); + // Delete event + $res = $caldav->DeleteCalendar($href); + if ($res) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->delete_calendar_dav(): event deleted"); + } + else { + ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->delete_calendar_dav(): error removing event, we will end with zombie events"); + } + $caldav->Logoff(); + } + else { + ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->delete_calendar_dav(): event not found, we will end with zombie events"); + } + } + else { + ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->delete_calendar_dav(): Error connecting with BackendCalDAV"); + } + } + } +} + + +function update_calendar_attendee($uid, $mailto, $status) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->update_calendar_attendee('%s', '%s', '%s'): Updating calendar event attendee", $uid, $mailto, $status)); + $updated = false; + + if ($uid === false) { + ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->update_calendar_attendee(): UID not found; report the full calendar object to developers"); + } + else { + if (defined('IMAP_MEETING_USE_CALDAV') && IMAP_MEETING_USE_CALDAV) { + $caldav = new BackendCalDAV(); + if ($caldav->Logon(Request::GetAuthUser(), Request::GetAuthDomain(), Request::GetAuthPassword())) { + $events = $caldav->FindCalendar($uid); + if (count($events) == 1) { + $href = $events[0]["href"]; + $etag = $events[0]["etag"]; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->update_calendar_attendee(): found event with href '%s' etag '%s'; updating", $href, $etag)); + + // Get Attendee status + $old_status = ""; + + if (strcasecmp($old_status, $status) != 0) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->update_calendar_attendee(): Before <%s>", $events[0]["data"])); + $ical = new iCalComponent(); + $ical->ParseFrom($events[0]["data"]); + $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", strtoupper($status), $mailto); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->update_calendar_attendee(): After <%s>", $ical->Render())); + $etag = $caldav->CreateUpdateCalendar($ical->Render(), $href, $etag); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->update_calendar_attendee(): Calendar updated with etag '%s'", $etag)); + // Update new status + $updated = true; + } + + $caldav->Logoff(); + } + else { + ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->update_calendar_attendee(): event not found or duplicated event"); + } + } + else { + ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->update_calendar_attendee(): Error connecting with BackendCalDAV"); + } + } + } + + return $updated; +} + +/** + * Detect if one message has one VCALENDAR part + * + * @param Mail_mimeDecode $message + * @return boolean + * @access private + */ +function has_calendar_object($message) { + if (is_calendar($message)) { + return true; + } + else { + if(isset($message->parts)) { + for ($i = 0; $i < count($message->parts); $i++) { + if (is_calendar($message->parts[$i])) { + return true; + } + } + } + } + + return false; +} + + +/** + * Detect if the message-part is VCALENDAR + * Content-Type: text/calendar; + * + * @param Mail_mimeDecode $message + * @return boolean + * @access private + */ +function is_calendar($message) { + return isset($message->ctype_primary) && isset($message->ctype_secondary) && $message->ctype_primary == "text" && $message->ctype_secondary == "calendar"; +} + + +/** + * Converts a text/calendar part into SyncMeetingRequest + * This is called on received messages, it's not called for events generated from the mobile + * + * @access private + * @param $part MIME part + * @param $output SyncMail object + * @param $is_sent_folder boolean + */ +function parse_meeting_calendar($part, &$output, $is_sent_folder) { + $ical = new iCalComponent(); + $ical->ParseFrom($part->body); + ZLog::Write(LOGLEVEL_WBXML, sprintf("BackendIMAP->parse_meeting_calendar(): %s", $part->body)); + + // Get UID + $uid = false; + $props = $ical->GetPropertiesByPath("VEVENT/UID"); + if (count($props) > 0) { + $uid = $props[0]->Value(); + } + + if (isset($part->ctype_parameters["method"])) { + switch (strtolower($part->ctype_parameters["method"])) { + case "cancel": + $output->messageclass = "IPM.Schedule.Meeting.Canceled"; + $output->meetingrequest->disallownewtimeproposal = 1; + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Event canceled, removing calendar object"); + delete_calendar_dav($uid); + break; + 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"); + $props = $ical->GetPropertiesByPath('VEVENT/ATTENDEE'); + + for ($i = 0; $i < count($props); $i++) { + $mailto = $props[$i]->Value(); + $props_params = $props[$i]->Parameters(); + $status = strtolower($props_params["PARTSTAT"]); + if (!$is_sent_folder) { + // Only evaluate received replies, not sent + $res = update_calendar_attendee($uid, $mailto, $status); + } + else { + $res = true; + } + if ($res) { + // Only set messageclass for replies changing my calendar object + switch ($status) { + case "accepted": + $output->messageclass = "IPM.Schedule.Meeting.Resp.Pos"; + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Update attendee -> accepted"); + break; + case "needs-action": + $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Update attendee -> needs-action"); + break; + case "tentative": + $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Update attendee -> tentative"); + break; + case "declined": + $output->messageclass = "IPM.Schedule.Meeting.Resp.Neg"; + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->parse_meeting_calendar(): Update attendee -> declined"); + break; + default: + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->parse_meeting_calendar() - Unknown reply status <%s>, please report it to the developers", $status)); + $output->messageclass = "IPM.Appointment"; + break; + } + } + } + $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; + default: + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->parse_meeting_calendar() - Unknown method <%s>, please report it to the developers", strtolower($part->headers["method"]))); + $output->messageclass = "IPM.Appointment"; + $output->meetingrequest->disallownewtimeproposal = 0; + break; + } + } + else { + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->parse_meeting_calendar() - No method header, please report it to the developers")); + $output->messageclass = "IPM.Appointment"; + } + + $props = $ical->GetPropertiesByPath('VEVENT/DTSTAMP'); + if (count($props) == 1) { + $output->meetingrequest->dtstamp = Utils::MakeUTCDate($props[0]->Value()); + } + $props = $ical->GetPropertiesByPath('VEVENT/UID'); + if (count($props) == 1) { + $output->meetingrequest->globalobjid = $props[0]->Value(); + } + $props = $ical->GetPropertiesByPath('VEVENT/DTSTART'); + if (count($props) == 1) { + $output->meetingrequest->starttime = Utils::MakeUTCDate($props[0]->Value()); + if (strlen($props[0]->Value()) == 8) { + $output->meetingrequest->alldayevent = 1; + } + } + $props = $ical->GetPropertiesByPath('VEVENT/DTEND'); + if (count($props) == 1) { + $output->meetingrequest->endtime = Utils::MakeUTCDate($props[0]->Value()); + if (strlen($props[0]->Value()) == 8) { + $output->meetingrequest->alldayevent = 1; + } + } + $props = $ical->GetPropertiesByPath('VEVENT/ORGANIZER'); + if (count($props) == 1) { + $output->meetingrequest->organizer = str_ireplace("MAILTO:", "", $props[0]->Value()); + } + $props = $ical->GetPropertiesByPath('VEVENT/LOCATION'); + if (count($props) == 1) { + $output->meetingrequest->location = $props[0]->Value(); + } + $props = $ical->GetPropertiesByPath('VEVENT/CLASS'); + if (count($props) == 1) { + switch ($props[0]->Value()) { + case "PUBLIC": + $output->meetingrequest->sensitivity = "0"; + break; + case "PRIVATE": + $output->meetingrequest->sensitivity = "2"; + break; + case "CONFIDENTIAL": + $output->meetingrequest->sensitivity = "3"; + break; + default: + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parse_meeting_calendar() - No sensitivity class. Using 2")); + $output->meetingrequest->sensitivity = "2"; + break; + } + } + + // Get $tz from first timezone + $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID"); + if (count($props) > 0) { + // TimeZones shouldn't have dots + $tzname = str_replace(".", "", $props[0]->Value()); + $tz = TimezoneUtil::GetFullTZFromTZName($tzname); + } + else { + $tz = TimezoneUtil::GetFullTZ(); + } + $output->meetingrequest->timezone = base64_encode(TimezoneUtil::getSyncBlobFromTZ($tz)); + + // Fixed values + $output->meetingrequest->instancetype = 0; + $output->meetingrequest->responserequested = 1; + $output->meetingrequest->busystatus = 2; + + // TODO: reminder + $output->meetingrequest->reminder = ""; +} + + + +/** + * Modify a text/calendar part to transform it in a reply + * + * @access private + * @param $part MIME part + * @param $response Response numeric value + * @param $condition_value string + * @return string MIME text/calendar + */ +function reply_meeting_calendar($part, $response, $username) { + $status_attendee = "ACCEPTED"; // 1 or default is ACCEPTED + $status_event = "CONFIRMED"; + switch ($response) { + case 1: + $status_attendee = "ACCEPTED"; + $status_event = "CONFIRMED"; + break; + case 2: + $status_attendee = $status_event = "TENTATIVE"; + break; + case 3: + // We won't hit this case ever, because we won't create an event if we are rejecting it + $status_attendee = "DECLINED"; + $status_event = "CANCELLED"; + break; + } + + $ical = new iCalComponent(); + $ical->ParseFrom($part->body); + + $ical->SetPValue("METHOD", "REPLY"); + $ical->SetCPParameterValue("VEVENT", "STATUS", $status_event, null); + // Update my information as attendee, but only mine + $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", $status_attendee, sprintf("MAILTO:%s", $username)); + $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "RSVP", null, sprintf("MAILTO:%s", $username)); + + return $ical->Render(); +} \ No newline at end of file diff --git a/sources/backend/imap/mime_encode.php b/sources/backend/imap/mime_encode.php index c5f3a7f..abe2a8a 100644 --- a/sources/backend/imap/mime_encode.php +++ b/sources/backend/imap/mime_encode.php @@ -70,6 +70,7 @@ function add_sub_part(&$email, $part) { } //FIXME: dfilename => filename if (isset($part->d_parameters)) { + $params['headers_charset'] = 'utf-8'; foreach ($part->d_parameters as $k => $v) { $params[$k] = $v; } @@ -252,31 +253,9 @@ function build_mime_message($message) { return $built_message; } -/** - * Detect if the message-part is VCALENDAR - * Content-Type: text/calendar; - * - * @param Mail_mimeDecode $message - * @return boolean - * @access public - */ -function is_calendar($message) { - $res = false; - - if (isset($message->ctype_primary) && isset($message->ctype_secondary)) { - if ($message->ctype_primary == "text" && $message->ctype_secondary == "calendar") { - $res = true; - } - } - - return $res; -} - /** * Detect if the message-part is SMIME - * Content-Type: multipart/signed; - * Content-Type: application/pkcs7-mime; * * @param Mail_mimeDecode $message * @return boolean @@ -286,10 +265,43 @@ function is_smime($message) { $res = false; if (isset($message->ctype_primary) && isset($message->ctype_secondary)) { - if (($message->ctype_primary == "multipart" && $message->ctype_secondary == "signed") || ($message->ctype_primary == "application" && $message->ctype_secondary == "pkcs7-mime")) { - $res = true; + $smime_types = array(array("multipart", "signed"), array("application", "pkcs7-mime"), array("application", "x-pkcs7-mime"), array("multipart", "encrypted")); + for ($i = 0; $i < count($smime_types) && !$res; $i++) { + $res = ($message->ctype_primary == $smime_types[$i][0] && $message->ctype_secondary == $smime_types[$i][1]); } } return $res; +} + + +/** + * Detect if the message-part is SMIME, encrypted but not signed + * #190, KD 2015-06-04 + * + * @param Mail_mimeDecode $message + * @return boolean + * @access public + */ +function is_encrypted($message) { + $res = false; + + if (is_smime($message) && !($message->ctype_primary == "multipart" && $message->ctype_secondary == "signed")) { + $res = true; + } + + return $res; +} + + +/** + * Detect if the message is multipart. + * #198, KD 2015-06-15 + * + * @param Mail_mimeDecode $message + * @return boolean + * @access public + */ +function is_multipart($message) { + return isset($message->ctype_primary) && $message->ctype_primary == "multipart"; } \ No newline at end of file diff --git a/sources/backend/imap/user_identity.php b/sources/backend/imap/user_identity.php new file mode 100644 index 0000000..05c1077 --- /dev/null +++ b/sources/backend/imap/user_identity.php @@ -0,0 +1,232 @@ +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 + foreach (unserialize(IMAP_FROM_LDAP_FIELDS) as $field) { + $ret_value = str_replace('#'.$field, $items[0][$field][0], $ret_value); + } + if ($encode) { + $ret_value = encodeFrom($ret_value); + } + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromLdap() - No entry found in LDAP")); + } + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromLdap() - Not authenticated in LDAP server")); + } + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromLdap() - Not connected to LDAP server")); + } + } + catch(Exception $ex) { + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getIdentityFromLdap() - Error getting From value from LDAP server: %s", $ex)); + } + + if ($ldap_conn != null) { + ldap_close($ldap_conn); + } + + return $ret_value; +} + + +/** + * Generate the "From" value stored in a SQL Database + * + * @access private + * @params string $username username value + * @params string $domain domain value + * @return string + */ +function getIdentityFromSql($username, $domain, $identity, $encode = true) { + $ret_value = $username; + + $dbh = $sth = $record = null; + try { + $dbh = new PDO(IMAP_FROM_SQL_DSN, IMAP_FROM_SQL_USER, IMAP_FROM_SQL_PASSWORD, unserialize(IMAP_FROM_SQL_OPTIONS)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromSql() - Connected to SQL Database")); + + $sql = str_replace('#username', $username, str_replace('#domain', $domain, IMAP_FROM_SQL_QUERY)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromSql() - Searching From with filter: %s", $sql)); + $sth = $dbh->prepare($sql); + $sth->execute(); + $record = $sth->fetch(PDO::FETCH_ASSOC); + if ($record) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromSql() - Found entry in SQL Database. Generating From")); + $ret_value = $identity; + foreach (unserialize(IMAP_FROM_SQL_FIELDS) as $field) { + $ret_value = str_replace('#'.$field, $record[$field], $ret_value); + } + if ($encode) { + $ret_value = encodeFrom($ret_value); + } + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromSql() - No entry found in SQL Database")); + } + } + catch(PDOException $ex) { + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getIdentityFromSql() - Error getting From value from SQL Database: %s", $ex)); + } + + $dbh = $sth = $record = null; + + return $ret_value; +} + +/** + * Generate the "From" value from the local posix passwd database + * + * @access private + * @params string $username username value + * @params string $domain domain value + * @return string + */ +function getIdentityFromPasswd($username, $domain, $identity, $encode = true) { + $ret_value = $username; + + try { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromPasswd() - Fetching info for user %s", $username)); + + $local_user = posix_getpwnam($username); + if ($local_user) { + $tmp = $local_user['gecos']; + $tmp = explode(',', $tmp); + $name = $tmp[0]; + unset($tmp); + + switch ($identity) { + case 'FROM': + if (strlen($domain) > 0) { + $ret_value = sprintf("%s <%s@%s>", $name, $username, $domain); + } else { + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getIdentityFromPasswd() - No domain passed. Cannot construct From address.")); + } + break; + case 'FULLNAME': + $ret_value = sprintf("%s", $name); + break; + } + if ($encode) { + $ret_value = encodeFrom($ret_value); + } + } else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getIdentityFromPasswd() - No entry found in Password database")); + } + } + catch(Exception $ex) { + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getIdentityFromPasswd() - Error getting From value from passwd database: %s", $ex)); + } + + return $ret_value; +} + + +/** + * Encode the From value as Base64 + * + * @access private + * @param string $from From value + * @return string + */ +function encodeFrom($from) { + $items = explode("<", $from); + $name = trim($items[0]); + return "=?UTF-8?B?" . base64_encode($name) . "?= <" . $items[1]; +} \ No newline at end of file diff --git a/sources/backend/ldap/config.php b/sources/backend/ldap/config.php index 586b993..02db4a5 100644 --- a/sources/backend/ldap/config.php +++ b/sources/backend/ldap/config.php @@ -55,6 +55,4 @@ define('LDAP_SERVER_PORT', '389'); define('LDAP_USER_DN', 'uid=%u,ou=mailaccount,dc=phppush,dc=com'); // LDAP BASE DNS -define('LDAP_BASE_DNS', 'Contacts:ou=addressbook,uid=%u,ou=mailaccount,dc=phppush,dc=com'); //Multiple values separator is | - -?> \ No newline at end of file +define('LDAP_BASE_DNS', 'Contacts:ou=addressbook,uid=%u,ou=mailaccount,dc=phppush,dc=com'); //Multiple values separator is | \ No newline at end of file diff --git a/sources/backend/ldap/ldap.php b/sources/backend/ldap/ldap.php index 940c7f9..2ad52c2 100644 --- a/sources/backend/ldap/ldap.php +++ b/sources/backend/ldap/ldap.php @@ -48,8 +48,6 @@ // config file require_once("backend/ldap/config.php"); -include_once('lib/default/diffbackend/diffbackend.php'); - class BackendLDAP extends BackendDiff { private $ldap_link; @@ -521,10 +519,6 @@ class BackendLDAP extends BackendDiff { return false; } - public function SetStarFlag($folderid, $id, $flags, $contentParameters) { - return false; - } - public function DeleteMessage($folderid, $id, $contentParameters) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->DeleteMessage('%s','%s')", $folderid, $id)); $base_dns = explode("|", LDAP_BASE_DNS); @@ -580,5 +574,4 @@ class BackendLDAP extends BackendDiff { public function GetSupportedASVersion() { return ZPush::ASV_14; } -} -?> +} \ No newline at end of file diff --git a/sources/backend/maildir/config.php b/sources/backend/maildir/config.php index 43d0675..154ee56 100644 --- a/sources/backend/maildir/config.php +++ b/sources/backend/maildir/config.php @@ -47,5 +47,3 @@ define('MAILDIR_BASE', '/tmp'); define('MAILDIR_SUBDIR', 'Maildir'); - -?> \ No newline at end of file diff --git a/sources/backend/maildir/maildir.php b/sources/backend/maildir/maildir.php index b859e74..b4f1435 100644 --- a/sources/backend/maildir/maildir.php +++ b/sources/backend/maildir/maildir.php @@ -56,11 +56,6 @@ // config file require_once("backend/maildir/config.php"); -include_once('lib/default/diffbackend/diffbackend.php'); - -include_once('include/mimeDecode.php'); -require_once('include/z_RFC822.php'); - class BackendMaildir extends BackendDiff { /**---------------------------------------------------------------------------------------------------------- * default backend methods @@ -141,7 +136,6 @@ class BackendMaildir extends BackendDiff { $message = Mail_mimeDecode::decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\n", 'charset' => 'utf-8')); - include_once('include/stringstreamwrapper.php'); $attachment = new SyncItemOperationsAttachment(); $attachment->data = StringStreamWrapper::Open($message->parts[$part]->body); if (isset($message->parts[$part]->ctype_primary) && isset($message->parts[$part]->ctype_secondary)) @@ -542,22 +536,6 @@ class BackendMaildir extends BackendDiff { return true; } - /** - * Changes the 'star' flag of a message on disk - * - * @param string $folderid id of the folder - * @param string $id id of the message - * @param int $flags star flag of the message - * @param ContentParameters $contentParameters - * - * @access public - * @return boolean status of the operation - * @throws StatusException could throw specific SYNC_STATUS_* exceptions - */ - public function SetStarFlag($folderid, $id, $flags, $contentParameters) { - return false; - } - /** * Called when the user has requested to delete (really delete) a message * @@ -735,6 +713,4 @@ class BackendMaildir extends BackendDiff { private function getPath() { return MAILDIR_BASE . "/" . $this->store . "/" . MAILDIR_SUBDIR . "/cur"; } -} - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/backend/searchldap/config.php b/sources/backend/searchldap/config.php index 439cfac..134990a 100644 --- a/sources/backend/searchldap/config.php +++ b/sources/backend/searchldap/config.php @@ -72,4 +72,3 @@ $ldap_field_map = array( SYNC_GAL_MOBILEPHONE => 'mobile', SYNC_GAL_EMAILADDRESS => 'mail', ); -?> \ No newline at end of file diff --git a/sources/backend/searchldap/searchldap.php b/sources/backend/searchldap/searchldap.php index 50d69e2..f9bcd9f 100644 --- a/sources/backend/searchldap/searchldap.php +++ b/sources/backend/searchldap/searchldap.php @@ -194,5 +194,4 @@ class BackendSearchLDAP implements ISearchProvider { return true; } -} -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/backend/vcarddir/config.php b/sources/backend/vcarddir/config.php index cc9032f..e841c56 100644 --- a/sources/backend/vcarddir/config.php +++ b/sources/backend/vcarddir/config.php @@ -46,5 +46,3 @@ // ********************** define('VCARDDIR_DIR', '/home/%u/.kde/share/apps/kabc/stdvcf'); - -?> \ No newline at end of file diff --git a/sources/backend/vcarddir/vcarddir.php b/sources/backend/vcarddir/vcarddir.php index e93a07f..4331e26 100644 --- a/sources/backend/vcarddir/vcarddir.php +++ b/sources/backend/vcarddir/vcarddir.php @@ -44,8 +44,6 @@ // config file require_once("backend/vcarddir/config.php"); -include_once('lib/default/diffbackend/diffbackend.php'); - class BackendVCardDir extends BackendDiff { /**---------------------------------------------------------------------------------------------------------- * default backend methods @@ -597,22 +595,6 @@ class BackendVCardDir extends BackendDiff { return false; } - /** - * Changes the 'star' flag of a message on disk - * - * @param string $folderid id of the folder - * @param string $id id of the message - * @param int $flags star flag of the message - * @param ContentParameters $contentParameters - * - * @access public - * @return boolean status of the operation - * @throws StatusException could throw specific SYNC_STATUS_* exceptions - */ - public function SetStarFlag($folderid, $id, $flags, $contentParameters) { - return false; - } - /** * Called when the user has requested to delete (really delete) a message * @@ -693,5 +675,4 @@ class BackendVCardDir extends BackendDiff { $data = str_replace(array('\\\\', '\\;', '\\,', '\\n','\\N'),array('\\', ';', ',', "\n", "\n"),$data); return $data; } -}; -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/backend/zarafa/config.php b/sources/backend/zarafa/config.php index e0a441c..625d86d 100644 --- a/sources/backend/zarafa/config.php +++ b/sources/backend/zarafa/config.php @@ -47,5 +47,3 @@ // Defines the server to which we want to connect define('MAPI_SERVER', 'file:///var/run/zarafa'); - -?> \ No newline at end of file diff --git a/sources/backend/zarafa/exporter.php b/sources/backend/zarafa/exporter.php index 31eaa78..76ad075 100644 --- a/sources/backend/zarafa/exporter.php +++ b/sources/backend/zarafa/exporter.php @@ -12,7 +12,7 @@ * * Created : 14.02.2011 * -* Copyright 2007 - 2013 Zarafa Deutschland GmbH +* Copyright 2007 - 2013, 2015 Zarafa Deutschland GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, @@ -210,7 +210,7 @@ class ExportChangesICS implements IExportChanges{ throw new StatusException("ExportChangesICS->InitializeExporter(): Error, exporter or essential data not available", SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_ERROR); // PHP wrapper - $phpwrapper = new PHPWrapper($this->session, $this->store, $importer); + $phpwrapper = new PHPWrapper($this->session, $this->store, $importer, $this->folderid); // with a folderid we are going to get content if($this->folderid) { @@ -295,4 +295,3 @@ class ExportChangesICS implements IExportChanges{ return false; } } -?> \ No newline at end of file diff --git a/sources/backend/zarafa/icalparser.php b/sources/backend/zarafa/icalparser.php index 09d2949..b0f9434 100644 --- a/sources/backend/zarafa/icalparser.php +++ b/sources/backend/zarafa/icalparser.php @@ -197,5 +197,3 @@ class ICalParser{ return gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); } } - -?> \ No newline at end of file diff --git a/sources/backend/zarafa/importer.php b/sources/backend/zarafa/importer.php index 5f67895..370c5a6 100644 --- a/sources/backend/zarafa/importer.php +++ b/sources/backend/zarafa/importer.php @@ -452,21 +452,7 @@ class ImportChangesICS implements IImportChanges { return true; } - - /** - * Imports a change in 'star' flag - * This can never conflict - * - * @param string $id - * @param int $flags - * - * @access public - * @return boolean - * @throws StatusException - */ - public function ImportMessageStarFlag($id, $flags) { - return false; - } + /** * Imports a move of a message. This occurs when a user moves an item to another folder @@ -489,10 +475,6 @@ class ImportChangesICS implements IImportChanges { if (strtolower($newfolder) == strtolower(bin2hex($this->folderid)) ) throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, source and destination are equal", $id, $newfolder), SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST); - // check if the source message is in the current syncinterval - if (!$this->isMessageInSyncInterval($id)) - throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Source message is outside the sync interval. Move not performed.", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); - // Get the entryid of the message we're moving $entryid = mapi_msgstore_entryidfromsourcekey($this->store, $this->folderid, hex2bin($id)); if(!$entryid) @@ -500,8 +482,18 @@ class ImportChangesICS implements IImportChanges { //open the source message $srcmessage = mapi_msgstore_openentry($this->store, $entryid); - if (!$srcmessage) - throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open source message: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); + if (!$srcmessage) { + $code = SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID; + // if we move to the trash and the source message is not found, we can also just tell the mobile that we successfully moved to avoid errors (ZP-624) + if ($newfolder == ZPush::GetBackend()->GetWasteBasket()) { + $code = SYNC_MOVEITEMSSTATUS_SUCCESS; + } + throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open source message: 0x%X", $id, $newfolder, mapi_last_hresult()), $code); + } + + // check if the source message is in the current syncinterval + if (!$this->isMessageInSyncInterval($id)) + throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Source message is outside the sync interval. Move not performed.", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); // get correct mapi store for the destination folder $dststore = ZPush::GetBackend()->GetMAPIStoreForFolderId(ZPush::GetAdditionalSyncFolderStore($newfolder), $newfolder); @@ -702,5 +694,4 @@ class ImportChangesICS implements IImportChanges { return $ret; } -} -?> +} \ No newline at end of file diff --git a/sources/backend/zarafa/listfolders.php b/sources/backend/zarafa/listfolders.php index 1b95b7d..f88dfac 100755 --- a/sources/backend/zarafa/listfolders.php +++ b/sources/backend/zarafa/listfolders.php @@ -47,6 +47,8 @@ define("PHP_MAPI_PATH", "/usr/share/php/mapi/"); define('MAPI_SERVER', 'file:///var/run/zarafa'); +define('SSLCERT_FILE', null); +define('SSLCERT_PASS', null); $supported_classes = array ( "IPF.Note" => "SYNC_FOLDER_TYPE_USER_MAIL", @@ -65,8 +67,8 @@ function main() { function listfolders_configure() { - if (!isset($_SERVER["TERM"]) || !isset($_SERVER["LOGNAME"])) { - echo "This script should not be called in a browser.\n"; + if (php_sapi_name() != "cli") { + printf("This script should not be called in a browser. Called from: %s\n", php_sapi_name()); exit(1); } @@ -83,32 +85,40 @@ function listfolders_configure() { } function listfolders_handle() { - $shortoptions = "l:h:u:p:"; + $shortoptions = "l:h:u:p:c:"; $options = getopt($shortoptions); $mapi = MAPI_SERVER; + $sslcert_file = SSLCERT_FILE; + $sslcert_pass = SSLCERT_PASS; $user = "SYSTEM"; $pass = ""; if (isset($options['h'])) $mapi = $options['h']; + // accept a remote user if (isset($options['u']) && isset($options['p'])) { $user = $options['u']; $pass = $options['p']; } + // accept a certificate and passwort for login + else if (isset($options['c']) && isset($options['p'])) { + $sslcert_file = $options['c']; + $sslcert_pass = $options['p']; + } - $zarafaAdmin = listfolders_zarafa_admin_setup($mapi, $user, $pass); + $zarafaAdmin = listfolders_zarafa_admin_setup($mapi, $user, $pass, $sslcert_file, $sslcert_pass); if (isset($zarafaAdmin['adminStore']) && isset($options['l'])) { listfolders_getlist($zarafaAdmin['adminStore'], $zarafaAdmin['session'], trim($options['l'])); } else { - echo "Usage:\nlistfolders.php [actions] [options]\n\nActions: [-l username]\n\t-l username\tlist folders of user, for public folder use 'SYSTEM'\n\nGlobal options: [-h path] [[-u remoteuser] [-p password]]\n\t-h path\t\tconnect through , e.g. file:///var/run/socket\n\t-u authuser\tlogin as authenticated administration user\n\t-p authpassword\tpassword of the remoteuser\n\n"; + echo "Usage:\nlistfolders.php [actions] [options]\n\nActions: [-l username]\n\t-l username\tlist folders of user, for public folder use 'SYSTEM'\n\nGlobal options: [-h path] [[-u remoteuser] [-p password]] [[-c certificate_path] [-p password]]\n\t-h path\t\tconnect through , e.g. file:///var/run/socket or https://10.0.0.1:237/zarafa\n\t-u remoteuser\tlogin as authenticated administration user\n\t-c certificate\tlogin with a ssl certificate located in this location, e.g. /etc/zarafa/ssl/client.pem\n\t-p password\tpassword of the remoteuser or certificate\n\n"; } } -function listfolders_zarafa_admin_setup ($mapi, $user, $pass) { - $session = @mapi_logon_zarafa($user, $pass, $mapi); +function listfolders_zarafa_admin_setup ($mapi, $user, $pass, $sslcert_file, $sslcert_pass) { + $session = @mapi_logon_zarafa($user, $pass, $mapi, $sslcert_file, $sslcert_pass); if (!$session) { echo "User '$user' could not login. The script will exit. Errorcode: 0x". sprintf("%x", mapi_last_hresult()) . "\n"; @@ -180,5 +190,3 @@ function listfolders_getlist ($adminStore, $session, $user) { } } } - -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/class.baseexception.php b/sources/backend/zarafa/mapi/class.baseexception.php index 6ac22fc..77f9406 100644 --- a/sources/backend/zarafa/mapi/class.baseexception.php +++ b/sources/backend/zarafa/mapi/class.baseexception.php @@ -223,4 +223,3 @@ class BaseException extends Exception // @TODO getTrace and getTraceAsString } -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/class.baserecurrence.php b/sources/backend/zarafa/mapi/class.baserecurrence.php index e4b4cf4..0cac9c7 100644 --- a/sources/backend/zarafa/mapi/class.baserecurrence.php +++ b/sources/backend/zarafa/mapi/class.baserecurrence.php @@ -1942,4 +1942,3 @@ return $a["start"] == $b["start"] ? 0 : ($a["start"] > $b["start"] ? 1 : -1 ); } } -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/class.freebusypublish.php b/sources/backend/zarafa/mapi/class.freebusypublish.php index 47d62bf..36c8229 100644 --- a/sources/backend/zarafa/mapi/class.freebusypublish.php +++ b/sources/backend/zarafa/mapi/class.freebusypublish.php @@ -394,4 +394,3 @@ class FreeBusyPublish { } } -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/class.mapiexception.php b/sources/backend/zarafa/mapi/class.mapiexception.php index c15e8fe..6b7d76b 100644 --- a/sources/backend/zarafa/mapi/class.mapiexception.php +++ b/sources/backend/zarafa/mapi/class.mapiexception.php @@ -104,4 +104,3 @@ if (function_exists('mapi_enable_exceptions')) { //mapi_enable_exceptions("mapiexception"); } -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/class.meetingrequest.php b/sources/backend/zarafa/mapi/class.meetingrequest.php index 6cfe0dd..3c4195c 100644 --- a/sources/backend/zarafa/mapi/class.meetingrequest.php +++ b/sources/backend/zarafa/mapi/class.meetingrequest.php @@ -3195,4 +3195,3 @@ If it is the first time this attendee has proposed a new date/time, increment th $this->meetingTimeInfo = $meetingTimeInfo; } } -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/class.recurrence.php b/sources/backend/zarafa/mapi/class.recurrence.php index 227d605..fcb56e6 100644 --- a/sources/backend/zarafa/mapi/class.recurrence.php +++ b/sources/backend/zarafa/mapi/class.recurrence.php @@ -1574,4 +1574,3 @@ .... ULONGx2 Constant: { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}. */ -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/class.taskrecurrence.php b/sources/backend/zarafa/mapi/class.taskrecurrence.php index 4087216..9d92c61 100644 --- a/sources/backend/zarafa/mapi/class.taskrecurrence.php +++ b/sources/backend/zarafa/mapi/class.taskrecurrence.php @@ -461,4 +461,3 @@ } } } -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/class.taskrequest.php b/sources/backend/zarafa/mapi/class.taskrequest.php index 779ad50..04b4ecc 100644 --- a/sources/backend/zarafa/mapi/class.taskrequest.php +++ b/sources/backend/zarafa/mapi/class.taskrequest.php @@ -1033,4 +1033,3 @@ $this->doUpdate($prefix, $prefixComplete); } } -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/mapi.util.php b/sources/backend/zarafa/mapi/mapi.util.php index c132c4d..8bdaeaa 100644 --- a/sources/backend/zarafa/mapi/mapi.util.php +++ b/sources/backend/zarafa/mapi/mapi.util.php @@ -336,4 +336,3 @@ function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequest // properties that the caller did not request (recurring, etc). This shouldn't be a problem though. return $result; } -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/mapicode.php b/sources/backend/zarafa/mapi/mapicode.php index 66905b8..b698d13 100644 --- a/sources/backend/zarafa/mapi/mapicode.php +++ b/sources/backend/zarafa/mapi/mapicode.php @@ -244,7 +244,3 @@ define('SYNC_E_UNSYNCHRONIZED', make_mapi_e(0x805)); define('SYNC_W_PROGRESS', make_mapi_s(0x820)); define('SYNC_W_CLIENT_CHANGE_NEWER', make_mapi_s(0x821)); - - - -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/mapidefs.php b/sources/backend/zarafa/mapi/mapidefs.php index ca17dca..111375c 100644 --- a/sources/backend/zarafa/mapi/mapidefs.php +++ b/sources/backend/zarafa/mapi/mapidefs.php @@ -664,5 +664,3 @@ define('fnevTableModified' ,0x00000100); define('fnevStatusObjectModified' ,0x00000200); define('fnevReservedForMapi' ,0x40000000); define('fnevExtended' ,0x80000000); - -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/mapiguid.php b/sources/backend/zarafa/mapi/mapiguid.php index 5dd4a67..7ece46a 100644 --- a/sources/backend/zarafa/mapi/mapiguid.php +++ b/sources/backend/zarafa/mapi/mapiguid.php @@ -70,5 +70,3 @@ define('PS_INTERNET_HEADERS', makeguid("{00020386-0000-0000-c0 // sk added for Z-Push define ('PSETID_AirSync', makeguid("{71035549-0739-4DCB-9163-00F0580DBBDF}")); - -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapi/mapitags.php b/sources/backend/zarafa/mapi/mapitags.php index 18def2f..7264c34 100644 --- a/sources/backend/zarafa/mapi/mapitags.php +++ b/sources/backend/zarafa/mapi/mapitags.php @@ -1252,5 +1252,3 @@ define('PR_ZC_CONTACT_FOLDER_NAMES' ,mapi_prop_tag(PT_MV_TSTRI //Properties defined for Z-Push define('PR_TODO_ITEM_FLAGS' ,mapi_prop_tag(PT_LONG, 0x0E2B)); - -?> \ No newline at end of file diff --git a/sources/backend/zarafa/mapimapping.php b/sources/backend/zarafa/mapimapping.php index 0c5be9f..2d6795d 100644 --- a/sources/backend/zarafa/mapimapping.php +++ b/sources/backend/zarafa/mapimapping.php @@ -378,6 +378,7 @@ class MAPIMapping { "startdate" => "PT_SYSTIME:PSETID_Task:0x8104", "subject" => PR_SUBJECT, "rtf" => PR_RTF_COMPRESSED, + "html" => PR_HTML, ); } @@ -518,7 +519,8 @@ class MAPIMapping { "attachnum" => PR_ATTACH_NUM, "attachdatabin" => PR_ATTACH_DATA_BIN, "internetcpid" => PR_INTERNET_CPID, + "rtf" => PR_RTF_COMPRESSED, + "rtfinsync" => PR_RTF_IN_SYNC, ); } -} -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/backend/zarafa/mapiphpwrapper.php b/sources/backend/zarafa/mapiphpwrapper.php index 0b27c6a..10cadf9 100644 --- a/sources/backend/zarafa/mapiphpwrapper.php +++ b/sources/backend/zarafa/mapiphpwrapper.php @@ -12,7 +12,7 @@ * * Created : 14.02.2011 * -* Copyright 2007 - 2013 Zarafa Deutschland GmbH +* Copyright 2007 - 2015 Zarafa Deutschland GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, @@ -59,6 +59,7 @@ class PHPWrapper { private $mapiprovider; private $store; private $contentparameters; + private $folderid; /** @@ -66,15 +67,17 @@ class PHPWrapper { * * @param ressource $session * @param ressource $store - * @param IImportChanges $importer incoming changes from ICS are forwarded here + * @param IImportChanges $importer incoming changes from ICS are forwarded here. + * @param string $folderid the folder this wrapper was configured for. * * @access public * @return */ - public function PHPWrapper($session, $store, $importer) { + public function PHPWrapper($session, $store, $importer, $folderid) { $this->importer = &$importer; $this->store = $store; $this->mapiprovider = new MAPIProvider($session, $this->store); + $this->folderid = $folderid; } /** @@ -117,6 +120,7 @@ class PHPWrapper { $mapimessage = mapi_msgstore_openentry($this->store, $entryid); try { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("PHPWrapper->ImportMessageChange(): Getting message from MAPIProvider, sourcekey: '%s', parentsourcekey: '%s', entryid: '%s'", bin2hex($sourcekey), bin2hex($parentsourcekey), bin2hex($entryid))); $message = $this->mapiprovider->GetMessage($mapimessage, $this->contentparameters); } catch (SyncObjectBrokenException $mbe) { @@ -156,6 +160,13 @@ class PHPWrapper { * @return */ public function ImportMessageDeletion($flags, $sourcekeys) { + $amount = count($sourcekeys); + if ($amount > 1000) { + throw new StatusException(sprintf("PHPWrapper->ImportMessageDeletion(): Received %d remove requests from ICS for folder '%s' (max. 1000 allowed). Triggering folder re-sync.", $amount, bin2hex($this->folderid)), SYNC_STATUS_INVALIDSYNCKEY, null, LOGLEVEL_ERROR); + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("PHPWrapper->ImportMessageDeletion(): Received %d remove requests from ICS", $amount)); + } foreach($sourcekeys as $sourcekey) { $this->importer->ImportMessageDeletion(bin2hex($sourcekey)); } @@ -189,16 +200,13 @@ class PHPWrapper { /** * Imports a single folder change * - * @param mixed $props sourcekey of the changed folder + * @param array $props properties of the changed folder * * @access public * @return */ function ImportFolderChange($props) { - $sourcekey = $props[PR_SOURCE_KEY]; - $entryid = mapi_msgstore_entryidfromsourcekey($this->store, $sourcekey); - $mapifolder = mapi_msgstore_openentry($this->store, $entryid); - $folder = $this->mapiprovider->GetFolder($mapifolder); + $folder = $this->mapiprovider->GetFolder($props); // do not import folder if there is something "wrong" with it if ($folder === false) @@ -223,6 +231,4 @@ class PHPWrapper { } return 0; } -} - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/backend/zarafa/mapiprovider.php b/sources/backend/zarafa/mapiprovider.php index 85a4444..7e90402 100644 --- a/sources/backend/zarafa/mapiprovider.php +++ b/sources/backend/zarafa/mapiprovider.php @@ -46,6 +46,8 @@ class MAPIProvider { private $store; private $zRFC822; private $addressbook; + private $storeProps; + private $inboxProps; /** * Constructor of the MAPI Provider @@ -277,8 +279,9 @@ class MAPIProvider { } } - //set attendee's status and type if they're available - if (isset($row[PR_RECIPIENT_TRACKSTATUS])) + //set attendee's status and type if they're available and if we are the organizer + $storeprops = $this->getStoreProps(); + if (isset($row[PR_RECIPIENT_TRACKSTATUS]) && $messageprops[$appointmentprops["representingentryid"]] == $storeprops[PR_MAILBOX_OWNER_ENTRYID]) $attendee->attendeestatus = $row[PR_RECIPIENT_TRACKSTATUS]; if (isset($row[PR_RECIPIENT_TYPE])) $attendee->attendeetype = $row[PR_RECIPIENT_TYPE]; @@ -319,6 +322,11 @@ class MAPIProvider { $message->busystatus = fbFree; } + // If the busystatus has the value of -1, we should be interpreted as tentative (1) / ZP-581 + if (isset($message->busystatus) && $message->busystatus == -1) { + $message->busystatus = fbTentative; + } + return $message; } @@ -468,6 +476,12 @@ class MAPIProvider { if (isset($exception->busystatus) && $exception->busystatus == fbWorkingElsewhere) { $exception->busystatus = fbFree; } + + // If the busystatus has the value of -1, we should be interpreted as tentative (1) / ZP-581 + if (isset($exception->busystatus) && $exception->busystatus == -1) { + $exception->busystatus = fbTentative; + } + array_push($syncMessage->exceptions, $exception); } @@ -622,15 +636,29 @@ class MAPIProvider { if(!isset($message->meetingrequest->sensitivity)) $message->meetingrequest->sensitivity = 0; + // If the user is working from a location other than the office the busystatus should be interpreted as free. + if (isset($message->meetingrequest->busystatus) && $message->meetingrequest->busystatus == fbWorkingElsewhere) { + $message->meetingrequest->busystatus = fbFree; + } + + // If the busystatus has the value of -1, we should be interpreted as tentative (1) / ZP-581 + if (isset($message->meetingrequest->busystatus) && $message->meetingrequest->busystatus == -1) { + $message->meetingrequest->busystatus = fbTentative; + } + // if a meeting request response hasn't been processed yet, // do it so that the attendee status is updated on the mobile if(!isset($messageprops[$emailproperties["processed"]])) { - $req = new Meetingrequest($this->store, $mapimessage, $this->session); - if ($req->isMeetingRequestResponse()) { - $req->processMeetingRequestResponse(); - } - if ($req->isMeetingCancellation()) { - $req->processMeetingCancellation(); + // check if we are not sending the MR so we can process it - ZP-581 + $cuser = ZPush::GetBackend()->GetUserDetails(ZPush::GetBackend()->GetCurrentUsername()); + if(isset($cuser["emailaddress"]) && $cuser["emailaddress"] != $fromaddr) { + $req = new Meetingrequest($this->store, $mapimessage, $this->session); + if ($req->isMeetingRequestResponse()) { + $req->processMeetingRequestResponse(); + } + if ($req->isMeetingCancellation()) { + $req->processMeetingCancellation(); + } } } $message->contentclass = DEFAULT_CALENDAR_CONTENTCLASS; @@ -789,18 +817,17 @@ class MAPIProvider { } /** - * Reads a folder object from MAPI + * Creates a SyncFolder from MAPI properties. * - * @param mixed $mapimessage + * @param mixed $folderprops * * @access public * @return SyncFolder */ - public function GetFolder($mapifolder) { + public function GetFolder($folderprops) { $folder = new SyncFolder(); - $folderprops = mapi_getprops($mapifolder, array(PR_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_ENTRYID, PR_CONTAINER_CLASS, PR_ATTR_HIDDEN)); - $storeprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID)); + $storeprops = $this->getStoreProps(); if(!isset($folderprops[PR_DISPLAY_NAME]) || !isset($folderprops[PR_PARENT_ENTRYID]) || @@ -818,6 +845,12 @@ class MAPIProvider { return false; } + // ignore certain undesired folders, like "RSS Feeds" + if (isset($folderprops[PR_CONTAINER_CLASS]) && $folderprops[PR_CONTAINER_CLASS] == "IPF.Note.OutlookHomepage") { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->GetFolder(): folder '%s' should not be synchronized", $folderprops[PR_DISPLAY_NAME])); + return false; + } + $folder->serverid = bin2hex($folderprops[PR_SOURCE_KEY]); if($folderprops[PR_PARENT_ENTRYID] == $storeprops[PR_IPM_SUBTREE_ENTRYID]) $folder->parentid = "0"; @@ -840,9 +873,8 @@ class MAPIProvider { * @return long */ public function GetFolderType($entryid, $class = false) { - $storeprops = mapi_getprops($this->store, array(PR_IPM_OUTBOX_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_IPM_SENTMAIL_ENTRYID)); - $inbox = mapi_msgstore_getreceivefolder($this->store); - $inboxprops = mapi_getprops($inbox, array(PR_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_TASK_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_JOURNAL_ENTRYID)); + $storeprops = $this->getStoreProps(); + $inboxprops = $this->getInboxProps(); if($entryid == $inboxprops[PR_ENTRYID]) return SYNC_FOLDER_TYPE_INBOX; @@ -1255,6 +1287,7 @@ class MAPIProvider { $representingprops = $this->getProps($mapimessage, $p); if (!isset($representingprops[$appointmentprops["representingentryid"]])) { + // TODO use getStoreProps $storeProps = mapi_getprops($this->store, array(PR_MAILBOX_OWNER_ENTRYID)); $props[$appointmentprops["representingentryid"]] = $storeProps[PR_MAILBOX_OWNER_ENTRYID]; $displayname = $this->getFullnameFromEntryID($storeProps[PR_MAILBOX_OWNER_ENTRYID]); @@ -1518,9 +1551,10 @@ class MAPIProvider { // "start" and "end" are in GMT when passing to class.recurrence // set recurrence start here because it's calculated differently for tasks and appointments $recur["start"] = $task->recurrence->start; - $recur["regen"] = $task->regenerate; + $recur["regen"] = (isset($task->recurrence->regenerate) && $task->recurrence->regenerate) ? 1 : 0; //Also add dates to $recur $recur["duedate"] = $task->duedate; + $recur["complete"] = (isset($task->complete) && $task->complete) ? 1 : 0; $recurrence->setRecurrence($recur); } @@ -2607,6 +2641,33 @@ class MAPIProvider { } return $this->addressbook; } -} -?> \ No newline at end of file + /** + * Gets the required store properties. + * + * @access private + * @return array + */ + private function getStoreProps() { + if (!isset($this->storeProps) || empty($this->storeProps)) { + ZLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->getStoreProps(): Getting store properties."); + $this->storeProps = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_ENTRYID, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID, PR_MAILBOX_OWNER_ENTRYID)); + } + return $this->storeProps; + } + + /** + * Gets the required inbox properties. + * + * @access private + * @return array + */ + private function getInboxProps() { + if (!isset($this->inboxProps) || empty($this->inboxProps)) { + ZLog::Write(LOGLEVEL_DEBUG, "MAPIProvider->getInboxProps(): Getting inbox properties."); + $inbox = mapi_msgstore_getreceivefolder($this->store); + $this->inboxProps = mapi_getprops($inbox, array(PR_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_TASK_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_JOURNAL_ENTRYID)); + } + return $this->inboxProps; + } +} \ No newline at end of file diff --git a/sources/backend/zarafa/mapistreamwrapper.php b/sources/backend/zarafa/mapistreamwrapper.php index a8f5e23..3fb5cdf 100644 --- a/sources/backend/zarafa/mapistreamwrapper.php +++ b/sources/backend/zarafa/mapistreamwrapper.php @@ -143,6 +143,4 @@ class MAPIStreamWrapper { } } -stream_wrapper_register(MAPIStreamWrapper::PROTOCOL, "MAPIStreamWrapper") - -?> \ No newline at end of file +stream_wrapper_register(MAPIStreamWrapper::PROTOCOL, "MAPIStreamWrapper"); diff --git a/sources/backend/zarafa/mapiutils.php b/sources/backend/zarafa/mapiutils.php index fd97fe3..238bd3b 100644 --- a/sources/backend/zarafa/mapiutils.php +++ b/sources/backend/zarafa/mapiutils.php @@ -482,6 +482,104 @@ class MAPIUtils { ); } -} + /** + * Calculates the native body type of a message using available properties. Refer to oxbbody. + * + * @param array $messageprops + * + * @access public + * @return int + */ + public static function GetNativeBodyType($messageprops) { + //check if the properties are set and get the error code if needed + if (!isset($messageprops[PR_BODY])) $messageprops[PR_BODY] = self::getError(PR_BODY, $messageprops); + if (!isset($messageprops[PR_RTF_COMPRESSED])) $messageprops[PR_RTF_COMPRESSED] = self::getError(PR_RTF_COMPRESSED, $messageprops); + if (!isset($messageprops[PR_HTML])) $messageprops[PR_HTML] = self::getError(PR_HTML, $messageprops); + if (!isset($messageprops[PR_RTF_IN_SYNC])) $messageprops[PR_RTF_IN_SYNC] = self::getError(PR_RTF_IN_SYNC, $messageprops); -?> \ No newline at end of file + if ( // 1 + ($messageprops[PR_BODY] == MAPI_E_NOT_FOUND) && + ($messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_FOUND) && + ($messageprops[PR_HTML] == MAPI_E_NOT_FOUND)) + return SYNC_BODYPREFERENCE_PLAIN; + elseif ( // 2 + ($messageprops[PR_BODY] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_FOUND) && + ($messageprops[PR_HTML] == MAPI_E_NOT_FOUND)) + return SYNC_BODYPREFERENCE_PLAIN; + elseif ( // 3 + ($messageprops[PR_BODY] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_HTML] == MAPI_E_NOT_FOUND)) + return SYNC_BODYPREFERENCE_RTF; + elseif ( // 4 + ($messageprops[PR_BODY] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_HTML] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_RTF_IN_SYNC])) + return SYNC_BODYPREFERENCE_RTF; + elseif ( // 5 + ($messageprops[PR_BODY] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_HTML] == MAPI_E_NOT_ENOUGH_MEMORY) && + (!$messageprops[PR_RTF_IN_SYNC])) + return SYNC_BODYPREFERENCE_HTML; + elseif ( // 6 + ($messageprops[PR_RTF_COMPRESSED] != MAPI_E_NOT_FOUND || $messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_HTML] != MAPI_E_NOT_FOUND || $messageprops[PR_HTML] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_RTF_IN_SYNC])) + return SYNC_BODYPREFERENCE_RTF; + elseif ( // 7 + ($messageprops[PR_RTF_COMPRESSED] != MAPI_E_NOT_FOUND || $messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_HTML] != MAPI_E_NOT_FOUND || $messageprops[PR_HTML] == MAPI_E_NOT_ENOUGH_MEMORY) && + (!$messageprops[PR_RTF_IN_SYNC])) + return SYNC_BODYPREFERENCE_HTML; + elseif ( // 8 + ($messageprops[PR_BODY] != MAPI_E_NOT_FOUND || $messageprops[PR_BODY] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_RTF_COMPRESSED] != MAPI_E_NOT_FOUND || $messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_RTF_IN_SYNC])) + return SYNC_BODYPREFERENCE_RTF; + elseif ( // 9.1 + ($messageprops[PR_BODY] != MAPI_E_NOT_FOUND || $messageprops[PR_BODY] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_RTF_COMPRESSED] != MAPI_E_NOT_FOUND || $messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_ENOUGH_MEMORY) && + (!$messageprops[PR_RTF_IN_SYNC])) + return SYNC_BODYPREFERENCE_PLAIN; + elseif ( // 9.2 + ($messageprops[PR_RTF_COMPRESSED] != MAPI_E_NOT_FOUND || $messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_BODY] == MAPI_E_NOT_FOUND) && + ($messageprops[PR_HTML] == MAPI_E_NOT_FOUND)) + return SYNC_BODYPREFERENCE_RTF; + elseif ( // 9.3 + ($messageprops[PR_BODY] != MAPI_E_NOT_FOUND || $messageprops[PR_BODY] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_FOUND) && + ($messageprops[PR_HTML] == MAPI_E_NOT_FOUND)) + return SYNC_BODYPREFERENCE_PLAIN; + elseif ( // 9.4 + ($messageprops[PR_HTML] != MAPI_E_NOT_FOUND || $messageprops[PR_HTML] == MAPI_E_NOT_ENOUGH_MEMORY) && + ($messageprops[PR_BODY] == MAPI_E_NOT_FOUND) && + ($messageprops[PR_RTF_COMPRESSED] == MAPI_E_NOT_FOUND)) + return SYNC_BODYPREFERENCE_HTML; + else // 10 + return SYNC_BODYPREFERENCE_PLAIN; + } + + /** + * Returns the error code for a given property. Helper for getNativeBodyType function. + * + * @param int $tag + * @param array $messageprops + * + * @access private + * @return int (MAPI_ERROR_CODE) + */ + private static function getError($tag, $messageprops) { + $prBodyError = mapi_prop_tag(PT_ERROR, mapi_prop_id($tag)); + if(isset($messageprops[$prBodyError]) && mapi_is_error($messageprops[$prBodyError])) { + if($messageprops[$prBodyError] == MAPI_E_NOT_ENOUGH_MEMORY_32BIT || + $messageprops[$prBodyError] == MAPI_E_NOT_ENOUGH_MEMORY_64BIT) { + return MAPI_E_NOT_ENOUGH_MEMORY; + } + } + return MAPI_E_NOT_FOUND; + } +} diff --git a/sources/backend/zarafa/tnefparser.php b/sources/backend/zarafa/tnefparser.php index d9eece6..76fc966 100644 --- a/sources/backend/zarafa/tnefparser.php +++ b/sources/backend/zarafa/tnefparser.php @@ -718,4 +718,3 @@ class TNEFParser { return NOERROR; } } -?> \ No newline at end of file diff --git a/sources/backend/zarafa/zarafa.php b/sources/backend/zarafa/zarafa.php index 93c9d55..335fd4a 100644 --- a/sources/backend/zarafa/zarafa.php +++ b/sources/backend/zarafa/zarafa.php @@ -164,7 +164,15 @@ class BackendZarafa implements IBackend, ISearchProvider { try { // check if notifications are available in php-mapi if(function_exists('mapi_feature') && mapi_feature('LOGONFLAGS')) { - $this->session = @mapi_logon_zarafa($user, $pass, MAPI_SERVER, null, null, 0); + // send Z-Push version and user agent to ZCP - ZP-589 + if (Utils::CheckMapiExtVersion('7.2.0')) { + $zpush_version = 'Z-Push_' . @constant('ZPUSH_VERSION'); + $user_agent = $_SERVER['HTTP_USER_AGENT']; + $this->session = @mapi_logon_zarafa($user, $pass, MAPI_SERVER, null, null, 0, $zpush_version, $user_agent); + } + else { + $this->session = @mapi_logon_zarafa($user, $pass, MAPI_SERVER, null, null, 0); + } $this->notifications = true; } // old fashioned session @@ -453,18 +461,14 @@ class BackendZarafa implements IBackend, ISearchProvider { // @see http://jira.zarafa.com/browse/ZP-68 $meetingRequestProps = MAPIMapping::GetMeetingRequestProperties(); $meetingRequestProps = getPropIdsFromStrings($this->store, $meetingRequestProps); - $props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS, $meetingRequestProps["goidtag"], $sendMailProps["internetcpid"])); + $props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS, $meetingRequestProps["goidtag"], $sendMailProps["internetcpid"], $sendMailProps["body"], $sendMailProps["html"], $sendMailProps["rtf"], $sendMailProps["rtfinsync"])); - // Convert sent message's body to UTF-8. - // @see http://jira.zarafa.com/browse/ZP-505 - if (isset($props[$sendMailProps["internetcpid"]]) && $props[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sent email cpid is not unicode (%d). Set it to unicode and convert email body.", $props[$sendMailProps["internetcpid"]])); + // Convert sent message's body to UTF-8 if it was a HTML message. + // @see http://jira.zarafa.com/browse/ZP-505 and http://jira.zarafa.com/browse/ZP-555 + if (isset($props[$sendMailProps["internetcpid"]]) && $props[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8 && MAPIUtils::GetNativeBodyType($props) == SYNC_BODYPREFERENCE_HTML) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sent email cpid is not unicode (%d). Set it to unicode and convert email html body.", $props[$sendMailProps["internetcpid"]])); $mapiprops[$sendMailProps["internetcpid"]] = INTERNET_CPID_UTF8; - $body = MAPIUtils::readPropStream($mapimessage, PR_BODY); - $body = Utils::ConvertCodepageStringToUtf8($props[$sendMailProps["internetcpid"]], $body); - $mapiprops[$sendMailProps["body"]] = $body; - $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML); $bodyHtml = Utils::ConvertCodepageStringToUtf8($props[$sendMailProps["internetcpid"]], $bodyHtml); $mapiprops[$sendMailProps["html"]] = $bodyHtml; @@ -968,9 +972,9 @@ class BackendZarafa implements IBackend, ISearchProvider { * @throws StatusException */ public function GetGALSearchResults($searchquery, $searchrange){ - // only return users from who the displayName or the username starts with $name + // only return users whose displayName or the username starts with $name //TODO: use PR_ANR for this restriction instead of PR_DISPLAY_NAME and PR_ACCOUNT - $addrbook = mapi_openaddressbook($this->session); + $addrbook = $this->getAddressbook(); if ($addrbook) $ab_entryid = mapi_ab_getdefaultdir($addrbook); if ($ab_entryid) @@ -1003,12 +1007,16 @@ class BackendZarafa implements IBackend, ISearchProvider { $querycnt = mapi_table_getrowcount($table); //do not return more results as requested in range $querylimit = (($rangeend + 1) < $querycnt) ? ($rangeend + 1) : $querycnt; - $items['range'] = ($querylimit > 0) ? $rangestart.'-'.($querylimit - 1) : '0-0'; - $items['searchtotal'] = $querycnt; + if ($querycnt > 0) $abentries = mapi_table_queryrows($table, array(PR_ACCOUNT, PR_DISPLAY_NAME, PR_SMTP_ADDRESS, PR_BUSINESS_TELEPHONE_NUMBER, PR_GIVEN_NAME, PR_SURNAME, PR_MOBILE_TELEPHONE_NUMBER, PR_HOME_TELEPHONE_NUMBER, PR_TITLE, PR_COMPANY_NAME, PR_OFFICE_LOCATION), $rangestart, $querylimit); for ($i = 0; $i < $querylimit; $i++) { + if (!isset($abentries[$i][PR_SMTP_ADDRESS])) { + ZLog::Write(LOGLEVEL_WARN, sprintf("The GAL entry '%s' does not have an email address and will be ignored.", w2u($abentries[$i][PR_DISPLAY_NAME]))); + continue; + } + $items[$i][SYNC_GAL_DISPLAYNAME] = w2u($abentries[$i][PR_DISPLAY_NAME]); if (strlen(trim($items[$i][SYNC_GAL_DISPLAYNAME])) == 0) @@ -1047,6 +1055,9 @@ class BackendZarafa implements IBackend, ISearchProvider { if (isset($abentries[$i][PR_OFFICE_LOCATION])) $items[$i][SYNC_GAL_OFFICE] = w2u($abentries[$i][PR_OFFICE_LOCATION]); } + $nrResults = count($items); + $items['range'] = ($nrResults > 0) ? $rangestart.'-'.($nrResults - 1) : '0-0'; + $items['searchtotal'] = $nrResults; return $items; } @@ -1195,12 +1206,21 @@ class BackendZarafa implements IBackend, ISearchProvider { */ public function GetUserDetails($username) { ZLog::Write(LOGLEVEL_WBXML, sprintf("ZarafaBackend->GetUserDetails for '%s'.", $username)); - $zarafauserinfo = @mapi_zarafa_getuser_by_name($this->defaultstore, $username); + $zarafauserinfo = @mapi_zarafa_getuser_by_name($this->store, $username); $userDetails['emailaddress'] = (isset($zarafauserinfo['emailaddress']) && $zarafauserinfo['emailaddress']) ? $zarafauserinfo['emailaddress'] : false; $userDetails['fullname'] = (isset($zarafauserinfo['fullname']) && $zarafauserinfo['fullname']) ? $zarafauserinfo['fullname'] : false; return $userDetails; } + /** + * Returns the username of the currently active user + * + * @access public + * @return String + */ + public function GetCurrentUsername() { + return $this->storeName; + } /**---------------------------------------------------------------------------------------------------------- * Private methods @@ -1880,5 +1900,3 @@ class BackendZarafa implements IBackend, ISearchProvider { * DEPRECATED legacy class */ class BackendICS extends BackendZarafa {} - -?> \ No newline at end of file diff --git a/sources/composer.json b/sources/composer.json new file mode 100644 index 0000000..e7f29fa --- /dev/null +++ b/sources/composer.json @@ -0,0 +1,6 @@ +{ + "autoload": { + "classmap": ["autodiscover/", "include/", "lib/"], + "files": ["version.php", "lib/core/zpush-utils.php", "lib/core/zpushdefs.php", "lib/utils/compat.php"] + } +} diff --git a/sources/config.php b/sources/config.php index 759e7e8..9b51d70 100644 --- a/sources/config.php +++ b/sources/config.php @@ -48,11 +48,15 @@ define('TIMEZONE', ''); // Defines the base path on the server - define('BASE_PATH', dirname($_SERVER['SCRIPT_FILENAME']). '/'); + define('BASE_PATH', dirname(__FILE__) . '/'); // Try to set unlimited timeout define('SCRIPT_TIMEOUT', 0); + // Your PHP could have a bug when base64 encoding: https://bugs.php.net/bug.php?id=68532 + // NOTE: Run "php testing/testing-bug68532fixed.php" to know what value put here + define('BUG68532FIXED', true); + // When accessing through a proxy, the "X-Forwarded-For" header contains the original remote IP define('USE_X_FORWARDED_FOR_HEADER', false); @@ -60,6 +64,10 @@ // This setting specifies the owner parameter in the certificate to look at. define("CERTIFICATE_OWNER_PARAMETER", "SSL_CLIENT_S_DN_CN"); + // Location of the trusted CA, e.g. '/etc/ssl/certs/EmailCA.pem' + // Uncomment and modify the following line if the validation of the certificates fails. + // define('CAINFO', '/etc/ssl/certs/EmailCA.pem'); + /* * Whether to use the complete email address as a login name * (e.g. user@company.com) or the username only (user). @@ -68,7 +76,7 @@ * false - use the username only (default). * true - use the complete email address. */ - define('USE_FULLEMAIL_FOR_LOGIN', false); + define('USE_FULLEMAIL_FOR_LOGIN', true); /********************************************************************************** * Device pre-authorization. Useful when using Z-Push as a standalone product. @@ -153,9 +161,18 @@ define('LOGUSERLEVEL', LOGLEVEL_DEVICEID); $specialLogUsers = array(); - // Location of the trusted CA, e.g. '/etc/ssl/certs/EmailCA.pem' - // Uncomment and modify the following line if the validation of the certificates fails. - // define('CAINFO', '/etc/ssl/certs/EmailCA.pem'); + // If you want to disable log to file, and log to syslog instead + define('LOG_SYSLOG_ENABLED', false); + // false will log to local syslog, otherwise put the remote syslog IP here + define('LOG_SYSLOG_HOST', false); + // Syslog port + define('LOG_SYSLOG_PORT', 514); + // Program showed in the syslog. Useful if you have more than one instance login to the same syslog + define('LOG_SYSLOG_PROGRAM', '[z-push]'); + + + define('LOG_MEMORY_PROFILER', true); + define('LOG_MEMORY_PROFILER_FILE', '/var/log/z-push/memory_profile'); /********************************************************************************** * Mobile settings @@ -238,8 +255,8 @@ // in the semantic sanity checks and contacts with larger photos are not synchronized. // This limitation is not being followed by the ActiveSync clients which set much bigger // contact photos. You can override the default value of the max photo size. - // default: 49152 - 48 KB default max photo size in bytes - define('SYNC_CONTACTS_MAXPICTURESIZE', 49152); + // default: 5242880 - 5 MB default max photo size in bytes + define('SYNC_CONTACTS_MAXPICTURESIZE', 5242880); // Over the WebserviceUsers command it is possible to retrieve a list of all // known devices and users on this Z-Push system. The authenticated user needs to have @@ -261,6 +278,28 @@ // the backend data provider define('BACKEND_PROVIDER', ''); + // top collector backend class name + // Default is: TopCollector + // Options: ["TopCollector", "TopCollectorRedis"] + define('TOP_COLLECTOR_BACKEND', 'TopCollector'); + + // ping tracking backend class name + // Default is: PingTracking + // Options: ["PingTracking", "PingTrackingRedis"] + define('PING_TRACKING_BACKEND', 'PingTracking'); + + // loop detection backend class name + // Default is: LoopDetection + // Options: ["LoopDetection", "LoopDetectionRedis"] + define('LOOP_DETECTION_BACKEND', 'LoopDetection'); + + // If using the Redis backends (for top, ping and lookp) make sure to set this values as necessary + define('IPC_REDIS_IP', '127.0.0.1'); + define('IPC_REDIS_PORT', 6379); + // Database name/index in Redis: 0 by default + // NOTE: this database must be exclusive for z-push, since its content will be ERASED. You are warned. + define('IPC_REDIS_DATABASE', 0); + /********************************************************************************** * Search provider settings * @@ -328,5 +367,3 @@ ), */ ); - -?> \ No newline at end of file diff --git a/sources/docker/README.md b/sources/docker/README.md new file mode 100644 index 0000000..66e660b --- /dev/null +++ b/sources/docker/README.md @@ -0,0 +1,103 @@ +# Docker Images + +You can run a Z-Push server using Docker containers. That is really usefull for developing, but it also can be used in production servers. + + +Here are the basic instructions for a Nginx+PHP-FPM deployment. Feel free to contribute your Apache or other server approach. + + +## Using Docker Composer (In progress) + + +### Create and build basic images + + docker-compose -f basic.yml up + + + + + +## Manual method + + +### Build a PHP-FPM image + + cd php-fpm + docker build -t fmbiete/centos_zpush_php_fpm . + + +### Build a NGINX image + + cd nginx + docker build -t fmbiete/centos_zpush_nginx . + +**NOTE**: this includes a SSL self-signed certificate (2048 bytes - valid until 2025), but it's intended only for development or testing uses. In production replace it with a real one. + + +### Create MariaDB container (optional for SQLStateMachine) + + docker run --name zpush_mariadb -e MYSQL_ROOT_PASSWORD=root_password -e MYSQL_USER=user_name -e MYSQL_PASSWORD=user_password -e MYSQL_DATABASE=database -v mariadb_lib:/var/lib/mysql -p3306:3306 -d fbiete/centos_epel_mariadb:10 + +**TODO**: Replace *mariadb_lib* with the full path when you will store the database files +**TODO**: If using selinux remember to change the context type for *mariadb_lib* +**TODO**: Replace *root_password*, *user_name*, *user_password*, *database* with the right values + +#### Load database schema + + mysql -u root -proot_password database -h 127.0.0.1 < sql/mysql.sql + + +### Create Redis container (optional for TopCollectorRedis, LoopDetectionRedis or PingTrackingRedis) + + docker run --name zpush_redis -v redis_data:/data -p 6379:6379 -d fbiete/centos_epel_redis:2.8 + +**TODO**: Replace *redis_data* with the full path when you will store the database files +**TODO**: If using selinux remember to change the context type for *redis_data* + +### Create PHP-FPM container + + docker run -d --name zpush_php_fpm -v zpush_repo:/var/www/z-push fmbiete/centos_zpush_php_fpm + +#### With MariaDB + + docker run -d --name zpush_php_fpm -v zpush_repo:/var/www/z-push --link zpush_mariadb:zpushmariadb fmbiete/centos_zpush_php_fpm + +#### With Redis + + docker run -d --name zpush_php_fpm -v zpush_repo:/var/www/z-push --link zpush_redis:zpushredis fmbiete/centos_zpush_php_fpm + +**TODO**: Replace *zpush_repo* with the full path to Z-Push code +**TODO**: Remember to zpushmariadb and zpushredis as server name in the config for MariaDB and Redis + + +### Create NGINX container + + docker run -d --name zpush_nginx -v zpush_repo:/var/www/z-push --link zpush_php_fpm:zpushphpfpm -p 443:443 fmbiete/centos_zpush_nginx + +**TODO**: Replace *zpush_repo* with the full path to Z-Push code + + +### Stop containers + + docker stop zpush_nginx + docker stop zpush_php_fpm + docker stop zpush_mariadb + docker stop zpush_redis + + +### Start containers + + docker start zpush_mariadb + docker start zpush_redis + docker start zpush_php_fpm + docker start zpush_nginx + + +### Remove containers + + docker rm zpush_nginx + docker rm zpush_php_fpm + docker rm zpush_mariadb + docker rm zpush_redis + +**NOTE**: The order of the containers in the operation is important \ No newline at end of file diff --git a/sources/docker/basic.yml b/sources/docker/basic.yml new file mode 100644 index 0000000..8b92139 --- /dev/null +++ b/sources/docker/basic.yml @@ -0,0 +1,13 @@ +phpfpm: + build: php-fpm/ + volumes: + - ..:/var/www/z-push:Z +nginx: + build: nginx/ + volumes_from: + - phpfpm + links: + - phpfpm:zpushphpfpm + ports: + - "80:80" + - "443:443" diff --git a/sources/docker/nginx/Dockerfile b/sources/docker/nginx/Dockerfile new file mode 100644 index 0000000..8b3248d --- /dev/null +++ b/sources/docker/nginx/Dockerfile @@ -0,0 +1,16 @@ +FROM fbiete/centos_epel_nginx:1.8 +MAINTAINER Francisco Miguel Biete + +RUN mkdir /var/www /var/www/z-push /etc/ssl/nginx \ +&& chown -R nginx:nginx /var/www + +COPY localhost.crt /etc/ssl/nginx/localhost.crt +COPY localhost.key /etc/ssl/nginx/localhost.key + +COPY nginx.conf /etc/nginx/ + +VOLUME /var/www/z-push + +CMD [ "nginx", "-g", "daemon off;" ] + + diff --git a/sources/docker/nginx/localhost.crt b/sources/docker/nginx/localhost.crt new file mode 100644 index 0000000..66a95e4 --- /dev/null +++ b/sources/docker/nginx/localhost.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDfzCCAmegAwIBAgIJAMNks+T7RrPhMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg +Q29tcGFueSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTAzMTYwNzQwMzZa +Fw0yNTAzMTMwNzQwMzZaMFYxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0 +IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxEjAQBgNVBAMMCWxv +Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALhw96+bbz0I +5tPNAUJo/TG3Qi8RzkHyeGeg+dbMckS2Y3gcaa4E8nsnuKisbmHZu6Zgc7P8sr9d +V6qTsz07aJuy5pdt3+y9oiHPd05EZ4aZSXbfdGLAwr94D6R3AI6zv6lA+cCAHIIu +A4tgOmjtykre632dDDVLyDyxVTekn28q6ag+6vDnj9gyABsvER7WsJpi1Af6HxH2 +/tM1EtCKam5SNVy9+lQs3/pXk8r8kKvKVyrewhTzy4F8IRVi0vXtcW7wtkDwO1Ti ++CN1C1ETQZ2jfTk7Z9xGaFbS5cIEbHH3AmBgJjT396pUBQEqQVHBsHxvmhFhKMBi +ejvFbTYFz8cCAwEAAaNQME4wHQYDVR0OBBYEFECo2oRuFvk9sUOwRzZ+BeH48YJR +MB8GA1UdIwQYMBaAFECo2oRuFvk9sUOwRzZ+BeH48YJRMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggEBAK5ZATJ3Oh+0bXXdPMSTCZDgsGYpm5/BrUiAbqXX +mMRWyx6nUF6QqDu6Fku+Jgo0RwhXz7VfwI1JNXWvDsoEnjCbWJ2+njH08qBn9Xex +wtxL+kwnjXVeZgskUa9sAP+nr2gyzhjyRFjx1W3gZQeJ9VY2pDKLpW2NTkUOEhOH +YzLSUzVlXdQUauiYglzqip7dUId5VeDXHC8merB7Iq8h7QxD0WVHyjlgSjWEH8Gq +MDaY+n6CyPXkmusNJlQoWB/CJcLfr3tSVvaqmZ49K3OZph3DCKiGnSqqFi5OqKLg +YkcculYQGwfUkqZPqTb++MTsKkuaQPk4UDbPYAYhHJnBT+A= +-----END CERTIFICATE----- diff --git a/sources/docker/nginx/localhost.key b/sources/docker/nginx/localhost.key new file mode 100644 index 0000000..ede0d56 --- /dev/null +++ b/sources/docker/nginx/localhost.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuHD3r5tvPQjm080BQmj9MbdCLxHOQfJ4Z6D51sxyRLZjeBxp +rgTyeye4qKxuYdm7pmBzs/yyv11XqpOzPTtom7Lml23f7L2iIc93TkRnhplJdt90 +YsDCv3gPpHcAjrO/qUD5wIAcgi4Di2A6aO3KSt7rfZ0MNUvIPLFVN6SfbyrpqD7q +8OeP2DIAGy8RHtawmmLUB/ofEfb+0zUS0IpqblI1XL36VCzf+leTyvyQq8pXKt7C +FPPLgXwhFWLS9e1xbvC2QPA7VOL4I3ULURNBnaN9OTtn3EZoVtLlwgRscfcCYGAm +NPf3qlQFASpBUcGwfG+aEWEowGJ6O8VtNgXPxwIDAQABAoIBACbhMWUkN9u+36Gw +Kl7McOsk/V+cukTujvERXvknmcLgS7GLE7/qLQ9G/UcZKh+YXVUiKeG8GBX84DkF +75etyUxg9vje4YAvLVlBOZ4XD1exQmo7inYyuhrQfUOnDkgGnhVYrA0nNFtAxeCA +hW+PCMClozCUhXlKo0gf/Z3AJxewsVm4x+G73aU9t/yxm6A4Z2OEYGfD240uhfC/ +fEVNpL3xs2TPP4W90yw+Q+oZ0g/hmXKgFQ1PujD5+hGtiRLQ3v1Apb1LhPTz3pPg +OK/VSWbDXBSESqU5aFfk3NP7KSnVfxkUVQk9MAnofq2p3BrPv0ovjgliyMHOV88/ +6xdkIBkCgYEA5QFSH63n9WuQz2ZHR7YrU/i8ATrQ9ex1RawLAVZA+YN+GC2/Kd+c +GDyB1ed2v7o/DzCbiVE9WGhCLUE2WDp8J+MVIbgVDYnCx6wXLaMtkzSMtQMT6UV4 ++ks6FqvrjmzwdvtrX31XfHCvsGkPhHKeoUU0SnIwvbyxq2sNBdH0E2UCgYEAzi7Y +KIQ/0ryekr6Mf8pAOaprxJX4ykArvTTGwzVazaumTNW0AArEnKNdebB+kVVko4h0 +2kUPbJTNwGTxJiuu7HJGE1vla++wlpyPryqghVPIgwVIQOjkV3guq8BbOPyB/xtv +9yVJZqeJc0Jj+ZV7Cm+kQ514F3e1cpidYUUjQbsCgYA+Jj+dbVr4Vfr07nMF2UCl +B2oug0HWnBevkuNht4DmtnLwKOoqeQ8p3LH31Vt66RbYDn8Ho06cwZ7EHWCcTTMI +uC4x+n1sMSj1e5TGw/RIcQiGz5EFy97rPqNDJ+FDw/j2sYEQZznpAcQMgla9wUWf +yuJIGfl0ZNNrDCB6peIxqQKBgQCKTo4dj6koefJ9SWkCB+/RPuqPsnJzaVxtzUtP +gyjoMi6Z9/iI1rBQyp1XlfcxEnEx6cVI7W6NTbw/RPcmvcLXRUiQj+Jz5xMz1M3l +mNiY1zz39sEjGZaivjHAcIZA0dF6CTOwO8jjHZtsP6rEr2sb8wvjd2wpgdmrh4h6 +yV//JQKBgQDL4q0becCvRcC8HYPt5LkxWHLvvOlP4Z8x19DzMw0xhvhqvtk7cuxy +ioQckj/9Qa5icYqXGUY1eg2wSMe4mJdCbosXFDdPi3pW7eXJwmQmVCwHx0INDq6z +Xn5hG0ZRioytwV8aqbq8k1PLHm6mmY71dH+riou2JwFD2py7RqBJqQ== +-----END RSA PRIVATE KEY----- diff --git a/sources/docker/nginx/nginx.conf b/sources/docker/nginx/nginx.conf new file mode 100644 index 0000000..d38c148 --- /dev/null +++ b/sources/docker/nginx/nginx.conf @@ -0,0 +1,91 @@ +user nginx; +worker_processes 1; + +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + sendfile on; + + keepalive_timeout 65; + + # max_execution_time is 900 + proxy_read_timeout 910; + + # Disable SSLv3 to protect us from POODLE & company + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + + server { + listen *:443; + + server_name localhost; + + ssl on; + ssl_certificate /etc/ssl/nginx/localhost.crt; + ssl_certificate_key /etc/ssl/nginx/localhost.key; + + root /var/www/z-push; + index index.php; + + error_log /var/log/nginx/zpush-error.log; + access_log /var/log/nginx/zpush-access.log; + + # Attachments 20MB max + client_max_body_size 20m; + client_body_buffer_size 128k; + + location / { + try_files $uri $uri/ index.php; + } + + location /Microsoft-Server-ActiveSync { + rewrite ^(.*)$ /index.php last; + } + + location ~ .php$ { + include /etc/nginx/fastcgi_params; + fastcgi_index index.php; + fastcgi_param HTTPS on; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_pass zpushphpfpm:9000; + # PHP max_execution_time is set to 900 + fastcgi_read_timeout 910; + } + + + # You don't really need all of the next, but I have them in my server + location = /robots.txt { + access_log off; + log_not_found off; + } + + location = /favicon.ico { + return 204; + access_log off; + log_not_found off; + } + + location ~ ^\. { + access_log on; + log_not_found off; + deny all; + } + + location ~ ~$ { + access_log off; + log_not_found off; + deny all; + } + } +} \ No newline at end of file diff --git a/sources/docker/php-fpm/Dockerfile b/sources/docker/php-fpm/Dockerfile new file mode 100644 index 0000000..3409aa8 --- /dev/null +++ b/sources/docker/php-fpm/Dockerfile @@ -0,0 +1,21 @@ +FROM fbiete/centos_epel_php_fpm:5.6 +MAINTAINER Francisco Miguel Biete + +RUN yum clean all \ +&& yum install -y --enablerepo=remi-php56 \ +php-pecl-memprof \ +mailcap \ +&& yum clean all \ +&& cd /usr/local/src \ +&& curl -LSs https://gitlab.com/davical-project/awl/repository/archive.tar.gz | tar xz \ +&& echo "include_path=.:/usr/share/pear:/usr/share/php:/usr/local/src/awl.git/inc" >> /etc/php.ini \ +&& sed -i 's/max_execution_time = 30/max_execution_time = 900/g' /etc/php.ini \ +&& sed -i 's/max_input_time = 60/max_input_time = 300/g' /etc/php.ini \ +&& sed -i 's/post_max_size = 8M/post_max_size = 20M/g' /etc/php.ini \ +&& sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 20M/g' /etc/php.ini \ +&& mkdir /var/log/z-push /var/lib/z-push \ +&& chown -R apache:apache /var/log/z-push /var/lib/z-push + +VOLUME /var/www/z-push + + diff --git a/sources/include/Auth/SASL.php b/sources/include/Auth/SASL.php index 7ccce4e..22acd8b 100755 --- a/sources/include/Auth/SASL.php +++ b/sources/include/Auth/SASL.php @@ -145,6 +145,4 @@ class Auth_SASL ZLog::Write(LOGLEVEL_ERROR, "Auth_SASL error: ". $message); return false; } -} - -?> +} \ No newline at end of file diff --git a/sources/include/Auth/SASL/Anonymous.php b/sources/include/Auth/SASL/Anonymous.php index dfccab6..ac0f0e9 100755 --- a/sources/include/Auth/SASL/Anonymous.php +++ b/sources/include/Auth/SASL/Anonymous.php @@ -43,8 +43,6 @@ * @package Auth_SASL */ -require_once('include/Auth/SASL/Common.php'); - class Auth_SASL_Anonymous extends Auth_SASL_Common { /** @@ -67,5 +65,4 @@ class Auth_SASL_Anonymous extends Auth_SASL_Common { return $token; } -} -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/include/Auth/SASL/Common.php b/sources/include/Auth/SASL/Common.php index af2641f..e641cbe 100755 --- a/sources/include/Auth/SASL/Common.php +++ b/sources/include/Auth/SASL/Common.php @@ -125,5 +125,4 @@ class Auth_SASL_Common ZLog::Write(LOGLEVEL_ERROR, "SCRAM error: ". $message); return false; } -} -?> +} \ No newline at end of file diff --git a/sources/include/Auth/SASL/CramMD5.php b/sources/include/Auth/SASL/CramMD5.php index 09e7667..97abcba 100755 --- a/sources/include/Auth/SASL/CramMD5.php +++ b/sources/include/Auth/SASL/CramMD5.php @@ -43,8 +43,6 @@ * @package Auth_SASL */ -require_once('include/Auth/SASL/Common.php'); - class Auth_SASL_CramMD5 extends Auth_SASL_Common { /** @@ -64,5 +62,4 @@ class Auth_SASL_CramMD5 extends Auth_SASL_Common { return $user . ' ' . $this->_HMAC_MD5($pass, $challenge); } -} -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/include/Auth/SASL/DigestMD5.php b/sources/include/Auth/SASL/DigestMD5.php index 892c217..2065756 100755 --- a/sources/include/Auth/SASL/DigestMD5.php +++ b/sources/include/Auth/SASL/DigestMD5.php @@ -43,8 +43,6 @@ * @package Auth_SASL */ -require_once('include/Auth/SASL/Common.php'); - class Auth_SASL_DigestMD5 extends Auth_SASL_Common { /** @@ -193,5 +191,4 @@ class Auth_SASL_DigestMD5 extends Auth_SASL_Common return base64_encode($str); } } -} -?> +} \ No newline at end of file diff --git a/sources/include/Auth/SASL/External.php b/sources/include/Auth/SASL/External.php index 7e96d8e..fc01bb1 100644 --- a/sources/include/Auth/SASL/External.php +++ b/sources/include/Auth/SASL/External.php @@ -43,8 +43,6 @@ * @package Auth_SASL */ -require_once('include/Auth/SASL/Common.php'); - class Auth_SASL_External extends Auth_SASL_Common { /** @@ -59,5 +57,4 @@ class Auth_SASL_External extends Auth_SASL_Common { return $authzid; } -} -?> +} \ No newline at end of file diff --git a/sources/include/Auth/SASL/Login.php b/sources/include/Auth/SASL/Login.php index cf988f5..b6cec5e 100755 --- a/sources/include/Auth/SASL/Login.php +++ b/sources/include/Auth/SASL/Login.php @@ -46,8 +46,6 @@ * @package Auth_SASL */ -require_once('include/Auth/SASL/Common.php'); - class Auth_SASL_Login extends Auth_SASL_Common { /** @@ -61,5 +59,4 @@ class Auth_SASL_Login extends Auth_SASL_Common { return sprintf('LOGIN %s %s', $user, $pass); } -} -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/include/Auth/SASL/Plain.php b/sources/include/Auth/SASL/Plain.php index 4de1813..03b483b 100755 --- a/sources/include/Auth/SASL/Plain.php +++ b/sources/include/Auth/SASL/Plain.php @@ -43,8 +43,6 @@ * @package Auth_SASL */ -require_once('include/Auth/SASL/Common.php'); - class Auth_SASL_Plain extends Auth_SASL_Common { /** @@ -59,5 +57,4 @@ class Auth_SASL_Plain extends Auth_SASL_Common { return $authzid . chr(0) . $authcid . chr(0) . $pass; } -} -?> +} \ No newline at end of file diff --git a/sources/include/Auth/SASL/SCRAM.php b/sources/include/Auth/SASL/SCRAM.php index 5a5dd3b..d9e1ef1 100644 --- a/sources/include/Auth/SASL/SCRAM.php +++ b/sources/include/Auth/SASL/SCRAM.php @@ -46,8 +46,6 @@ * @package Auth_SASL */ -require_once('include/Auth/SASL/Common.php'); - class Auth_SASL_SCRAM extends Auth_SASL_Common { /** @@ -300,6 +298,4 @@ class Auth_SASL_SCRAM extends Auth_SASL_Common return base64_encode($str); } } -} - -?> +} \ No newline at end of file diff --git a/sources/include/Mail.php b/sources/include/Mail.php index 645bff7..b1ba915 100644 --- a/sources/include/Mail.php +++ b/sources/include/Mail.php @@ -86,7 +86,6 @@ class Mail static function &factory($driver, $params = array()) { $driver = strtolower($driver); - @include_once 'include/Mail/' . $driver . '.php'; $class = 'Mail_' . $driver; if (class_exists($class)) { $mailer = new $class($params); @@ -197,7 +196,6 @@ class Mail foreach ($headers as $key => $value) { if (strcasecmp($key, 'From') === 0) { - include_once 'include/z_RFC822.php'; $parser = new Mail_RFC822(); $addresses = $parser->parseAddressList($value, 'localhost', false); //if (is_a($addresses, 'PEAR_Error')) { @@ -255,8 +253,6 @@ class Mail */ function parseRecipients($recipients) { - include_once 'include/z_RFC822.php'; - // if we're passed an array, assume addresses are valid and // implode them before parsing. if (is_array($recipients)) { @@ -282,6 +278,9 @@ class Mail } } + // Remove duplicated + $recipients = array_unique($recipients); + return $recipients; } diff --git a/sources/include/Mail/smtp.php b/sources/include/Mail/smtp.php index 8388aec..9318f5d 100644 --- a/sources/include/Mail/smtp.php +++ b/sources/include/Mail/smtp.php @@ -174,6 +174,24 @@ class Mail_smtp extends Mail { */ var $pipelining; + /** + * Require verification of SSL certificate used. + * @var bool + */ + var $verify_peer = true; + + /** + * Require verification of peer name + * @var bool + */ + var $verify_peer_name = true; + + /** + * Allow self-signed certificates. Requires verify_peer + * @var bool + */ + var $allow_self_signed = false; + /** * Constructor. * @@ -211,6 +229,9 @@ class Mail_smtp extends Mail { if (isset($params['debug'])) $this->debug = (bool)$params['debug']; if (isset($params['persist'])) $this->persist = (bool)$params['persist']; if (isset($params['pipelining'])) $this->pipelining = (bool)$params['pipelining']; + if (isset($params['verify_peer'])) $this->verify_peer = (bool)$params['verify_peer']; + if (isset($params['verify_peer_name'])) $this->verify_peer_name = (bool)$params['verify_peer_name']; + if (isset($params['allow_self_signed'])) $this->allow_self_signed = (bool)$params['allow_self_signed']; // Deprecated options if (isset($params['verp'])) { @@ -309,22 +330,6 @@ class Mail_smtp extends Mail { return $recipients; } - // FIX: Cc and Bcc headers are sent, but we need to make sure that the recipient list contains them - foreach (array("CC", "cc", "Cc", "BCC", "Bcc", "bcc") as $key) { - if (!empty($headers[$key])) { - $extra_recipients = $this->parseRecipients($headers[$key]); - if ($extra_recipients === false) { - $this->_smtp->rset(); - return $extra_recipients; - } - $recipients = array_merge($recipients, $extra_recipients); - } - } - - // Remove repeated rcptTo - $recipients = array_unique($recipients); - - foreach ($recipients as $recipient) { $res = $this->_smtp->rcptTo($recipient); //if (is_a($res, 'PEAR_Error')) { @@ -378,11 +383,15 @@ class Mail_smtp extends Mail { return $this->_smtp; } - include_once 'include/Net/SMTP.php'; $this->_smtp = &new Net_SMTP($this->host, $this->port, $this->localhost, - $this->pipelining); + $this->pipelining, + 0, //timeout + null, //socket_options + $this->verify_peer, + $this->verify_peer_name, + $this->allow_self_signed); /* If we still don't have an SMTP object at this point, fail. */ if (is_object($this->_smtp) === false) { @@ -471,7 +480,8 @@ class Mail_smtp extends Mail { /* Build our standardized error string. */ return $text - . ' [SMTP: ' . $error->getMessage() +// . ' [SMTP: ' . $error->getMessage() + . ' [SMTP: ' . " (code: $code, response: $response)]"; } diff --git a/sources/include/Net/SMTP.php b/sources/include/Net/SMTP.php index 2f65078..7b15281 100644 --- a/sources/include/Net/SMTP.php +++ b/sources/include/Net/SMTP.php @@ -33,7 +33,6 @@ //require_once 'PEAR.php'; //require_once 'PEAR/Exception.php'; -require_once 'include/Net/Socket.php'; /** * Provides an implementation of the SMTP protocol using PEAR's @@ -160,6 +159,27 @@ class Net_SMTP */ protected $_esmtp = array(); + /** + * Require verification of SSL certificate used. + * + * @var bool + */ + protected $_verify_peer; + + /** + * Require verification of peer name + * + * @var bool + */ + protected $_verify_peer_name; + + /** + * Allow self-signed certificates. Requires verify_peer + * + * @var bool + */ + protected $_allow_self_signed; + /** * Instantiates a new Net_SMTP object, overriding any defaults * with parameters that are passed in. @@ -177,10 +197,14 @@ class Net_SMTP * @param boolean $pipeling Use SMTP command pipelining * @param integer $timeout Socket I/O timeout in seconds. * @param array $socket_options Socket stream_context_create() options. + * @param boolean $verify_peer Require verification of SSL certificate used + * @param boolean $verify_peer_name Require verification of peer name + * @param boolean $allow_self_signed Allow self-signed certificates. Requires verify_peer */ public function __construct($host = null, $port = null, $localhost = null, $pipelining = false, $timeout = 0, - $socket_options = null) + $socket_options = null, + $verify_peer = true, $verify_peer_name = true, $allow_self_signed = false) { if (isset($host)) { $this->host = $host; @@ -195,14 +219,33 @@ class Net_SMTP $this->_socket = new Net_Socket(); $this->_socket_options = $socket_options; + + // SSL connection, we need to modify the socket_options + if (strpos($this->host, "ssl://") === 0) { + if ($this->_socket_options == null) + $this->_socket_options = array(); + + if (!array_key_exists('ssl', $this->_socket_options)) + $this->_socket_options['ssl'] = array(); + + $this->_socket_options['ssl']['verify_peer'] = $verify_peer; + $this->_socket_options['ssl']['allow_self_signed'] = $allow_self_signed; + // This option was introduced in 5.6 + if (version_compare(phpversion(), "5.6.0", ">=")) + $this->_socket_options['ssl']['verify_peer_name'] = $verify_peer_name; + } + $this->_timeout = $timeout; + // We also need this for use in the STARTTLS command + $this->_verify_peer = $verify_peer; + $this->_verify_peer_name = $verify_peer_name; + $this->_allow_self_signed = $allow_self_signed; + /* Include the Auth_SASL package. If the package is available, we * enable the authentication methods that depend upon it. */ - if (@include_once 'include/Auth/SASL.php') { - $this->setAuthMethod('CRAM-MD5', array($this, '_authCram_MD5')); - $this->setAuthMethod('DIGEST-MD5', array($this, '_authDigest_MD5')); - } + $this->setAuthMethod('CRAM-MD5', array($this, '_authCram_MD5')); + $this->setAuthMethod('DIGEST-MD5', array($this, '_authDigest_MD5')); /* These standard authentication methods are always available. */ $this->setAuthMethod('LOGIN', array($this, '_authLogin'), false); @@ -262,8 +305,9 @@ class Net_SMTP $result = $this->_socket->write($data); if ($result === false) { - return Net_SMTP::raiseError('Failed to write to socket: ' . $result->getMessage(), - $result); +// return Net_SMTP::raiseError('Failed to write to socket: ' . $result->getMessage(), +// $result); + return Net_SMTP::raiseError('Failed to write to socket: '); } return $result; @@ -330,7 +374,8 @@ class Net_SMTP if (empty($line)) { $this->disconnect(); return Net_SMTP::raiseError('Connection was closed', - null, PEAR_ERROR_RETURN); +// null, PEAR_ERROR_RETURN); + null, 1); } /* Read the code and store the rest in the arguments array. */ @@ -361,7 +406,8 @@ class Net_SMTP } return Net_SMTP::raiseError('Invalid response code received from server', - $this->_code, PEAR_ERROR_RETURN); +// $this->_code, PEAR_ERROR_RETURN); + $this->_code, 1); } /** @@ -429,8 +475,9 @@ class Net_SMTP $this->_socket_options); //if (PEAR::isError($result)) { if ($result === false) { - return Net_SMTP::raiseError('Failed to connect socket: ' . - $result->getMessage()); +// return Net_SMTP::raiseError('Failed to connect socket: ' . +// $result->getMessage()); + return Net_SMTP::raiseError('Failed to connect socket: '); } /* @@ -509,7 +556,8 @@ class Net_SMTP //if (PEAR::isError($this->_parseResponse(250))) { if (($this->_parseResponse(250)) === false) { return Net_SMTP::raiseError('HELO was not accepted: ', $this->_code, - PEAR_ERROR_RETURN); +// PEAR_ERROR_RETURN); + 1); } return true; @@ -548,7 +596,8 @@ class Net_SMTP } return Net_SMTP::raiseError('No supported authentication methods', - null, PEAR_ERROR_RETURN); +// null, PEAR_ERROR_RETURN); + null, 1); } /** @@ -587,7 +636,7 @@ class Net_SMTP return $result; } //if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) { - if (($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) === false) { + if (($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT, $this->_verify_peer, $this->_verify_peer_name, $this->_allow_self_signed)) === false) { return $result; } elseif ($result !== true) { return Net_SMTP::raiseError('STARTTLS failed'); diff --git a/sources/include/Net/Socket.php b/sources/include/Net/Socket.php index b01aa15..97e3fab 100644 --- a/sources/include/Net/Socket.php +++ b/sources/include/Net/Socket.php @@ -680,6 +680,9 @@ class Net_Socket * and false to disable encryption. * @param integer $type Type of encryption. See stream_socket_enable_crypto() * for values. + * @param boolean $verify_peer Require verification of SSL certificate used + * @param boolean $verify_peer_name Require verification of peer name + * @param boolean $allow_self_signed Allow self-signed certificates. Requires verify_peer * * @see http://se.php.net/manual/en/function.stream-socket-enable-crypto.php * @access public @@ -688,12 +691,19 @@ class Net_Socket * A PEAR_Error object is returned if the socket is not * connected */ - function enableCrypto($enabled, $type) + function enableCrypto($enabled, $type, $verify_peer, $verify_peer_name, $allow_self_signed) { if (version_compare(phpversion(), "5.1.0", ">=")) { if (!is_resource($this->fp)) { return $this->raiseError('not connected'); } + + // 5.6.0 Added verify_peer_name. verify_peer default changed to TRUE. + if (version_compare(phpversion(), "5.6.0", ">=")) + stream_context_set_option($this->fp, array('ssl' => array('verify_peer' => $verify_peer, 'verify_peer_name' => $verify_peer_name, 'allow_self_signed' => $allow_self_signed))); + else + stream_context_set_option($this->fp, array('ssl' => array('verify_peer' => $verify_peer, 'allow_self_signed' => $allow_self_signed))); + return @stream_socket_enable_crypto($this->fp, $enabled, $type); } else { $msg = 'Net_Socket::enableCrypto() requires php version >= 5.1.0'; diff --git a/sources/include/caldav-client-v2.php b/sources/include/caldav-client-v2.php deleted file mode 100644 index 7f79d6f..0000000 --- a/sources/include/caldav-client-v2.php +++ /dev/null @@ -1,1113 +0,0 @@ - -* @copyright Andrew McMillan -* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL version 3 or later -* Update xbgmsharp -*/ - -require_once('XMLDocument.php'); - -/** -* A class for holding basic calendar information -* @package awl -*/ -class CalendarInfo { - public $url, $displayname, $getctag, $id; - - function __construct( $url, $displayname = null, $getctag = null, $id = null ) { - $this->url = $url; - $this->displayname = $displayname; - $this->getctag = $getctag; - $this->id = $id; - } - - function __toString() { - return( '(URL: '.$this->url.' Ctag: '.$this->getctag.' Displayname: '.$this->displayname .')'. "\n" ); - } -} - - -/** -* A class for accessing DAViCal via CalDAV, as a client -* -* @package awl -*/ -class CalDAVClient { - /** - * Server, username, password, calendar - * - * @var string - */ - protected $base_url, $user, $pass, $entry, $protocol, $server, $port, $http_auth; - - /** - * The principal-URL we're using - */ - protected $principal_url; - - /** - * The calendar-URL we're using - */ - protected $calendar_url; - - /** - * The calendar-home-set we're using - */ - protected $calendar_home_set; - - /** - * The calendar_urls we have discovered - */ - protected $calendar_urls; - - /** - * The useragent which is send to the caldav server - * - * @var string - */ - public $user_agent = 'DAViCalClient'; - - protected $headers = array(); - protected $body = ""; - protected $requestMethod = "GET"; - protected $httpRequest = ""; // for debugging http headers sent - protected $xmlRequest = ""; // for debugging xml sent - protected $xmlResponse = ""; // xml received - protected $httpResponseCode = 0; // http response code - protected $httpResponseHeaders = ""; - protected $httpResponseBody = ""; - - protected $parser; // our XML parser object - - private $debug = false; // Whether we are debugging - - /** - * Constructor, initialises the class - * - * @param string $base_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( $base_url, $user, $pass ) { - $this->user = $user; - $this->pass = $pass; - $this->headers = array(); - $this->http_auth = array( - 'method' => 'basic' - ); - - if ( preg_match( '#^(https?)://([a-z0-9.-]+)(:([0-9]+))?(/.*)$#', $base_url, $matches ) ) { - $this->server = $matches[2]; - $this->base_url = $matches[5]; - if ( $matches[1] == 'https' ) { - $this->protocol = 'ssl'; - $this->port = 443; - } - else { - $this->protocol = 'tcp'; - $this->port = 80; - } - if ( $matches[4] != '' ) { - $this->port = intval($matches[4]); - } - } - else { - trigger_error("Invalid URL: '".$base_url."'", E_USER_ERROR); - } - } - - - /** - * Call this to enable / disable debugging. It will return the prior value of the debugging flag. - * @param boolean $new_value The new value for debugging. - * @return boolean The previous value, in case you want to restore it later. - */ - function SetDebug( $new_value ) { - $old_value = $this->debug; - if ( $new_value ) - $this->debug = true; - else - $this->debug = false; - return $old_value; - } - - - - /** - * 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 - */ - function SetDepth( $depth = '0' ) { - $this->headers['depth'] = 'Depth: '. ($depth == '1' ? "1" : ($depth == 'infinity' ? $depth : "0") ); - } - - /** - * Add a Depth: header. Valid values are 1 or infinity - * - * @param int $depth The depth, default to infinity - */ - function SetUserAgent( $user_agent = null ) { - if ( !isset($user_agent) ) $user_agent = $this->user_agent; - $this->user_agent = $user_agent; - } - - /** - * Add a Content-type: header. - * - * @param string $type The content type - */ - function SetContentType( $type ) { - $this->headers['content-type'] = "Content-type: $type"; - } - - /** - * 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 - */ - function ParseResponse( $response ) { - $pos = strpos($response, 'xmlResponse = trim(substr($response, $pos)); - $this->xmlResponse = preg_replace('{>[^>]*$}s', '>',$this->xmlResponse ); - $parser = xml_parser_create_ns('UTF-8'); - xml_parser_set_option ( $parser, XML_OPTION_SKIP_WHITE, 1 ); - xml_parser_set_option ( $parser, XML_OPTION_CASE_FOLDING, 0 ); - - if ( xml_parse_into_struct( $parser, $this->xmlResponse, $this->xmlnodes, $this->xmltags ) === 0 ) { - if ( $this->debug ) printf( "XML parsing error: %s - %s\n", xml_get_error_code($parser), xml_error_string(xml_get_error_code($parser)) ); -// debug_print_backtrace(); -// echo "\nNodes array............................................................\n"; print_r( $this->xmlnodes ); -// echo "\nTags array............................................................\n"; print_r( $this->xmltags ); - if ( $this->debug ) printf( "\nXML Reponse:\n%s\n", $this->xmlResponse ); - } - - xml_parser_free($parser); - } - } - - /** - * Output http request headers - * - * @return HTTP headers - */ - function GetHttpRequest() { - return $this->httpRequest; - } - /** - * Output http response headers - * - * @return HTTP headers - */ - function GetResponseHeaders() { - return $this->httpResponseHeaders; - } - /** - * Output http response body - * - * @return HTTP body - */ - function GetResponseBody() { - return $this->httpResponseBody; - } - /** - * Output xml request - * - * @return raw xml - */ - function GetXmlRequest() { - return $this->xmlRequest; - } - /** - * Output xml response - * - * @return raw xml - */ - function GetXmlResponse() { - return $this->xmlResponse; - } - - /** - * Send a request to the server - * - * @param string $url The URL to make the request to - * - * @return string The content of the response from the server - */ - function DoRequest( $url = null, $switch_auth = false ) { - if(!defined("_FSOCK_TIMEOUT")){ define("_FSOCK_TIMEOUT", 10); } - $headers = array(); - - if ( !isset($url) ) $url = $this->base_url; - $this->request_url = $url; - $url = preg_replace('{^https?://[^/]+}', '', $url); - // URLencode if it isn't already - if ( preg_match( '{[^%?&=+,.-_/a-z0-9]}', $url ) ) { - $url = str_replace(rawurlencode('/'),'/',rawurlencode($url)); - $url = str_replace(rawurlencode('?'),'?',$url); - $url = str_replace(rawurlencode('&'),'&',$url); - $url = str_replace(rawurlencode('='),'=',$url); - $url = str_replace(rawurlencode('+'),'+',$url); - $url = str_replace(rawurlencode(','),',',$url); - } - $headers[] = $this->requestMethod." ". $url . " HTTP/1.1"; - if( 'basic' != $this->http_auth['method'] && !isset( $this->http_auth['stale'] )) { - // then it's digest... - $digest_A1 = md5( $this->user .':' . $this->http_auth['realm'] . ':' . $this->pass ); - $digest_A2 = md5( $this->requestMethod.':'.$url ); - $digest = 'Authorization: Digest username="'.$this->user.'", realm="'.$this->http_auth['realm'].'", nonce="'.$this->http_auth['nonce'].'", uri="'.$url.'", algorithm=MD5, response="'.md5($digest_A1.':'.$this->http_auth['nonce'].':'.$digest_A2).'"'; - // todo: loop through challenges - $headers[] = $digest; - } else { - $headers[] = "Authorization: Basic ".base64_encode($this->user .":". $this->pass ); - } - $headers[] = "Host: ".$this->server .":".$this->port; - - if ( !isset($this->headers['content-type']) ) $this->headers['content-type'] = "Content-type: text/plain"; - foreach( $this->headers as $ii => $head ) { - $headers[] = $head; - } - $headers[] = "Content-Length: " . strlen($this->body); - $headers[] = "User-Agent: " . $this->user_agent; - $headers[] = 'Connection: close'; - $this->httpRequest = join("\r\n",$headers); - $this->xmlRequest = $this->body; - - $this->xmlResponse = ''; - - $fip = fsockopen( $this->protocol . '://' . $this->server, $this->port, $errno, $errstr, _FSOCK_TIMEOUT); //error handling? - if ( !(get_resource_type($fip) == 'stream') ) return false; - if ( !fwrite($fip, $this->httpRequest."\r\n\r\n".$this->body) ) { fclose($fip); return false; } - $response = ""; - while( !feof($fip) ) { $response .= fgets($fip,8192); } - fclose($fip); - - list( $this->httpResponseHeaders, $this->httpResponseBody ) = preg_split( '{\r?\n\r?\n}s', $response, 2 ); - $header_lines = preg_split( '{\r?\n}', $this->httpResponseHeaders ); - if( count( preg_grep( '{^HTTP/1.1 3\d\d.*}i', $header_lines ) ) > 0 && count( $matches = preg_grep( '{^Location: (.*)}i', $header_lines ) ) > 0 ) { - // todo: cache move? - return $this->DoRequest( substr( end( $matches ), 10 ), $switch_auth ); - } - - $can_switch_auth = false; - if( true != $switch_auth && count( preg_grep( '{^HTTP/1.1 401 Unauthorized.*}i', $header_lines ) ) > 0 && 'basic' == $this->http_auth['method'] ) { - $can_switch_auth = true; - } - if( count( $digest = preg_grep( '{^WWW-Authenticate: digest.+}', $header_lines ) ) > 0 ) { - $digest_string = end( $digest ); - $this->http_auth['method'] = 'digest'; - $this->http_auth['challenge'] = array(); - - foreach( array( 'realm', 'domain', 'nonce', 'stale' ) as $c ) { - if( preg_match( '{.+?'.$c.'="([^"]+)".*?}', $digest_string, $matches ) ) { - $this->http_auth[$c] = $matches[1]; - } - } - foreach( array( 'opaque', 'algorithm', 'qop-options', 'auth-param' ) as $c ) { - if( preg_match( '{.+?'.$c.'="([^"]+)".*?}', $digest_string, $matches ) ) { - $this->http_auth['challenge'][$c] = $matches[1]; - } - } - if( isset( $this->http_auth['challenge']['qop-options'] ) ) { - if( preg_match( '{.+?qop-value="([^"]+)".*?}', $digest_string, $matches ) ) { - $this->http_auth['challenge'][$c] = $matches[1]; - } - } - if( $can_switch_auth ) { - return $this->DoRequest( $url, true ); - } - } - if ( preg_match( '{Transfer-Encoding: chunked}i', $this->httpResponseHeaders ) ) $this->Unchunk(); - if ( preg_match('/HTTP\/\d\.\d (\d{3})/', $this->httpResponseHeaders, $status) ) - $this->httpResponseCode = intval($status[1]); - else - $this->httpResponseCode = 0; - - $this->headers = array(); // reset the headers array for our next request - $this->ParseResponse($this->httpResponseBody); - return $response; - } - - - /** - * Unchunk a chunked response - */ - function Unchunk() { - $content = ''; - $chunks = $this->httpResponseBody; - // printf( "\n================================\n%s\n================================\n", $chunks ); - do { - $bytes = 0; - if ( preg_match('{^((\r\n)?\s*([ 0-9a-fA-F]+)(;[^\n]*)?\r?\n)}', $chunks, $matches ) ) { - $octets = $matches[3]; - $bytes = hexdec($octets); - $pos = strlen($matches[1]); - // printf( "Chunk size 0x%s (%d)\n", $octets, $bytes ); - if ( $bytes > 0 ) { - // printf( "---------------------------------\n%s\n---------------------------------\n", substr($chunks,$pos,$bytes) ); - $content .= substr($chunks,$pos,$bytes); - $chunks = substr($chunks,$pos + $bytes + 2); - // printf( "+++++++++++++++++++++++++++++++++\n%s\n+++++++++++++++++++++++++++++++++\n", $chunks ); - } - } - else { - $content .= $chunks; - } - } - while( $bytes > 0 ); - $this->httpResponseBody = $content; - // printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n%s\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", $content ); - } - - - /** - * 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 ) { - $this->requestMethod = "OPTIONS"; - $this->body = ""; - $headers = $this->DoRequest($url); - $options_header = preg_replace( '/^.*Allow: ([a-z, ]+)\r?\n.*/is', '$1', $headers ); - $options = array_flip( preg_split( '/[, ]+/', $options_header )); - return $options; - } - - - - /** - * 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 ) { - $this->body = $xml; - $this->requestMethod = $request_method; - $this->SetContentType("text/xml"); - return $this->DoRequest($url); - } - - - - /** - * Get a single item from the server. - * - * @param string $url The URL to GET - */ - function DoGETRequest( $url ) { - $this->body = ""; - $this->requestMethod = "GET"; - return $this->DoRequest( $url ); - } - - - /** - * Get the HEAD of a single item from the server. - * - * @param string $url The URL to HEAD - */ - function DoHEADRequest( $url ) { - $this->body = ""; - $this->requestMethod = "HEAD"; - return $this->DoRequest( $url ); - } - - - /** - * 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 ) { - $this->body = $icalendar; - - $this->requestMethod = "PUT"; - if ( $etag != null ) { - $this->SetMatch( ($etag != '*'), $etag ); - } - $this->SetContentType('text/calendar; charset=utf-8'); - $this->DoRequest($url); - - $etag = null; - if ( preg_match( '{^ETag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders, $matches ) ) $etag = $matches[1]; - if ( !isset($etag) || $etag == '' ) { - if ( $this->debug ) printf( "No etag in:\n%s\n", $this->httpResponseHeaders ); - $save_request = $this->httpRequest; - $save_response_headers = $this->httpResponseHeaders; - $this->DoHEADRequest( $url ); - if ( preg_match( '{^Etag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders, $matches ) ) $etag = $matches[1]; - if ( !isset($etag) || $etag == '' ) { - if ( $this->debug ) printf( "Still No etag in:\n%s\n", $this->httpResponseHeaders ); - } - $this->httpRequest = $save_request; - $this->httpResponseHeaders = $save_response_headers; - } - return $etag; - } - - - /** - * 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 ) { - $this->body = ""; - - $this->requestMethod = "DELETE"; - if ( $etag != null ) { - $this->SetMatch( true, $etag ); - } - $this->DoRequest($url); - return $this->httpResponseCode; - } - - - /** - * 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' ) ); - $prop = new XMLElement('prop'); - foreach( $props AS $v ) { - $xml->NSElement($prop,$v); - } - - $this->body = $xml->Render('propfind',$prop ); - - $this->requestMethod = "PROPFIND"; - $this->SetContentType("text/xml"); - $this->DoRequest($url); - return $this->GetXmlResponse(); - } - - - /** - * Get/Set the Principal URL - * - * @param $url string The Principal URL to set - */ - function PrincipalURL( $url = null ) { - if ( isset($url) ) { - $this->principal_url = $url; - } - return $this->principal_url; - } - - - /** - * 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) ) $urls = array($urls); - $this->calendar_home_set = $urls; - } - return $this->calendar_home_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) ) $urls = array($urls); - $this->calendar_urls = $urls; - } - return $this->calendar_urls; - } - - - /** - * 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; - if ( $this->xmlnodes[$j]['tag'] == 'DAV::href' ) { - return rawurldecode($this->xmlnodes[$j]['value']); - } - } - return null; - } - - - /** - * 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]; - while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::href' ) { -// printf( "Node[$j]: %s\n", $this->xmlnodes[$j]['tag']); - if ( $this->xmlnodes[$j]['tag'] == 'DAV::status' && $this->xmlnodes[$j]['value'] != 'HTTP/1.1 200 OK' ) return null; - } -// printf( "Node[$j]: %s\n", $this->xmlnodes[$j]['tag']); - if ( $j > 0 && isset($this->xmlnodes[$j]['value']) ) { -// printf( "Value[$j]: %s\n", $this->xmlnodes[$j]['value']); - return rawurldecode($this->xmlnodes[$j]['value']); - } - } - else { - if ( $this->debug ) printf( "xmltags[$tagname] or xmltags[$tagname][$i] is not set\n"); - } - return null; - } - - - /** - * 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]; - while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::resourcetype' ); - if ( $j > 0 ) { - while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::href' ); - if ( $j > 0 && isset($this->xmlnodes[$j]['value']) ) { - return rawurldecode($this->xmlnodes[$j]['value']); - } - } - } - return null; - } - - - /** - * 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']; - $status = ''; - while ( $this->xmlnodes[++$nodenum]['level'] >= $level ) { - if ( $this->xmlnodes[$nodenum]['tag'] == 'DAV::propstat' ) { - if ( $this->xmlnodes[$nodenum]['type'] == 'open' ) { - $props = array(); - $status = ''; - } - else { - if ( $status == 'HTTP/1.1 200 OK' ) break; - } - } - elseif ( !isset($this->xmlnodes[$nodenum]) || !is_array($this->xmlnodes[$nodenum]) ) { - break; - } - elseif ( $this->xmlnodes[$nodenum]['tag'] == 'DAV::status' ) { - $status = $this->xmlnodes[$nodenum]['value']; - } - else { - $props[] = $this->xmlnodes[$nodenum]; - } - } - return $props; - } - - - /** - * 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( $this->base_url, array('resourcetype', 'current-user-principal', 'owner', 'principal-URL', - 'urn:ietf:params:xml:ns:caldav:calendar-home-set'), 1); - - $principal_url = $this->HrefForProp('DAV::principal'); - - if ( !isset($principal_url) ) { - foreach( array('DAV::current-user-principal', 'DAV::principal-URL', 'DAV::owner') AS $href ) { - if ( !isset($principal_url) ) { - $principal_url = $this->HrefValueInside($href); - } - } - } - - return $this->PrincipalURL($principal_url); - } - - - /** - * 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(); - } - if ( $recursed ) { - $this->DoPROPFINDRequest( $this->principal_url, array('urn:ietf:params:xml:ns:caldav:calendar-home-set'), 0); - } - - $calendar_home = array(); - foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-home-set'] AS $k => $v ) { - if ( $this->xmlnodes[$v]['type'] != 'open' ) continue; - while( $this->xmlnodes[++$v]['type'] != 'close' && $this->xmlnodes[$v]['tag'] != 'urn:ietf:params:xml:ns:caldav:calendar-home-set' ) { -// printf( "Tag: '%s' = '%s'\n", $this->xmlnodes[$v]['tag'], $this->xmlnodes[$v]['value']); - if ( $this->xmlnodes[$v]['tag'] == 'DAV::href' && isset($this->xmlnodes[$v]['value']) ) - $calendar_home[] = rawurldecode($this->xmlnodes[$v]['value']); - } - } - - if ( !$recursed && count($calendar_home) < 1 ) { - $calendar_home = $this->FindCalendarHome(true); - } - - return $this->CalendarHomeSet($calendar_home); - } - - - /** - * Find the calendars, from the calendar_home_set - */ - function FindCalendars( $recursed=false ) { - if ( !isset($this->calendar_home_set[0]) ) { - $this->FindCalendarHome(); - } - $this->DoPROPFINDRequest( $this->calendar_home_set[0], array('resourcetype','displayname','http://calendarserver.org/ns/:getctag'), 1); - - $calendars = array(); - if ( isset($this->xmltags['urn:ietf:params:xml:ns:caldav:calendar']) ) { - $calendar_urls = array(); - foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar'] AS $k => $v ) { - $calendar_urls[$this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar', $k)] = 1; - } - - foreach( $this->xmltags['DAV::href'] AS $i => $hnode ) { - $href = rawurldecode($this->xmlnodes[$hnode]['value']); - - if ( !isset($calendar_urls[$href]) ) continue; - -// printf("Seems '%s' is a calendar.\n", $href ); - - $calendar = new CalendarInfo($href); - $ok_props = $this->GetOKProps($hnode); - foreach( $ok_props AS $v ) { -// printf("Looking at: %s[%s]\n", $href, $v['tag'] ); - switch( $v['tag'] ) { - case 'http://calendarserver.org/ns/:getctag': - $calendar->getctag = $v['value']; - break; - case 'DAV::displayname': - $calendar->displayname = $v['value']; - break; - } - } - $calendar->id = rtrim(str_replace($this->calendar_home_set[0], "", $calendar->url), "/"); - $calendars[] = $calendar; - } - } - - return $this->CalendarUrls($calendars); - } - - - /** - * Find the calendars, from the calendar_home_set - */ - function GetCalendarDetails( $url = null ) { - if ( isset($url) ) $this->SetCalendar($url); - - if ( !isset($this->calendar_home_set[0]) ) { - $this->FindCalendarHome(); - } - - $calendar_properties = array( 'resourcetype', 'displayname', 'http://calendarserver.org/ns/:getctag', 'urn:ietf:params:xml:ns:caldav:calendar-timezone', 'supported-report-set' ); - $this->DoPROPFINDRequest( $this->calendar_url, $calendar_properties, 0); - - $hnode = $this->xmltags['DAV::href'][0]; - $href = rawurldecode($this->xmlnodes[$hnode]['value']); - - $calendar = new CalendarInfo($href); - $ok_props = $this->GetOKProps($hnode); - foreach( $ok_props AS $k => $v ) { - $name = preg_replace( '{^.*:}', '', $v['tag'] ); - if ( isset($v['value'] ) ) { - $calendar->{$name} = $v['value']; - } -/* else { - printf( "Calendar property '%s' has no text content\n", $v['tag'] ); - }*/ - } - $calendar->id = rtrim(str_replace($this->calendar_home_set[0], "", $calendar->url), "/"); - - return $calendar; - } - - - /** - * Get all etags for a calendar - */ - function GetCollectionETags( $url = null ) { - if ( isset($url) ) $this->SetCalendar($url); - - $this->DoPROPFINDRequest( $this->calendar_url, array('getetag'), 1); - - $etags = array(); - if ( isset($this->xmltags['DAV::getetag']) ) { - foreach( $this->xmltags['DAV::getetag'] AS $k => $v ) { - $href = $this->HrefForProp('DAV::getetag', $k); - if ( isset($href) && isset($this->xmlnodes[$v]['value']) ) $etags[$href] = $this->xmlnodes[$v]['value']; - } - } - - return $etags; - } - - - /** - * 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); - - $hrefs = ''; - foreach( $event_hrefs AS $k => $href ) { - $href = str_replace( rawurlencode('/'),'/',rawurlencode($href)); - $hrefs .= ''.$href.''; - } - $this->body = << - - -$hrefs - -EOXML; - - $this->requestMethod = "REPORT"; - $this->SetContentType("text/xml"); - $this->DoRequest( $this->calendar_url ); - - $events = array(); - if ( isset($this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-data']) ) { - foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-data'] AS $k => $v ) { - $href = $this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar-data', $k); -// echo "Calendar-data:\n"; print_r($this->xmlnodes[$v]); - $events[$href] = $this->xmlnodes[$v]['value']; - } - } - else { - foreach( $event_hrefs AS $k => $href ) { - $this->DoGETRequest($href); - $events[$href] = $this->httpResponseBody; - } - } - - return $events; - } - - - /** - * 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 '$response_type' - * part, 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 - * - * @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 = '' ) { - - if ( !empty($url) ) $this->SetCalendar($url); - - $this->body = << - - - - - $filter - -EOXML; - - $this->requestMethod = "REPORT"; - $this->SetContentType("text/xml"); - $this->DoRequest( $this->calendar_url ); - - $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; - } - break; - case 'DAV::href': - $response['href'] = basename( rawurldecode($v['value']) ); - break; - case 'DAV::getetag': - $response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']); - break; - case 'urn:ietf:params:xml:ns:caldav:calendar-data': - $response['data'] = $v['value']; - break; - } - } - return $report; - } - - - /** - * Get the 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 arrays. Each event array will have a 'href', 'etag' and 'event' - * part, where the 'href' is relative to the calendar and the event contains the - * definition of the event in iCalendar format. - * - * @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, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery() - */ - function GetEvents( $start = null, $finish = null, $relative_url = null ) { - $filter = ""; - if ( isset($start) && isset($finish) ) - $range = ""; - else - $range = ''; - - $filter = << - - - $range - - - -EOFILTER; - - return $this->DoCalendarQuery($filter, $relative_url); - } - - - /** - * Get the todo's 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 arrays. Each event array will have a 'href', 'etag' and 'event' - * part, where the 'href' is relative to the calendar and the event contains the - * definition of the event in iCalendar format. - * - * @param timestamp $start The start time for the period - * @param timestamp $finish The finish time for the period - * @param boolean $completed Whether to include completed tasks - * @param boolean $cancelled Whether to include cancelled tasks - * @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, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery() - */ - function GetTodos( $start, $finish, $completed = false, $cancelled = false, $relative_url = null ) { - - if ( $start && $finish ) { -$time_range = << -EOTIME; - } - else { - $time_range = ""; - } - - // Warning! May contain traces of double negatives... - $neg_cancelled = ( $cancelled === true ? "no" : "yes" ); - $neg_completed = ( $cancelled === true ? "no" : "yes" ); - - $filter = << - - - - COMPLETED - - - CANCELLED - $time_range - - - -EOFILTER; - - return $this->DoCalendarQuery($filter, $relative_url); - } - - - /** - * 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 ) { - $filter = << - - - - $uid - - - - -EOFILTER; - } - - return $this->DoCalendarQuery($filter, $relative_url); - } - - - /** - * 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 ); - } - -} - -/** -* Usage example -* -* $cal = new CalDAVClient( "http://calendar.example.com/caldav.php/username/calendar/", "username", "password", "calendar" ); -* $options = $cal->DoOptionsRequest(); -* if ( isset($options["PROPFIND"]) ) { -* // Fetch some information about the events in that calendar -* $cal->SetDepth(1); -* $folder_xml = $cal->DoXMLRequest("PROPFIND", '' ); -* } -* // Fetch all events for February -* $events = $cal->GetEvents("20070101T000000Z","20070201T000000Z"); -* foreach ( $events AS $k => $event ) { -* do_something_with_event_data( $event['data'] ); -* } -* $acc = array(); -* $acc["google"] = array( -* "user"=>"kunsttherapie@gmail.com", -* "pass"=>"xxxxx", -* "server"=>"ssl://www.google.com", -* "port"=>"443", -* "uri"=>"https://www.google.com/calendar/dav/kunsttherapie@gmail.com/events/", -* ); -* -* $acc["davical"] = array( -* "user"=>"some_user", -* "pass"=>"big secret", -* "server"=>"calendar.foo.bar", -* "port"=>"80", -* "uri"=>"http://calendar.foo.bar/caldav.php/some_user/home/", -* ); -* //******************************* -* -* $account = $acc["davical"]; -* -* //******************************* -* $cal = new CalDAVClient( $account["uri"], $account["user"], $account["pass"], "", $account["server"], $account["port"] ); -* $options = $cal->DoOptionsRequest(); -* print_r($options); -* -* //******************************* -* //******************************* -* -* $xmlC = << -* -* -* -* -* -* -* -* -* PROPP; -* //if ( isset($options["PROPFIND"]) ) { -* // Fetch some information about the events in that calendar -* // $cal->SetDepth(1); -* // $folder_xml = $cal->DoXMLRequest("PROPFIND", $xmlC); -* // print_r( $folder_xml); -* //} -* -* // Fetch all events for February -* $events = $cal->GetEvents("20090201T000000Z","20090301T000000Z"); -* foreach ( $events as $k => $event ) { -* print_r($event['data']); -* print "\n---------------------------------------------\n"; -* } -* -* //******************************* -* //******************************* -*/ diff --git a/sources/include/iCalendar.php b/sources/include/iCalendar.php index 3a66c5a..a84fbbc 100644 --- a/sources/include/iCalendar.php +++ b/sources/include/iCalendar.php @@ -119,7 +119,16 @@ class iCalProp { * @param string $propstring The string from the iCalendar which contains this property. */ function ParseFrom( $propstring ) { - $this->rendered = (strlen($propstring) < 72 ? $propstring : null); // Only pre-rendered if we didn't unescape it +// $this->rendered = (strlen($propstring) < 72 ? $propstring : null); // Only pre-rendered if we didn't unescape it + // FMBIETE - unset rendered content; if we alter some properties inside an object (VEVENT/ATTENDEE for example) we won't see the changes calling Render + // FIXME: if you find the bug, let me know + //$ical = new iCalComponent(); + //$ical->ParseFrom(VCALENDAR DATA); + // Doing this will refresh the rendered data, but if this line is not executed, you won't see PARTSTAT changed + //$ical->SetPValue("METHOD", "REPLY"); + //$ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", "ACCEPTED"); + //printf("%s\n", $ical->Render()); + unset($this->rendered); $unescaped = preg_replace( '{\\\\[nN]}', "\n", $propstring); @@ -227,7 +236,13 @@ class iCalProp { */ function SetParameterValue( $name, $value ) { if ( isset($this->rendered) ) unset($this->rendered); - $this->parameters[$name] = $value; + // Unset parameter + if ($value === null) { + unset($this->parameters[$name]); + } + else { + $this->parameters[$name] = $value; + } } /** @@ -553,7 +568,13 @@ class iCalComponent { for ( $i = 0; $i < count($this->properties); $i++ ) { if ( $this->properties[$i]->Name() == $type ) { if ( isset($this->rendered) ) unset($this->rendered); - $this->properties[$i]->Value($value); + // FMBIETE - unset property + if ($value == null) { + unset($this->properties[$i]); + } + else { + $this->properties[$i]->Value($value); + } } } } @@ -566,14 +587,22 @@ class iCalComponent { * @param string $property_name Type/Name of the property * @param string $parameter_name Type/Name of the parameter * @param string $value New value of the parameter + * @param string $condition_value Change the parameter_value only if the property_value is equals to condition_value */ - function SetCPParameterValue( $component_type, $property_name, $parameter_name, $value ) { + function SetCPParameterValue( $component_type, $property_name, $parameter_name, $value, $condition_value = null ) { for ( $j = 0; $j < count($this->components); $j++ ) { if ( $this->components[$j]->GetType() == $component_type ) { for ( $i = 0; $i < count($this->components[$j]->properties); $i++ ) { if ( $this->components[$j]->properties[$i]->Name() == $property_name ) { if ( isset($this->components[$j]->rendered) ) unset($this->components[$j]->rendered); - $this->components[$j]->properties[$i]->SetParameterValue($parameter_name, $value); + if ($condition_value === null) { + $this->components[$j]->properties[$i]->SetParameterValue($parameter_name, $value); + } + else { + if (strcasecmp($this->components[$j]->properties[$i]->Value(), $condition_value) == 0) { + $this->components[$j]->properties[$i]->SetParameterValue($parameter_name, $value); + } + } } } } diff --git a/sources/include/mimeDecode.php b/sources/include/mimeDecode.php index dd38e50..6b11cb2 100644 --- a/sources/include/mimeDecode.php +++ b/sources/include/mimeDecode.php @@ -52,7 +52,7 @@ * @author Sean Coates * @copyright 2003-2006 PEAR * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @version CVS: $Id: mimeDecode.php 305875 2010-12-01 07:17:10Z alan_k $ + * @version CVS: $Id: mimeDecode.php 335147 2014-10-27 08:41:39Z alan_k $ * @link http://pear.php.net/package/Mail_mime */ @@ -68,7 +68,6 @@ * * used "old" method of checking if called statically, as this is deprecated between php 5.0.0 and 5.3.0 * (isStatic of decode() around line 215) - * */ /** @@ -160,6 +159,7 @@ class Mail_mimeDecode */ var $_decode_headers; + /** * Flag to determine whether to include attached messages * as body in the returned object. Depends on $_include_bodies @@ -178,7 +178,7 @@ class Mail_mimeDecode * @param string The input to decode * @access public */ - function Mail_mimeDecode($input, $deprecated_linefeed = '') + function Mail_mimeDecode($input) { list($header, $body) = $this->_splitBodyHeader($input); @@ -202,6 +202,7 @@ class Mail_mimeDecode * decode_bodies - Whether to decode the bodies * of the parts. (Transfer encoding) * decode_headers - Whether to decode headers + * * input - If called statically, this will be treated * as the input * charset - convert all data to this charset @@ -211,7 +212,7 @@ class Mail_mimeDecode function decode($params = null) { // determine if this method has been called statically - $isStatic = !(isset($this) && get_class($this) == __CLASS__); + $isStatic = empty($this) || !is_a($this, __CLASS__); // Have we been called statically? // If so, create an object and pass details to that. @@ -237,6 +238,11 @@ class Mail_mimeDecode $this->_charset = isset($params['charset']) ? strtolower($params['charset']) : 'utf-8'; + + if (is_string($this->_decode_headers) && !function_exists('iconv')) { + $this->raiseError('header decode conversion requested, however iconv is missing'); + } + $structure = $this->_decode($this->_header, $this->_body); if ($structure === false) { $structure = $this->raiseError($this->_error); @@ -263,7 +269,7 @@ class Mail_mimeDecode $headers = $this->_parseHeaders($headers); foreach ($headers as $value) { - $value['value'] = $this->_decode_headers ? $this->_decodeHeader($value['value']) : $value['value']; + $value['value'] = $this->_decodeHeader($value['value']); if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) { $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]); $return->headers[strtolower($value['name'])][] = $value['value']; @@ -284,7 +290,12 @@ class Mail_mimeDecode case 'content-type': $content_type = $this->_parseHeaderValue($headers[$key]['value']); - if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) { + if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)\; name=\"([0-9a-z+.-]+)/i', $headers[$key]['value'], $regs)) { + $return->ctype_primary = $regs[1]; + $return->ctype_secondary = $regs[2]; + $return->filename = $regs[3]; + } + elseif (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) { $return->ctype_primary = $regs[1]; $return->ctype_secondary = $regs[2]; } @@ -317,22 +328,24 @@ class Mail_mimeDecode case 'text/plain': $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset; - $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body) : null; + $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset, true) : $body) : null; break; case 'text/html': $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset; - $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body) : null; + $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset, true) : $body) : null; break; + case 'multipart/signed': // PGP + case 'multipart/encrypted': // #190 encrypted parts will be treated as normal ones case 'multipart/parallel': case 'multipart/appledouble': // Appledouble mail case 'multipart/report': // RFC1892 - case 'multipart/signed': // PGP case 'multipart/digest': case 'multipart/alternative': case 'multipart/related': + case 'multipart/relative': //#20431 - android case 'multipart/mixed': case 'application/vnd.wap.multipart.related': if(!isset($content_type['other']['boundary'])){ @@ -353,6 +366,7 @@ class Mail_mimeDecode break; case 'message/rfc822': + case 'message/delivery-status': // #bug #18693 if ($this->_rfc822_bodies) { $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset; @@ -364,8 +378,29 @@ class Mail_mimeDecode 'decode_bodies' => $this->_decode_bodies, 'decode_headers' => $this->_decode_headers)); unset($obj); + + // #213, KD 2015-06-29 - Always inline them because there is no "type" to them (they're text) + $return->disposition = 'inline'; break; + // #190, KD 2015-06-09 - Add type for S/MIME Encrypted messages; these must have the filename set explicitly (it won't work otherwise) + //and then falls through for the rest on purpose. + case 'application/x-pkcs7-mime': + case 'application/pkcs7-mime': + if (!isset($content_transfer_encoding['value'])) { + $content_transfer_encoding['value'] = 'base64'; + } + // if there is no explicit charset, then don't try to convert to default charset, and make sure that only text mimetypes are converted + $charset = (isset($return->ctype_parameters['charset']) && ((isset($return->ctype_primary) && $return->ctype_primary == 'text') || !isset($return->ctype_primary)) ) ? $return->ctype_parameters['charset'] : ''; + $part->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value'], $charset, false) : $body); + $ctype = explode('/', strtolower($content_type['value'])); + $part->ctype_parameters['name'] = 'smime.p7m'; + $part->ctype_primary = $ctype[0]; + $part->ctype_secondary = $ctype[1]; + $part->d_parameters['size'] = strlen($part->body); + $return->parts[] = $part; + // Fall through intentionally + default: if(!isset($content_transfer_encoding['value'])) $content_transfer_encoding['value'] = '7bit'; @@ -460,7 +495,6 @@ class Mail_mimeDecode */ function _parseHeaders($input) { - if ($input !== '') { // Unfold the input $input = preg_replace("/\r?\n/", "\r\n", $input); @@ -471,12 +505,25 @@ class Mail_mimeDecode $input = preg_replace("/\r\n(\t| )+/", ' ', $input); $headers = explode("\r\n", trim($input)); - + $got_start = false; foreach ($headers as $value) { + if (!$got_start) { + // munge headers for mbox style from + if ($value[0] == '>') { + $value = substring($value, 1); // remove mbox > + } + if (substr($value,0,5) == 'From ') { + $value = 'Return-Path: ' . substr($value, 5); + } else { + $got_start = true; + } + } + $hdr_name = substr($value, 0, $pos = strpos($value, ':')); $hdr_value = substr($value, $pos+1); - if($hdr_value[0] == ' ') + if($hdr_value[0] == ' ') { $hdr_value = substr($hdr_value, 1); + } $return[] = array( 'name' => $hdr_name, @@ -497,15 +544,25 @@ class Mail_mimeDecode * robust as it could be. Eg. header comments * in the wrong place will probably break it. * + * Extra things this can handle + * filename*0=...... + * filename*1=...... + * + * This is where lines are broken in, and need merging. + * + * filename*0*=ENC'lang'urlencoded data. + * filename*1*=ENC'lang'urlencoded data. + * + * + * * @param string Header value to parse * @return array Contains parsed result * @access private */ function _parseHeaderValue($input) { - - if (($pos = strpos($input, ';')) === false) { - $input = $this->_decode_headers ? $this->_decodeHeader($input) : $input; + if (($pos = strpos($input, ';')) === false) { + $input = $this->_decodeHeader($input); $return['value'] = trim($input); return $return; } @@ -513,7 +570,7 @@ class Mail_mimeDecode $value = substr($input, 0, $pos); - $value = $this->_decode_headers ? $this->_decodeHeader($value) : $value; + $value = $this->_decodeHeader($value); $return['value'] = trim($value); $input = trim(substr($input, $pos+1)); @@ -557,7 +614,6 @@ class Mail_mimeDecode if ($key) { // a key without a value.. $key= trim($key); $return['other'][$key] = ''; - $return['other'][strtolower($key)] = ''; } $key = ''; } @@ -574,7 +630,11 @@ class Mail_mimeDecode $i++; continue; // skip leading spaces after '=' or after '"' } - if (!$escaped && ($c == '"' || $c == "'")) { + + // do not de-quote 'xxx*= itesm.. + $key_is_trans = $key[strlen($key)-1] == '*'; + + if (!$key_is_trans && !$escaped && ($c == '"' || $c == "'")) { // start quoted area.. $q = $c; // in theory should not happen raw text in value part.. @@ -586,25 +646,7 @@ class Mail_mimeDecode // got end.... if (!$escaped && $c == ';') { - $val = trim($val); - $added = false; - if (preg_match('/\*[0-9]+$/', $key)) { - // this is the extended aaa*0=...;aaa*1=.... code - // it assumes the pieces arrive in order, and are valid... - $key = preg_replace('/\*[0-9]+$/', '', $key); - if (isset($return['other'][$key])) { - $return['other'][$key] .= $val; - if (strtolower($key) != $key) { - $return['other'][strtolower($key)] .= $val; - } - $added = true; - } - // continue and use standard setters.. - } - if (!$added) { - $return['other'][$key] = $val; - $return['other'][strtolower($key)] = $val; - } + $return['other'][$key] = trim($val); $val = false; $key = ''; $lq = false; @@ -636,29 +678,58 @@ class Mail_mimeDecode if (strlen(trim($key)) || $val !== false) { $val = trim($val); - $added = false; - if ($val !== false && preg_match('/\*[0-9]+$/', $key)) { - // no dupes due to our crazy regexp. - $key = preg_replace('/\*[0-9]+$/', '', $key); - if (isset($return['other'][$key])) { - $return['other'][$key] .= $val; - if (strtolower($key) != $key) { - $return['other'][strtolower($key)] .= $val; - } - $added = true; - } - // continue and use standard setters.. - } - if (!$added) { - $return['other'][$key] = $val; - $return['other'][strtolower($key)] = $val; - } + + $return['other'][$key] = $val; } + + + $clean_others = array(); + // merge added values. eg. *1[*] + foreach($return['other'] as $key =>$val) { + if (preg_match('/\*[0-9]+\**$/', $key)) { + $key = preg_replace('/(.*)\*[0-9]+(\**)$/', '\1\2', $key); + if (isset($clean_others[$key])) { + $clean_others[$key] .= $val; + continue; + } + + } + $clean_others[$key] = $val; + + } + + // handle language translation of '*' ending others. + foreach( $clean_others as $key =>$val) { + if ( $key[strlen($key)-1] != '*') { + $clean_others[strtolower($key)] = $val; + continue; + } + unset($clean_others[$key]); + $key = substr($key,0,-1); + //extended-initial-value := [charset] "'" [language] "'" + // extended-other-values + $match = array(); + $info = preg_match("/^([^']+)'([^']*)'(.*)$/", $val, $match); + + $clean_others[$key] = urldecode($match[3]); + $clean_others[strtolower($key)] = $clean_others[$key]; + $clean_others[strtolower($key).'-charset'] = $match[1]; + $clean_others[strtolower($key).'-language'] = $match[2]; + + + } + + + $return['other'] = $clean_others; + // decode values. foreach($return['other'] as $key =>$val) { - $return['other'][$key] = $this->_decode_headers ? $this->_decodeHeader($val) : $val; + $charset = isset($return['other'][$key . '-charset']) ? + $return['other'][$key . '-charset'] : false; + + $return['other'][$key] = $this->_decodeHeader($val, $charset); } - //print_r($return); + return $return; } @@ -670,7 +741,7 @@ class Mail_mimeDecode * @return array Contains array of resulting mime parts * @access private */ - function _boundarySplit($input, $boundary) + function _boundarySplit($input, $boundary, $eatline = false) { $parts = array(); @@ -680,7 +751,10 @@ class Mail_mimeDecode if ($boundary == $bs_check) { $boundary = $bs_possible; } - $tmp = preg_split("/--".preg_quote($boundary, '/')."((?=\s)|--)/", $input); + // eatline is used by multipart/signed. + $tmp = $eatline ? + preg_split("/\r?\n--".preg_quote($boundary, '/')."(|--)\n/", $input) : + preg_split("/--".preg_quote($boundary, '/')."((?=\s)|--)/", $input); $len = count($tmp) -1; for ($i = 1; $i < $len; $i++) { @@ -708,19 +782,23 @@ class Mail_mimeDecode */ function _decodeHeader($input) { + if (!$this->_decode_headers) { + return $input; + } // Remove white space between encoded-words $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input); $encodedwords = false; $charset = ''; + // For each encoded-word... while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) { $encodedwords = true; - $encoded = $matches[1]; - $charset = $matches[2]; + $encoded = $matches[1]; + $charset = $matches[2]; $encoding = $matches[3]; - $text = $matches[4]; + $text = $matches[4]; switch (strtolower($encoding)) { case 'b': @@ -730,13 +808,13 @@ class Mail_mimeDecode case 'q': $text = str_replace('_', ' ', $text); preg_match_all('/=([a-f0-9]{2})/i', $text, $matches); - foreach($matches[1] as $value) { - $text = str_replace('='.$value, chr(hexdec($value)), $text); - } + foreach ($matches[1] as $value) + $text = str_replace('=' . $value, chr(hexdec($value)), $text); break; } - $input = str_replace($encoded, $this->_autoconvert_encoding($text, $charset), $input); + $text = $this->_autoconvert_encoding($text, $charset); + $input = str_replace($encoded, $text, $input); } if (!$encodedwords) { @@ -761,21 +839,20 @@ class Mail_mimeDecode { switch (strtolower($encoding)) { case 'quoted-printable': - $input_decoded = $this->_quotedPrintableDecode($input); - return $detectCharset ? $this->_autoconvert_encoding($input_decoded, $charset) : $input_decoded; + $input = $this->_quotedPrintableDecode($input); break; case 'base64': - $input_decoded = base64_decode($input); - return $detectCharset ? $this->_autoconvert_encoding($input_decoded, $charset) : $input_decoded; + $input = base64_decode($input); break; case '7bit': case '8bit': default: - return $detectCharset ? $this->_autoconvert_encoding($input, $charset) : $input; break; } + + return $detectCharset ? $this->_autoconvert_encoding($input, $charset) : $input; } /** @@ -805,17 +882,26 @@ class Mail_mimeDecode if (function_exists("mb_detect_order")) { $mb_order = array_merge(array($supposed_encoding), mb_detect_order()); set_error_handler('Mail_mimeDecode::_iconv_notice_handler'); + + // Default value in case of error + $detected_encoding = $supposed_encoding; + try { - $input_converted = iconv(mb_detect_encoding($input, $mb_order, true), $this->_charset, $input); + $detected_encoding = mb_detect_encoding($input, $mb_order, true); + // In some cases mb_detect_encoding returns an empty string + if ($detected_encoding === false || strlen($detected_encoding) == 0) { + $detected_encoding = $supposed_encoding; + } + $input_converted = iconv($detected_encoding, $this->_charset, $input); } catch(Exception $ex) { $this->raiseError($ex->getMessage()); } restore_error_handler(); - if (strlen($input_converted) == 0 && strlen($input) != 0) { - ZLog::Write(LOGLEVEL_INFO, "Mail_mimeDecode() - Text cannot be correctly decoded, using original text. Expect encoding errors"); - $input_converted = mb_convert_encoding($input, 'UTF-8', 'UTF-8'); + if ($input_converted === false || mb_strlen($input_converted, $this->_charset) !== mb_strlen($input, $detected_encoding)) { + ZLog::Write(LOGLEVEL_DEBUG, "Mail_mimeDecode()::_autoconvert_encoding(): Text cannot be correctly decoded, using original text. This will be ok if the part is not text, otherwise expect encoding errors"); + $input_converted = $input; } } @@ -836,7 +922,10 @@ class Mail_mimeDecode $input = preg_replace("/=\r?\n/", '', $input); // Replace encoded characters - $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input); + + $cb = create_function('$matches', ' return chr(hexdec($matches[0]));'); + + $input = preg_replace_callback( '/=([a-f0-9]{2})/i', $cb, $input); return $input; } @@ -923,14 +1012,21 @@ class Mail_mimeDecode * @param string $message mimedecode message(part) * @param string $message message subtype * @param string &$body body reference + * @param boolean $replace_nr replace \n\r with \n * * @return void * @access public */ - static function getBodyRecursive($message, $subtype, &$body) { + static function getBodyRecursive($message, $subtype, &$body, $replace_nr = false) { if(!isset($message->ctype_primary)) return; - if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body)) - $body .= $message->body; + if(strcasecmp($message->ctype_primary, "text") == 0 && strcasecmp($message->ctype_secondary, $subtype) == 0 && isset($message->body)) { + if ($replace_nr) { + $body .= str_replace("\n", "\r\n", str_replace("\r", "", $message->body)); + } + else { + $body .= $message->body; + } + } if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) { foreach($message->parts as $part) { @@ -938,7 +1034,7 @@ class Mail_mimeDecode // Content-Type: text/plain; charset=us-ascii; name="hareandtoroise.txt" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="hareandtoroise.txt" // We don't want to show that file text (outlook doesn't show it), so if we have content-disposition we don't apply recursivity if(!isset($part->disposition)) { - Mail_mimeDecode::getBodyRecursive($part, $subtype, $body); + Mail_mimeDecode::getBodyRecursive($part, $subtype, $body, $replace_nr); } } } @@ -1117,4 +1213,5 @@ class Mail_mimeDecode ZLog::Write(LOGLEVEL_ERROR, "mimeDecode error: ". $message); return false; } -} // End of class \ No newline at end of file + +} // End of class diff --git a/sources/include/stringstreamwrapper.php b/sources/include/stringstreamwrapper.php deleted file mode 100644 index 90b8ea4..0000000 --- a/sources/include/stringstreamwrapper.php +++ /dev/null @@ -1,144 +0,0 @@ -. -* -* Consult LICENSE file for details -************************************************/ - -class StringStreamWrapper { - const PROTOCOL = "stringstream"; - - private $stringstream; - private $position; - private $stringlength; - - /** - * Opens the stream - * The string to be streamed is passed over the context - * - * @param string $path Specifies the URL that was passed to the original function - * @param string $mode The mode used to open the file, as detailed for fopen() - * @param int $options Holds additional flags set by the streams API - * @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options, - * opened_path should be set to the full path of the file/resource that was actually opened. - * - * @access public - * @return boolean - */ - public function stream_open($path, $mode, $options, &$opened_path) { - $contextOptions = stream_context_get_options($this->context); - if (!isset($contextOptions[self::PROTOCOL]['string'])) - return false; - - $this->position = 0; - - // this is our stream! - $this->stringstream = $contextOptions[self::PROTOCOL]['string']; - - $this->stringlength = strlen($this->stringstream); - ZLog::Write(LOGLEVEL_DEBUG, sprintf("StringStreamWrapper::stream_open(): initialized stream length: %d", $this->stringlength)); - - return true; - } - - /** - * Reads from stream - * - * @param int $len amount of bytes to be read - * - * @access public - * @return string - */ - public function stream_read($len) { - $data = substr($this->stringstream, $this->position, $len); - $this->position += strlen($data); - return $data; - } - - /** - * Returns the current position on stream - * - * @access public - * @return int - */ - public function stream_tell() { - return $this->position; - } - - /** - * Indicates if 'end of file' is reached - * - * @access public - * @return boolean - */ - public function stream_eof() { - return ($this->position >= $this->stringlength); - } - - /** - * Retrieves information about a stream - * - * @access public - * @return array - */ - public function stream_stat() { - return array( - 7 => $this->stringlength, - 'size' => $this->stringlength, - ); - } - - /** - * Instantiates a StringStreamWrapper - * - * @param string $string The string to be wrapped - * - * @access public - * @return StringStreamWrapper - */ - static public function Open($string) { - $context = stream_context_create(array(self::PROTOCOL => array('string' => &$string))); - return fopen(self::PROTOCOL . "://",'r', false, $context); - } -} - -stream_wrapper_register(StringStreamWrapper::PROTOCOL, "StringStreamWrapper") - -?> \ No newline at end of file diff --git a/sources/include/z_RFC822.php b/sources/include/z_RFC822.php index 52c9c60..739897f 100644 --- a/sources/include/z_RFC822.php +++ b/sources/include/z_RFC822.php @@ -2,7 +2,7 @@ /** * RFC 822 Email address list validation Utility * - * PHP versions 4 and 5 + * PHP version 5 * * LICENSE: * @@ -63,10 +63,11 @@ * * @author Richard Heyes * @author Chuck Hagenbuch - * @version $Revision: 294749 $ + * @version $Revision$ * @license BSD * @package Mail */ + class Mail_RFC822 { /** @@ -120,7 +121,6 @@ class Mail_RFC822 { /** * The number of groups that have been found in the address list. * @var integer $num_groups - * @access public */ var $num_groups = 0; @@ -141,7 +141,6 @@ class Mail_RFC822 { * Sets up the object. The address must either be set here or when * calling parseAddressList(). One or the other. * - * @access public * @param string $address The address(es) to validate. * @param string $default_domain Default domain/host etc. If not supplied, will be set to localhost. * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing. @@ -149,7 +148,7 @@ class Mail_RFC822 { * * @return object Mail_RFC822 A new Mail_RFC822 object. */ - function Mail_RFC822($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) + public function __construct($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) { if (isset($address)) $this->address = $address; if (isset($default_domain)) $this->default_domain = $default_domain; @@ -162,7 +161,6 @@ class Mail_RFC822 { * Starts the whole process. The address must either be set here * or when creating the object. One or the other. * - * @access public * @param string $address The address(es) to validate. * @param string $default_domain Default domain/host etc. * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing. @@ -170,7 +168,7 @@ class Mail_RFC822 { * * @return array A structured array of addresses. */ - function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) + public function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) { if (!isset($this) || !isset($this->mailRFC822)) { $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit); @@ -178,6 +176,7 @@ class Mail_RFC822 { } if (isset($address)) $this->address = $address; + // z-push addition if (strlen(trim($this->address)) == 0) return array(); if (isset($default_domain)) $this->default_domain = $default_domain; if (isset($nest_groups)) $this->nestGroups = $nest_groups; @@ -223,11 +222,10 @@ class Mail_RFC822 { /** * Splits an address into separate addresses. * - * @access private * @param string $address The addresses to split. * @return boolean Success or failure. */ - function _splitAddresses($address) + protected function _splitAddresses($address) { if (!empty($this->limit) && count($this->addresses) == $this->limit) { return ''; @@ -299,11 +297,10 @@ class Mail_RFC822 { /** * Checks for a group at the start of the string. * - * @access private * @param string $address The address to check. * @return boolean Whether or not there is a group at the start of the string. */ - function _isGroup($address) + protected function _isGroup($address) { // First comma not in quotes, angles or escaped: $parts = explode(',', $address); @@ -323,12 +320,11 @@ class Mail_RFC822 { /** * A common function that will check an exploded string. * - * @access private * @param array $parts The exloded string. * @param string $char The char that was exploded on. * @return mixed False if the string contains unclosed quotes/brackets, or the string on success. */ - function _splitCheck($parts, $char) + protected function _splitCheck($parts, $char) { $string = $parts[0]; @@ -356,12 +352,11 @@ class Mail_RFC822 { /** * Checks if a string has unclosed quotes or not. * - * @access private * @param string $string The string to check. * @return boolean True if there are unclosed quotes inside the string, * false otherwise. */ - function _hasUnclosedQuotes($string) + protected function _hasUnclosedQuotes($string) { $string = trim($string); $iMax = strlen($string); @@ -393,12 +388,11 @@ class Mail_RFC822 { * Checks if a string has an unclosed brackets or not. IMPORTANT: * This function handles both angle brackets and square brackets; * - * @access private * @param string $string The string to check. * @param string $chars The characters to check for. * @return boolean True if there are unclosed brackets inside the string, false otherwise. */ - function _hasUnclosedBrackets($string, $chars) + protected function _hasUnclosedBrackets($string, $chars) { $num_angle_start = substr_count($string, $chars[0]); $num_angle_end = substr_count($string, $chars[1]); @@ -417,13 +411,12 @@ class Mail_RFC822 { /** * Sub function that is used only by hasUnclosedBrackets(). * - * @access private * @param string $string The string to check. * @param integer &$num The number of occurences. * @param string $char The character to count. * @return integer The number of occurences of $char in $string, adjusted for backslashes. */ - function _hasUnclosedBracketsSub($string, &$num, $char) + protected function _hasUnclosedBracketsSub($string, &$num, $char) { $parts = explode($char, $string); for ($i = 0; $i < count($parts); $i++){ @@ -439,11 +432,10 @@ class Mail_RFC822 { /** * Function to begin checking the address. * - * @access private * @param string $address The address to validate. * @return mixed False on failure, or a structured array of address information on success. */ - function _validateAddress($address) + protected function _validateAddress($address) { $is_group = false; $addresses = array(); @@ -484,14 +476,6 @@ class Mail_RFC822 { $addresses[] = $address['address']; } - // Check that $addresses is set, if address like this: - // Groupname:; - // Then errors were appearing. - if (!count($addresses)){ - $this->error = 'Empty group.'; - return false; - } - // Trim the whitespace from all of the address strings. array_map('trim', $addresses); @@ -532,11 +516,10 @@ class Mail_RFC822 { /** * Function to validate a phrase. * - * @access private * @param string $phrase The phrase to check. * @return boolean Success or failure. */ - function _validatePhrase($phrase) + protected function _validatePhrase($phrase) { // Splits on one or more Tab or space. $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY); @@ -573,11 +556,10 @@ class Mail_RFC822 { * can split a list of addresses up before encoding personal names * (umlauts, etc.), for example. * - * @access private * @param string $atom The string to check. * @return boolean Success or failure. */ - function _validateAtom($atom) + protected function _validateAtom($atom) { if (!$this->validate) { // Validation has been turned off; assume the atom is okay. @@ -606,11 +588,10 @@ class Mail_RFC822 { * Function to validate quoted string, which is: * quoted-string = <"> *(qtext/quoted-pair) <"> * - * @access private * @param string $qstring The string to check * @return boolean Success or failure. */ - function _validateQuotedString($qstring) + protected function _validateQuotedString($qstring) { // Leading and trailing " $qstring = substr($qstring, 1, -1); @@ -624,11 +605,10 @@ class Mail_RFC822 { * mailbox = addr-spec ; simple address * / phrase route-addr ; name and route-addr * - * @access public * @param string &$mailbox The string to check. * @return boolean Success or failure. */ - function validateMailbox(&$mailbox) + public function validateMailbox(&$mailbox) { // A couple of defaults. $phrase = ''; @@ -718,11 +698,10 @@ class Mail_RFC822 { * Angle brackets have already been removed at the point of * getting to this function. * - * @access private * @param string $route_addr The string to check. * @return mixed False on failure, or an array containing validated address/route information on success. */ - function _validateRouteAddr($route_addr) + protected function _validateRouteAddr($route_addr) { // Check for colon. if (strpos($route_addr, ':') !== false) { @@ -768,11 +747,10 @@ class Mail_RFC822 { * Function to validate a route, which is: * route = 1#("@" domain) ":" * - * @access private * @param string $route The string to check. * @return mixed False on failure, or the validated $route on success. */ - function _validateRoute($route) + protected function _validateRoute($route) { // Split on comma. $domains = explode(',', trim($route)); @@ -791,11 +769,10 @@ class Mail_RFC822 { * * domain = sub-domain *("." sub-domain) * - * @access private * @param string $domain The string to check. * @return mixed False on failure, or the validated domain on success. */ - function _validateDomain($domain) + protected function _validateDomain($domain) { // Note the different use of $subdomains and $sub_domains $subdomains = explode('.', $domain); @@ -819,11 +796,10 @@ class Mail_RFC822 { * Function to validate a subdomain: * subdomain = domain-ref / domain-literal * - * @access private * @param string $subdomain The string to check. * @return boolean Success or failure. */ - function _validateSubdomain($subdomain) + protected function _validateSubdomain($subdomain) { if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){ if (!$this->_validateDliteral($arr[1])) return false; @@ -839,11 +815,10 @@ class Mail_RFC822 { * Function to validate a domain literal: * domain-literal = "[" *(dtext / quoted-pair) "]" * - * @access private * @param string $dliteral The string to check. * @return boolean Success or failure. */ - function _validateDliteral($dliteral) + protected function _validateDliteral($dliteral) { return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\'; } @@ -853,11 +828,10 @@ class Mail_RFC822 { * * addr-spec = local-part "@" domain * - * @access private * @param string $addr_spec The string to check. * @return mixed False on failure, or the validated addr-spec on success. */ - function _validateAddrSpec($addr_spec) + protected function _validateAddrSpec($addr_spec) { $addr_spec = trim($addr_spec); @@ -884,17 +858,16 @@ class Mail_RFC822 { * Function to validate the local part of an address: * local-part = word *("." word) * - * @access private * @param string $local_part * @return mixed False on failure, or the validated local part on success. */ - function _validateLocalPart($local_part) + protected function _validateLocalPart($local_part) { $parts = explode('.', $local_part); $words = array(); // Split the local_part into words. - while (count($parts) > 0){ + while (count($parts) > 0) { $words[] = $this->_splitCheck($parts, '.'); for ($i = 0; $i < $this->index + 1; $i++) { array_shift($parts); @@ -903,6 +876,10 @@ class Mail_RFC822 { // Validate each word. foreach ($words as $word) { + // word cannot be empty (#17317) + if ($word === '') { + return false; + } // If this word contains an unquoted space, it is invalid. (6.2.4) if (strpos($word, ' ') && $word[0] !== '"') { diff --git a/sources/include/z_RTF.php b/sources/include/z_RTF.php index 5f5f818..9330717 100644 --- a/sources/include/z_RTF.php +++ b/sources/include/z_RTF.php @@ -31,14 +31,14 @@ It was modified by me (Andreas Brodowski) to allow compressed RTF being uncompressed by code I ported from Java to PHP and adapted according the needs of Z-Push. - + Currently it is being used to detect empty RTF Streams from Nokia Phones in MfE Clients - - It needs to be used by other backend writers that needs to have notes in calendar, appointment or tasks + + It needs to be used by other backend writers that needs to have notes in calendar, appointment or tasks objects to be written to their databases since devices send them usually in RTF Format... With Zarafa you can write them directly to DB and Zarafa is doing the conversion job. Other Groupware systems usually don't have this possibility... - + */ class rtf { @@ -77,7 +77,7 @@ class rtf { 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF, 0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D, ); - + var $rtf; // rtf core stream var $rtf_len; // length in characters of the stream (get performace due avoiding calling strlen everytime) var $err = array(); // array of error message, no entities on no error @@ -157,14 +157,14 @@ class rtf { "strikethru" => "strike", ); - + function rtf() { $this->rtf_len = 0; $this->rtf = ''; $this->out = ''; } - + // loadrtf - load the raw rtf data to be converted by this class // data = the raw rtf function loadrtf($data) { @@ -177,7 +177,7 @@ class rtf { }; return true; } - + function output($typ) { switch($typ) { case "ascii": $this->wantASCII = true; break; @@ -194,16 +194,16 @@ class rtf { $in = 16; if ($header['cSize'] != strlen($src)-4) { debugLog("Stream too short"); - return false; + return false; } - + if ($header['crc32'] != $this->LZRTFCalcCRC32($src,16,(($header['cSize']+4))-16)) { debugLog("CRC MISMATCH"); return false; } - + if ($header['magic'] == 0x414c454d) { // uncompressed RTF - return as is. - $dest = substr($src,$in,$header['uSize']); + $dest = substr($src,$in,$header['uSize']); } else if ($header['magic'] == 0x75465a4c) { // compressed RTF - uncompress. $dst = $this->LZRTF_HDR_DATA; $out = $this->LZRTF_HDR_LEN; @@ -233,7 +233,7 @@ class rtf { debugLog("Unknown Magic"); return false; } - + return $dest; } @@ -241,14 +241,14 @@ class rtf { // buf = the whole rtf data part // off = start point of crc calculation // len = length of data to calculate CRC for - // function is necessary since in RTF there is no XOR 0xffffffff being done (said to be 0x00 unsafe CRC32 calculation + // function is necessary since in RTF there is no XOR 0xffffffff being done (said to be 0x00 unsafe CRC32 calculation function LZRTFCalcCRC32($buf, $off, $len) { $c=0; $end = $off + $len; for($i=$off;$i < $end;$i++) { $c=$this->CRC32_TABLE[($c ^ ord($buf{$i})) & 0xFF] ^ (($c >> 8) & 0x00ffffff); } - return $c; + return $c; } function parserInit() { /* Default values according to the specs */ @@ -257,9 +257,9 @@ class rtf { "beginparagraph" => true, ); } - + function parseControl($control, $parameter) { - switch ($control) { + switch ($control) { case "fonttbl": // font table definition start $this->flags["fonttbl"] = true; // signal fonttable control words they are allowed to behave as expected break; @@ -276,14 +276,14 @@ class rtf { case "fs": // sets the current fontsize; is used by stylesheets (which are therefore generated on the fly $this->flags["fontsize"] = $parameter; break; - + case "qc": // handle center alignment $this->flags["alignment"] = "center"; break; case "qr": // handle right alignment $this->flags["alignment"] = "right"; break; - + case "pard": // reset paragraph settings (only alignment) $this->flags["alignment"] = ""; break; @@ -361,7 +361,7 @@ class rtf { /* Dispatch the control word to the output stream */ - + function flushControl() { if(preg_match("/^([A-Za-z]+)(-?[0-9]*) ?$/", $this->cword, $match)) { $this->parseControl($match[1], $match[2]); @@ -377,7 +377,7 @@ class rtf { /* If output stream supports comments, dispatch it */ - + function flushComment($comment) { if($this->wantXML || $this->wantHTML) { $this->out.=""; @@ -391,7 +391,7 @@ class rtf { function flushGroup($state) { if($state == "open") { /* push onto the stack */ array_push($this->stack, $this->flags); - + if($this->wantXML) $this->out.=""; } @@ -432,7 +432,7 @@ class rtf { } } } - + /* flush text in queue */ @@ -445,7 +445,7 @@ class rtf { $this->flags["fonttbl_want_fcharset"] = ""; $this->queue = ""; } - + // output logic if (strlen($this->queue)) { /* @@ -474,7 +474,7 @@ class rtf { } $this->out .= "\">"; } - + /* define new style for that span */ $this->styles["f".$this->flags["fonttbl_current_read"]."s".$this->flags["fontsize"]] = "font-family:".$this->fonttable[$this->flags["fonttbl_current_read"]]["charset"]." font-size:".$this->flags["fontsize"].";"; /* write span start */ @@ -498,10 +498,10 @@ class rtf { /* handle special charactes like \'ef */ - + function flushSpecial($special) { if(strlen($special) == 2) { - if($this->wantASCII) + if($this->wantASCII) $this->out .= chr(hexdec('0x'.$special)); else if($this->wantXML) $this->out .= ""; @@ -591,7 +591,7 @@ class rtf { case "{": if($this->cw) { $this->flushControl(); - $this->cw = false; + $this->cw = false; $this->cfirst = false; } else $this->flushQueue(); @@ -601,15 +601,15 @@ class rtf { case "}": if($this->cw) { $this->flushControl(); - $this->cw = false; + $this->cw = false; $this->cfirst = false; } else $this->flushQueue(); - + $this->flushGroup("close"); break; - case "\\": - if($this->cfirst) { // catches '\\' + case "\\": + if($this->cfirst) { // catches '\\' $this->queue .= "\\"; // replaced single quotes $this->cfirst = false; $this->cw = false; @@ -644,10 +644,10 @@ class rtf { $this->flushSpecial($this->rtf[$i+1].$this->rtf[$i+2]); $i+=2; $specialmatch = true; - $this->cw = false; - $this->cfirst = false; + $this->cw = false; + $this->cfirst = false; $this->cword = ""; - } else + } else if(preg_match("/^[{}\*]$/", $this->rtf[$i])) { $this->flushComment("control symbols not yet handled"); $specialmatch = true; @@ -686,10 +686,10 @@ class rtf { $this->queue .= $this->rtf[$i]; break; } - } else + } else $this->queue .= $this->rtf[$i]; } - + } $i++; } @@ -701,7 +701,4 @@ class rtf { $this->makeStyles(); } } -} - - -?> +} \ No newline at end of file diff --git a/sources/include/z_caldav.php b/sources/include/z_caldav.php index 204e573..234da81 100644 --- a/sources/include/z_caldav.php +++ b/sources/include/z_caldav.php @@ -8,7 +8,7 @@ * caldav-client-v2.php by xbgmsharp . * * Copyright Andrew McMillan (original caldav-client-v2.php), Jean-Louis Dupond (cURL code), xbgmsharp (bugfixes) -* Copyright Thorsten Köster +* Copyright Thorsten Köster * License GNU LGPL version 3 or later (http://www.gnu.org/licenses/lgpl-3.0.txt) */ @@ -69,6 +69,11 @@ class CalDAVClient { */ protected $calendar_urls; + /** + * Construct URL + */ + protected $url; + /** * The useragent which is send to the caldav server * @@ -89,8 +94,7 @@ class CalDAVClient { * * @var resource */ - private $curl; - + private $curl = false; private $synctoken = array(); @@ -102,25 +106,61 @@ class CalDAVClient { * @param string $pass The password for that user */ function __construct( $caldav_url, $user, $pass ) { + $this->url = $caldav_url; $this->user = $user; $this->pass = $pass; $this->auth = $user . ':' . $pass; $this->headers = array(); $parsed_url = parse_url($caldav_url); - if ($parsed_url == FALSE) { - ZLog::Write(LOGLEVEL_ERROR, sprintf('Couldn\'t parse URL: %s', $caldav_url)); - } else - $this->server = $parsed_url['scheme'] . '://' . $parsed_url['host'] . ':' . $parsed_url['port']; - $this->base_url = $parsed_url['path']; -// $this->base_url .= !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; -// $this->base_url .= !empty($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + if ($parsed_url === false) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendCalDAV->caldav_backend(): Couldn't parse URL: %s", $caldav_url)); + return; + } - if (substr($this->base_url, -1, 1) !== '/') { - $this->base_url = $this->base_url . '/'; - } + $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'] : ''; + + 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'); + + switch ($this->httpResponseCode) { + case 200: + case 207: + case 401: + $status = true; + break; + default: + $status = false; + } + + return $status; + } + + /** + * 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 @@ -178,7 +218,7 @@ class CalDAVClient { public function curl_init() { - if (empty($this->curl)) { + if ($this->curl === false) { $this->curl = curl_init(); curl_setopt($this->curl, CURLOPT_HEADER, true); curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, false); @@ -210,6 +250,7 @@ class CalDAVClient { curl_setopt($this->curl, CURLOPT_URL, $url); curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, 30); // 30 seconds it's already too big if ($content !== null) { @@ -231,9 +272,9 @@ class CalDAVClient { $this->xmlResponse = ''; - //ZLog::Write(LOGLEVEL_DEBUG, sprintf("Request:\n%s\n", $content)); +// ZLog::Write(LOGLEVEL_DEBUG, sprintf("Request:\n%s\n", $content)); $response = curl_exec($this->curl); - //ZLog::Write(LOGLEVEL_DEBUG, sprintf("Reponse:\n%s\n", $response)); +// ZLog::Write(LOGLEVEL_DEBUG, sprintf("Reponse:\n%s\n", $response)); $header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE); $this->httpResponseCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); $this->httpResponseHeaders = trim(substr($response, 0, $header_size)); @@ -253,7 +294,7 @@ class CalDAVClient { * @return array The allowed options */ function DoOptionsRequest( $url = null ) { - $headers = $this->DoRequest($url, "OPTIONS"); + $headers = $this->DoRequest($url === null ? $this->url : $url, "OPTIONS"); $options_header = preg_replace( '/^.*Allow: ([a-z, ]+)\r?\n.*/is', '$1', $headers ); $options = array_flip( preg_split( '/[, ]+/', $options_header )); return $options; @@ -315,7 +356,6 @@ class CalDAVClient { } if ( !isset($etag) || $etag == '' ) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("No etag in:\n%s\n", $this->httpResponseHeaders)); - $save_request = $this->httpRequest; $save_response_headers = $this->httpResponseHeaders; $this->DoHEADRequest( $url ); if ( preg_match( '{^Etag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders, $matches ) ) { @@ -324,7 +364,6 @@ class CalDAVClient { if ( !isset($etag) || $etag == '' ) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Still No etag in:\n%s\n", $this->httpResponseHeaders)); } - $this->httpRequest = $save_request; $this->httpResponseHeaders = $save_response_headers; } return $etag; @@ -904,8 +943,9 @@ EOFILTER; $this->SetCalendar($relative_url); } + $hasToken = !$initial && isset($this->synctoken[$this->calendar_url]); if ($support_dav_sync) { - $token = ($initial ? "" : $this->synctoken[$this->calendar_url]); + $token = ($hasToken ? $this->synctoken[$this->calendar_url] : ""); $body = << @@ -938,12 +978,13 @@ EOXML; $this->DoRequest($this->calendar_url, "REPORT", $body, "text/xml"); $report = array(); - foreach( $this->xmlnodes as $k => $v ) { - switch( $v['tag'] ) { + foreach ($this->xmlnodes as $k => $v) { + switch ($v['tag']) { case 'DAV::response': - if ( $v['type'] == 'open' ) { + if ($v['type'] == 'open') { $response = array(); - } elseif ( $v['type'] == 'close' ) { + } + elseif ($v['type'] == 'close') { $report[] = $response; } break; @@ -966,7 +1007,13 @@ EOXML; 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?'); + } + return $report; } -} +} \ No newline at end of file diff --git a/sources/include/z_carddav.php b/sources/include/z_carddav.php index 5858f46..a61bea5 100644 --- a/sources/include/z_carddav.php +++ b/sources/include/z_carddav.php @@ -160,7 +160,7 @@ class carddav_backend * * @var resource */ - private $curl; + private $curl = false; /** * Debug on or off @@ -546,7 +546,6 @@ EOFXMLGETXMLVCARD; switch($result['http_code']) { case 200: case 207: - case 401: $status = true; break; } @@ -771,7 +770,7 @@ EOFXSL; * @return void */ public function curl_init() { - if (empty($this->curl)) { + if ($this->curl === false) { $this->curl = curl_init(); curl_setopt($this->curl, CURLOPT_HEADER, true); curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, false); @@ -885,8 +884,17 @@ EOFXSL; * @return void */ public function __destruct() { - if (!empty($this->curl)) { + $this->disconnect(); + } + + /** + * Disconnect curl connection + * + */ + public function disconnect() { + if ($this->curl !== false) { curl_close($this->curl); + $this->curl = false; } } -} +} \ No newline at end of file diff --git a/sources/index.php b/sources/index.php index 345c808..0df69eb 100644 --- a/sources/index.php +++ b/sources/index.php @@ -43,85 +43,29 @@ * Consult LICENSE file for details ************************************************/ -ob_start(null, 1048576); + +// #190, KD 2015-06-08 - We are missing the flags to truncate the buffer in PHP >= 5.4 +if (version_compare(phpversion(), '5.4.0') < 0) { + ob_start(null, 1048576); +} +else { + ob_start(null, 1048576, PHP_OUTPUT_HANDLER_STDFLAGS); +} // ignore user abortions because this can lead to weird errors - see ZP-239 ignore_user_abort(true); -include_once('lib/exceptions/exceptions.php'); -include_once('lib/utils/utils.php'); -include_once('lib/utils/compat.php'); -include_once('lib/utils/timezoneutil.php'); -include_once('lib/core/zpushdefs.php'); -include_once('lib/core/stateobject.php'); -include_once('lib/core/interprocessdata.php'); -include_once('lib/core/pingtracking.php'); -include_once('lib/core/topcollector.php'); -include_once('lib/core/loopdetection.php'); -include_once('lib/core/asdevice.php'); -include_once('lib/core/statemanager.php'); -include_once('lib/core/devicemanager.php'); -include_once('lib/core/zpush.php'); -include_once('lib/core/zlog.php'); -include_once('lib/core/paddingfilter.php'); -include_once('lib/interface/ibackend.php'); -include_once('lib/interface/ichanges.php'); -include_once('lib/interface/iexportchanges.php'); -include_once('lib/interface/iimportchanges.php'); -include_once('lib/interface/isearchprovider.php'); -include_once('lib/interface/istatemachine.php'); -include_once('lib/core/streamer.php'); -include_once('lib/core/streamimporter.php'); -include_once('lib/core/synccollections.php'); -include_once('lib/core/hierarchycache.php'); -include_once('lib/core/changesmemorywrapper.php'); -include_once('lib/core/syncparameters.php'); -include_once('lib/core/bodypreference.php'); -include_once('lib/core/contentparameters.php'); -include_once('lib/wbxml/wbxmldefs.php'); -include_once('lib/wbxml/wbxmldecoder.php'); -include_once('lib/wbxml/wbxmlencoder.php'); -include_once('lib/syncobjects/syncobject.php'); -include_once('lib/syncobjects/syncbasebody.php'); -include_once('lib/syncobjects/syncbaseattachment.php'); -include_once('lib/syncobjects/syncmailflags.php'); -include_once('lib/syncobjects/syncrecurrence.php'); -include_once('lib/syncobjects/syncappointment.php'); -include_once('lib/syncobjects/syncappointmentexception.php'); -include_once('lib/syncobjects/syncattachment.php'); -include_once('lib/syncobjects/syncattendee.php'); -include_once('lib/syncobjects/syncmeetingrequestrecurrence.php'); -include_once('lib/syncobjects/syncmeetingrequest.php'); -include_once('lib/syncobjects/syncmail.php'); -include_once('lib/syncobjects/syncnote.php'); -include_once('lib/syncobjects/synccontact.php'); -include_once('lib/syncobjects/syncfolder.php'); -include_once('lib/syncobjects/syncprovisioning.php'); -include_once('lib/syncobjects/synctaskrecurrence.php'); -include_once('lib/syncobjects/synctask.php'); -include_once('lib/syncobjects/syncoofmessage.php'); -include_once('lib/syncobjects/syncoof.php'); -include_once('lib/syncobjects/syncuserinformation.php'); -include_once('lib/syncobjects/syncdeviceinformation.php'); -include_once('lib/syncobjects/syncdevicepassword.php'); -include_once('lib/syncobjects/syncitemoperationsattachment.php'); -include_once('lib/syncobjects/syncsendmail.php'); -include_once('lib/syncobjects/syncsendmailsource.php'); -include_once('lib/syncobjects/syncvalidatecert.php'); -include_once('lib/syncobjects/syncresolverecipients.php'); -include_once('lib/syncobjects/syncresolverecipient.php'); -include_once('lib/syncobjects/syncresolverecipientsoptions.php'); -include_once('lib/syncobjects/syncresolverecipientsavailability.php'); -include_once('lib/syncobjects/syncresolverecipientscertificates.php'); -include_once('lib/syncobjects/syncresolverecipientspicture.php'); -include_once('lib/default/backend.php'); -include_once('lib/default/searchprovider.php'); -include_once('lib/request/request.php'); -include_once('lib/request/requestprocessor.php'); - -include_once('config.php'); -include_once('version.php'); +require_once 'vendor/autoload.php'; +require_once 'config.php'; +if (defined('LOG_MEMORY_PROFILER') && LOG_MEMORY_PROFILER) { + if (function_exists('memprof_enable')) { + memprof_enable(); + } + else { + ZLog::Write(LOGLEVEL_WARN, "Memory profiler is enabled but the php-pecl-memprof extension was not found. Install and enable it"); + } +} // Attempt to set maximum execution time ini_set('max_execution_time', SCRIPT_TIMEOUT); @@ -206,43 +150,24 @@ include_once('version.php'); } RequestProcessor::Initialize(); - if(!RequestProcessor::HandleRequest()) - throw new WBXMLException(ZLog::GetWBXMLDebugInfo()); + RequestProcessor::HandleRequest(); // eventually the RequestProcessor wants to send other headers to the mobile foreach (RequestProcessor::GetSpecialHeaders() as $header) header($header); - // stream the data - $len = ob_get_length(); - $data = ob_get_contents(); - ob_end_clean(); - // log amount of data transferred // TODO check $len when streaming more data (e.g. Attachments), as the data will be send chunked - ZPush::GetDeviceManager()->SentData($len); - - // Unfortunately, even though Z-Push can stream the data to the client - // with a chunked encoding, using chunked encoding breaks the progress bar - // on the PDA. So the data is de-chunk here, written a content-length header and - // data send as a 'normal' packet. If the output packet exceeds 1MB (see ob_start) - // then it will be sent as a chunked packet anyway because PHP will have to flush - // the buffer. - if(!headers_sent()) - header("Content-Length: $len"); - - // send vnd.ms-sync.wbxml content type header if there is no content - // otherwise text/html content type is added which might break some devices - if ($len == 0) - header("Content-Type: application/vnd.ms-sync.wbxml"); - - print $data; - - // destruct backend after all data is on the stream - $backend->Logoff(); + ZPush::GetDeviceManager()->SentData(ob_get_length()); } catch (NoPostRequestException $nopostex) { + $len = ob_get_length(); + if ($len) { + ZLog::Write(LOGLEVEL_WARN, sprintf("Cleaning %d octets of data", $len)); + ob_clean(); + } + if ($nopostex->getCode() == NoPostRequestException::OPTIONS_REQUEST) { header(ZPush::GetServerHeader()); header(ZPush::GetSupportedProtocolVersions()); @@ -258,6 +183,12 @@ include_once('version.php'); } catch (Exception $ex) { + $len = ob_get_length(); + if ($len) { + ZLog::Write(LOGLEVEL_WARN, sprintf("Cleaning %d octets of data", $len)); + ob_clean(); + } + if (Request::GetUserAgent()) ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent())); $exclass = get_class($ex); @@ -285,7 +216,7 @@ include_once('version.php'); // This could be a WBXML problem.. try to get the complete request else if ($ex instanceof WBXMLException) { - ZLog::Write(LOGLEVEL_FATAL, "Request could not be processed correctly due to a WBXMLException. Please report this."); + ZLog::Write(LOGLEVEL_FATAL, "Request could not be processed correctly due to a WBXMLException. Please report this including WBXML debug data logged. Be aware that the debug data could contain confidential information."); } // Try to output some kind of error information. This is only possible if @@ -306,10 +237,29 @@ include_once('version.php'); ZPush::GetTopCollector()->AnnounceInformation(get_class($ex), true); } + // FinishResponse + ZPush::FinishResponse(); + // destruct backend after all data is on the stream + ZPush::GetBackend()->Logoff(); + // save device data if the DeviceManager is available if (ZPush::GetDeviceManager(false)) ZPush::GetDeviceManager()->Save(); // end gracefully - ZLog::Write(LOGLEVEL_DEBUG, '-------- End'); -?> \ No newline at end of file + if (version_compare(phpversion(), '5.4.0') < 0) { + $time_used = number_format(time() - $_SERVER["REQUEST_TIME"], 4); + } + else { + $time_used = number_format(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 4); + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("-------- End - max mem: %s/%s - time: %s - code: %s", memory_get_peak_usage(false), memory_get_peak_usage(true), $time_used, http_response_code())); + + +if (defined('LOG_MEMORY_PROFILER') && LOG_MEMORY_PROFILER) { + if (function_exists('memprof_enable')) { + // Be aware that the pid is not unique, so we will overwrite the output in some cases. But using the pid will be easier to relate the dump with the log lines + memprof_dump_callgrind(fopen(LOG_MEMORY_PROFILER_FILE . "_" . getmypid(), "w")); + } +} \ No newline at end of file diff --git a/sources/lib/core/asdevice.php b/sources/lib/core/asdevice.php index c7e7377..ea0b025 100644 --- a/sources/lib/core/asdevice.php +++ b/sources/lib/core/asdevice.php @@ -687,5 +687,3 @@ class ASDevice extends StateObject { } } - -?> \ No newline at end of file diff --git a/sources/lib/core/bodypreference.php b/sources/lib/core/bodypreference.php index 057d8f7..364ae21 100644 --- a/sources/lib/core/bodypreference.php +++ b/sources/lib/core/bodypreference.php @@ -65,4 +65,3 @@ class BodyPreference extends StateObject { return (count($this->data) > 0); } } -?> \ No newline at end of file diff --git a/sources/lib/core/changesmemorywrapper.php b/sources/lib/core/changesmemorywrapper.php index 97b711f..ab2963f 100644 --- a/sources/lib/core/changesmemorywrapper.php +++ b/sources/lib/core/changesmemorywrapper.php @@ -115,7 +115,6 @@ class ChangesMemoryWrapper extends HierarchyCache implements IImportChanges, IEx public function LoadConflicts($contentparameters, $state) { return true; } public function ConfigContentParameters($contentparameters) { return true; } public function ImportMessageReadFlag($id, $flags) { return true; } - public function ImportMessageStarFlag($id, $flags) { return true; } public function ImportMessageMove($id, $newfolder) { return true; } /**---------------------------------------------------------------------------------------------------------- @@ -346,5 +345,3 @@ class ChangesMemoryWrapper extends HierarchyCache implements IImportChanges, IEx $this->step = 0; } } - -?> \ No newline at end of file diff --git a/sources/lib/core/contentparameters.php b/sources/lib/core/contentparameters.php index 8d68ff1..d3cee6e 100644 --- a/sources/lib/core/contentparameters.php +++ b/sources/lib/core/contentparameters.php @@ -137,5 +137,3 @@ class ContentParameters extends StateObject { return true; } } - -?> \ No newline at end of file diff --git a/sources/lib/core/devicemanager.php b/sources/lib/core/devicemanager.php index d0fdfe9..fe1a5e5 100644 --- a/sources/lib/core/devicemanager.php +++ b/sources/lib/core/devicemanager.php @@ -91,7 +91,7 @@ class DeviceManager { else throw new FatalNotImplementedException("Can not proceed without a device id."); - $this->loopdetection = new LoopDetection(); + $this->loopdetection = ZPush::GetLoopDetection(); $this->loopdetection->ProcessLoopDetectionInit(); $this->loopdetection->ProcessLoopDetectionPreviousConnectionFailed(); @@ -427,6 +427,8 @@ class DeviceManager { return true; } + if (!is_object($message)) + throw new Exception("DeviceManager->DoNotStreamMessage(): message isn't an object"); // message is semantically incorrect if (!$message->Check(true)) { $this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_SEMANTICERR); @@ -478,7 +480,7 @@ class DeviceManager { if (defined("SYNC_MAX_ITEMS") && SYNC_MAX_ITEMS < $items) { if ($queuedmessages > SYNC_MAX_ITEMS) - ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetWindowSize() overwriting max itmes requested of %d by %d forced in configuration.", $items, SYNC_MAX_ITEMS)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetWindowSize() overwriting max items requested of %d by %d forced in configuration.", $items, SYNC_MAX_ITEMS)); $items = SYNC_MAX_ITEMS; } @@ -899,5 +901,3 @@ class DeviceManager { return $this->latestFolder; } } - -?> \ No newline at end of file diff --git a/sources/lib/core/hierarchycache.php b/sources/lib/core/hierarchycache.php index 983430a..139a124 100644 --- a/sources/lib/core/hierarchycache.php +++ b/sources/lib/core/hierarchycache.php @@ -212,5 +212,3 @@ class HierarchyCache { } } - -?> \ No newline at end of file diff --git a/sources/lib/core/paddingfilter.php b/sources/lib/core/paddingfilter.php deleted file mode 100644 index d5a7a12..0000000 --- a/sources/lib/core/paddingfilter.php +++ /dev/null @@ -1,101 +0,0 @@ -. -* -* Consult LICENSE file for details -************************************************/ - -/* Define our filter class - * - * Usage: stream_filter_append($stream, 'padding.X'); - * where X is a number a stream will be padded to be - * multiple of (e.g. padding.3 will pad the stream - * to be multiple of 3 which is useful in base64 - * encoding). - * - * */ -class padding_filter extends php_user_filter { - private $padding = 4; // default padding - - /** - * This method is called whenever data is read from or written to the attached stream - * - * @see php_user_filter::filter() - * - * @param resource $in - * @param resource $out - * @param int $consumed - * @param boolean $closing - * - * @access public - * @return int - * - */ - function filter($in, $out, &$consumed, $closing) { - while ($bucket = stream_bucket_make_writeable($in)) { - if ($this->padding != 0 && $bucket->datalen < 8192) { - $bucket->data .= str_pad($bucket->data, $this->padding, 0x0); - } - $consumed += ($this->padding != 0 && $bucket->datalen < 8192) ? ($bucket->datalen + $this->padding) : $bucket->datalen; - stream_bucket_append($out, $bucket); - } - return PSFS_PASS_ON; - } - - /** - * Called when creating the filter - * - * @see php_user_filter::onCreate() - * - * @access public - * @return boolean - */ - function onCreate() { - $delim = strrpos($this->filtername, '.'); - if ($delim !== false) { - $padding = substr($this->filtername, $delim + 1); - if (is_numeric($padding)) - $this->padding = $padding; - } - return true; - } -} - -stream_filter_register("padding.*", "padding_filter"); -?> \ No newline at end of file diff --git a/sources/lib/core/statemanager.php b/sources/lib/core/statemanager.php index 28d5a47..afc6f9d 100644 --- a/sources/lib/core/statemanager.php +++ b/sources/lib/core/statemanager.php @@ -316,8 +316,8 @@ class StateManager { */ public function SetBackendStorage($data, $type = self::BACKENDSTORAGE_PERMANENT) { if ($type == self::BACKENDSTORAGE_STATE) { - if (!$this->uuid) - throw new StateNotYetAvailableException(); + if (!$this->uuid) + throw new StateNotYetAvailableException(); // TODO serialization should be done in the StateMachine return $this->statemachine->SetState($data, $this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $this->uuid, $this->newStateCounter); @@ -537,4 +537,3 @@ class StateManager { mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ) ); } } -?> \ No newline at end of file diff --git a/sources/lib/core/stateobject.php b/sources/lib/core/stateobject.php index 3a280ab..c4542f6 100644 --- a/sources/lib/core/stateobject.php +++ b/sources/lib/core/stateobject.php @@ -197,7 +197,7 @@ class StateObject implements Serializable { return true; } - throw new FatalNotImplementedException(sprintf("StateObject->__call('%s'): not implemented. op: {$operator} args:". count($arguments), $name)); + throw new FatalNotImplementedException(sprintf("StateObject->__call('%s'): not implemented. op: {%s} args: %d", $name, $operator, count($arguments))); } /** @@ -264,5 +264,3 @@ class StateObject implements Serializable { throw new StateInvalidException("Unserialization failed as class was not found or not compatible"); } } - -?> \ No newline at end of file diff --git a/sources/lib/core/streamer.php b/sources/lib/core/streamer.php index 6dc8e81..d7df277 100644 --- a/sources/lib/core/streamer.php +++ b/sources/lib/core/streamer.php @@ -291,7 +291,7 @@ class Streamer implements Serializable { if ($encoder->getMultipart() && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_MULTIPART) { $encoder->addBodypartStream($this->$map[self::STREAMER_VAR]); $encoder->startTag(SYNC_ITEMOPERATIONS_PART); - $encoder->content(count($encoder->getBodypartsCount())); + $encoder->content($encoder->getBodypartsCount()); $encoder->endTag(); continue; } @@ -315,22 +315,10 @@ class Streamer implements Serializable { $encoder->content(strtoupper(bin2hex($this->$map[self::STREAMER_VAR]))); } else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM) { - //encode stream with base64 - $stream = $this->$map[self::STREAMER_VAR]; - $stat = fstat($stream); - // the padding size muss be calculated for the entire stream, - // the base64 filter seems to process 8192 byte chunks correctly itself - $padding = (isset($stat['size']) && $stat['size'] > 8192) ? ($stat['size'] % 3) : 0; - - $paddingfilter = stream_filter_append($stream, 'padding.'.$padding); - $base64filter = stream_filter_append($stream, 'convert.base64-encode'); - $d = ""; - while (!feof($stream)) { - $d .= fgets($stream, 4096); - } - $encoder->content($d); - stream_filter_remove($base64filter); - stream_filter_remove($paddingfilter); + //we need to encode in base64 + $encoder->content(Utils::EncodeBase64($this->$map[self::STREAMER_VAR])); + //memory ... + $this->$map[self::STREAMER_VAR] = null; } // implode comma or semicolon arrays into a string else if(isset($map[self::STREAMER_TYPE]) && is_array($this->$map[self::STREAMER_VAR]) && @@ -461,5 +449,3 @@ class Streamer implements Serializable { return 0; } } - -?> \ No newline at end of file diff --git a/sources/lib/core/streamimporter.php b/sources/lib/core/streamimporter.php index 4d9aa86..877cf4a 100644 --- a/sources/lib/core/streamimporter.php +++ b/sources/lib/core/streamimporter.php @@ -44,6 +44,7 @@ class ImportChangesStream implements IImportChanges { private $encoder; private $objclass; + private $classAsString; private $seenObjects; private $importedMsgs; private $checkForIgnoredMessages; @@ -189,41 +190,6 @@ class ImportChangesStream implements IImportChanges { return true; } - /** - * Imports a change in 'star' flag - * Can only be applied to SyncMail (Email) requests - * - * @param string $id - * @param int $flags - flagged/unflagged - * - * @access public - * @return boolean - */ - public function ImportMessageStarFlag($id, $flags) { - if(!($this->objclass instanceof SyncMail)) - return false; - - $this->importedMsgs++; - - $this->encoder->startTag(SYNC_MODIFY); - $this->encoder->startTag(SYNC_SERVERENTRYID); - $this->encoder->content($id); - $this->encoder->endTag(); - $this->encoder->startTag(SYNC_DATA); - $this->encoder->startTag(SYNC_POOMMAIL_FLAG); - $this->encoder->startTag(SYNC_POOMMAIL_FLAGSTATUS); - $this->encoder->content($flags == 1? "2" : "0"); - $this->encoder->endTag(); - $this->encoder->startTag(SYNC_POOMMAIL_FLAGTYPE); - $this->encoder->content("FollowUp"); - $this->encoder->endTag(); - $this->encoder->endTag(); - $this->encoder->endTag(); - $this->encoder->endTag(); - - return true; - } - /** * ImportMessageMove is not implemented, as this operation can not be streamed to a WBXMLEncoder * @@ -293,4 +259,3 @@ class ImportChangesStream implements IImportChanges { return $this->importedMsgs; } } -?> \ No newline at end of file diff --git a/sources/lib/core/synccollections.php b/sources/lib/core/synccollections.php index 17a3b31..fada0a9 100644 --- a/sources/lib/core/synccollections.php +++ b/sources/lib/core/synccollections.php @@ -54,6 +54,7 @@ class SyncCollections implements Iterator { const ERROR_NO_COLLECTIONS = 1; const ERROR_WRONG_HIERARCHY = 2; const OBSOLETE_CONNECTION = 3; + const HIERARCHY_CHANGED = 4; private $stateManager; @@ -220,7 +221,7 @@ class SyncCollections implements Iterator { $this->collections[$spa->GetFolderId()] = $spa; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->AddCollection(): Folder id '%s' : ref. PolicyKey '%s', ref. Lifetime '%s', last sync at '%s'", $spa->GetFolderId(), $spa->GetReferencePolicyKey(), $spa->GetReferenceLifetime(), $spa->GetLastSyncTime())); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->AddCollection(): Folder id '%s' : ref. PolicyKey '%s', ref. Lifetime '%s', last sync at '%s'", $spa->GetFolderId(), $spa->GetReferencePolicyKey(), $spa->GetReferenceLifetime(), $spa->GetLastSyncTime())); if ($spa->HasLastSyncTime() && $spa->GetLastSyncTime() > $this->lastSyncTime) { $this->lastSyncTime = $spa->GetLastSyncTime(); @@ -232,7 +233,7 @@ class SyncCollections implements Iterator { if ($spa->HasReferenceLifetime()) $this->refLifetime = $spa->GetReferenceLifetime(); - ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->AddCollection(): Updated reference PolicyKey '%s', reference Lifetime '%s', Last sync at '%s'", $this->refPolicyKey, $this->refLifetime, $this->lastSyncTime)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->AddCollection(): Updated reference PolicyKey '%s', reference Lifetime '%s', Last sync at '%s'", $this->refPolicyKey, $this->refLifetime, $this->lastSyncTime)); } return true; @@ -419,7 +420,7 @@ class SyncCollections implements Iterator { else $checkClasses = implode(" ", array_keys($classes)); - $pingTracking = new PingTracking(); + $pingTracking = ZPush::GetPingTracking(); $this->changes = array(); $changesAvailable = false; @@ -473,7 +474,7 @@ class SyncCollections implements Iterator { // Check if a hierarchy sync is necessary if (ZPush::GetDeviceManager()->IsHierarchySyncRequired()) - throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::ERROR_WRONG_HIERARCHY); + throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::HIERARCHY_CHANGED); // Check if there are newer requests // If so, this process should be terminated if more than 60 secs to go @@ -717,5 +718,3 @@ class SyncCollections implements Iterator { $this->stateManager = ZPush::GetDeviceManager()->GetStateManager(); } } - -?> \ No newline at end of file diff --git a/sources/lib/core/syncparameters.php b/sources/lib/core/syncparameters.php index 7029ad9..ea8deca 100644 --- a/sources/lib/core/syncparameters.php +++ b/sources/lib/core/syncparameters.php @@ -269,7 +269,7 @@ class SyncParameters extends StateObject { ZLog::Write(LOGLEVEL_DEBUG, "SyncParameters->UseCPO(): removed existing DEFAULT CPO as it is obsolete"); } - ZLOG::Write(LOGLEVEL_DEBUG, sprintf("SyncParameters->UseCPO('%s')", $options)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncParameters->UseCPO('%s')", $options)); $this->currentCPO = $options; $this->checkCPO($this->currentCPO); } @@ -347,8 +347,6 @@ class SyncParameters extends StateObject { elseif (isset($this->contentParameters[self::TASKOPTIONS])) $returnCPO = self::TASKOPTIONS; - if ($returnCPO != $options) - ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncParameters->normalizeType(): using %s for requested %s", $returnCPO, $options)); return $returnCPO; } // something unexpected happened, just return default, empty in the worst case @@ -410,10 +408,10 @@ class SyncParameters extends StateObject { * @return boolean */ protected function postUnserialize() { - // init with default options - $this->UseCPO(); + // init with the available CPO or default + $availableCPO = $this->normalizeType(self::DEFAULTOPTIONS); + $this->UseCPO($availableCPO); return true; } } -?> \ No newline at end of file diff --git a/sources/lib/core/zlog.php b/sources/lib/core/zlog.php index 5cc6d65..a4b7481 100644 --- a/sources/lib/core/zlog.php +++ b/sources/lib/core/zlog.php @@ -50,6 +50,7 @@ class ZLog { static private $lastLogs = array(); static private $userLog = false; static private $unAuthCache = array(); + static private $syslogEnabled = false; /** * Initializes the logging @@ -60,6 +61,11 @@ class ZLog { static public function Initialize() { global $specialLogUsers; + if (defined('LOG_SYSLOG_ENABLED') && LOG_SYSLOG_ENABLED) { + self::$syslogEnabled = true; + ZSyslog::Initialize(); + } + // define some constants for the logging if (!defined('LOGUSERLEVEL')) define('LOGUSERLEVEL', LOGLEVEL_OFF); @@ -111,7 +117,7 @@ class ZLog { $data = self::buildLogString($loglevel) . $message . "\n"; if ($loglevel <= LOGLEVEL) { - @file_put_contents(LOGFILE, $data, FILE_APPEND); + self::writeToLog($loglevel, $data, LOGFILE); } // should we write this into the user log? @@ -123,11 +129,11 @@ class ZLog { if (self::logToUserFile()) { // something was logged before the user was authenticated, write this to the log if (!empty(self::$unAuthCache)) { - @file_put_contents(LOGFILEDIR . self::logToUserFile() . ".log", implode('', self::$unAuthCache), FILE_APPEND); + self::writeToLog($loglevel, implode('', self::$unAuthCache), LOGFILEDIR . self::logToUserFile() . ".log"); self::$unAuthCache = array(); } // only use plain old a-z characters for the generic log file - @file_put_contents(LOGFILEDIR . self::logToUserFile() . ".log", $data, FILE_APPEND); + self::writeToLog($loglevel, $data, LOGFILEDIR . self::logToUserFile() . ".log"); } // the user is not authenticated yet, we save the log into memory for now else { @@ -136,7 +142,7 @@ class ZLog { } if (($loglevel & LOGLEVEL_FATAL) || ($loglevel & LOGLEVEL_ERROR)) { - @file_put_contents(LOGERRORFILE, $data, FILE_APPEND); + self::writeToLog($loglevel, $data, LOGERRORFILE); } if ($loglevel & LOGLEVEL_WBXMLSTACK) { @@ -205,7 +211,10 @@ class ZLog { if (!isset(self::$devid)) self::$devid = ''; - return Utils::GetFormattedTime() ." ". self::$pidstr . self::getLogLevelString($loglevel, (LOGLEVEL > LOGLEVEL_INFO)) ." ". self::$user . self::$devid; + if (self::$syslogEnabled) + return self::$pidstr . self::getLogLevelString($loglevel, (LOGLEVEL > LOGLEVEL_INFO)) . " " . self::$user . self::$devid; + else + return Utils::GetFormattedTime() . " " . self::$pidstr . self::getLogLevelString($loglevel, (LOGLEVEL > LOGLEVEL_INFO)) . " " . self::$user . self::$devid; } /** @@ -233,48 +242,29 @@ class ZLog { case LOGLEVEL_WBXMLSTACK: return "[WBXMLSTACK]"; break; } } -} -/**---------------------------------------------------------------------------------------------------------- - * Legacy debug stuff - */ - -// deprecated -// backwards compatible -function debugLog($message) { - ZLog::Write(LOGLEVEL_DEBUG, $message); -} - -// TODO review error handler -function zarafa_error_handler($errno, $errstr, $errfile, $errline, $errcontext) { - $bt = debug_backtrace(); - switch ($errno) { - case 8192: // E_DEPRECATED since PHP 5.3.0 - // do not handle this message - break; - - case E_NOTICE: - case E_WARNING: - // TODO check if there is a better way to avoid these messages - if (stripos($errfile,'interprocessdata') !== false && stripos($errstr,'shm_get_var()') !== false) - break; - ZLog::Write(LOGLEVEL_WARN, "$errfile:$errline $errstr ($errno)"); - break; - - default: - ZLog::Write(LOGLEVEL_ERROR, "trace error: $errfile:$errline $errstr ($errno) - backtrace: ". (count($bt)-1) . " steps"); - for($i = 1, $bt_length = count($bt); $i < $bt_length; $i++) { - $file = $line = "unknown"; - if (isset($bt[$i]['file'])) $file = $bt[$i]['file']; - if (isset($bt[$i]['line'])) $line = $bt[$i]['line']; - ZLog::Write(LOGLEVEL_ERROR, "trace: $i:". $file . ":" . $line. " - " . ((isset($bt[$i]['class']))? $bt[$i]['class'] . $bt[$i]['type']:""). $bt[$i]['function']. "()"); + /** + * Write the message to the log facility. + * + * @param int $loglevel + * @param string $data + * @param string $logfile + * + * @access private + * @return void + */ + static private function writeToLog($loglevel, $data, $logfile = null) { + if (self::$syslogEnabled) { + if (ZSyslog::send($loglevel, $data) === false) { + error_log("Unable to send to syslog"); + error_log($data); } - //throw new Exception("An error occured."); - break; + } + else { + if (@file_put_contents($logfile, $data, FILE_APPEND) === false) { + error_log(sprintf("Unable to write in %s", $logfile)); + error_log($data); + } + } } } - -error_reporting(E_ALL); -set_error_handler("zarafa_error_handler"); - -?> \ No newline at end of file diff --git a/sources/lib/core/zpush-utils.php b/sources/lib/core/zpush-utils.php new file mode 100644 index 0000000..92f0bcc --- /dev/null +++ b/sources/lib/core/zpush-utils.php @@ -0,0 +1,73 @@ + 7 +//STORE_SUPPORTS_UNICODE is true and the convertion will not be done +//for other backends. +function utf8_to_windows1252($string, $option = "", $force_convert = false) { + //if the store supports unicode return the string without converting it + if (!$force_convert && defined('STORE_SUPPORTS_UNICODE') && STORE_SUPPORTS_UNICODE == true) return $string; + + if (function_exists("iconv")){ + return @iconv("UTF-8", "Windows-1252" . $option, $string); + }else{ + return utf8_decode($string); // no euro support here + } +} + +function windows1252_to_utf8($string, $option = "", $force_convert = false) { + //if the store supports unicode return the string without converting it + if (!$force_convert && defined('STORE_SUPPORTS_UNICODE') && STORE_SUPPORTS_UNICODE == true) return $string; + + if (function_exists("iconv")){ + return @iconv("Windows-1252", "UTF-8" . $option, $string); + }else{ + return utf8_encode($string); // no euro support here + } +} + +function w2u($string) { return windows1252_to_utf8($string); } +function u2w($string) { return utf8_to_windows1252($string); } + +function w2ui($string) { return windows1252_to_utf8($string, "//TRANSLIT"); } +function u2wi($string) { return utf8_to_windows1252($string, "//TRANSLIT"); } + +/** + * @param string $message + * @deprecated + */ +function debugLog($message) { + ZLog::Write(LOGLEVEL_DEBUG, $message); +} + +// TODO review error handler +function zarafa_error_handler($errno, $errstr, $errfile, $errline, $errcontext) { + $bt = debug_backtrace(); + switch ($errno) { + case 8192: // E_DEPRECATED since PHP 5.3.0 + // do not handle this message + break; + + case E_NOTICE: + case E_WARNING: + // TODO check if there is a better way to avoid these messages + if (stripos($errfile,'interprocessdata') !== false && stripos($errstr,'shm_get_var()') !== false) + break; + ZLog::Write(LOGLEVEL_WARN, "$errfile:$errline $errstr ($errno)"); + break; + + default: + ZLog::Write(LOGLEVEL_ERROR, "trace error: $errfile:$errline $errstr ($errno) - backtrace: ". (count($bt)-1) . " steps"); + for($i = 1, $bt_length = count($bt); $i < $bt_length; $i++) { + $file = $line = "unknown"; + if (isset($bt[$i]['file'])) $file = $bt[$i]['file']; + if (isset($bt[$i]['line'])) $line = $bt[$i]['line']; + ZLog::Write(LOGLEVEL_ERROR, "trace: $i:". $file . ":" . $line. " - " . ((isset($bt[$i]['class']))? $bt[$i]['class'] . $bt[$i]['type']:""). $bt[$i]['function']. "()"); + } + //throw new Exception("An error occured."); + break; + } +} + +error_reporting(E_ALL); +set_error_handler("zarafa_error_handler"); diff --git a/sources/lib/core/zpush.php b/sources/lib/core/zpush.php index b4316aa..1fb4383 100644 --- a/sources/lib/core/zpush.php +++ b/sources/lib/core/zpush.php @@ -56,13 +56,11 @@ class ZPush { const CLASS_OTHERTYPES = 4; // AS versions - const ASV_1 = "1.0"; - const ASV_2 = "2.0"; - const ASV_21 = "2.1"; const ASV_25 = "2.5"; const ASV_12 = "12.0"; const ASV_121 = "12.1"; const ASV_14 = "14.0"; + const ASV_141 = "14.1"; /** * Command codes for base64 encoded requests (AS >= 12.1) @@ -87,12 +85,8 @@ class ZPush { const COMMAND_RESOLVERECIPIENTS = 21; const COMMAND_VALIDATECERT = 22; - // Deprecated commands + // Deprecated commands (AS >= 14) const COMMAND_GETHIERARCHY = -1; - const COMMAND_CREATECOLLECTION = -2; - const COMMAND_DELETECOLLECTION = -3; - const COMMAND_MOVECOLLECTION = -4; - const COMMAND_NOTIFY = -5; // Webservice commands const COMMAND_WEBSERVICE_DEVICE = -100; @@ -109,48 +103,68 @@ class ZPush { "BackendMaildir" ); + // Versions 1.0, 2.0, 2.1 and 2.5 are deprecated (ZP-604) static private $supportedASVersions = array( - self::ASV_1, - self::ASV_2, - self::ASV_21, - self::ASV_25, self::ASV_12, self::ASV_121, - self::ASV_14 + self::ASV_14, + self::ASV_141 ); static private $supportedCommands = array( - // COMMAND // AS VERSION // REQUESTHANDLER // OTHER SETTINGS - self::COMMAND_SYNC => array(self::ASV_1, self::REQUESTHANDLER => "Sync"), - self::COMMAND_SENDMAIL => array(self::ASV_1, self::REQUESTHANDLER => "SendMail"), - self::COMMAND_SMARTFORWARD => array(self::ASV_1, self::REQUESTHANDLER => "SendMail"), - self::COMMAND_SMARTREPLY => array(self::ASV_1, self::REQUESTHANDLER => "SendMail"), - self::COMMAND_GETATTACHMENT => array(self::ASV_1, self::REQUESTHANDLER => "GetAttachment"), - self::COMMAND_GETHIERARCHY => array(self::ASV_1, self::REQUESTHANDLER => "GetHierarchy", self::HIERARCHYCOMMAND), // deprecated but implemented - self::COMMAND_CREATECOLLECTION => array(self::ASV_1), // deprecated & not implemented - self::COMMAND_DELETECOLLECTION => array(self::ASV_1), // deprecated & not implemented - self::COMMAND_MOVECOLLECTION => array(self::ASV_1), // deprecated & not implemented - self::COMMAND_FOLDERSYNC => array(self::ASV_2, self::REQUESTHANDLER => "FolderSync", self::HIERARCHYCOMMAND), - self::COMMAND_FOLDERCREATE => array(self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND), - self::COMMAND_FOLDERDELETE => array(self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND), - self::COMMAND_FOLDERUPDATE => array(self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND), - self::COMMAND_MOVEITEMS => array(self::ASV_1, self::REQUESTHANDLER => "MoveItems"), - self::COMMAND_GETITEMESTIMATE => array(self::ASV_1, self::REQUESTHANDLER => "GetItemEstimate"), - self::COMMAND_MEETINGRESPONSE => array(self::ASV_1, self::REQUESTHANDLER => "MeetingResponse"), - self::COMMAND_RESOLVERECIPIENTS => array(self::ASV_1, self::REQUESTHANDLER => "ResolveRecipients"), - self::COMMAND_VALIDATECERT => array(self::ASV_1, self::REQUESTHANDLER => "ValidateCert"), - self::COMMAND_PROVISION => array(self::ASV_25, self::REQUESTHANDLER => "Provisioning", self::UNAUTHENTICATED, self::UNPROVISIONED), - self::COMMAND_SEARCH => array(self::ASV_1, self::REQUESTHANDLER => "Search"), - self::COMMAND_PING => array(self::ASV_2, self::REQUESTHANDLER => "Ping", self::UNPROVISIONED), - self::COMMAND_NOTIFY => array(self::ASV_1, self::REQUESTHANDLER => "Notify"), // deprecated & not implemented - self::COMMAND_ITEMOPERATIONS => array(self::ASV_12, self::REQUESTHANDLER => "ItemOperations"), - self::COMMAND_SETTINGS => array(self::ASV_12, self::REQUESTHANDLER => "Settings"), + // COMMAND // AS VERSION // OTHER SETTINGS + self::COMMAND_SYNC => array(self::ASV_25), + self::COMMAND_SENDMAIL => array(self::ASV_25), + self::COMMAND_SMARTFORWARD => array(self::ASV_25), + self::COMMAND_SMARTREPLY => array(self::ASV_25), + self::COMMAND_GETATTACHMENT => array(self::ASV_25), + self::COMMAND_GETHIERARCHY => array(self::ASV_25, self::HIERARCHYCOMMAND), // deprecated (AS >= 14) + self::COMMAND_FOLDERSYNC => array(self::ASV_25, self::HIERARCHYCOMMAND), + self::COMMAND_FOLDERCREATE => array(self::ASV_25, self::HIERARCHYCOMMAND), + self::COMMAND_FOLDERDELETE => array(self::ASV_25, self::HIERARCHYCOMMAND), + self::COMMAND_FOLDERUPDATE => array(self::ASV_25, self::HIERARCHYCOMMAND), + self::COMMAND_MOVEITEMS => array(self::ASV_25), + self::COMMAND_GETITEMESTIMATE => array(self::ASV_25), + self::COMMAND_MEETINGRESPONSE => array(self::ASV_25), + self::COMMAND_RESOLVERECIPIENTS => array(self::ASV_25), + self::COMMAND_VALIDATECERT => array(self::ASV_25), + self::COMMAND_PROVISION => array(self::ASV_25, self::UNAUTHENTICATED, self::UNPROVISIONED), + self::COMMAND_SEARCH => array(self::ASV_25), + self::COMMAND_PING => array(self::ASV_25, self::UNPROVISIONED), + self::COMMAND_ITEMOPERATIONS => array(self::ASV_12), + self::COMMAND_SETTINGS => array(self::ASV_12), - self::COMMAND_WEBSERVICE_DEVICE => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND), - self::COMMAND_WEBSERVICE_USERS => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND), - ); + self::COMMAND_WEBSERVICE_DEVICE => array(self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND), + self::COMMAND_WEBSERVICE_USERS => array(self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND), + ); + static private $requestHandler = array( + // COMMAND // REQUESTHANDLER + self::COMMAND_SYNC => "Sync", + self::COMMAND_SENDMAIL => "SendMail", + self::COMMAND_SMARTFORWARD => "SendMail", + self::COMMAND_SMARTREPLY => "SendMail", + self::COMMAND_GETATTACHMENT => "GetAttachment", + self::COMMAND_GETHIERARCHY => "GetHierarchy", // deprecated (AS >= 14) + self::COMMAND_FOLDERSYNC => "FolderSync", + self::COMMAND_FOLDERCREATE => "FolderChange", + self::COMMAND_FOLDERDELETE => "FolderChange", + self::COMMAND_FOLDERUPDATE => "FolderChange", + self::COMMAND_MOVEITEMS => "MoveItems", + self::COMMAND_GETITEMESTIMATE => "GetItemEstimate", + self::COMMAND_MEETINGRESPONSE => "MeetingResponse", + self::COMMAND_RESOLVERECIPIENTS => "ResolveRecipients", + self::COMMAND_VALIDATECERT => "ValidateCert", + self::COMMAND_PROVISION => "Provisioning", + self::COMMAND_SEARCH => "Search", + self::COMMAND_PING => "Ping", + self::COMMAND_ITEMOPERATIONS => "ItemOperations", + self::COMMAND_SETTINGS => "Settings", + + self::COMMAND_WEBSERVICE_DEVICE => "Webservice", + self::COMMAND_WEBSERVICE_USERS => "Webservice", + ); static private $classes = array( "Email" => array( @@ -205,8 +219,8 @@ class ZPush { */ static public function CheckConfig() { // check the php version - if (version_compare(phpversion(),'5.1.0') < 0) - throw new FatalException("The configured PHP version is too old. Please make sure at least PHP 5.1 is used."); + if (version_compare(phpversion(), '5.3.0') < 0) + throw new FatalException("The configured PHP version is too old. Please make sure at least PHP 5.3 is used."); // some basic checks if (!defined('BASE_PATH')) @@ -354,18 +368,16 @@ class ZPush { else { // Initialize the default StateMachine if (defined('STATE_MACHINE') && STATE_MACHINE == 'SQL') { - include_once('lib/default/sqlstatemachine.php'); ZPush::$stateMachine = new SqlStateMachine(); } else { - include_once('lib/default/filestatemachine.php'); ZPush::$stateMachine = new FileStateMachine(); } } if (ZPush::$stateMachine->GetStateVersion() !== ZPush::GetLatestStateVersion()) { if (class_exists("TopCollector")) self::GetTopCollector()->AnnounceInformation("Run migration script!", true); - throw new HTTPReturnCodeException(sprintf("The state version available to the %s is not the latest version - please run the state upgrade script. See release notes for more information.", get_class(ZPush::$stateMachine), 503)); + throw new HTTPReturnCodeException(sprintf("The state version available to the %s is not the latest version - please run the state upgrade script. See release notes for more information.", get_class(ZPush::$stateMachine)), HTTP_CODE_500); } } return ZPush::$stateMachine; @@ -403,10 +415,33 @@ class ZPush { * @return object TopCollector */ static public function GetTopCollector() { - if (!isset(ZPush::$topCollector)) - ZPush::$topCollector = new TopCollector(); + if (!isset(self::$topCollector)) { + $class = defined('TOP_COLLECTOR_BACKEND') ? TOP_COLLECTOR_BACKEND : 'TopCollector'; + self::$topCollector = new $class(); + } + return self::$topCollector; + } - return ZPush::$topCollector; + /** + * Returns an instance of PingTracking + * + * @access public + * @return object IPingTracking + */ + static public function GetPingTracking() { + $class = defined('PING_TRACKING_BACKEND') ? PING_TRACKING_BACKEND : 'PingTracking'; + return new $class(); + } + + /** + * Returns an instance of LoopDetection + * + * @access public + * @return object ILoopDetection + */ + static public function GetLoopDetection() { + $class = defined('LOOP_DETECTION_BACKEND') ? LOOP_DETECTION_BACKEND : 'LoopDetection'; + return new $class(); } /** @@ -686,9 +721,7 @@ END; * @return string */ static public function GetSupportedProtocolVersions($valueOnly = false) { - //$versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions)+1))); - // Removing support for AS 1.0, 2.0, 2.1 - That will make Outlook 2013 works - $versions = implode(',', array_slice(self::$supportedASVersions, 3, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions)+1))); + $versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions)+1))); ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetSupportedProtocolVersions(): " . $versions); if ($valueOnly === true) @@ -725,18 +758,10 @@ END; * @return RequestProcessor sub-class */ static public function GetRequestHandlerForCommand($commandCode) { - if (!array_key_exists($commandCode, self::$supportedCommands) || - !array_key_exists(self::REQUESTHANDLER, self::$supportedCommands[$commandCode]) ) + if (!array_key_exists($commandCode, self::$requestHandler)) throw new FatalNotImplementedException(sprintf("Command '%s' has no request handler or class", Utils::GetCommandFromCode($commandCode))); - $class = self::$supportedCommands[$commandCode][self::REQUESTHANDLER]; - if ($class == "Webservice") - $handlerclass = REAL_BASE_PATH . "lib/webservice/webservice.php"; - else - $handlerclass = REAL_BASE_PATH . "lib/request/" . strtolower($class) . ".php"; - - if (is_file($handlerclass)) - include($handlerclass); + $class = self::$requestHandler[$commandCode]; if (class_exists($class)) return new $class(); @@ -830,5 +855,22 @@ END; return $defcapa; } + /** + * End Response + * + * @access public + */ + public static function FinishResponse() { + $len = ob_get_length(); + if ($len !== false) { + if (!headers_sent()) { + header(sprintf("Content-Length: %s", $len)); + if ($len == 0) + header("Content-Type:"); + } + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Flushing %d, headers already sent? %s", $len , headers_sent() ? "yes" : "no")); + if (!ob_end_flush()) + ZLog::Write(LOGLEVEL_ERROR, "Unable to flush buffer!?"); + } + } } -?> \ No newline at end of file diff --git a/sources/lib/core/zpushdefs.php b/sources/lib/core/zpushdefs.php index 04ec8f5..0e6ebbb 100644 --- a/sources/lib/core/zpushdefs.php +++ b/sources/lib/core/zpushdefs.php @@ -41,1027 +41,1035 @@ * Consult LICENSE file for details ************************************************/ -define("SYNC_SYNCHRONIZE","Synchronize"); -define("SYNC_REPLIES","Replies"); -define("SYNC_ADD","Add"); -define("SYNC_MODIFY","Modify"); -define("SYNC_REMOVE","Remove"); -define("SYNC_FETCH","Fetch"); -define("SYNC_SYNCKEY","SyncKey"); -define("SYNC_CLIENTENTRYID","ClientEntryId"); -define("SYNC_SERVERENTRYID","ServerEntryId"); -define("SYNC_STATUS","Status"); -define("SYNC_FOLDER","Folder"); -define("SYNC_FOLDERTYPE","FolderType"); -define("SYNC_VERSION","Version"); -define("SYNC_FOLDERID","FolderId"); -define("SYNC_GETCHANGES","GetChanges"); -define("SYNC_MOREAVAILABLE","MoreAvailable"); -define("SYNC_MAXITEMS","MaxItems"); -define("SYNC_WINDOWSIZE","WindowSize"); //MaxItems before z-push 2 -define("SYNC_PERFORM","Perform"); -define("SYNC_OPTIONS","Options"); -define("SYNC_FILTERTYPE","FilterType"); -define("SYNC_TRUNCATION","Truncation"); -define("SYNC_RTFTRUNCATION","RtfTruncation"); -define("SYNC_CONFLICT","Conflict"); -define("SYNC_FOLDERS","Folders"); -define("SYNC_DATA","Data"); -define("SYNC_DELETESASMOVES","DeletesAsMoves"); -define("SYNC_NOTIFYGUID","NotifyGUID"); -define("SYNC_SUPPORTED","Supported"); -define("SYNC_SOFTDELETE","SoftDelete"); -define("SYNC_MIMESUPPORT","MIMESupport"); -define("SYNC_MIMETRUNCATION","MIMETruncation"); -define("SYNC_NEWMESSAGE","NewMessage"); -define("SYNC_WAIT","Wait"); //12.1 and 14.0 -define("SYNC_LIMIT","Limit"); //12.1 and 14.0 -define("SYNC_PARTIAL","Partial"); //12.1 and 14.0 -define("SYNC_CONVERSATIONMODE","ConversationMode"); //14.0 -define("SYNC_HEARTBEATINTERVAL","HeartbeatInterval"); //14.0 +const EN_TYPE = 1; +const EN_TAG = 2; +const EN_CONTENT = 3; +const EN_FLAGS = 4; +const EN_ATTRIBUTES = 5; +const EN_TYPE_STARTTAG = 1; +const EN_TYPE_ENDTAG = 2; +const EN_TYPE_CONTENT = 3; +const EN_FLAGS_CONTENT = 1; +const EN_FLAGS_ATTRIBUTES = 2; + +const SYNC_SYNCHRONIZE = "Synchronize"; +const SYNC_REPLIES = "Replies"; +const SYNC_ADD = "Add"; +const SYNC_MODIFY = "Modify"; +const SYNC_REMOVE = "Remove"; +const SYNC_FETCH = "Fetch"; +const SYNC_SYNCKEY = "SyncKey"; +const SYNC_CLIENTENTRYID = "ClientEntryId"; +const SYNC_SERVERENTRYID = "ServerEntryId"; +const SYNC_STATUS = "Status"; +const SYNC_FOLDER = "Folder"; +const SYNC_FOLDERTYPE = "FolderType"; +const SYNC_VERSION = "Version"; +const SYNC_FOLDERID = "FolderId"; +const SYNC_GETCHANGES = "GetChanges"; +const SYNC_MOREAVAILABLE = "MoreAvailable"; +const SYNC_MAXITEMS = "MaxItems"; +const SYNC_WINDOWSIZE = "WindowSize"; //MaxItems before z-push 2 +const SYNC_PERFORM = "Perform"; +const SYNC_OPTIONS = "Options"; +const SYNC_FILTERTYPE = "FilterType"; +const SYNC_TRUNCATION = "Truncation"; +const SYNC_RTFTRUNCATION = "RtfTruncation"; +const SYNC_CONFLICT = "Conflict"; +const SYNC_FOLDERS = "Folders"; +const SYNC_DATA = "Data"; +const SYNC_DELETESASMOVES = "DeletesAsMoves"; +const SYNC_NOTIFYGUID = "NotifyGUID"; +const SYNC_SUPPORTED = "Supported"; +const SYNC_SOFTDELETE = "SoftDelete"; +const SYNC_MIMESUPPORT = "MIMESupport"; +const SYNC_MIMETRUNCATION = "MIMETruncation"; +const SYNC_NEWMESSAGE = "NewMessage"; +const SYNC_WAIT = "Wait"; //12.1 and 14.0 +const SYNC_LIMIT = "Limit"; //12.1 and 14.0 +const SYNC_PARTIAL = "Partial"; //12.1 and 14.0 +const SYNC_CONVERSATIONMODE = "ConversationMode"; //14.0 +const SYNC_HEARTBEATINTERVAL = "HeartbeatInterval"; //14.0 // POOMCONTACTS -define("SYNC_POOMCONTACTS_ANNIVERSARY","POOMCONTACTS:Anniversary"); -define("SYNC_POOMCONTACTS_ASSISTANTNAME","POOMCONTACTS:AssistantName"); -define("SYNC_POOMCONTACTS_ASSISTNAMEPHONENUMBER","POOMCONTACTS:AssistnamePhoneNumber"); -define("SYNC_POOMCONTACTS_BIRTHDAY","POOMCONTACTS:Birthday"); -define("SYNC_POOMCONTACTS_BODY","POOMCONTACTS:Body"); -define("SYNC_POOMCONTACTS_BODYSIZE","POOMCONTACTS:BodySize"); -define("SYNC_POOMCONTACTS_BODYTRUNCATED","POOMCONTACTS:BodyTruncated"); -define("SYNC_POOMCONTACTS_BUSINESS2PHONENUMBER","POOMCONTACTS:Business2PhoneNumber"); -define("SYNC_POOMCONTACTS_BUSINESSCITY","POOMCONTACTS:BusinessCity"); -define("SYNC_POOMCONTACTS_BUSINESSCOUNTRY","POOMCONTACTS:BusinessCountry"); -define("SYNC_POOMCONTACTS_BUSINESSPOSTALCODE","POOMCONTACTS:BusinessPostalCode"); -define("SYNC_POOMCONTACTS_BUSINESSSTATE","POOMCONTACTS:BusinessState"); -define("SYNC_POOMCONTACTS_BUSINESSSTREET","POOMCONTACTS:BusinessStreet"); -define("SYNC_POOMCONTACTS_BUSINESSFAXNUMBER","POOMCONTACTS:BusinessFaxNumber"); -define("SYNC_POOMCONTACTS_BUSINESSPHONENUMBER","POOMCONTACTS:BusinessPhoneNumber"); -define("SYNC_POOMCONTACTS_CARPHONENUMBER","POOMCONTACTS:CarPhoneNumber"); -define("SYNC_POOMCONTACTS_CATEGORIES","POOMCONTACTS:Categories"); -define("SYNC_POOMCONTACTS_CATEGORY","POOMCONTACTS:Category"); -define("SYNC_POOMCONTACTS_CHILDREN","POOMCONTACTS:Children"); -define("SYNC_POOMCONTACTS_CHILD","POOMCONTACTS:Child"); -define("SYNC_POOMCONTACTS_COMPANYNAME","POOMCONTACTS:CompanyName"); -define("SYNC_POOMCONTACTS_DEPARTMENT","POOMCONTACTS:Department"); -define("SYNC_POOMCONTACTS_EMAIL1ADDRESS","POOMCONTACTS:Email1Address"); -define("SYNC_POOMCONTACTS_EMAIL2ADDRESS","POOMCONTACTS:Email2Address"); -define("SYNC_POOMCONTACTS_EMAIL3ADDRESS","POOMCONTACTS:Email3Address"); -define("SYNC_POOMCONTACTS_FILEAS","POOMCONTACTS:FileAs"); -define("SYNC_POOMCONTACTS_FIRSTNAME","POOMCONTACTS:FirstName"); -define("SYNC_POOMCONTACTS_HOME2PHONENUMBER","POOMCONTACTS:Home2PhoneNumber"); -define("SYNC_POOMCONTACTS_HOMECITY","POOMCONTACTS:HomeCity"); -define("SYNC_POOMCONTACTS_HOMECOUNTRY","POOMCONTACTS:HomeCountry"); -define("SYNC_POOMCONTACTS_HOMEPOSTALCODE","POOMCONTACTS:HomePostalCode"); -define("SYNC_POOMCONTACTS_HOMESTATE","POOMCONTACTS:HomeState"); -define("SYNC_POOMCONTACTS_HOMESTREET","POOMCONTACTS:HomeStreet"); -define("SYNC_POOMCONTACTS_HOMEFAXNUMBER","POOMCONTACTS:HomeFaxNumber"); -define("SYNC_POOMCONTACTS_HOMEPHONENUMBER","POOMCONTACTS:HomePhoneNumber"); -define("SYNC_POOMCONTACTS_JOBTITLE","POOMCONTACTS:JobTitle"); -define("SYNC_POOMCONTACTS_LASTNAME","POOMCONTACTS:LastName"); -define("SYNC_POOMCONTACTS_MIDDLENAME","POOMCONTACTS:MiddleName"); -define("SYNC_POOMCONTACTS_MOBILEPHONENUMBER","POOMCONTACTS:MobilePhoneNumber"); -define("SYNC_POOMCONTACTS_OFFICELOCATION","POOMCONTACTS:OfficeLocation"); -define("SYNC_POOMCONTACTS_OTHERCITY","POOMCONTACTS:OtherCity"); -define("SYNC_POOMCONTACTS_OTHERCOUNTRY","POOMCONTACTS:OtherCountry"); -define("SYNC_POOMCONTACTS_OTHERPOSTALCODE","POOMCONTACTS:OtherPostalCode"); -define("SYNC_POOMCONTACTS_OTHERSTATE","POOMCONTACTS:OtherState"); -define("SYNC_POOMCONTACTS_OTHERSTREET","POOMCONTACTS:OtherStreet"); -define("SYNC_POOMCONTACTS_PAGERNUMBER","POOMCONTACTS:PagerNumber"); -define("SYNC_POOMCONTACTS_RADIOPHONENUMBER","POOMCONTACTS:RadioPhoneNumber"); -define("SYNC_POOMCONTACTS_SPOUSE","POOMCONTACTS:Spouse"); -define("SYNC_POOMCONTACTS_SUFFIX","POOMCONTACTS:Suffix"); -define("SYNC_POOMCONTACTS_TITLE","POOMCONTACTS:Title"); -define("SYNC_POOMCONTACTS_WEBPAGE","POOMCONTACTS:WebPage"); -define("SYNC_POOMCONTACTS_YOMICOMPANYNAME","POOMCONTACTS:YomiCompanyName"); -define("SYNC_POOMCONTACTS_YOMIFIRSTNAME","POOMCONTACTS:YomiFirstName"); -define("SYNC_POOMCONTACTS_YOMILASTNAME","POOMCONTACTS:YomiLastName"); -define("SYNC_POOMCONTACTS_RTF","POOMCONTACTS:Rtf"); -define("SYNC_POOMCONTACTS_PICTURE","POOMCONTACTS:Picture"); -define("SYNC_POOMCONTACTS_ALIAS","POOMCONTACTS:Alias"); //14.0 -define("SYNC_POOMCONTACTS_WEIGHEDRANK","POOMCONTACTS:WeightedRank"); //14.0 +const SYNC_POOMCONTACTS_ANNIVERSARY = "POOMCONTACTS:Anniversary"; +const SYNC_POOMCONTACTS_ASSISTANTNAME = "POOMCONTACTS:AssistantName"; +const SYNC_POOMCONTACTS_ASSISTNAMEPHONENUMBER = "POOMCONTACTS:AssistnamePhoneNumber"; +const SYNC_POOMCONTACTS_BIRTHDAY = "POOMCONTACTS:Birthday"; +const SYNC_POOMCONTACTS_BODY = "POOMCONTACTS:Body"; +const SYNC_POOMCONTACTS_BODYSIZE = "POOMCONTACTS:BodySize"; +const SYNC_POOMCONTACTS_BODYTRUNCATED = "POOMCONTACTS:BodyTruncated"; +const SYNC_POOMCONTACTS_BUSINESS2PHONENUMBER = "POOMCONTACTS:Business2PhoneNumber"; +const SYNC_POOMCONTACTS_BUSINESSCITY = "POOMCONTACTS:BusinessCity"; +const SYNC_POOMCONTACTS_BUSINESSCOUNTRY = "POOMCONTACTS:BusinessCountry"; +const SYNC_POOMCONTACTS_BUSINESSPOSTALCODE = "POOMCONTACTS:BusinessPostalCode"; +const SYNC_POOMCONTACTS_BUSINESSSTATE = "POOMCONTACTS:BusinessState"; +const SYNC_POOMCONTACTS_BUSINESSSTREET = "POOMCONTACTS:BusinessStreet"; +const SYNC_POOMCONTACTS_BUSINESSFAXNUMBER = "POOMCONTACTS:BusinessFaxNumber"; +const SYNC_POOMCONTACTS_BUSINESSPHONENUMBER = "POOMCONTACTS:BusinessPhoneNumber"; +const SYNC_POOMCONTACTS_CARPHONENUMBER = "POOMCONTACTS:CarPhoneNumber"; +const SYNC_POOMCONTACTS_CATEGORIES = "POOMCONTACTS:Categories"; +const SYNC_POOMCONTACTS_CATEGORY = "POOMCONTACTS:Category"; +const SYNC_POOMCONTACTS_CHILDREN = "POOMCONTACTS:Children"; +const SYNC_POOMCONTACTS_CHILD = "POOMCONTACTS:Child"; +const SYNC_POOMCONTACTS_COMPANYNAME = "POOMCONTACTS:CompanyName"; +const SYNC_POOMCONTACTS_DEPARTMENT = "POOMCONTACTS:Department"; +const SYNC_POOMCONTACTS_EMAIL1ADDRESS = "POOMCONTACTS:Email1Address"; +const SYNC_POOMCONTACTS_EMAIL2ADDRESS = "POOMCONTACTS:Email2Address"; +const SYNC_POOMCONTACTS_EMAIL3ADDRESS = "POOMCONTACTS:Email3Address"; +const SYNC_POOMCONTACTS_FILEAS = "POOMCONTACTS:FileAs"; +const SYNC_POOMCONTACTS_FIRSTNAME = "POOMCONTACTS:FirstName"; +const SYNC_POOMCONTACTS_HOME2PHONENUMBER = "POOMCONTACTS:Home2PhoneNumber"; +const SYNC_POOMCONTACTS_HOMECITY = "POOMCONTACTS:HomeCity"; +const SYNC_POOMCONTACTS_HOMECOUNTRY = "POOMCONTACTS:HomeCountry"; +const SYNC_POOMCONTACTS_HOMEPOSTALCODE = "POOMCONTACTS:HomePostalCode"; +const SYNC_POOMCONTACTS_HOMESTATE = "POOMCONTACTS:HomeState"; +const SYNC_POOMCONTACTS_HOMESTREET = "POOMCONTACTS:HomeStreet"; +const SYNC_POOMCONTACTS_HOMEFAXNUMBER = "POOMCONTACTS:HomeFaxNumber"; +const SYNC_POOMCONTACTS_HOMEPHONENUMBER = "POOMCONTACTS:HomePhoneNumber"; +const SYNC_POOMCONTACTS_JOBTITLE = "POOMCONTACTS:JobTitle"; +const SYNC_POOMCONTACTS_LASTNAME = "POOMCONTACTS:LastName"; +const SYNC_POOMCONTACTS_MIDDLENAME = "POOMCONTACTS:MiddleName"; +const SYNC_POOMCONTACTS_MOBILEPHONENUMBER = "POOMCONTACTS:MobilePhoneNumber"; +const SYNC_POOMCONTACTS_OFFICELOCATION = "POOMCONTACTS:OfficeLocation"; +const SYNC_POOMCONTACTS_OTHERCITY = "POOMCONTACTS:OtherCity"; +const SYNC_POOMCONTACTS_OTHERCOUNTRY = "POOMCONTACTS:OtherCountry"; +const SYNC_POOMCONTACTS_OTHERPOSTALCODE = "POOMCONTACTS:OtherPostalCode"; +const SYNC_POOMCONTACTS_OTHERSTATE = "POOMCONTACTS:OtherState"; +const SYNC_POOMCONTACTS_OTHERSTREET = "POOMCONTACTS:OtherStreet"; +const SYNC_POOMCONTACTS_PAGERNUMBER = "POOMCONTACTS:PagerNumber"; +const SYNC_POOMCONTACTS_RADIOPHONENUMBER = "POOMCONTACTS:RadioPhoneNumber"; +const SYNC_POOMCONTACTS_SPOUSE = "POOMCONTACTS:Spouse"; +const SYNC_POOMCONTACTS_SUFFIX = "POOMCONTACTS:Suffix"; +const SYNC_POOMCONTACTS_TITLE = "POOMCONTACTS:Title"; +const SYNC_POOMCONTACTS_WEBPAGE = "POOMCONTACTS:WebPage"; +const SYNC_POOMCONTACTS_YOMICOMPANYNAME = "POOMCONTACTS:YomiCompanyName"; +const SYNC_POOMCONTACTS_YOMIFIRSTNAME = "POOMCONTACTS:YomiFirstName"; +const SYNC_POOMCONTACTS_YOMILASTNAME = "POOMCONTACTS:YomiLastName"; +const SYNC_POOMCONTACTS_RTF = "POOMCONTACTS:Rtf"; +const SYNC_POOMCONTACTS_PICTURE = "POOMCONTACTS:Picture"; +const SYNC_POOMCONTACTS_ALIAS = "POOMCONTACTS:Alias"; //14.0 +const SYNC_POOMCONTACTS_WEIGHEDRANK = "POOMCONTACTS:WeightedRank"; //14.0 // POOMMAIL -define("SYNC_POOMMAIL_ATTACHMENT","POOMMAIL:Attachment"); -define("SYNC_POOMMAIL_ATTACHMENTS","POOMMAIL:Attachments"); -define("SYNC_POOMMAIL_ATTNAME","POOMMAIL:AttName"); -define("SYNC_POOMMAIL_ATTSIZE","POOMMAIL:AttSize"); -define("SYNC_POOMMAIL_ATTOID","POOMMAIL:AttOid"); -define("SYNC_POOMMAIL_ATTMETHOD","POOMMAIL:AttMethod"); -define("SYNC_POOMMAIL_ATTREMOVED","POOMMAIL:AttRemoved"); -define("SYNC_POOMMAIL_BODY","POOMMAIL:Body"); -define("SYNC_POOMMAIL_BODYSIZE","POOMMAIL:BodySize"); -define("SYNC_POOMMAIL_BODYTRUNCATED","POOMMAIL:BodyTruncated"); -define("SYNC_POOMMAIL_DATERECEIVED","POOMMAIL:DateReceived"); -define("SYNC_POOMMAIL_DISPLAYNAME","POOMMAIL:DisplayName"); -define("SYNC_POOMMAIL_DISPLAYTO","POOMMAIL:DisplayTo"); -define("SYNC_POOMMAIL_IMPORTANCE","POOMMAIL:Importance"); -define("SYNC_POOMMAIL_MESSAGECLASS","POOMMAIL:MessageClass"); -define("SYNC_POOMMAIL_SUBJECT","POOMMAIL:Subject"); -define("SYNC_POOMMAIL_READ","POOMMAIL:Read"); -define("SYNC_POOMMAIL_TO","POOMMAIL:To"); -define("SYNC_POOMMAIL_CC","POOMMAIL:Cc"); -define("SYNC_POOMMAIL_FROM","POOMMAIL:From"); -define("SYNC_POOMMAIL_REPLY_TO","POOMMAIL:Reply-To"); -define("SYNC_POOMMAIL_ALLDAYEVENT","POOMMAIL:AllDayEvent"); -define("SYNC_POOMMAIL_CATEGORIES","POOMMAIL:Categories"); //not supported in 12.1 -define("SYNC_POOMMAIL_CATEGORY","POOMMAIL:Category"); //not supported in 12.1 -define("SYNC_POOMMAIL_DTSTAMP","POOMMAIL:DtStamp"); -define("SYNC_POOMMAIL_ENDTIME","POOMMAIL:EndTime"); -define("SYNC_POOMMAIL_INSTANCETYPE","POOMMAIL:InstanceType"); -define("SYNC_POOMMAIL_BUSYSTATUS","POOMMAIL:BusyStatus"); -define("SYNC_POOMMAIL_LOCATION","POOMMAIL:Location"); -define("SYNC_POOMMAIL_MEETINGREQUEST","POOMMAIL:MeetingRequest"); -define("SYNC_POOMMAIL_ORGANIZER","POOMMAIL:Organizer"); -define("SYNC_POOMMAIL_RECURRENCEID","POOMMAIL:RecurrenceId"); -define("SYNC_POOMMAIL_REMINDER","POOMMAIL:Reminder"); -define("SYNC_POOMMAIL_RESPONSEREQUESTED","POOMMAIL:ResponseRequested"); -define("SYNC_POOMMAIL_RECURRENCES","POOMMAIL:Recurrences"); -define("SYNC_POOMMAIL_RECURRENCE","POOMMAIL:Recurrence"); -define("SYNC_POOMMAIL_TYPE","POOMMAIL:Type"); -define("SYNC_POOMMAIL_UNTIL","POOMMAIL:Until"); -define("SYNC_POOMMAIL_OCCURRENCES","POOMMAIL:Occurrences"); -define("SYNC_POOMMAIL_INTERVAL","POOMMAIL:Interval"); -define("SYNC_POOMMAIL_DAYOFWEEK","POOMMAIL:DayOfWeek"); -define("SYNC_POOMMAIL_DAYOFMONTH","POOMMAIL:DayOfMonth"); -define("SYNC_POOMMAIL_WEEKOFMONTH","POOMMAIL:WeekOfMonth"); -define("SYNC_POOMMAIL_MONTHOFYEAR","POOMMAIL:MonthOfYear"); -define("SYNC_POOMMAIL_STARTTIME","POOMMAIL:StartTime"); -define("SYNC_POOMMAIL_SENSITIVITY","POOMMAIL:Sensitivity"); -define("SYNC_POOMMAIL_TIMEZONE","POOMMAIL:TimeZone"); -define("SYNC_POOMMAIL_GLOBALOBJID","POOMMAIL:GlobalObjId"); -define("SYNC_POOMMAIL_THREADTOPIC","POOMMAIL:ThreadTopic"); -define("SYNC_POOMMAIL_MIMEDATA","POOMMAIL:MIMEData"); -define("SYNC_POOMMAIL_MIMETRUNCATED","POOMMAIL:MIMETruncated"); -define("SYNC_POOMMAIL_MIMESIZE","POOMMAIL:MIMESize"); -define("SYNC_POOMMAIL_INTERNETCPID","POOMMAIL:InternetCPID"); -define("SYNC_POOMMAIL_FLAG", "POOMMAIL:Flag"); //12.0, 12.1 and 14.0 -define("SYNC_POOMMAIL_FLAGSTATUS", "POOMMAIL:FlagStatus"); //12.0, 12.1 and 14.0 -define("SYNC_POOMMAIL_CONTENTCLASS", "POOMMAIL:ContentClass"); //12.0, 12.1 and 14.0 -define("SYNC_POOMMAIL_FLAGTYPE", "POOMMAIL:FlagType"); //12.0, 12.1 and 14.0 -define("SYNC_POOMMAIL_COMPLETETIME", "POOMMAIL:CompleteTime"); //14.0 -define("SYNC_POOMMAIL_DISALLOWNEWTIMEPROPOSAL", "POOMMAIL:DisallowNewTimeProposal"); //14.0 +const SYNC_POOMMAIL_ATTACHMENT = "POOMMAIL:Attachment"; +const SYNC_POOMMAIL_ATTACHMENTS = "POOMMAIL:Attachments"; +const SYNC_POOMMAIL_ATTNAME = "POOMMAIL:AttName"; +const SYNC_POOMMAIL_ATTSIZE = "POOMMAIL:AttSize"; +const SYNC_POOMMAIL_ATTOID = "POOMMAIL:AttOid"; +const SYNC_POOMMAIL_ATTMETHOD = "POOMMAIL:AttMethod"; +const SYNC_POOMMAIL_ATTREMOVED = "POOMMAIL:AttRemoved"; +const SYNC_POOMMAIL_BODY = "POOMMAIL:Body"; +const SYNC_POOMMAIL_BODYSIZE = "POOMMAIL:BodySize"; +const SYNC_POOMMAIL_BODYTRUNCATED = "POOMMAIL:BodyTruncated"; +const SYNC_POOMMAIL_DATERECEIVED = "POOMMAIL:DateReceived"; +const SYNC_POOMMAIL_DISPLAYNAME = "POOMMAIL:DisplayName"; +const SYNC_POOMMAIL_DISPLAYTO = "POOMMAIL:DisplayTo"; +const SYNC_POOMMAIL_IMPORTANCE = "POOMMAIL:Importance"; +const SYNC_POOMMAIL_MESSAGECLASS = "POOMMAIL:MessageClass"; +const SYNC_POOMMAIL_SUBJECT = "POOMMAIL:Subject"; +const SYNC_POOMMAIL_READ = "POOMMAIL:Read"; +const SYNC_POOMMAIL_TO = "POOMMAIL:To"; +const SYNC_POOMMAIL_CC = "POOMMAIL:Cc"; +const SYNC_POOMMAIL_FROM = "POOMMAIL:From"; +const SYNC_POOMMAIL_REPLY_TO = "POOMMAIL:Reply-To"; +const SYNC_POOMMAIL_ALLDAYEVENT = "POOMMAIL:AllDayEvent"; +const SYNC_POOMMAIL_CATEGORIES = "POOMMAIL:Categories"; //not supported in 12.1 +const SYNC_POOMMAIL_CATEGORY = "POOMMAIL:Category"; //not supported in 12.1 +const SYNC_POOMMAIL_DTSTAMP = "POOMMAIL:DtStamp"; +const SYNC_POOMMAIL_ENDTIME = "POOMMAIL:EndTime"; +const SYNC_POOMMAIL_INSTANCETYPE = "POOMMAIL:InstanceType"; +const SYNC_POOMMAIL_BUSYSTATUS = "POOMMAIL:BusyStatus"; +const SYNC_POOMMAIL_LOCATION = "POOMMAIL:Location"; +const SYNC_POOMMAIL_MEETINGREQUEST = "POOMMAIL:MeetingRequest"; +const SYNC_POOMMAIL_ORGANIZER = "POOMMAIL:Organizer"; +const SYNC_POOMMAIL_RECURRENCEID = "POOMMAIL:RecurrenceId"; +const SYNC_POOMMAIL_REMINDER = "POOMMAIL:Reminder"; +const SYNC_POOMMAIL_RESPONSEREQUESTED = "POOMMAIL:ResponseRequested"; +const SYNC_POOMMAIL_RECURRENCES = "POOMMAIL:Recurrences"; +const SYNC_POOMMAIL_RECURRENCE = "POOMMAIL:Recurrence"; +const SYNC_POOMMAIL_TYPE = "POOMMAIL:Type"; +const SYNC_POOMMAIL_UNTIL = "POOMMAIL:Until"; +const SYNC_POOMMAIL_OCCURRENCES = "POOMMAIL:Occurrences"; +const SYNC_POOMMAIL_INTERVAL = "POOMMAIL:Interval"; +const SYNC_POOMMAIL_DAYOFWEEK = "POOMMAIL:DayOfWeek"; +const SYNC_POOMMAIL_DAYOFMONTH = "POOMMAIL:DayOfMonth"; +const SYNC_POOMMAIL_WEEKOFMONTH = "POOMMAIL:WeekOfMonth"; +const SYNC_POOMMAIL_MONTHOFYEAR = "POOMMAIL:MonthOfYear"; +const SYNC_POOMMAIL_STARTTIME = "POOMMAIL:StartTime"; +const SYNC_POOMMAIL_SENSITIVITY = "POOMMAIL:Sensitivity"; +const SYNC_POOMMAIL_TIMEZONE = "POOMMAIL:TimeZone"; +const SYNC_POOMMAIL_GLOBALOBJID = "POOMMAIL:GlobalObjId"; +const SYNC_POOMMAIL_THREADTOPIC = "POOMMAIL:ThreadTopic"; +const SYNC_POOMMAIL_MIMEDATA = "POOMMAIL:MIMEData"; +const SYNC_POOMMAIL_MIMETRUNCATED = "POOMMAIL:MIMETruncated"; +const SYNC_POOMMAIL_MIMESIZE = "POOMMAIL:MIMESize"; +const SYNC_POOMMAIL_INTERNETCPID = "POOMMAIL:InternetCPID"; +const SYNC_POOMMAIL_FLAG = "POOMMAIL:Flag"; //12.0, 12.1 and 14.0 +const SYNC_POOMMAIL_FLAGSTATUS = "POOMMAIL:FlagStatus"; //12.0, 12.1 and 14.0 +const SYNC_POOMMAIL_CONTENTCLASS = "POOMMAIL:ContentClass"; //12.0, 12.1 and 14.0 +const SYNC_POOMMAIL_FLAGTYPE = "POOMMAIL:FlagType"; //12.0, 12.1 and 14.0 +const SYNC_POOMMAIL_COMPLETETIME = "POOMMAIL:CompleteTime"; //14.0 +const SYNC_POOMMAIL_DISALLOWNEWTIMEPROPOSAL = "POOMMAIL:DisallowNewTimeProposal"; //14.0 // AIRNOTIFY -define("SYNC_AIRNOTIFY_NOTIFY","AirNotify:Notify"); -define("SYNC_AIRNOTIFY_NOTIFICATION","AirNotify:Notification"); -define("SYNC_AIRNOTIFY_VERSION","AirNotify:Version"); -define("SYNC_AIRNOTIFY_LIFETIME","AirNotify:Lifetime"); -define("SYNC_AIRNOTIFY_DEVICEINFO","AirNotify:DeviceInfo"); -define("SYNC_AIRNOTIFY_ENABLE","AirNotify:Enable"); -define("SYNC_AIRNOTIFY_FOLDER","AirNotify:Folder"); -define("SYNC_AIRNOTIFY_SERVERENTRYID","AirNotify:ServerEntryId"); -define("SYNC_AIRNOTIFY_DEVICEADDRESS","AirNotify:DeviceAddress"); -define("SYNC_AIRNOTIFY_VALIDCARRIERPROFILES","AirNotify:ValidCarrierProfiles"); -define("SYNC_AIRNOTIFY_CARRIERPROFILE","AirNotify:CarrierProfile"); -define("SYNC_AIRNOTIFY_STATUS","AirNotify:Status"); -define("SYNC_AIRNOTIFY_REPLIES","AirNotify:Replies"); -define("SYNC_AIRNOTIFY_VERSION='1.1'","AirNotify:Version='1.1'"); -define("SYNC_AIRNOTIFY_DEVICES","AirNotify:Devices"); -define("SYNC_AIRNOTIFY_DEVICE","AirNotify:Device"); -define("SYNC_AIRNOTIFY_ID","AirNotify:Id"); -define("SYNC_AIRNOTIFY_EXPIRY","AirNotify:Expiry"); -define("SYNC_AIRNOTIFY_NOTIFYGUID","AirNotify:NotifyGUID"); +const SYNC_AIRNOTIFY_NOTIFY = "AirNotify:Notify"; +const SYNC_AIRNOTIFY_NOTIFICATION = "AirNotify:Notification"; +const SYNC_AIRNOTIFY_VERSION = "AirNotify:Version"; +const SYNC_AIRNOTIFY_LIFETIME = "AirNotify:Lifetime"; +const SYNC_AIRNOTIFY_DEVICEINFO = "AirNotify:DeviceInfo"; +const SYNC_AIRNOTIFY_ENABLE = "AirNotify:Enable"; +const SYNC_AIRNOTIFY_FOLDER = "AirNotify:Folder"; +const SYNC_AIRNOTIFY_SERVERENTRYID = "AirNotify:ServerEntryId"; +const SYNC_AIRNOTIFY_DEVICEADDRESS = "AirNotify:DeviceAddress"; +const SYNC_AIRNOTIFY_VALIDCARRIERPROFILES = "AirNotify:ValidCarrierProfiles"; +const SYNC_AIRNOTIFY_CARRIERPROFILE = "AirNotify:CarrierProfile"; +const SYNC_AIRNOTIFY_STATUS = "AirNotify:Status"; +const SYNC_AIRNOTIFY_REPLIES = "AirNotify:Replies"; +const SYNC_AIRNOTIFY_VERSION11 = "AirNotify:Version='1.1'"; +const SYNC_AIRNOTIFY_DEVICES = "AirNotify:Devices"; +const SYNC_AIRNOTIFY_DEVICE = "AirNotify:Device"; +const SYNC_AIRNOTIFY_ID = "AirNotify:Id"; +const SYNC_AIRNOTIFY_EXPIRY = "AirNotify:Expiry"; +const SYNC_AIRNOTIFY_NOTIFYGUID = "AirNotify:NotifyGUID"; // POOMCAL -define("SYNC_POOMCAL_TIMEZONE","POOMCAL:Timezone"); -define("SYNC_POOMCAL_ALLDAYEVENT","POOMCAL:AllDayEvent"); -define("SYNC_POOMCAL_ATTENDEES","POOMCAL:Attendees"); -define("SYNC_POOMCAL_ATTENDEE","POOMCAL:Attendee"); -define("SYNC_POOMCAL_EMAIL","POOMCAL:Email"); -define("SYNC_POOMCAL_NAME","POOMCAL:Name"); -define("SYNC_POOMCAL_BODY","POOMCAL:Body"); -define("SYNC_POOMCAL_BODYTRUNCATED","POOMCAL:BodyTruncated"); -define("SYNC_POOMCAL_BUSYSTATUS","POOMCAL:BusyStatus"); -define("SYNC_POOMCAL_CATEGORIES","POOMCAL:Categories"); -define("SYNC_POOMCAL_CATEGORY","POOMCAL:Category"); -define("SYNC_POOMCAL_RTF","POOMCAL:Rtf"); -define("SYNC_POOMCAL_DTSTAMP","POOMCAL:DtStamp"); -define("SYNC_POOMCAL_ENDTIME","POOMCAL:EndTime"); -define("SYNC_POOMCAL_EXCEPTION","POOMCAL:Exception"); -define("SYNC_POOMCAL_EXCEPTIONS","POOMCAL:Exceptions"); -define("SYNC_POOMCAL_DELETED","POOMCAL:Deleted"); -define("SYNC_POOMCAL_EXCEPTIONSTARTTIME","POOMCAL:ExceptionStartTime"); -define("SYNC_POOMCAL_LOCATION","POOMCAL:Location"); -define("SYNC_POOMCAL_MEETINGSTATUS","POOMCAL:MeetingStatus"); -define("SYNC_POOMCAL_ORGANIZEREMAIL","POOMCAL:OrganizerEmail"); -define("SYNC_POOMCAL_ORGANIZERNAME","POOMCAL:OrganizerName"); -define("SYNC_POOMCAL_RECURRENCE","POOMCAL:Recurrence"); -define("SYNC_POOMCAL_TYPE","POOMCAL:Type"); -define("SYNC_POOMCAL_UNTIL","POOMCAL:Until"); -define("SYNC_POOMCAL_OCCURRENCES","POOMCAL:Occurrences"); -define("SYNC_POOMCAL_INTERVAL","POOMCAL:Interval"); -define("SYNC_POOMCAL_DAYOFWEEK","POOMCAL:DayOfWeek"); -define("SYNC_POOMCAL_DAYOFMONTH","POOMCAL:DayOfMonth"); -define("SYNC_POOMCAL_WEEKOFMONTH","POOMCAL:WeekOfMonth"); -define("SYNC_POOMCAL_MONTHOFYEAR","POOMCAL:MonthOfYear"); -define("SYNC_POOMCAL_REMINDER","POOMCAL:Reminder"); -define("SYNC_POOMCAL_SENSITIVITY","POOMCAL:Sensitivity"); -define("SYNC_POOMCAL_SUBJECT","POOMCAL:Subject"); -define("SYNC_POOMCAL_STARTTIME","POOMCAL:StartTime"); -define("SYNC_POOMCAL_UID","POOMCAL:UID"); -define("SYNC_POOMCAL_ATTENDEESTATUS","POOMCAL:Attendee_Status"); //12.0, 12.1 and 14.0 -define("SYNC_POOMCAL_ATTENDEETYPE","POOMCAL:Attendee_Type"); //12.0, 12.1 and 14.0 -define("SYNC_POOMCAL_ATTACHMENT","POOMCAL:Attachment"); //12.0, 12.1 and 14.0 -define("SYNC_POOMCAL_ATTACHMENTS","POOMCAL:Attachments"); //12.0, 12.1 and 14.0 -define("SYNC_POOMCAL_ATTNAME","POOMCAL:AttName"); //12.0, 12.1 and 14.0 -define("SYNC_POOMCAL_ATTSIZE","POOMCAL:AttSize"); //12.0, 12.1 and 14.0 -define("SYNC_POOMCAL_ATTOID","POOMCAL:AttOid"); //12.0, 12.1 and 14.0 -define("SYNC_POOMCAL_ATTMETHOD","POOMCAL:AttMethod"); //12.0, 12.1 and 14.0 -define("SYNC_POOMCAL_ATTREMOVED","POOMCAL:AttRemoved"); //12.0, 12.1 and 14.0 -define("SYNC_POOMCAL_DISPLAYNAME","POOMCAL:DisplayName"); //12.0, 12.1 and 14.0 -define("SYNC_POOMCAL_DISALLOWNEWTIMEPROPOSAL","POOMCAL:DisallowNewTimeProposal"); //14.0 -define("SYNC_POOMCAL_RESPONSEREQUESTED","POOMCAL:ResponseRequested"); //14.0 -define("SYNC_POOMCAL_APPOINTMENTREPLYTIME","POOMCAL:AppointmentReplyTime"); //14.0 -define("SYNC_POOMCAL_RESPONSETYPE","POOMCAL:ResponseType"); //14.0 -define("SYNC_POOMCAL_CALENDARTYPE","POOMCAL:CalendarType"); //14.0 -define("SYNC_POOMCAL_ISLEAPMONTH","POOMCAL:IsLeapMonth"); //14.0 -define("SYNC_POOMCAL_FIRSTDAYOFWEEK","POOMCAL:FirstDayOfWeek"); //post 14.0 -define("SYNC_POOMCAL_ONLINEMEETINGINTERNALLINK","POOMCAL:OnlineMeetingInternalLink"); //post 14.0 -define("SYNC_POOMCAL_ONLINEMEETINGEXTERNALLINK","POOMCAL:OnlineMeetingExternalLink"); //post 14.0 +const SYNC_POOMCAL_TIMEZONE = "POOMCAL:Timezone"; +const SYNC_POOMCAL_ALLDAYEVENT = "POOMCAL:AllDayEvent"; +const SYNC_POOMCAL_ATTENDEES = "POOMCAL:Attendees"; +const SYNC_POOMCAL_ATTENDEE = "POOMCAL:Attendee"; +const SYNC_POOMCAL_EMAIL = "POOMCAL:Email"; +const SYNC_POOMCAL_NAME = "POOMCAL:Name"; +const SYNC_POOMCAL_BODY = "POOMCAL:Body"; +const SYNC_POOMCAL_BODYTRUNCATED = "POOMCAL:BodyTruncated"; +const SYNC_POOMCAL_BUSYSTATUS = "POOMCAL:BusyStatus"; +const SYNC_POOMCAL_CATEGORIES = "POOMCAL:Categories"; +const SYNC_POOMCAL_CATEGORY = "POOMCAL:Category"; +const SYNC_POOMCAL_RTF = "POOMCAL:Rtf"; +const SYNC_POOMCAL_DTSTAMP = "POOMCAL:DtStamp"; +const SYNC_POOMCAL_ENDTIME = "POOMCAL:EndTime"; +const SYNC_POOMCAL_EXCEPTION = "POOMCAL:Exception"; +const SYNC_POOMCAL_EXCEPTIONS = "POOMCAL:Exceptions"; +const SYNC_POOMCAL_DELETED = "POOMCAL:Deleted"; +const SYNC_POOMCAL_EXCEPTIONSTARTTIME = "POOMCAL:ExceptionStartTime"; +const SYNC_POOMCAL_LOCATION = "POOMCAL:Location"; +const SYNC_POOMCAL_MEETINGSTATUS = "POOMCAL:MeetingStatus"; +const SYNC_POOMCAL_ORGANIZEREMAIL = "POOMCAL:OrganizerEmail"; +const SYNC_POOMCAL_ORGANIZERNAME = "POOMCAL:OrganizerName"; +const SYNC_POOMCAL_RECURRENCE = "POOMCAL:Recurrence"; +const SYNC_POOMCAL_TYPE = "POOMCAL:Type"; +const SYNC_POOMCAL_UNTIL = "POOMCAL:Until"; +const SYNC_POOMCAL_OCCURRENCES = "POOMCAL:Occurrences"; +const SYNC_POOMCAL_INTERVAL = "POOMCAL:Interval"; +const SYNC_POOMCAL_DAYOFWEEK = "POOMCAL:DayOfWeek"; +const SYNC_POOMCAL_DAYOFMONTH = "POOMCAL:DayOfMonth"; +const SYNC_POOMCAL_WEEKOFMONTH = "POOMCAL:WeekOfMonth"; +const SYNC_POOMCAL_MONTHOFYEAR = "POOMCAL:MonthOfYear"; +const SYNC_POOMCAL_REMINDER = "POOMCAL:Reminder"; +const SYNC_POOMCAL_SENSITIVITY = "POOMCAL:Sensitivity"; +const SYNC_POOMCAL_SUBJECT = "POOMCAL:Subject"; +const SYNC_POOMCAL_STARTTIME = "POOMCAL:StartTime"; +const SYNC_POOMCAL_UID = "POOMCAL:UID"; +const SYNC_POOMCAL_ATTENDEESTATUS = "POOMCAL:Attendee_Status"; //12.0, 12.1 and 14.0 +const SYNC_POOMCAL_ATTENDEETYPE = "POOMCAL:Attendee_Type"; //12.0, 12.1 and 14.0 +const SYNC_POOMCAL_ATTACHMENT = "POOMCAL:Attachment"; //12.0, 12.1 and 14.0 +const SYNC_POOMCAL_ATTACHMENTS = "POOMCAL:Attachments"; //12.0, 12.1 and 14.0 +const SYNC_POOMCAL_ATTNAME = "POOMCAL:AttName"; //12.0, 12.1 and 14.0 +const SYNC_POOMCAL_ATTSIZE = "POOMCAL:AttSize"; //12.0, 12.1 and 14.0 +const SYNC_POOMCAL_ATTOID = "POOMCAL:AttOid"; //12.0, 12.1 and 14.0 +const SYNC_POOMCAL_ATTMETHOD = "POOMCAL:AttMethod"; //12.0, 12.1 and 14.0 +const SYNC_POOMCAL_ATTREMOVED = "POOMCAL:AttRemoved"; //12.0, 12.1 and 14.0 +const SYNC_POOMCAL_DISPLAYNAME = "POOMCAL:DisplayName"; //12.0, 12.1 and 14.0 +const SYNC_POOMCAL_DISALLOWNEWTIMEPROPOSAL = "POOMCAL:DisallowNewTimeProposal"; //14.0 +const SYNC_POOMCAL_RESPONSEREQUESTED = "POOMCAL:ResponseRequested"; //14.0 +const SYNC_POOMCAL_APPOINTMENTREPLYTIME = "POOMCAL:AppointmentReplyTime"; //14.0 +const SYNC_POOMCAL_RESPONSETYPE = "POOMCAL:ResponseType"; //14.0 +const SYNC_POOMCAL_CALENDARTYPE = "POOMCAL:CalendarType"; //14.0 +const SYNC_POOMCAL_ISLEAPMONTH = "POOMCAL:IsLeapMonth"; //14.0 +const SYNC_POOMCAL_FIRSTDAYOFWEEK = "POOMCAL:FirstDayOfWeek"; //post 14.0 +const SYNC_POOMCAL_ONLINEMEETINGINTERNALLINK = "POOMCAL:OnlineMeetingInternalLink"; //post 14.0 +const SYNC_POOMCAL_ONLINEMEETINGEXTERNALLINK = "POOMCAL:OnlineMeetingExternalLink"; //post 14.0 // Move -define("SYNC_MOVE_MOVES","Move:Moves"); -define("SYNC_MOVE_MOVE","Move:Move"); -define("SYNC_MOVE_SRCMSGID","Move:SrcMsgId"); -define("SYNC_MOVE_SRCFLDID","Move:SrcFldId"); -define("SYNC_MOVE_DSTFLDID","Move:DstFldId"); -define("SYNC_MOVE_RESPONSE","Move:Response"); -define("SYNC_MOVE_STATUS","Move:Status"); -define("SYNC_MOVE_DSTMSGID","Move:DstMsgId"); +const SYNC_MOVE_MOVES = "Move:Moves"; +const SYNC_MOVE_MOVE = "Move:Move"; +const SYNC_MOVE_SRCMSGID = "Move:SrcMsgId"; +const SYNC_MOVE_SRCFLDID = "Move:SrcFldId"; +const SYNC_MOVE_DSTFLDID = "Move:DstFldId"; +const SYNC_MOVE_RESPONSE = "Move:Response"; +const SYNC_MOVE_STATUS = "Move:Status"; +const SYNC_MOVE_DSTMSGID = "Move:DstMsgId"; // GetItemEstimate -define("SYNC_GETITEMESTIMATE_GETITEMESTIMATE","GetItemEstimate:GetItemEstimate"); -define("SYNC_GETITEMESTIMATE_VERSION","GetItemEstimate:Version"); -define("SYNC_GETITEMESTIMATE_FOLDERS","GetItemEstimate:Folders"); -define("SYNC_GETITEMESTIMATE_FOLDER","GetItemEstimate:Folder"); -define("SYNC_GETITEMESTIMATE_FOLDERTYPE","GetItemEstimate:FolderType"); -define("SYNC_GETITEMESTIMATE_FOLDERID","GetItemEstimate:FolderId"); -define("SYNC_GETITEMESTIMATE_DATETIME","GetItemEstimate:DateTime"); -define("SYNC_GETITEMESTIMATE_ESTIMATE","GetItemEstimate:Estimate"); -define("SYNC_GETITEMESTIMATE_RESPONSE","GetItemEstimate:Response"); -define("SYNC_GETITEMESTIMATE_STATUS","GetItemEstimate:Status"); +const SYNC_GETITEMESTIMATE_GETITEMESTIMATE = "GetItemEstimate:GetItemEstimate"; +const SYNC_GETITEMESTIMATE_VERSION = "GetItemEstimate:Version"; +const SYNC_GETITEMESTIMATE_FOLDERS = "GetItemEstimate:Folders"; +const SYNC_GETITEMESTIMATE_FOLDER = "GetItemEstimate:Folder"; +const SYNC_GETITEMESTIMATE_FOLDERTYPE = "GetItemEstimate:FolderType"; +const SYNC_GETITEMESTIMATE_FOLDERID = "GetItemEstimate:FolderId"; +const SYNC_GETITEMESTIMATE_DATETIME = "GetItemEstimate:DateTime"; +const SYNC_GETITEMESTIMATE_ESTIMATE = "GetItemEstimate:Estimate"; +const SYNC_GETITEMESTIMATE_RESPONSE = "GetItemEstimate:Response"; +const SYNC_GETITEMESTIMATE_STATUS = "GetItemEstimate:Status"; // FolderHierarchy -define("SYNC_FOLDERHIERARCHY_FOLDERS","FolderHierarchy:Folders"); -define("SYNC_FOLDERHIERARCHY_FOLDER","FolderHierarchy:Folder"); -define("SYNC_FOLDERHIERARCHY_DISPLAYNAME","FolderHierarchy:DisplayName"); -define("SYNC_FOLDERHIERARCHY_SERVERENTRYID","FolderHierarchy:ServerEntryId"); -define("SYNC_FOLDERHIERARCHY_PARENTID","FolderHierarchy:ParentId"); -define("SYNC_FOLDERHIERARCHY_TYPE","FolderHierarchy:Type"); -define("SYNC_FOLDERHIERARCHY_RESPONSE","FolderHierarchy:Response"); -define("SYNC_FOLDERHIERARCHY_STATUS","FolderHierarchy:Status"); -define("SYNC_FOLDERHIERARCHY_CONTENTCLASS","FolderHierarchy:ContentClass"); -define("SYNC_FOLDERHIERARCHY_CHANGES","FolderHierarchy:Changes"); -define("SYNC_FOLDERHIERARCHY_ADD","FolderHierarchy:Add"); -define("SYNC_FOLDERHIERARCHY_REMOVE","FolderHierarchy:Remove"); -define("SYNC_FOLDERHIERARCHY_UPDATE","FolderHierarchy:Update"); -define("SYNC_FOLDERHIERARCHY_SYNCKEY","FolderHierarchy:SyncKey"); -define("SYNC_FOLDERHIERARCHY_FOLDERCREATE","FolderHierarchy:FolderCreate"); -define("SYNC_FOLDERHIERARCHY_FOLDERDELETE","FolderHierarchy:FolderDelete"); -define("SYNC_FOLDERHIERARCHY_FOLDERUPDATE","FolderHierarchy:FolderUpdate"); -define("SYNC_FOLDERHIERARCHY_FOLDERSYNC","FolderHierarchy:FolderSync"); -define("SYNC_FOLDERHIERARCHY_COUNT","FolderHierarchy:Count"); -define("SYNC_FOLDERHIERARCHY_VERSION","FolderHierarchy:Version"); +const SYNC_FOLDERHIERARCHY_FOLDERS = "FolderHierarchy:Folders"; +const SYNC_FOLDERHIERARCHY_FOLDER = "FolderHierarchy:Folder"; +const SYNC_FOLDERHIERARCHY_DISPLAYNAME = "FolderHierarchy:DisplayName"; +const SYNC_FOLDERHIERARCHY_SERVERENTRYID = "FolderHierarchy:ServerEntryId"; +const SYNC_FOLDERHIERARCHY_PARENTID = "FolderHierarchy:ParentId"; +const SYNC_FOLDERHIERARCHY_TYPE = "FolderHierarchy:Type"; +const SYNC_FOLDERHIERARCHY_RESPONSE = "FolderHierarchy:Response"; +const SYNC_FOLDERHIERARCHY_STATUS = "FolderHierarchy:Status"; +const SYNC_FOLDERHIERARCHY_CONTENTCLASS = "FolderHierarchy:ContentClass"; +const SYNC_FOLDERHIERARCHY_CHANGES = "FolderHierarchy:Changes"; +const SYNC_FOLDERHIERARCHY_ADD = "FolderHierarchy:Add"; +const SYNC_FOLDERHIERARCHY_REMOVE = "FolderHierarchy:Remove"; +const SYNC_FOLDERHIERARCHY_UPDATE = "FolderHierarchy:Update"; +const SYNC_FOLDERHIERARCHY_SYNCKEY = "FolderHierarchy:SyncKey"; +const SYNC_FOLDERHIERARCHY_FOLDERCREATE = "FolderHierarchy:FolderCreate"; +const SYNC_FOLDERHIERARCHY_FOLDERDELETE = "FolderHierarchy:FolderDelete"; +const SYNC_FOLDERHIERARCHY_FOLDERUPDATE = "FolderHierarchy:FolderUpdate"; +const SYNC_FOLDERHIERARCHY_FOLDERSYNC = "FolderHierarchy:FolderSync"; +const SYNC_FOLDERHIERARCHY_COUNT = "FolderHierarchy:Count"; +const SYNC_FOLDERHIERARCHY_VERSION = "FolderHierarchy:Version"; // only for internal use - never to be streamed to the mobile -define("SYNC_FOLDERHIERARCHY_IGNORE_STORE","FolderHierarchy:IgnoreStore"); +const SYNC_FOLDERHIERARCHY_IGNORE_STORE = "FolderHierarchy:IgnoreStore"; // MeetingResponse -define("SYNC_MEETINGRESPONSE_CALENDARID","MeetingResponse:CalendarId"); -define("SYNC_MEETINGRESPONSE_FOLDERID","MeetingResponse:FolderId"); -define("SYNC_MEETINGRESPONSE_MEETINGRESPONSE","MeetingResponse:MeetingResponse"); -define("SYNC_MEETINGRESPONSE_REQUESTID","MeetingResponse:RequestId"); -define("SYNC_MEETINGRESPONSE_REQUEST","MeetingResponse:Request"); -define("SYNC_MEETINGRESPONSE_RESULT","MeetingResponse:Result"); -define("SYNC_MEETINGRESPONSE_STATUS","MeetingResponse:Status"); -define("SYNC_MEETINGRESPONSE_USERRESPONSE","MeetingResponse:UserResponse"); -define("SYNC_MEETINGRESPONSE_VERSION","MeetingResponse:Version"); -define("SYNC_MEETINGRESPONSE_INSTANCEID","MeetingResponse:InstanceId"); +const SYNC_MEETINGRESPONSE_CALENDARID = "MeetingResponse:CalendarId"; +const SYNC_MEETINGRESPONSE_FOLDERID = "MeetingResponse:FolderId"; +const SYNC_MEETINGRESPONSE_MEETINGRESPONSE = "MeetingResponse:MeetingResponse"; +const SYNC_MEETINGRESPONSE_REQUESTID = "MeetingResponse:RequestId"; +const SYNC_MEETINGRESPONSE_REQUEST = "MeetingResponse:Request"; +const SYNC_MEETINGRESPONSE_RESULT = "MeetingResponse:Result"; +const SYNC_MEETINGRESPONSE_STATUS = "MeetingResponse:Status"; +const SYNC_MEETINGRESPONSE_USERRESPONSE = "MeetingResponse:UserResponse"; +const SYNC_MEETINGRESPONSE_VERSION = "MeetingResponse:Version"; +const SYNC_MEETINGRESPONSE_INSTANCEID = "MeetingResponse:InstanceId"; // POOMTASKS -define("SYNC_POOMTASKS_BODY","POOMTASKS:Body"); -define("SYNC_POOMTASKS_BODYSIZE","POOMTASKS:BodySize"); -define("SYNC_POOMTASKS_BODYTRUNCATED","POOMTASKS:BodyTruncated"); -define("SYNC_POOMTASKS_CATEGORIES","POOMTASKS:Categories"); -define("SYNC_POOMTASKS_CATEGORY","POOMTASKS:Category"); -define("SYNC_POOMTASKS_COMPLETE","POOMTASKS:Complete"); -define("SYNC_POOMTASKS_DATECOMPLETED","POOMTASKS:DateCompleted"); -define("SYNC_POOMTASKS_DUEDATE","POOMTASKS:DueDate"); -define("SYNC_POOMTASKS_UTCDUEDATE","POOMTASKS:UtcDueDate"); -define("SYNC_POOMTASKS_IMPORTANCE","POOMTASKS:Importance"); -define("SYNC_POOMTASKS_RECURRENCE","POOMTASKS:Recurrence"); -define("SYNC_POOMTASKS_TYPE","POOMTASKS:Type"); -define("SYNC_POOMTASKS_START","POOMTASKS:Start"); -define("SYNC_POOMTASKS_UNTIL","POOMTASKS:Until"); -define("SYNC_POOMTASKS_OCCURRENCES","POOMTASKS:Occurrences"); -define("SYNC_POOMTASKS_INTERVAL","POOMTASKS:Interval"); -define("SYNC_POOMTASKS_DAYOFWEEK","POOMTASKS:DayOfWeek"); -define("SYNC_POOMTASKS_DAYOFMONTH","POOMTASKS:DayOfMonth"); -define("SYNC_POOMTASKS_WEEKOFMONTH","POOMTASKS:WeekOfMonth"); -define("SYNC_POOMTASKS_MONTHOFYEAR","POOMTASKS:MonthOfYear"); -define("SYNC_POOMTASKS_REGENERATE","POOMTASKS:Regenerate"); -define("SYNC_POOMTASKS_DEADOCCUR","POOMTASKS:DeadOccur"); -define("SYNC_POOMTASKS_REMINDERSET","POOMTASKS:ReminderSet"); -define("SYNC_POOMTASKS_REMINDERTIME","POOMTASKS:ReminderTime"); -define("SYNC_POOMTASKS_SENSITIVITY","POOMTASKS:Sensitivity"); -define("SYNC_POOMTASKS_STARTDATE","POOMTASKS:StartDate"); -define("SYNC_POOMTASKS_UTCSTARTDATE","POOMTASKS:UtcStartDate"); -define("SYNC_POOMTASKS_SUBJECT","POOMTASKS:Subject"); -define("SYNC_POOMTASKS_RTF","POOMTASKS:Rtf"); -define("SYNC_POOMTASKS_ORDINALDATE","POOMTASKS:OrdinalDate"); //12.0, 12.1 and 14.0 -define("SYNC_POOMTASKS_SUBORDINALDATE","POOMTASKS:SubOrdinalDate"); //12.0, 12.1 and 14.0 -define("SYNC_POOMTASKS_CALENDARTYPE","POOMTASKS:CalendarType"); //14.0 -define("SYNC_POOMTASKS_ISLEAPMONTH","POOMTASKS:IsLeapMonth"); //14.0 -define("SYNC_POOMTASKS_FIRSTDAYOFWEEK","POOMTASKS:FirstDayOfWeek"); // post 14.0 +const SYNC_POOMTASKS_BODY = "POOMTASKS:Body"; +const SYNC_POOMTASKS_BODYSIZE = "POOMTASKS:BodySize"; +const SYNC_POOMTASKS_BODYTRUNCATED = "POOMTASKS:BodyTruncated"; +const SYNC_POOMTASKS_CATEGORIES = "POOMTASKS:Categories"; +const SYNC_POOMTASKS_CATEGORY = "POOMTASKS:Category"; +const SYNC_POOMTASKS_COMPLETE = "POOMTASKS:Complete"; +const SYNC_POOMTASKS_DATECOMPLETED = "POOMTASKS:DateCompleted"; +const SYNC_POOMTASKS_DUEDATE = "POOMTASKS:DueDate"; +const SYNC_POOMTASKS_UTCDUEDATE = "POOMTASKS:UtcDueDate"; +const SYNC_POOMTASKS_IMPORTANCE = "POOMTASKS:Importance"; +const SYNC_POOMTASKS_RECURRENCE = "POOMTASKS:Recurrence"; +const SYNC_POOMTASKS_TYPE = "POOMTASKS:Type"; +const SYNC_POOMTASKS_START = "POOMTASKS:Start"; +const SYNC_POOMTASKS_UNTIL = "POOMTASKS:Until"; +const SYNC_POOMTASKS_OCCURRENCES = "POOMTASKS:Occurrences"; +const SYNC_POOMTASKS_INTERVAL = "POOMTASKS:Interval"; +const SYNC_POOMTASKS_DAYOFWEEK = "POOMTASKS:DayOfWeek"; +const SYNC_POOMTASKS_DAYOFMONTH = "POOMTASKS:DayOfMonth"; +const SYNC_POOMTASKS_WEEKOFMONTH = "POOMTASKS:WeekOfMonth"; +const SYNC_POOMTASKS_MONTHOFYEAR = "POOMTASKS:MonthOfYear"; +const SYNC_POOMTASKS_REGENERATE = "POOMTASKS:Regenerate"; +const SYNC_POOMTASKS_DEADOCCUR = "POOMTASKS:DeadOccur"; +const SYNC_POOMTASKS_REMINDERSET = "POOMTASKS:ReminderSet"; +const SYNC_POOMTASKS_REMINDERTIME = "POOMTASKS:ReminderTime"; +const SYNC_POOMTASKS_SENSITIVITY = "POOMTASKS:Sensitivity"; +const SYNC_POOMTASKS_STARTDATE = "POOMTASKS:StartDate"; +const SYNC_POOMTASKS_UTCSTARTDATE = "POOMTASKS:UtcStartDate"; +const SYNC_POOMTASKS_SUBJECT = "POOMTASKS:Subject"; +const SYNC_POOMTASKS_RTF = "POOMTASKS:Rtf"; +const SYNC_POOMTASKS_ORDINALDATE = "POOMTASKS:OrdinalDate"; //12.0, 12.1 and 14.0 +const SYNC_POOMTASKS_SUBORDINALDATE = "POOMTASKS:SubOrdinalDate"; //12.0, 12.1 and 14.0 +const SYNC_POOMTASKS_CALENDARTYPE = "POOMTASKS:CalendarType"; //14.0 +const SYNC_POOMTASKS_ISLEAPMONTH = "POOMTASKS:IsLeapMonth"; //14.0 +const SYNC_POOMTASKS_FIRSTDAYOFWEEK = "POOMTASKS:FirstDayOfWeek"; // post 14.0 // ResolveRecipients -define("SYNC_RESOLVERECIPIENTS_RESOLVERECIPIENTS","ResolveRecipients:ResolveRecipients"); -define("SYNC_RESOLVERECIPIENTS_RESPONSE","ResolveRecipients:Response"); -define("SYNC_RESOLVERECIPIENTS_STATUS","ResolveRecipients:Status"); -define("SYNC_RESOLVERECIPIENTS_TYPE","ResolveRecipients:Type"); -define("SYNC_RESOLVERECIPIENTS_RECIPIENT","ResolveRecipients:Recipient"); -define("SYNC_RESOLVERECIPIENTS_DISPLAYNAME","ResolveRecipients:DisplayName"); -define("SYNC_RESOLVERECIPIENTS_EMAILADDRESS","ResolveRecipients:EmailAddress"); -define("SYNC_RESOLVERECIPIENTS_CERTIFICATES","ResolveRecipients:Certificates"); -define("SYNC_RESOLVERECIPIENTS_CERTIFICATE","ResolveRecipients:Certificate"); -define("SYNC_RESOLVERECIPIENTS_MINICERTIFICATE","ResolveRecipients:MiniCertificate"); -define("SYNC_RESOLVERECIPIENTS_OPTIONS","ResolveRecipients:Options"); -define("SYNC_RESOLVERECIPIENTS_TO","ResolveRecipients:To"); -define("SYNC_RESOLVERECIPIENTS_CERTIFICATERETRIEVAL","ResolveRecipients:CertificateRetrieval"); -define("SYNC_RESOLVERECIPIENTS_RECIPIENTCOUNT","ResolveRecipients:RecipientCount"); -define("SYNC_RESOLVERECIPIENTS_MAXCERTIFICATES","ResolveRecipients:MaxCertificates"); -define("SYNC_RESOLVERECIPIENTS_MAXAMBIGUOUSRECIPIENTS","ResolveRecipients:MaxAmbiguousRecipients"); -define("SYNC_RESOLVERECIPIENTS_CERTIFICATECOUNT","ResolveRecipients:CertificateCount"); -define("SYNC_RESOLVERECIPIENTS_AVAILABILITY","ResolveRecipients:Availability"); //14.0 -define("SYNC_RESOLVERECIPIENTS_STARTTIME","ResolveRecipients:StartTime"); //14.0 -define("SYNC_RESOLVERECIPIENTS_ENDTIME","ResolveRecipients:EndTime"); //14.0 -define("SYNC_RESOLVERECIPIENTS_MERGEDFREEBUSY","ResolveRecipients:MergedFreeBusy"); //14.0 -define("SYNC_RESOLVERECIPIENTS_PICTURE","ResolveRecipients:Picture"); //post 14.0 -define("SYNC_RESOLVERECIPIENTS_MAXSIZE","ResolveRecipients:MaxSize"); //post 14.0 -define("SYNC_RESOLVERECIPIENTS_DATA","ResolveRecipients:Data"); //post 14.0 -define("SYNC_RESOLVERECIPIENTS_MAXPICTURES","ResolveRecipients:MaxPictures"); //post 14.0 +const SYNC_RESOLVERECIPIENTS_RESOLVERECIPIENTS = "ResolveRecipients:ResolveRecipients"; +const SYNC_RESOLVERECIPIENTS_RESPONSE = "ResolveRecipients:Response"; +const SYNC_RESOLVERECIPIENTS_STATUS = "ResolveRecipients:Status"; +const SYNC_RESOLVERECIPIENTS_TYPE = "ResolveRecipients:Type"; +const SYNC_RESOLVERECIPIENTS_RECIPIENT = "ResolveRecipients:Recipient"; +const SYNC_RESOLVERECIPIENTS_DISPLAYNAME = "ResolveRecipients:DisplayName"; +const SYNC_RESOLVERECIPIENTS_EMAILADDRESS = "ResolveRecipients:EmailAddress"; +const SYNC_RESOLVERECIPIENTS_CERTIFICATES = "ResolveRecipients:Certificates"; +const SYNC_RESOLVERECIPIENTS_CERTIFICATE = "ResolveRecipients:Certificate"; +const SYNC_RESOLVERECIPIENTS_MINICERTIFICATE = "ResolveRecipients:MiniCertificate"; +const SYNC_RESOLVERECIPIENTS_OPTIONS = "ResolveRecipients:Options"; +const SYNC_RESOLVERECIPIENTS_TO = "ResolveRecipients:To"; +const SYNC_RESOLVERECIPIENTS_CERTIFICATERETRIEVAL = "ResolveRecipients:CertificateRetrieval"; +const SYNC_RESOLVERECIPIENTS_RECIPIENTCOUNT = "ResolveRecipients:RecipientCount"; +const SYNC_RESOLVERECIPIENTS_MAXCERTIFICATES = "ResolveRecipients:MaxCertificates"; +const SYNC_RESOLVERECIPIENTS_MAXAMBIGUOUSRECIPIENTS = "ResolveRecipients:MaxAmbiguousRecipients"; +const SYNC_RESOLVERECIPIENTS_CERTIFICATECOUNT = "ResolveRecipients:CertificateCount"; +const SYNC_RESOLVERECIPIENTS_AVAILABILITY = "ResolveRecipients:Availability"; //14.0 +const SYNC_RESOLVERECIPIENTS_STARTTIME = "ResolveRecipients:StartTime"; //14.0 +const SYNC_RESOLVERECIPIENTS_ENDTIME = "ResolveRecipients:EndTime"; //14.0 +const SYNC_RESOLVERECIPIENTS_MERGEDFREEBUSY = "ResolveRecipients:MergedFreeBusy"; //14.0 +const SYNC_RESOLVERECIPIENTS_PICTURE = "ResolveRecipients:Picture"; //post 14.0 +const SYNC_RESOLVERECIPIENTS_MAXSIZE = "ResolveRecipients:MaxSize"; //post 14.0 +const SYNC_RESOLVERECIPIENTS_DATA = "ResolveRecipients:Data"; //post 14.0 +const SYNC_RESOLVERECIPIENTS_MAXPICTURES = "ResolveRecipients:MaxPictures"; //post 14.0 // ValidateCert -define("SYNC_VALIDATECERT_VALIDATECERT","ValidateCert:ValidateCert"); -define("SYNC_VALIDATECERT_CERTIFICATES","ValidateCert:Certificates"); -define("SYNC_VALIDATECERT_CERTIFICATE","ValidateCert:Certificate"); -define("SYNC_VALIDATECERT_CERTIFICATECHAIN","ValidateCert:CertificateChain"); -define("SYNC_VALIDATECERT_CHECKCRL","ValidateCert:CheckCRL"); -define("SYNC_VALIDATECERT_STATUS","ValidateCert:Status"); +const SYNC_VALIDATECERT_VALIDATECERT = "ValidateCert:ValidateCert"; +const SYNC_VALIDATECERT_CERTIFICATES = "ValidateCert:Certificates"; +const SYNC_VALIDATECERT_CERTIFICATE = "ValidateCert:Certificate"; +const SYNC_VALIDATECERT_CERTIFICATECHAIN = "ValidateCert:CertificateChain"; +const SYNC_VALIDATECERT_CHECKCRL = "ValidateCert:CheckCRL"; +const SYNC_VALIDATECERT_STATUS = "ValidateCert:Status"; // POOMCONTACTS2 -define("SYNC_POOMCONTACTS2_CUSTOMERID","POOMCONTACTS2:CustomerId"); -define("SYNC_POOMCONTACTS2_GOVERNMENTID","POOMCONTACTS2:GovernmentId"); -define("SYNC_POOMCONTACTS2_IMADDRESS","POOMCONTACTS2:IMAddress"); -define("SYNC_POOMCONTACTS2_IMADDRESS2","POOMCONTACTS2:IMAddress2"); -define("SYNC_POOMCONTACTS2_IMADDRESS3","POOMCONTACTS2:IMAddress3"); -define("SYNC_POOMCONTACTS2_MANAGERNAME","POOMCONTACTS2:ManagerName"); -define("SYNC_POOMCONTACTS2_COMPANYMAINPHONE","POOMCONTACTS2:CompanyMainPhone"); -define("SYNC_POOMCONTACTS2_ACCOUNTNAME","POOMCONTACTS2:AccountName"); -define("SYNC_POOMCONTACTS2_NICKNAME","POOMCONTACTS2:NickName"); -define("SYNC_POOMCONTACTS2_MMS","POOMCONTACTS2:MMS"); +const SYNC_POOMCONTACTS2_CUSTOMERID = "POOMCONTACTS2:CustomerId"; +const SYNC_POOMCONTACTS2_GOVERNMENTID = "POOMCONTACTS2:GovernmentId"; +const SYNC_POOMCONTACTS2_IMADDRESS = "POOMCONTACTS2:IMAddress"; +const SYNC_POOMCONTACTS2_IMADDRESS2 = "POOMCONTACTS2:IMAddress2"; +const SYNC_POOMCONTACTS2_IMADDRESS3 = "POOMCONTACTS2:IMAddress3"; +const SYNC_POOMCONTACTS2_MANAGERNAME = "POOMCONTACTS2:ManagerName"; +const SYNC_POOMCONTACTS2_COMPANYMAINPHONE = "POOMCONTACTS2:CompanyMainPhone"; +const SYNC_POOMCONTACTS2_ACCOUNTNAME = "POOMCONTACTS2:AccountName"; +const SYNC_POOMCONTACTS2_NICKNAME = "POOMCONTACTS2:NickName"; +const SYNC_POOMCONTACTS2_MMS = "POOMCONTACTS2:MMS"; // Ping -define("SYNC_PING_PING","Ping:Ping"); -define("SYNC_PING_STATUS","Ping:Status"); -define("SYNC_PING_LIFETIME", "Ping:LifeTime"); -define("SYNC_PING_FOLDERS", "Ping:Folders"); -define("SYNC_PING_FOLDER", "Ping:Folder"); -define("SYNC_PING_SERVERENTRYID", "Ping:ServerEntryId"); -define("SYNC_PING_FOLDERTYPE", "Ping:FolderType"); -define("SYNC_PING_MAXFOLDERS", "Ping:MaxFolders"); //missing in < z-push 2 -define("SYNC_PING_VERSION", "Ping:Version"); //missing in < z-push 2 +const SYNC_PING_PING = "Ping:Ping"; +const SYNC_PING_STATUS = "Ping:Status"; +const SYNC_PING_LIFETIME = "Ping:LifeTime"; +const SYNC_PING_FOLDERS = "Ping:Folders"; +const SYNC_PING_FOLDER = "Ping:Folder"; +const SYNC_PING_SERVERENTRYID = "Ping:ServerEntryId"; +const SYNC_PING_FOLDERTYPE = "Ping:FolderType"; +const SYNC_PING_MAXFOLDERS = "Ping:MaxFolders"; //missing in < z-push 2 +const SYNC_PING_VERSION = "Ping:Version"; //missing in < z-push 2 //Provision -define("SYNC_PROVISION_PROVISION", "Provision:Provision"); -define("SYNC_PROVISION_POLICIES", "Provision:Policies"); -define("SYNC_PROVISION_POLICY", "Provision:Policy"); -define("SYNC_PROVISION_POLICYTYPE", "Provision:PolicyType"); -define("SYNC_PROVISION_POLICYKEY", "Provision:PolicyKey"); -define("SYNC_PROVISION_DATA", "Provision:Data"); -define("SYNC_PROVISION_STATUS", "Provision:Status"); -define("SYNC_PROVISION_REMOTEWIPE", "Provision:RemoteWipe"); -define("SYNC_PROVISION_EASPROVISIONDOC", "Provision:EASProvisionDoc"); -define("SYNC_PROVISION_DEVPWENABLED", "Provision:DevicePasswordEnabled"); -define("SYNC_PROVISION_ALPHANUMPWREQ", "Provision:AlphanumericDevicePasswordRequired"); -define("SYNC_PROVISION_DEVENCENABLED", "Provision:DeviceEncryptionEnabled"); -define("SYNC_PROVISION_REQSTORAGECARDENC", "Provision:RequireStorageCardEncryption"); -define("SYNC_PROVISION_PWRECOVERYENABLED", "Provision:PasswordRecoveryEnabled"); -define("SYNC_PROVISION_DOCBROWSEENABLED", "Provision:DocumentBrowseEnabled"); -define("SYNC_PROVISION_ATTENABLED", "Provision:AttachmentsEnabled"); -define("SYNC_PROVISION_MINDEVPWLENGTH", "Provision:MinDevicePasswordLength"); -define("SYNC_PROVISION_MAXINACTTIMEDEVLOCK", "Provision:MaxInactivityTimeDeviceLock"); -define("SYNC_PROVISION_MAXDEVPWFAILEDATTEMPTS", "Provision:MaxDevicePasswordFailedAttempts"); -define("SYNC_PROVISION_MAXATTSIZE", "Provision:MaxAttachmentSize"); -define("SYNC_PROVISION_ALLOWSIMPLEDEVPW", "Provision:AllowSimpleDevicePassword"); -define("SYNC_PROVISION_DEVPWEXPIRATION", "Provision:DevicePasswordExpiration"); -define("SYNC_PROVISION_DEVPWHISTORY", "Provision:DevicePasswordHistory"); -define("SYNC_PROVISION_ALLOWSTORAGECARD", "Provision:AllowStorageCard"); -define("SYNC_PROVISION_ALLOWCAM", "Provision:AllowCamera"); -define("SYNC_PROVISION_REQDEVENC", "Provision:RequireDeviceEncryption"); -define("SYNC_PROVISION_ALLOWUNSIGNEDAPPS", "Provision:AllowUnsignedApplications"); -define("SYNC_PROVISION_ALLOWUNSIGNEDINSTALLATIONPACKAGES", "Provision:AllowUnsignedInstallationPackages"); -define("SYNC_PROVISION_MINDEVPWCOMPLEXCHARS", "Provision:MinDevicePasswordComplexCharacters"); -define("SYNC_PROVISION_ALLOWWIFI", "Provision:AllowWiFi"); -define("SYNC_PROVISION_ALLOWTEXTMESSAGING", "Provision:AllowTextMessaging"); -define("SYNC_PROVISION_ALLOWPOPIMAPEMAIL", "Provision:AllowPOPIMAPEmail"); -define("SYNC_PROVISION_ALLOWBLUETOOTH", "Provision:AllowBluetooth"); -define("SYNC_PROVISION_ALLOWIRDA", "Provision:AllowIrDA"); -define("SYNC_PROVISION_REQMANUALSYNCWHENROAM", "Provision:RequireManualSyncWhenRoaming"); -define("SYNC_PROVISION_ALLOWDESKTOPSYNC", "Provision:AllowDesktopSync"); -define("SYNC_PROVISION_MAXCALAGEFILTER", "Provision:MaxCalendarAgeFilter"); -define("SYNC_PROVISION_ALLOWHTMLEMAIL", "Provision:AllowHTMLEmail"); -define("SYNC_PROVISION_MAXEMAILAGEFILTER", "Provision:MaxEmailAgeFilter"); -define("SYNC_PROVISION_MAXEMAILBODYTRUNCSIZE", "Provision:MaxEmailBodyTruncationSize"); -define("SYNC_PROVISION_MAXEMAILHTMLBODYTRUNCSIZE", "Provision:MaxEmailHTMLBodyTruncationSize"); -define("SYNC_PROVISION_REQSIGNEDSMIMEMESSAGES", "Provision:RequireSignedSMIMEMessages"); -define("SYNC_PROVISION_REQENCSMIMEMESSAGES", "Provision:RequireEncryptedSMIMEMessages"); -define("SYNC_PROVISION_REQSIGNEDSMIMEALGORITHM", "Provision:RequireSignedSMIMEAlgorithm"); -define("SYNC_PROVISION_REQENCSMIMEALGORITHM", "Provision:RequireEncryptionSMIMEAlgorithm"); -define("SYNC_PROVISION_ALLOWSMIMEENCALGORITHNEG", "Provision:AllowSMIMEEncryptionAlgorithmNegotiation"); -define("SYNC_PROVISION_ALLOWSMIMESOFTCERTS", "Provision:AllowSMIMESoftCerts"); -define("SYNC_PROVISION_ALLOWBROWSER", "Provision:AllowBrowser"); -define("SYNC_PROVISION_ALLOWCONSUMEREMAIL", "Provision:AllowConsumerEmail"); -define("SYNC_PROVISION_ALLOWREMOTEDESKTOP", "Provision:AllowRemoteDesktop"); -define("SYNC_PROVISION_ALLOWINTERNETSHARING", "Provision:AllowInternetSharing"); -define("SYNC_PROVISION_UNAPPROVEDINROMAPPLIST", "Provision:UnapprovedInROMApplicationList"); -define("SYNC_PROVISION_APPNAME", "Provision:ApplicationName"); -define("SYNC_PROVISION_APPROVEDAPPLIST", "Provision:ApprovedApplicationList"); -define("SYNC_PROVISION_HASH", "Provision:Hash"); - +const SYNC_PROVISION_PROVISION = "Provision:Provision"; +const SYNC_PROVISION_POLICIES = "Provision:Policies"; +const SYNC_PROVISION_POLICY = "Provision:Policy"; +const SYNC_PROVISION_POLICYTYPE = "Provision:PolicyType"; +const SYNC_PROVISION_POLICYKEY = "Provision:PolicyKey"; +const SYNC_PROVISION_DATA = "Provision:Data"; +const SYNC_PROVISION_STATUS = "Provision:Status"; +const SYNC_PROVISION_REMOTEWIPE = "Provision:RemoteWipe"; +const SYNC_PROVISION_EASPROVISIONDOC = "Provision:EASProvisionDoc"; +const SYNC_PROVISION_DEVPWENABLED = "Provision:DevicePasswordEnabled"; +const SYNC_PROVISION_ALPHANUMPWREQ = "Provision:AlphanumericDevicePasswordRequired"; +const SYNC_PROVISION_DEVENCENABLED = "Provision:DeviceEncryptionEnabled"; +const SYNC_PROVISION_REQSTORAGECARDENC = "Provision:RequireStorageCardEncryption"; +const SYNC_PROVISION_PWRECOVERYENABLED = "Provision:PasswordRecoveryEnabled"; +const SYNC_PROVISION_DOCBROWSEENABLED = "Provision:DocumentBrowseEnabled"; +const SYNC_PROVISION_ATTENABLED = "Provision:AttachmentsEnabled"; +const SYNC_PROVISION_MINDEVPWLENGTH = "Provision:MinDevicePasswordLength"; +const SYNC_PROVISION_MAXINACTTIMEDEVLOCK = "Provision:MaxInactivityTimeDeviceLock"; +const SYNC_PROVISION_MAXDEVPWFAILEDATTEMPTS = "Provision:MaxDevicePasswordFailedAttempts"; +const SYNC_PROVISION_MAXATTSIZE = "Provision:MaxAttachmentSize"; +const SYNC_PROVISION_ALLOWSIMPLEDEVPW = "Provision:AllowSimpleDevicePassword"; +const SYNC_PROVISION_DEVPWEXPIRATION = "Provision:DevicePasswordExpiration"; +const SYNC_PROVISION_DEVPWHISTORY = "Provision:DevicePasswordHistory"; +const SYNC_PROVISION_ALLOWSTORAGECARD = "Provision:AllowStorageCard"; +const SYNC_PROVISION_ALLOWCAM = "Provision:AllowCamera"; +const SYNC_PROVISION_REQDEVENC = "Provision:RequireDeviceEncryption"; +const SYNC_PROVISION_ALLOWUNSIGNEDAPPS = "Provision:AllowUnsignedApplications"; +const SYNC_PROVISION_ALLOWUNSIGNEDINSTALLATIONPACKAGES = "Provision:AllowUnsignedInstallationPackages"; +const SYNC_PROVISION_MINDEVPWCOMPLEXCHARS = "Provision:MinDevicePasswordComplexCharacters"; +const SYNC_PROVISION_ALLOWWIFI = "Provision:AllowWiFi"; +const SYNC_PROVISION_ALLOWTEXTMESSAGING = "Provision:AllowTextMessaging"; +const SYNC_PROVISION_ALLOWPOPIMAPEMAIL = "Provision:AllowPOPIMAPEmail"; +const SYNC_PROVISION_ALLOWBLUETOOTH = "Provision:AllowBluetooth"; +const SYNC_PROVISION_ALLOWIRDA = "Provision:AllowIrDA"; +const SYNC_PROVISION_REQMANUALSYNCWHENROAM = "Provision:RequireManualSyncWhenRoaming"; +const SYNC_PROVISION_ALLOWDESKTOPSYNC = "Provision:AllowDesktopSync"; +const SYNC_PROVISION_MAXCALAGEFILTER = "Provision:MaxCalendarAgeFilter"; +const SYNC_PROVISION_ALLOWHTMLEMAIL = "Provision:AllowHTMLEmail"; +const SYNC_PROVISION_MAXEMAILAGEFILTER = "Provision:MaxEmailAgeFilter"; +const SYNC_PROVISION_MAXEMAILBODYTRUNCSIZE = "Provision:MaxEmailBodyTruncationSize"; +const SYNC_PROVISION_MAXEMAILHTMLBODYTRUNCSIZE = "Provision:MaxEmailHTMLBodyTruncationSize"; +const SYNC_PROVISION_REQSIGNEDSMIMEMESSAGES = "Provision:RequireSignedSMIMEMessages"; +const SYNC_PROVISION_REQENCSMIMEMESSAGES = "Provision:RequireEncryptedSMIMEMessages"; +const SYNC_PROVISION_REQSIGNEDSMIMEALGORITHM = "Provision:RequireSignedSMIMEAlgorithm"; +const SYNC_PROVISION_REQENCSMIMEALGORITHM = "Provision:RequireEncryptionSMIMEAlgorithm"; +const SYNC_PROVISION_ALLOWSMIMEENCALGORITHNEG = "Provision:AllowSMIMEEncryptionAlgorithmNegotiation"; +const SYNC_PROVISION_ALLOWSMIMESOFTCERTS = "Provision:AllowSMIMESoftCerts"; +const SYNC_PROVISION_ALLOWBROWSER = "Provision:AllowBrowser"; +const SYNC_PROVISION_ALLOWCONSUMEREMAIL = "Provision:AllowConsumerEmail"; +const SYNC_PROVISION_ALLOWREMOTEDESKTOP = "Provision:AllowRemoteDesktop"; +const SYNC_PROVISION_ALLOWINTERNETSHARING = "Provision:AllowInternetSharing"; +const SYNC_PROVISION_UNAPPROVEDINROMAPPLIST = "Provision:UnapprovedInROMApplicationList"; +const SYNC_PROVISION_APPNAME = "Provision:ApplicationName"; +const SYNC_PROVISION_APPROVEDAPPLIST = "Provision:ApprovedApplicationList"; +const SYNC_PROVISION_HASH = "Provision:Hash"; //Search -define("SYNC_SEARCH_SEARCH", "Search:Search"); -define("SYNC_SEARCH_STORE", "Search:Store"); -define("SYNC_SEARCH_NAME", "Search:Name"); -define("SYNC_SEARCH_QUERY", "Search:Query"); -define("SYNC_SEARCH_OPTIONS", "Search:Options"); -define("SYNC_SEARCH_RANGE", "Search:Range"); -define("SYNC_SEARCH_STATUS", "Search:Status"); -define("SYNC_SEARCH_RESPONSE", "Search:Response"); -define("SYNC_SEARCH_RESULT", "Search:Result"); -define("SYNC_SEARCH_PROPERTIES", "Search:Properties"); -define("SYNC_SEARCH_TOTAL", "Search:Total"); -define("SYNC_SEARCH_EQUALTO", "Search:EqualTo"); -define("SYNC_SEARCH_VALUE", "Search:Value"); -define("SYNC_SEARCH_AND", "Search:And"); -define("SYNC_SEARCH_OR", "Search:Or"); -define("SYNC_SEARCH_FREETEXT", "Search:FreeText"); -define("SYNC_SEARCH_DEEPTRAVERSAL", "Search:DeepTraversal"); -define("SYNC_SEARCH_LONGID", "Search:LongId"); -define("SYNC_SEARCH_REBUILDRESULTS", "Search:RebuildResults"); -define("SYNC_SEARCH_LESSTHAN", "Search:LessThan"); -define("SYNC_SEARCH_GREATERTHAN", "Search:GreaterThan"); -define("SYNC_SEARCH_SCHEMA", "Search:Schema"); -define("SYNC_SEARCH_SUPPORTED", "Search:Supported"); -define("SYNC_SEARCH_USERNAME", "Search:UserName"); //12.1 and 14.0 -define("SYNC_SEARCH_PASSWORD", "Search:Password"); //12.1 and 14.0 -define("SYNC_SEARCH_CONVERSATIONID", "Search:ConversationId"); //14.0 -define("SYNC_SEARCH_PICTURE","Search:Picture"); //post 14.0 -define("SYNC_SEARCH_MAXSIZE","Search:MaxSize"); //post 14.0 -define("SYNC_SEARCH_MAXPICTURES","Search:MaxPictures"); //post 14.0 +const SYNC_SEARCH_SEARCH = "Search:Search"; +const SYNC_SEARCH_STORE = "Search:Store"; +const SYNC_SEARCH_NAME = "Search:Name"; +const SYNC_SEARCH_QUERY = "Search:Query"; +const SYNC_SEARCH_OPTIONS = "Search:Options"; +const SYNC_SEARCH_RANGE = "Search:Range"; +const SYNC_SEARCH_STATUS = "Search:Status"; +const SYNC_SEARCH_RESPONSE = "Search:Response"; +const SYNC_SEARCH_RESULT = "Search:Result"; +const SYNC_SEARCH_PROPERTIES = "Search:Properties"; +const SYNC_SEARCH_TOTAL = "Search:Total"; +const SYNC_SEARCH_EQUALTO = "Search:EqualTo"; +const SYNC_SEARCH_VALUE = "Search:Value"; +const SYNC_SEARCH_AND = "Search:And"; +const SYNC_SEARCH_OR = "Search:Or"; +const SYNC_SEARCH_FREETEXT = "Search:FreeText"; +const SYNC_SEARCH_DEEPTRAVERSAL = "Search:DeepTraversal"; +const SYNC_SEARCH_LONGID = "Search:LongId"; +const SYNC_SEARCH_REBUILDRESULTS = "Search:RebuildResults"; +const SYNC_SEARCH_LESSTHAN = "Search:LessThan"; +const SYNC_SEARCH_GREATERTHAN = "Search:GreaterThan"; +const SYNC_SEARCH_SCHEMA = "Search:Schema"; +const SYNC_SEARCH_SUPPORTED = "Search:Supported"; +const SYNC_SEARCH_USERNAME = "Search:UserName"; //12.1 and 14.0 +const SYNC_SEARCH_PASSWORD = "Search:Password"; //12.1 and 14.0 +const SYNC_SEARCH_CONVERSATIONID = "Search:ConversationId"; //14.0 +const SYNC_SEARCH_PICTURE = "Search:Picture"; //post 14.0 +const SYNC_SEARCH_MAXSIZE = "Search:MaxSize"; //post 14.0 +const SYNC_SEARCH_MAXPICTURES = "Search:MaxPictures"; //post 14.0 //GAL -define("SYNC_GAL_DISPLAYNAME", "GAL:DisplayName"); -define("SYNC_GAL_PHONE", "GAL:Phone"); -define("SYNC_GAL_OFFICE", "GAL:Office"); -define("SYNC_GAL_TITLE", "GAL:Title"); -define("SYNC_GAL_COMPANY", "GAL:Company"); -define("SYNC_GAL_ALIAS", "GAL:Alias"); -define("SYNC_GAL_FIRSTNAME", "GAL:FirstName"); -define("SYNC_GAL_LASTNAME", "GAL:LastName"); -define("SYNC_GAL_HOMEPHONE", "GAL:HomePhone"); -define("SYNC_GAL_MOBILEPHONE", "GAL:MobilePhone"); -define("SYNC_GAL_EMAILADDRESS", "GAL:EmailAddress"); -define("SYNC_GAL_PICTURE","GAL:Picture"); //post 14.0 -define("SYNC_GAL_MAXSIZE","GAL:Status"); //post 14.0 -define("SYNC_GAL_DATA","GAL:Data"); //post 14.0 +const SYNC_GAL_DISPLAYNAME = "GAL:DisplayName"; +const SYNC_GAL_PHONE = "GAL:Phone"; +const SYNC_GAL_OFFICE = "GAL:Office"; +const SYNC_GAL_TITLE = "GAL:Title"; +const SYNC_GAL_COMPANY = "GAL:Company"; +const SYNC_GAL_ALIAS = "GAL:Alias"; +const SYNC_GAL_FIRSTNAME = "GAL:FirstName"; +const SYNC_GAL_LASTNAME = "GAL:LastName"; +const SYNC_GAL_HOMEPHONE = "GAL:HomePhone"; +const SYNC_GAL_MOBILEPHONE = "GAL:MobilePhone"; +const SYNC_GAL_EMAILADDRESS = "GAL:EmailAddress"; +const SYNC_GAL_PICTURE = "GAL:Picture"; //post 14.0 +const SYNC_GAL_MAXSIZE = "GAL:Status"; //post 14.0 +const SYNC_GAL_DATA = "GAL:Data"; //post 14.0 //AirSyncBase //12.0, 12.1 and 14.0 -define("SYNC_AIRSYNCBASE_BODYPREFERENCE", "AirSyncBase:BodyPreference"); -define("SYNC_AIRSYNCBASE_TYPE", "AirSyncBase:Type"); -define("SYNC_AIRSYNCBASE_TRUNCATIONSIZE", "AirSyncBase:TruncationSize"); -define("SYNC_AIRSYNCBASE_ALLORNONE", "AirSyncBase:AllOrNone"); -define("SYNC_AIRSYNCBASE_BODY", "AirSyncBase:Body"); -define("SYNC_AIRSYNCBASE_DATA", "AirSyncBase:Data"); -define("SYNC_AIRSYNCBASE_ESTIMATEDDATASIZE", "AirSyncBase:EstimatedDataSize"); -define("SYNC_AIRSYNCBASE_TRUNCATED", "AirSyncBase:Truncated"); -define("SYNC_AIRSYNCBASE_ATTACHMENTS", "AirSyncBase:Attachments"); -define("SYNC_AIRSYNCBASE_ATTACHMENT", "AirSyncBase:Attachment"); -define("SYNC_AIRSYNCBASE_DISPLAYNAME", "AirSyncBase:DisplayName"); -define("SYNC_AIRSYNCBASE_FILEREFERENCE", "AirSyncBase:FileReference"); -define("SYNC_AIRSYNCBASE_METHOD", "AirSyncBase:Method"); -define("SYNC_AIRSYNCBASE_CONTENTID", "AirSyncBase:ContentId"); -define("SYNC_AIRSYNCBASE_CONTENTLOCATION", "AirSyncBase:ContentLocation"); //not used -define("SYNC_AIRSYNCBASE_ISINLINE", "AirSyncBase:IsInline"); -define("SYNC_AIRSYNCBASE_NATIVEBODYTYPE", "AirSyncBase:NativeBodyType"); -define("SYNC_AIRSYNCBASE_CONTENTTYPE", "AirSyncBase:ContentType"); -define("SYNC_AIRSYNCBASE_PREVIEW", "AirSyncBase:Preview"); //14.0 -define("SYNC_AIRSYNCBASE_BODYPARTPREFERENCE", "AirSyncBase:BodyPartPreference"); //post 14.0 -define("SYNC_AIRSYNCBASE_BODYPART", "AirSyncBase:BodyPart"); //post 14.0 -define("SYNC_AIRSYNCBASE_STATUS", "AirSyncBase:Status"); //post 14.0 +const SYNC_AIRSYNCBASE_BODYPREFERENCE = "AirSyncBase:BodyPreference"; +const SYNC_AIRSYNCBASE_TYPE = "AirSyncBase:Type"; +const SYNC_AIRSYNCBASE_TRUNCATIONSIZE = "AirSyncBase:TruncationSize"; +const SYNC_AIRSYNCBASE_ALLORNONE = "AirSyncBase:AllOrNone"; +const SYNC_AIRSYNCBASE_BODY = "AirSyncBase:Body"; +const SYNC_AIRSYNCBASE_DATA = "AirSyncBase:Data"; +const SYNC_AIRSYNCBASE_ESTIMATEDDATASIZE = "AirSyncBase:EstimatedDataSize"; +const SYNC_AIRSYNCBASE_TRUNCATED = "AirSyncBase:Truncated"; +const SYNC_AIRSYNCBASE_ATTACHMENTS = "AirSyncBase:Attachments"; +const SYNC_AIRSYNCBASE_ATTACHMENT = "AirSyncBase:Attachment"; +const SYNC_AIRSYNCBASE_DISPLAYNAME = "AirSyncBase:DisplayName"; +const SYNC_AIRSYNCBASE_FILEREFERENCE = "AirSyncBase:FileReference"; +const SYNC_AIRSYNCBASE_METHOD = "AirSyncBase:Method"; +const SYNC_AIRSYNCBASE_CONTENTID = "AirSyncBase:ContentId"; +const SYNC_AIRSYNCBASE_CONTENTLOCATION = "AirSyncBase:ContentLocation"; //not used +const SYNC_AIRSYNCBASE_ISINLINE = "AirSyncBase:IsInline"; +const SYNC_AIRSYNCBASE_NATIVEBODYTYPE = "AirSyncBase:NativeBodyType"; +const SYNC_AIRSYNCBASE_CONTENTTYPE = "AirSyncBase:ContentType"; +const SYNC_AIRSYNCBASE_PREVIEW = "AirSyncBase:Preview"; //14.0 +const SYNC_AIRSYNCBASE_BODYPARTPREFERENCE = "AirSyncBase:BodyPartPreference"; //post 14.0 +const SYNC_AIRSYNCBASE_BODYPART = "AirSyncBase:BodyPart"; //post 14.0 +const SYNC_AIRSYNCBASE_STATUS = "AirSyncBase:Status"; //post 14.0 //Settings //12.0, 12.1 and 14.0 -define("SYNC_SETTINGS_SETTINGS", "Settings:Settings"); -define("SYNC_SETTINGS_STATUS", "Settings:Status"); -define("SYNC_SETTINGS_GET", "Settings:Get"); -define("SYNC_SETTINGS_SET", "Settings:Set"); -define("SYNC_SETTINGS_OOF", "Settings:Oof"); -define("SYNC_SETTINGS_OOFSTATE", "Settings:OofState"); -define("SYNC_SETTINGS_STARTTIME", "Settings:StartTime"); -define("SYNC_SETTINGS_ENDTIME", "Settings:EndTime"); -define("SYNC_SETTINGS_OOFMESSAGE", "Settings:OofMessage"); -define("SYNC_SETTINGS_APPLIESTOINTERVAL", "Settings:AppliesToInternal"); -define("SYNC_SETTINGS_APPLIESTOEXTERNALKNOWN", "Settings:AppliesToExternalKnown"); -define("SYNC_SETTINGS_APPLIESTOEXTERNALUNKNOWN", "Settings:AppliesToExternalUnknown"); -define("SYNC_SETTINGS_ENABLED", "Settings:Enabled"); -define("SYNC_SETTINGS_REPLYMESSAGE", "Settings:ReplyMessage"); -define("SYNC_SETTINGS_BODYTYPE", "Settings:BodyType"); -define("SYNC_SETTINGS_DEVICEPW", "Settings:DevicePassword"); -define("SYNC_SETTINGS_PW", "Settings:Password"); -define("SYNC_SETTINGS_DEVICEINFORMATION", "Settings:DeviceInformaton"); -define("SYNC_SETTINGS_MODEL", "Settings:Model"); -define("SYNC_SETTINGS_IMEI", "Settings:IMEI"); -define("SYNC_SETTINGS_FRIENDLYNAME", "Settings:FriendlyName"); -define("SYNC_SETTINGS_OS", "Settings:OS"); -define("SYNC_SETTINGS_OSLANGUAGE", "Settings:OSLanguage"); -define("SYNC_SETTINGS_PHONENUMBER", "Settings:PhoneNumber"); -define("SYNC_SETTINGS_USERINFORMATION", "Settings:UserInformation"); -define("SYNC_SETTINGS_EMAILADDRESSES", "Settings:EmailAddresses"); -define("SYNC_SETTINGS_SMPTADDRESS", "Settings:SmtpAddress"); -define("SYNC_SETTINGS_USERAGENT", "Settings:UserAgent"); //12.1 and 14.0 -define("SYNC_SETTINGS_ENABLEOUTBOUNDSMS", "Settings:EnableOutboundSMS"); //14.0 -define("SYNC_SETTINGS_MOBILEOPERATOR", "Settings:MobileOperator"); //14.0 -define("SYNC_SETTINGS_PRIMARYSMTPADDRESS", "Settings:PrimarySmtpAddress"); -define("SYNC_SETTINGS_ACCOUNTS", "Settings:Accounts"); -define("SYNC_SETTINGS_ACCOUNT", "Settings:Account"); -define("SYNC_SETTINGS_ACCOUNTID", "Settings:AccountId"); -define("SYNC_SETTINGS_ACCOUNTNAME", "Settings:AccountName"); -define("SYNC_SETTINGS_USERDISPLAYNAME", "Settings:UserDisplayName"); //12.1 and 14.0 -define("SYNC_SETTINGS_SENDDISABLED", "Settings:SendDisabled"); //14.0 -define("SYNC_SETTINGS_IHSMANAGEMENTINFORMATION", "Settings:ihsManagementInformation"); //14.0 +const SYNC_SETTINGS_SETTINGS = "Settings:Settings"; +const SYNC_SETTINGS_STATUS = "Settings:Status"; +const SYNC_SETTINGS_GET = "Settings:Get"; +const SYNC_SETTINGS_SET = "Settings:Set"; +const SYNC_SETTINGS_OOF = "Settings:Oof"; +const SYNC_SETTINGS_OOFSTATE = "Settings:OofState"; +const SYNC_SETTINGS_STARTTIME = "Settings:StartTime"; +const SYNC_SETTINGS_ENDTIME = "Settings:EndTime"; +const SYNC_SETTINGS_OOFMESSAGE = "Settings:OofMessage"; +const SYNC_SETTINGS_APPLIESTOINTERVAL = "Settings:AppliesToInternal"; +const SYNC_SETTINGS_APPLIESTOEXTERNALKNOWN = "Settings:AppliesToExternalKnown"; +const SYNC_SETTINGS_APPLIESTOEXTERNALUNKNOWN = "Settings:AppliesToExternalUnknown"; +const SYNC_SETTINGS_ENABLED = "Settings:Enabled"; +const SYNC_SETTINGS_REPLYMESSAGE = "Settings:ReplyMessage"; +const SYNC_SETTINGS_BODYTYPE = "Settings:BodyType"; +const SYNC_SETTINGS_DEVICEPW = "Settings:DevicePassword"; +const SYNC_SETTINGS_PW = "Settings:Password"; +const SYNC_SETTINGS_DEVICEINFORMATION = "Settings:DeviceInformaton"; +const SYNC_SETTINGS_MODEL = "Settings:Model"; +const SYNC_SETTINGS_IMEI = "Settings:IMEI"; +const SYNC_SETTINGS_FRIENDLYNAME = "Settings:FriendlyName"; +const SYNC_SETTINGS_OS = "Settings:OS"; +const SYNC_SETTINGS_OSLANGUAGE = "Settings:OSLanguage"; +const SYNC_SETTINGS_PHONENUMBER = "Settings:PhoneNumber"; +const SYNC_SETTINGS_USERINFORMATION = "Settings:UserInformation"; +const SYNC_SETTINGS_EMAILADDRESSES = "Settings:EmailAddresses"; +const SYNC_SETTINGS_SMPTADDRESS = "Settings:SmtpAddress"; +const SYNC_SETTINGS_USERAGENT = "Settings:UserAgent"; //12.1 and 14.0 +const SYNC_SETTINGS_ENABLEOUTBOUNDSMS = "Settings:EnableOutboundSMS"; //14.0 +const SYNC_SETTINGS_MOBILEOPERATOR = "Settings:MobileOperator"; //14.0 +const SYNC_SETTINGS_PRIMARYSMTPADDRESS = "Settings:PrimarySmtpAddress"; +const SYNC_SETTINGS_ACCOUNTS = "Settings:Accounts"; +const SYNC_SETTINGS_ACCOUNT = "Settings:Account"; +const SYNC_SETTINGS_ACCOUNTID = "Settings:AccountId"; +const SYNC_SETTINGS_ACCOUNTNAME = "Settings:AccountName"; +const SYNC_SETTINGS_USERDISPLAYNAME = "Settings:UserDisplayName"; //12.1 and 14.0 +const SYNC_SETTINGS_SENDDISABLED = "Settings:SendDisabled"; //14.0 +const SYNC_SETTINGS_IHSMANAGEMENTINFORMATION = "Settings:ihsManagementInformation"; //14.0 // only for internal use - never to be streamed to the mobile -define("SYNC_SETTINGS_PROP_STATUS", "Settings:PropertyStatus"); +const SYNC_SETTINGS_PROP_STATUS = "Settings:PropertyStatus"; //DocumentLibrary //12.0, 12.1 and 14.0 -define("SYNC_DOCUMENTLIBRARY_LINKID", "DocumentLibrary:LinkId"); -define("SYNC_DOCUMENTLIBRARY_DISPLAYNAME", "DocumentLibrary:DisplayName"); -define("SYNC_DOCUMENTLIBRARY_ISFOLDER", "DocumentLibrary:IsFolder"); -define("SYNC_DOCUMENTLIBRARY_CREATIONDATE", "DocumentLibrary:CreationDate"); -define("SYNC_DOCUMENTLIBRARY_LASTMODIFIEDDATE", "DocumentLibrary:LastModifiedDate"); -define("SYNC_DOCUMENTLIBRARY_ISHIDDEN", "DocumentLibrary:IsHidden"); -define("SYNC_DOCUMENTLIBRARY_CONTENTLENGTH", "DocumentLibrary:ContentLength"); -define("SYNC_DOCUMENTLIBRARY_CONTENTTYPE", "DocumentLibrary:ContentType"); +const SYNC_DOCUMENTLIBRARY_LINKID = "DocumentLibrary:LinkId"; +const SYNC_DOCUMENTLIBRARY_DISPLAYNAME = "DocumentLibrary:DisplayName"; +const SYNC_DOCUMENTLIBRARY_ISFOLDER = "DocumentLibrary:IsFolder"; +const SYNC_DOCUMENTLIBRARY_CREATIONDATE = "DocumentLibrary:CreationDate"; +const SYNC_DOCUMENTLIBRARY_LASTMODIFIEDDATE = "DocumentLibrary:LastModifiedDate"; +const SYNC_DOCUMENTLIBRARY_ISHIDDEN = "DocumentLibrary:IsHidden"; +const SYNC_DOCUMENTLIBRARY_CONTENTLENGTH = "DocumentLibrary:ContentLength"; +const SYNC_DOCUMENTLIBRARY_CONTENTTYPE = "DocumentLibrary:ContentType"; //ItemOperations //12.0, 12.1 and 14.0 -define("SYNC_ITEMOPERATIONS_ITEMOPERATIONS", "ItemOperations:ItemOperations"); -define("SYNC_ITEMOPERATIONS_FETCH", "ItemOperations:Fetch"); -define("SYNC_ITEMOPERATIONS_STORE", "ItemOperations:Store"); -define("SYNC_ITEMOPERATIONS_OPTIONS", "ItemOperations:Options"); -define("SYNC_ITEMOPERATIONS_RANGE", "ItemOperations:Range"); -define("SYNC_ITEMOPERATIONS_TOTAL", "ItemOperations:Total"); -define("SYNC_ITEMOPERATIONS_PROPERTIES", "ItemOperations:Properties"); -define("SYNC_ITEMOPERATIONS_DATA", "ItemOperations:Data"); -define("SYNC_ITEMOPERATIONS_STATUS", "ItemOperations:Status"); -define("SYNC_ITEMOPERATIONS_RESPONSE", "ItemOperations:Response"); -define("SYNC_ITEMOPERATIONS_VERSIONS", "ItemOperations:Version"); -define("SYNC_ITEMOPERATIONS_SCHEMA", "ItemOperations:Schema"); -define("SYNC_ITEMOPERATIONS_PART", "ItemOperations:Part"); -define("SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS", "ItemOperations:EmptyFolderContents"); -define("SYNC_ITEMOPERATIONS_DELETESUBFOLDERS", "ItemOperations:DeleteSubFolders"); -define("SYNC_ITEMOPERATIONS_USERNAME", "ItemOperations:UserName"); //12.1 and 14.0 -define("SYNC_ITEMOPERATIONS_PASSWORD", "ItemOperations:Password"); //12.1 and 14.0 -define("SYNC_ITEMOPERATIONS_MOVE", "ItemOperations:Move"); //14.0 -define("SYNC_ITEMOPERATIONS_DSTFLDID", "ItemOperations:DstFldId"); //14.0 -define("SYNC_ITEMOPERATIONS_CONVERSATIONID", "ItemOperations:ConversationId"); //14.0 -define("SYNC_ITEMOPERATIONS_MOVEALWAYS", "ItemOperations:MoveAlways"); //14.0 +const SYNC_ITEMOPERATIONS_ITEMOPERATIONS = "ItemOperations:ItemOperations"; +const SYNC_ITEMOPERATIONS_FETCH = "ItemOperations:Fetch"; +const SYNC_ITEMOPERATIONS_STORE = "ItemOperations:Store"; +const SYNC_ITEMOPERATIONS_OPTIONS = "ItemOperations:Options"; +const SYNC_ITEMOPERATIONS_RANGE = "ItemOperations:Range"; +const SYNC_ITEMOPERATIONS_TOTAL = "ItemOperations:Total"; +const SYNC_ITEMOPERATIONS_PROPERTIES = "ItemOperations:Properties"; +const SYNC_ITEMOPERATIONS_DATA = "ItemOperations:Data"; +const SYNC_ITEMOPERATIONS_STATUS = "ItemOperations:Status"; +const SYNC_ITEMOPERATIONS_RESPONSE = "ItemOperations:Response"; +const SYNC_ITEMOPERATIONS_VERSIONS = "ItemOperations:Version"; +const SYNC_ITEMOPERATIONS_SCHEMA = "ItemOperations:Schema"; +const SYNC_ITEMOPERATIONS_PART = "ItemOperations:Part"; +const SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS = "ItemOperations:EmptyFolderContents"; +const SYNC_ITEMOPERATIONS_DELETESUBFOLDERS = "ItemOperations:DeleteSubFolders"; +const SYNC_ITEMOPERATIONS_USERNAME = "ItemOperations:UserName"; //12.1 and 14.0 +const SYNC_ITEMOPERATIONS_PASSWORD = "ItemOperations:Password"; //12.1 and 14.0 +const SYNC_ITEMOPERATIONS_MOVE = "ItemOperations:Move"; //14.0 +const SYNC_ITEMOPERATIONS_DSTFLDID = "ItemOperations:DstFldId"; //14.0 +const SYNC_ITEMOPERATIONS_CONVERSATIONID = "ItemOperations:ConversationId"; //14.0 +const SYNC_ITEMOPERATIONS_MOVEALWAYS = "ItemOperations:MoveAlways"; //14.0 //ComposeMail //14.0 -define("SYNC_COMPOSEMAIL_SENDMAIL", "ComposeMail:SendMail"); -define("SYNC_COMPOSEMAIL_SMARTFORWARD", "ComposeMail:SmartForward"); -define("SYNC_COMPOSEMAIL_SMARTREPLY", "ComposeMail:SmartReply"); -define("SYNC_COMPOSEMAIL_SAVEINSENTITEMS", "ComposeMail:SaveInSentItems"); -define("SYNC_COMPOSEMAIL_REPLACEMIME", "ComposeMail:ReplaceMime"); -define("SYNC_COMPOSEMAIL_TYPE", "ComposeMail:Type"); -define("SYNC_COMPOSEMAIL_SOURCE", "ComposeMail:Source"); -define("SYNC_COMPOSEMAIL_FOLDERID", "ComposeMail:FolderId"); -define("SYNC_COMPOSEMAIL_ITEMID", "ComposeMail:ItemId"); -define("SYNC_COMPOSEMAIL_LONGID", "ComposeMail:LongId"); -define("SYNC_COMPOSEMAIL_INSTANCEID", "ComposeMail:InstanceId"); -define("SYNC_COMPOSEMAIL_MIME", "ComposeMail:MIME"); -define("SYNC_COMPOSEMAIL_CLIENTID", "ComposeMail:ClientId"); -define("SYNC_COMPOSEMAIL_STATUS", "ComposeMail:Status"); -define("SYNC_COMPOSEMAIL_ACCOUNTID", "ComposeMail:AccountId"); +const SYNC_COMPOSEMAIL_SENDMAIL = "ComposeMail:SendMail"; +const SYNC_COMPOSEMAIL_SMARTFORWARD = "ComposeMail:SmartForward"; +const SYNC_COMPOSEMAIL_SMARTREPLY = "ComposeMail:SmartReply"; +const SYNC_COMPOSEMAIL_SAVEINSENTITEMS = "ComposeMail:SaveInSentItems"; +const SYNC_COMPOSEMAIL_REPLACEMIME = "ComposeMail:ReplaceMime"; +const SYNC_COMPOSEMAIL_TYPE = "ComposeMail:Type"; +const SYNC_COMPOSEMAIL_SOURCE = "ComposeMail:Source"; +const SYNC_COMPOSEMAIL_FOLDERID = "ComposeMail:FolderId"; +const SYNC_COMPOSEMAIL_ITEMID = "ComposeMail:ItemId"; +const SYNC_COMPOSEMAIL_LONGID = "ComposeMail:LongId"; +const SYNC_COMPOSEMAIL_INSTANCEID = "ComposeMail:InstanceId"; +const SYNC_COMPOSEMAIL_MIME = "ComposeMail:MIME"; +const SYNC_COMPOSEMAIL_CLIENTID = "ComposeMail:ClientId"; +const SYNC_COMPOSEMAIL_STATUS = "ComposeMail:Status"; +const SYNC_COMPOSEMAIL_ACCOUNTID = "ComposeMail:AccountId"; // only for internal use - never to be streamed to the mobile -define("SYNC_COMPOSEMAIL_REPLYFLAG","ComposeMail:ReplyFlag"); -define("SYNC_COMPOSEMAIL_FORWARDFLAG","ComposeMail:ForwardFlag"); +const SYNC_COMPOSEMAIL_REPLYFLAG = "ComposeMail:ReplyFlag"; +const SYNC_COMPOSEMAIL_FORWARDFLAG = "ComposeMail:ForwardFlag"; //POOMMAIL2 //14.0 -define("SYNC_POOMMAIL2_UMCALLERID", "POOMMAIL2:UmCallerId"); -define("SYNC_POOMMAIL2_UMUSERNOTES", "POOMMAIL2:UmUserNotes"); -define("SYNC_POOMMAIL2_UMATTDURATION", "POOMMAIL2:UmAttDuration"); -define("SYNC_POOMMAIL2_UMATTORDER", "POOMMAIL2:UmAttOrder"); -define("SYNC_POOMMAIL2_CONVERSATIONID", "POOMMAIL2:ConversationId"); -define("SYNC_POOMMAIL2_CONVERSATIONINDEX", "POOMMAIL2:ConversationIndex"); -define("SYNC_POOMMAIL2_LASTVERBEXECUTED", "POOMMAIL2:LastVerbExecuted"); -define("SYNC_POOMMAIL2_LASTVERBEXECUTIONTIME", "POOMMAIL2:LastVerbExecutionTime"); -define("SYNC_POOMMAIL2_RECEIVEDASBCC", "POOMMAIL2:ReceivedAsBcc"); -define("SYNC_POOMMAIL2_SENDER", "POOMMAIL2:Sender"); -define("SYNC_POOMMAIL2_CALENDARTYPE", "POOMMAIL2:CalendarType"); -define("SYNC_POOMMAIL2_ISLEAPMONTH", "POOMMAIL2:IsLeapMonth"); -define("SYNC_POOMMAIL2_ACCOUNTID", "POOMMAIL2:AccountId"); -define("SYNC_POOMMAIL2_FIRSTDAYOFWEEK", "POOMMAIL2:FirstDayOfWeek"); -define("SYNC_POOMMAIL2_MEETINGMESSAGETYPE", "POOMMAIL2:MeetingMessageType"); +const SYNC_POOMMAIL2_UMCALLERID = "POOMMAIL2:UmCallerId"; +const SYNC_POOMMAIL2_UMUSERNOTES = "POOMMAIL2:UmUserNotes"; +const SYNC_POOMMAIL2_UMATTDURATION = "POOMMAIL2:UmAttDuration"; +const SYNC_POOMMAIL2_UMATTORDER = "POOMMAIL2:UmAttOrder"; +const SYNC_POOMMAIL2_CONVERSATIONID = "POOMMAIL2:ConversationId"; +const SYNC_POOMMAIL2_CONVERSATIONINDEX = "POOMMAIL2:ConversationIndex"; +const SYNC_POOMMAIL2_LASTVERBEXECUTED = "POOMMAIL2:LastVerbExecuted"; +const SYNC_POOMMAIL2_LASTVERBEXECUTIONTIME = "POOMMAIL2:LastVerbExecutionTime"; +const SYNC_POOMMAIL2_RECEIVEDASBCC = "POOMMAIL2:ReceivedAsBcc"; +const SYNC_POOMMAIL2_SENDER = "POOMMAIL2:Sender"; +const SYNC_POOMMAIL2_CALENDARTYPE = "POOMMAIL2:CalendarType"; +const SYNC_POOMMAIL2_ISLEAPMONTH = "POOMMAIL2:IsLeapMonth"; +const SYNC_POOMMAIL2_ACCOUNTID = "POOMMAIL2:AccountId"; +const SYNC_POOMMAIL2_FIRSTDAYOFWEEK = "POOMMAIL2:FirstDayOfWeek"; +const SYNC_POOMMAIL2_MEETINGMESSAGETYPE = "POOMMAIL2:MeetingMessageType"; //Notes //14.0 -define("SYNC_NOTES_SUBJECT", "Notes:Subject"); -define("SYNC_NOTES_MESSAGECLASS", "Notes:MessageClass"); -define("SYNC_NOTES_LASTMODIFIEDDATE", "Notes:LastModifiedDate"); -define("SYNC_NOTES_CATEGORIES", "Notes:Categories"); -define("SYNC_NOTES_CATEGORY", "Notes:Category"); +const SYNC_NOTES_SUBJECT = "Notes:Subject"; +const SYNC_NOTES_MESSAGECLASS = "Notes:MessageClass"; +const SYNC_NOTES_LASTMODIFIEDDATE = "Notes:LastModifiedDate"; +const SYNC_NOTES_CATEGORIES = "Notes:Categories"; +const SYNC_NOTES_CATEGORY = "Notes:Category"; //RightsManagement //post 14.0 -define("SYNC_RIGHTSMANAGEMENT_SUPPORT", "RightsManagement:RightsManagementSupport"); -define("SYNC_RIGHTSMANAGEMENT_TEMPLATES", "RightsManagement:RightsManagementTemplates"); -define("SYNC_RIGHTSMANAGEMENT_TEMPLATE", "RightsManagement:RightsManagementTemplate"); -define("SYNC_RIGHTSMANAGEMENT_LICENSE", "RightsManagement:RightsManagementLicense"); -define("SYNC_RIGHTSMANAGEMENT_EDITALLOWED", "RightsManagement:EditAllowed"); -define("SYNC_RIGHTSMANAGEMENT_REPLYALLOWED", "RightsManagement:ReplyAllowed"); -define("SYNC_RIGHTSMANAGEMENT_REPLYALLALLOWED", "RightsManagement:ReplyAllAllowed"); -define("SYNC_RIGHTSMANAGEMENT_FORWARDALLOWED", "RightsManagement:ForwardAllowed"); -define("SYNC_RIGHTSMANAGEMENT_MODIFYRECIPIENTSALLOWED", "RightsManagement:ModifyRecipientsAllowed"); -define("SYNC_RIGHTSMANAGEMENT_EXTRACTALLOWED", "RightsManagement:ExtractAllowed"); -define("SYNC_RIGHTSMANAGEMENT_PRINTALLOWED", "RightsManagement:PrintAllowed"); -define("SYNC_RIGHTSMANAGEMENT_EXPORTALLOWED", "RightsManagement:ExportAllowed"); -define("SYNC_RIGHTSMANAGEMENT_PROGRAMMATICACCESSALLOWED", "RightsManagement:ProgrammaticAccessAllowed"); -define("SYNC_RIGHTSMANAGEMENT_RMOWNER", "RightsManagement:RMOwner"); -define("SYNC_RIGHTSMANAGEMENT_CONTENTEXPIRYDATE", "RightsManagement:ContentExpiryDate"); -define("SYNC_RIGHTSMANAGEMENT_TEMPLATEID", "RightsManagement:TemplateID"); -define("SYNC_RIGHTSMANAGEMENT_TEMPLATENAME", "RightsManagement:TemplateName"); -define("SYNC_RIGHTSMANAGEMENT_TEMPLATEDESCRIPTION", "RightsManagement:TemplateDescription"); -define("SYNC_RIGHTSMANAGEMENT_CONTENTOWNER", "RightsManagement:ContentOwner"); -define("SYNC_RIGHTSMANAGEMENT_REMOVERIGHTSMGNTDIST", "RightsManagement:RemoveRightsManagementDistribution"); +const SYNC_RIGHTSMANAGEMENT_SUPPORT = "RightsManagement:RightsManagementSupport"; +const SYNC_RIGHTSMANAGEMENT_TEMPLATES = "RightsManagement:RightsManagementTemplates"; +const SYNC_RIGHTSMANAGEMENT_TEMPLATE = "RightsManagement:RightsManagementTemplate"; +const SYNC_RIGHTSMANAGEMENT_LICENSE = "RightsManagement:RightsManagementLicense"; +const SYNC_RIGHTSMANAGEMENT_EDITALLOWED = "RightsManagement:EditAllowed"; +const SYNC_RIGHTSMANAGEMENT_REPLYALLOWED = "RightsManagement:ReplyAllowed"; +const SYNC_RIGHTSMANAGEMENT_REPLYALLALLOWED = "RightsManagement:ReplyAllAllowed"; +const SYNC_RIGHTSMANAGEMENT_FORWARDALLOWED = "RightsManagement:ForwardAllowed"; +const SYNC_RIGHTSMANAGEMENT_MODIFYRECIPIENTSALLOWED = "RightsManagement:ModifyRecipientsAllowed"; +const SYNC_RIGHTSMANAGEMENT_EXTRACTALLOWED = "RightsManagement:ExtractAllowed"; +const SYNC_RIGHTSMANAGEMENT_PRINTALLOWED = "RightsManagement:PrintAllowed"; +const SYNC_RIGHTSMANAGEMENT_EXPORTALLOWED = "RightsManagement:ExportAllowed"; +const SYNC_RIGHTSMANAGEMENT_PROGRAMMATICACCESSALLOWED = "RightsManagement:ProgrammaticAccessAllowed"; +const SYNC_RIGHTSMANAGEMENT_RMOWNER = "RightsManagement:RMOwner"; +const SYNC_RIGHTSMANAGEMENT_CONTENTEXPIRYDATE = "RightsManagement:ContentExpiryDate"; +const SYNC_RIGHTSMANAGEMENT_TEMPLATEID = "RightsManagement:TemplateID"; +const SYNC_RIGHTSMANAGEMENT_TEMPLATENAME = "RightsManagement:TemplateName"; +const SYNC_RIGHTSMANAGEMENT_TEMPLATEDESCRIPTION = "RightsManagement:TemplateDescription"; +const SYNC_RIGHTSMANAGEMENT_CONTENTOWNER = "RightsManagement:ContentOwner"; +const SYNC_RIGHTSMANAGEMENT_REMOVERIGHTSMGNTDIST = "RightsManagement:RemoveRightsManagementDistribution"; // Other constants -define("SYNC_FOLDER_TYPE_OTHER", 1); -define("SYNC_FOLDER_TYPE_INBOX", 2); -define("SYNC_FOLDER_TYPE_DRAFTS", 3); -define("SYNC_FOLDER_TYPE_WASTEBASKET", 4); -define("SYNC_FOLDER_TYPE_SENTMAIL", 5); -define("SYNC_FOLDER_TYPE_OUTBOX", 6); -define("SYNC_FOLDER_TYPE_TASK", 7); -define("SYNC_FOLDER_TYPE_APPOINTMENT", 8); -define("SYNC_FOLDER_TYPE_CONTACT", 9); -define("SYNC_FOLDER_TYPE_NOTE", 10); -define("SYNC_FOLDER_TYPE_JOURNAL", 11); -define("SYNC_FOLDER_TYPE_USER_MAIL", 12); -define("SYNC_FOLDER_TYPE_USER_APPOINTMENT", 13); -define("SYNC_FOLDER_TYPE_USER_CONTACT", 14); -define("SYNC_FOLDER_TYPE_USER_TASK", 15); -define("SYNC_FOLDER_TYPE_USER_JOURNAL", 16); -define("SYNC_FOLDER_TYPE_USER_NOTE", 17); -define("SYNC_FOLDER_TYPE_UNKNOWN", 18); -define("SYNC_FOLDER_TYPE_RECIPIENT_CACHE", 19); -define("SYNC_FOLDER_TYPE_DUMMY", 999999); +const SYNC_FOLDER_TYPE_OTHER = 1; +const SYNC_FOLDER_TYPE_INBOX = 2; +const SYNC_FOLDER_TYPE_DRAFTS = 3; +const SYNC_FOLDER_TYPE_WASTEBASKET = 4; +const SYNC_FOLDER_TYPE_SENTMAIL = 5; +const SYNC_FOLDER_TYPE_OUTBOX = 6; +const SYNC_FOLDER_TYPE_TASK = 7; +const SYNC_FOLDER_TYPE_APPOINTMENT = 8; +const SYNC_FOLDER_TYPE_CONTACT = 9; +const SYNC_FOLDER_TYPE_NOTE = 10; +const SYNC_FOLDER_TYPE_JOURNAL = 11; +const SYNC_FOLDER_TYPE_USER_MAIL = 12; +const SYNC_FOLDER_TYPE_USER_APPOINTMENT = 13; +const SYNC_FOLDER_TYPE_USER_CONTACT = 14; +const SYNC_FOLDER_TYPE_USER_TASK = 15; +const SYNC_FOLDER_TYPE_USER_JOURNAL = 16; +const SYNC_FOLDER_TYPE_USER_NOTE = 17; +const SYNC_FOLDER_TYPE_UNKNOWN = 18; +const SYNC_FOLDER_TYPE_RECIPIENT_CACHE = 19; +const SYNC_FOLDER_TYPE_DUMMY = 999999; -define("SYNC_CONFLICT_OVERWRITE_SERVER", 0); -define("SYNC_CONFLICT_OVERWRITE_PIM", 1); +const SYNC_CONFLICT_OVERWRITE_SERVER = 0; +const SYNC_CONFLICT_OVERWRITE_PIM = 1; -define("SYNC_FILTERTYPE_ALL", 0); -define("SYNC_FILTERTYPE_1DAY", 1); -define("SYNC_FILTERTYPE_3DAYS", 2); -define("SYNC_FILTERTYPE_1WEEK", 3); -define("SYNC_FILTERTYPE_2WEEKS", 4); -define("SYNC_FILTERTYPE_1MONTH", 5); -define("SYNC_FILTERTYPE_3MONTHS", 6); -define("SYNC_FILTERTYPE_6MONTHS", 7); -define("SYNC_FILTERTYPE_INCOMPLETETASKS", 8); +const SYNC_FILTERTYPE_ALL = 0; +const SYNC_FILTERTYPE_1DAY = 1; +const SYNC_FILTERTYPE_3DAYS = 2; +const SYNC_FILTERTYPE_1WEEK = 3; +const SYNC_FILTERTYPE_2WEEKS = 4; +const SYNC_FILTERTYPE_1MONTH = 5; +const SYNC_FILTERTYPE_3MONTHS = 6; +const SYNC_FILTERTYPE_6MONTHS = 7; +const SYNC_FILTERTYPE_INCOMPLETETASKS = 8; -define("SYNC_TRUNCATION_HEADERS", 0); -define("SYNC_TRUNCATION_512B", 1); -define("SYNC_TRUNCATION_1K", 2); -define("SYNC_TRUNCATION_2K", 3); -define("SYNC_TRUNCATION_5K", 4); -define("SYNC_TRUNCATION_10K", 5); -define("SYNC_TRUNCATION_20K", 6); -define("SYNC_TRUNCATION_50K", 7); -define("SYNC_TRUNCATION_100K", 8); -define("SYNC_TRUNCATION_ALL", 9); +const SYNC_TRUNCATION_HEADERS = 0; +const SYNC_TRUNCATION_512B = 1; +const SYNC_TRUNCATION_1K = 2; +const SYNC_TRUNCATION_2K = 3; +const SYNC_TRUNCATION_5K = 4; +const SYNC_TRUNCATION_10K = 5; +const SYNC_TRUNCATION_20K = 6; +const SYNC_TRUNCATION_50K = 7; +const SYNC_TRUNCATION_100K = 8; +const SYNC_TRUNCATION_ALL = 9; -define("SYNC_PROVISION_STATUS_SUCCESS", 1); -define("SYNC_PROVISION_STATUS_PROTERROR", 2); -define("SYNC_PROVISION_STATUS_SERVERERROR", 3); -define("SYNC_PROVISION_STATUS_DEVEXTMANAGED", 4); +const SYNC_PROVISION_STATUS_SUCCESS = 1; +const SYNC_PROVISION_STATUS_PROTERROR = 2; +const SYNC_PROVISION_STATUS_SERVERERROR = 3; +const SYNC_PROVISION_STATUS_DEVEXTMANAGED = 4; -define("SYNC_PROVISION_POLICYSTATUS_SUCCESS", 1); -define("SYNC_PROVISION_POLICYSTATUS_NOPOLICY", 2); -define("SYNC_PROVISION_POLICYSTATUS_UNKNOWNVALUE", 3); -define("SYNC_PROVISION_POLICYSTATUS_CORRUPTED", 4); -define("SYNC_PROVISION_POLICYSTATUS_POLKEYMISM", 5); +const SYNC_PROVISION_POLICYSTATUS_SUCCESS = 1; +const SYNC_PROVISION_POLICYSTATUS_NOPOLICY = 2; +const SYNC_PROVISION_POLICYSTATUS_UNKNOWNVALUE = 3; +const SYNC_PROVISION_POLICYSTATUS_CORRUPTED = 4; +const SYNC_PROVISION_POLICYSTATUS_POLKEYMISM = 5; -define("SYNC_PROVISION_RWSTATUS_NA", 0); -define("SYNC_PROVISION_RWSTATUS_OK", 1); -define("SYNC_PROVISION_RWSTATUS_PENDING", 2); -define("SYNC_PROVISION_RWSTATUS_REQUESTED", 4); -define("SYNC_PROVISION_RWSTATUS_WIPED", 8); +const SYNC_PROVISION_RWSTATUS_NA = 0; +const SYNC_PROVISION_RWSTATUS_OK = 1; +const SYNC_PROVISION_RWSTATUS_PENDING = 2; +const SYNC_PROVISION_RWSTATUS_REQUESTED = 4; +const SYNC_PROVISION_RWSTATUS_WIPED = 8; -define("SYNC_STATUS_SUCCESS", 1); -define("SYNC_STATUS_INVALIDSYNCKEY", 3); -define("SYNC_STATUS_PROTOCOLLERROR", 4); -define("SYNC_STATUS_SERVERERROR", 5); -define("SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR", 6); -define("SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT", 7); -define("SYNC_STATUS_OBJECTNOTFOUND", 8); -define("SYNC_STATUS_SYNCCANNOTBECOMPLETED", 9); -define("SYNC_STATUS_FOLDERHIERARCHYCHANGED", 12); -define("SYNC_STATUS_SYNCREQUESTINCOMPLETE", 13); -define("SYNC_STATUS_INVALIDWAITORHBVALUE", 14); -define("SYNC_STATUS_SYNCREQUESTINVALID", 15); -define("SYNC_STATUS_RETRY", 16); +const SYNC_STATUS_SUCCESS = 1; +const SYNC_STATUS_INVALIDSYNCKEY = 3; +const SYNC_STATUS_PROTOCOLLERROR = 4; +const SYNC_STATUS_SERVERERROR = 5; +const SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR = 6; +const SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT = 7; +const SYNC_STATUS_OBJECTNOTFOUND = 8; +const SYNC_STATUS_SYNCCANNOTBECOMPLETED = 9; +const SYNC_STATUS_FOLDERHIERARCHYCHANGED = 12; +const SYNC_STATUS_SYNCREQUESTINCOMPLETE = 13; +const SYNC_STATUS_INVALIDWAITORHBVALUE = 14; +const SYNC_STATUS_SYNCREQUESTINVALID = 15; +const SYNC_STATUS_RETRY = 16; -define("SYNC_FSSTATUS_SUCCESS", 1); -define("SYNC_FSSTATUS_FOLDEREXISTS", 2); -define("SYNC_FSSTATUS_SYSTEMFOLDER", 3); -define("SYNC_FSSTATUS_FOLDERDOESNOTEXIST", 4); -define("SYNC_FSSTATUS_PARENTNOTFOUND", 5); -define("SYNC_FSSTATUS_SERVERERROR", 6); -define("SYNC_FSSTATUS_REQUESTTIMEOUT", 8); -define("SYNC_FSSTATUS_SYNCKEYERROR", 9); -define("SYNC_FSSTATUS_MAILFORMEDREQ", 10); -define("SYNC_FSSTATUS_UNKNOWNERROR", 11); -define("SYNC_FSSTATUS_CODEUNKNOWN", 12); +const SYNC_FSSTATUS_SUCCESS = 1; +const SYNC_FSSTATUS_FOLDEREXISTS = 2; +const SYNC_FSSTATUS_SYSTEMFOLDER = 3; +const SYNC_FSSTATUS_FOLDERDOESNOTEXIST = 4; +const SYNC_FSSTATUS_PARENTNOTFOUND = 5; +const SYNC_FSSTATUS_SERVERERROR = 6; +const SYNC_FSSTATUS_REQUESTTIMEOUT = 8; +const SYNC_FSSTATUS_SYNCKEYERROR = 9; +const SYNC_FSSTATUS_MAILFORMEDREQ = 10; +const SYNC_FSSTATUS_UNKNOWNERROR = 11; +const SYNC_FSSTATUS_CODEUNKNOWN = 12; -define("SYNC_GETITEMESTSTATUS_SUCCESS", 1); -define("SYNC_GETITEMESTSTATUS_COLLECTIONINVALID", 2); -define("SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED", 3); -define("SYNC_GETITEMESTSTATUS_SYNCKKEYINVALID", 4); +const SYNC_GETITEMESTSTATUS_SUCCESS = 1; +const SYNC_GETITEMESTSTATUS_COLLECTIONINVALID = 2; +const SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED = 3; +const SYNC_GETITEMESTSTATUS_SYNCKKEYINVALID = 4; -define("SYNC_ITEMOPERATIONSSTATUS_SUCCESS", 1); -define("SYNC_ITEMOPERATIONSSTATUS_PROTERROR", 2); -define("SYNC_ITEMOPERATIONSSTATUS_SERVERERROR", 3); -define("SYNC_ITEMOPERATIONSSTATUS_DL_BADURI", 4); -define("SYNC_ITEMOPERATIONSSTATUS_DL_ACCESSDENIED", 5); -define("SYNC_ITEMOPERATIONSSTATUS_DL_NOTFOUND", 6); -define("SYNC_ITEMOPERATIONSSTATUS_DL_CONNFAILED", 7); -define("SYNC_ITEMOPERATIONSSTATUS_DL_BYTERANGEINVALID", 8); -define("SYNC_ITEMOPERATIONSSTATUS_DL_STOREUNKNOWN", 9); -define("SYNC_ITEMOPERATIONSSTATUS_DL_EMPTYFILE", 10); -define("SYNC_ITEMOPERATIONSSTATUS_DL_TOOLARGE", 11); -define("SYNC_ITEMOPERATIONSSTATUS_DL_IOFAILURE", 12); -define("SYNC_ITEMOPERATIONSSTATUS_CONVERSIONFAILED", 14); -define("SYNC_ITEMOPERATIONSSTATUS_INVALIDATT", 15); -define("SYNC_ITEMOPERATIONSSTATUS_BLOCKED", 16); -define("SYNC_ITEMOPERATIONSSTATUS_EMPTYFOLDER", 17); -define("SYNC_ITEMOPERATIONSSTATUS_CREDSREQUIRED", 18); -define("SYNC_ITEMOPERATIONSSTATUS_PROTOCOLERROR", 155); -define("SYNC_ITEMOPERATIONSSTATUS_UNSUPPORTEDACTION", 156); +const SYNC_ITEMOPERATIONSSTATUS_SUCCESS = 1; +const SYNC_ITEMOPERATIONSSTATUS_PROTERROR = 2; +const SYNC_ITEMOPERATIONSSTATUS_SERVERERROR = 3; +const SYNC_ITEMOPERATIONSSTATUS_DL_BADURI = 4; +const SYNC_ITEMOPERATIONSSTATUS_DL_ACCESSDENIED = 5; +const SYNC_ITEMOPERATIONSSTATUS_DL_NOTFOUND = 6; +const SYNC_ITEMOPERATIONSSTATUS_DL_CONNFAILED = 7; +const SYNC_ITEMOPERATIONSSTATUS_DL_BYTERANGEINVALID = 8; +const SYNC_ITEMOPERATIONSSTATUS_DL_STOREUNKNOWN = 9; +const SYNC_ITEMOPERATIONSSTATUS_DL_EMPTYFILE = 10; +const SYNC_ITEMOPERATIONSSTATUS_DL_TOOLARGE = 11; +const SYNC_ITEMOPERATIONSSTATUS_DL_IOFAILURE = 12; +const SYNC_ITEMOPERATIONSSTATUS_CONVERSIONFAILED = 14; +const SYNC_ITEMOPERATIONSSTATUS_INVALIDATT = 15; +const SYNC_ITEMOPERATIONSSTATUS_BLOCKED = 16; +const SYNC_ITEMOPERATIONSSTATUS_EMPTYFOLDER = 17; +const SYNC_ITEMOPERATIONSSTATUS_CREDSREQUIRED = 18; +const SYNC_ITEMOPERATIONSSTATUS_PROTOCOLERROR = 155; +const SYNC_ITEMOPERATIONSSTATUS_UNSUPPORTEDACTION = 156; -define("SYNC_MEETRESPSTATUS_SUCCESS", 1); -define("SYNC_MEETRESPSTATUS_INVALIDMEETREQ", 2); -define("SYNC_MEETRESPSTATUS_MAILBOXERROR", 3); -define("SYNC_MEETRESPSTATUS_SERVERERROR", 4); +const SYNC_MEETRESPSTATUS_SUCCESS = 1; +const SYNC_MEETRESPSTATUS_INVALIDMEETREQ = 2; +const SYNC_MEETRESPSTATUS_MAILBOXERROR = 3; +const SYNC_MEETRESPSTATUS_SERVERERROR = 4; -define("SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID", 1); -define("SYNC_MOVEITEMSSTATUS_INVALIDDESTID", 2); -define("SYNC_MOVEITEMSSTATUS_SUCCESS", 3); -define("SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST", 4); -define("SYNC_MOVEITEMSSTATUS_CANNOTMOVE", 5); -define("SYNC_MOVEITEMSSTATUS_SOURCEORDESTLOCKED", 7); +const SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID = 1; +const SYNC_MOVEITEMSSTATUS_INVALIDDESTID = 2; +const SYNC_MOVEITEMSSTATUS_SUCCESS = 3; +const SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST = 4; +const SYNC_MOVEITEMSSTATUS_CANNOTMOVE = 5; +const SYNC_MOVEITEMSSTATUS_SOURCEORDESTLOCKED = 7; -define("SYNC_PINGSTATUS_HBEXPIRED", 1); -define("SYNC_PINGSTATUS_CHANGES", 2); -define("SYNC_PINGSTATUS_FAILINGPARAMS", 3); -define("SYNC_PINGSTATUS_SYNTAXERROR", 4); -define("SYNC_PINGSTATUS_HBOUTOFRANGE", 5); -define("SYNC_PINGSTATUS_TOOMUCHFOLDERS", 6); -define("SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED", 7); -define("SYNC_PINGSTATUS_SERVERERROR", 8); +const SYNC_PINGSTATUS_HBEXPIRED = 1; +const SYNC_PINGSTATUS_CHANGES = 2; +const SYNC_PINGSTATUS_FAILINGPARAMS = 3; +const SYNC_PINGSTATUS_SYNTAXERROR = 4; +const SYNC_PINGSTATUS_HBOUTOFRANGE = 5; +const SYNC_PINGSTATUS_TOOMUCHFOLDERS = 6; +const SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED = 7; +const SYNC_PINGSTATUS_SERVERERROR = 8; -define("SYNC_RESOLVERECIPSSTATUS_SUCCESS", 1); -define("SYNC_RESOLVERECIPSSTATUS_PROTOCOLERROR", 5); -define("SYNC_RESOLVERECIPSSTATUS_SERVERERROR", 6); -define("SYNC_RESOLVERECIPSSTATUS_RESPONSE_SUCCESS", 1); -define("SYNC_RESOLVERECIPSSTATUS_RESPONSE_AMBRECIP", 2); -define("SYNC_RESOLVERECIPSSTATUS_RESPONSE_AMBRECIPPARTIAL", 3); -define("SYNC_RESOLVERECIPSSTATUS_RESPONSE_UNRESOLVEDRECIP", 4); -define("SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_SUCCESS", 1); -define("SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_NOVALIDCERT", 7); -define("SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_CERTLIMIT", 8); -define("SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_SUCCESS", 1); -define("SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_MORETHAN100", 160); -define("SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_MORETHAN20", 161); -define("SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_REISSUE", 162); -define("SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_FAILED", 163); -define("SYNC_RESOLVERECIPSSTATUS_PICTURE_SUCCESS", 1); -define("SYNC_RESOLVERECIPSSTATUS_PICTURE_NOFOTO", 173); -define("SYNC_RESOLVERECIPSSTATUS_PICTURE_MAXSIZEEXCEEDED", 174); -define("SYNC_RESOLVERECIPSSTATUS_PICTURE_MAXPICTURESEXCEEDED", 175); +const SYNC_RESOLVERECIPSSTATUS_SUCCESS = 1; +const SYNC_RESOLVERECIPSSTATUS_PROTOCOLERROR = 5; +const SYNC_RESOLVERECIPSSTATUS_SERVERERROR = 6; +const SYNC_RESOLVERECIPSSTATUS_RESPONSE_SUCCESS = 1; +const SYNC_RESOLVERECIPSSTATUS_RESPONSE_AMBRECIP = 2; +const SYNC_RESOLVERECIPSSTATUS_RESPONSE_AMBRECIPPARTIAL = 3; +const SYNC_RESOLVERECIPSSTATUS_RESPONSE_UNRESOLVEDRECIP = 4; +const SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_SUCCESS = 1; +const SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_NOVALIDCERT = 7; +const SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_CERTLIMIT = 8; +const SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_SUCCESS = 1; +const SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_MORETHAN100 = 160; +const SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_MORETHAN20 = 161; +const SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_REISSUE = 162; +const SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_FAILED = 163; +const SYNC_RESOLVERECIPSSTATUS_PICTURE_SUCCESS = 1; +const SYNC_RESOLVERECIPSSTATUS_PICTURE_NOFOTO = 173; +const SYNC_RESOLVERECIPSSTATUS_PICTURE_MAXSIZEEXCEEDED = 174; +const SYNC_RESOLVERECIPSSTATUS_PICTURE_MAXPICTURESEXCEEDED = 175; -define("SYNC_SEARCHSTATUS_SUCCESS", 1); -define("SYNC_SEARCHSTATUS_SERVERERROR", 3); -define("SYNC_SEARCHSTATUS_STORE_SUCCESS", 1); -define("SYNC_SEARCHSTATUS_STORE_REQINVALID", 2); -define("SYNC_SEARCHSTATUS_STORE_SERVERERROR", 3); -define("SYNC_SEARCHSTATUS_STORE_BADLINK", 4); -define("SYNC_SEARCHSTATUS_STORE_ACCESSDENIED", 5); -define("SYNC_SEARCHSTATUS_STORE_NOTFOUND", 6); -define("SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED", 7); -define("SYNC_SEARCHSTATUS_STORE_TOOCOMPLEX", 8); -define("SYNC_SEARCHSTATUS_STORE_TIMEDOUT", 10); -define("SYNC_SEARCHSTATUS_STORE_FOLDERSYNCREQ", 11); -define("SYNC_SEARCHSTATUS_STORE_ENDOFRETRANGE", 12); -define("SYNC_SEARCHSTATUS_STORE_ACCESSBLOCKED", 13); -define("SYNC_SEARCHSTATUS_STORE_CREDENTIALSREQ", 14); -define("SYNC_SEARCHSTATUS_PICTURE_SUCCESS", 1); -define("SYNC_SEARCHSTATUS_PICTURE_NOFOTO", 173); -define("SYNC_SEARCHSTATUS_PICTURE_MAXSIZEEXCEEDED", 174); -define("SYNC_SEARCHSTATUS_PICTURE_MAXPICTURESEXCEEDED", 175); +const SYNC_SEARCHSTATUS_SUCCESS = 1; +const SYNC_SEARCHSTATUS_SERVERERROR = 3; +const SYNC_SEARCHSTATUS_STORE_SUCCESS = 1; +const SYNC_SEARCHSTATUS_STORE_REQINVALID = 2; +const SYNC_SEARCHSTATUS_STORE_SERVERERROR = 3; +const SYNC_SEARCHSTATUS_STORE_BADLINK = 4; +const SYNC_SEARCHSTATUS_STORE_ACCESSDENIED = 5; +const SYNC_SEARCHSTATUS_STORE_NOTFOUND = 6; +const SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED = 7; +const SYNC_SEARCHSTATUS_STORE_TOOCOMPLEX = 8; +const SYNC_SEARCHSTATUS_STORE_TIMEDOUT = 10; +const SYNC_SEARCHSTATUS_STORE_FOLDERSYNCREQ = 11; +const SYNC_SEARCHSTATUS_STORE_ENDOFRETRANGE = 12; +const SYNC_SEARCHSTATUS_STORE_ACCESSBLOCKED = 13; +const SYNC_SEARCHSTATUS_STORE_CREDENTIALSREQ = 14; +const SYNC_SEARCHSTATUS_PICTURE_SUCCESS = 1; +const SYNC_SEARCHSTATUS_PICTURE_NOFOTO = 173; +const SYNC_SEARCHSTATUS_PICTURE_MAXSIZEEXCEEDED = 174; +const SYNC_SEARCHSTATUS_PICTURE_MAXPICTURESEXCEEDED = 175; -define("SYNC_SETTINGSSTATUS_SUCCESS", 1); -define("SYNC_SETTINGSSTATUS_PROTOCOLLERROR", 2); -define("SYNC_SETTINGSSTATUS_DEVINFO_SUCCESS", 1); -define("SYNC_SETTINGSSTATUS_DEVINFO_PROTOCOLLERROR", 2); -define("SYNC_SETTINGSSTATUS_DEVIPASS_SUCCESS", 1); -define("SYNC_SETTINGSSTATUS_DEVIPASS_PROTOCOLLERROR", 2); -define("SYNC_SETTINGSSTATUS_DEVIPASS_INVALIDARGS", 3); -define("SYNC_SETTINGSSTATUS_DEVIPASS_DENIED", 7); -define("SYNC_SETTINGSSTATUS_USERINFO_SUCCESS", 1); -define("SYNC_SETTINGSSTATUS_USERINFO_PROTOCOLLERROR", 2); +const SYNC_SETTINGSSTATUS_SUCCESS = 1; +const SYNC_SETTINGSSTATUS_PROTOCOLLERROR = 2; +const SYNC_SETTINGSSTATUS_DEVINFO_SUCCESS = 1; +const SYNC_SETTINGSSTATUS_DEVINFO_PROTOCOLLERROR = 2; +const SYNC_SETTINGSSTATUS_DEVIPASS_SUCCESS = 1; +const SYNC_SETTINGSSTATUS_DEVIPASS_PROTOCOLLERROR = 2; +const SYNC_SETTINGSSTATUS_DEVIPASS_INVALIDARGS = 3; +const SYNC_SETTINGSSTATUS_DEVIPASS_DENIED = 7; +const SYNC_SETTINGSSTATUS_USERINFO_SUCCESS = 1; +const SYNC_SETTINGSSTATUS_USERINFO_PROTOCOLLERROR = 2; -define("SYNC_SETTINGSOOF_DISABLED", 0); -define("SYNC_SETTINGSOOF_GLOBAL", 1); -define("SYNC_SETTINGSOOF_TIMEBASED", 2); +const SYNC_SETTINGSOOF_DISABLED = 0; +const SYNC_SETTINGSOOF_GLOBAL = 1; +const SYNC_SETTINGSOOF_TIMEBASED = 2; -define("SYNC_MIMETRUNCATION_ALL", 0); -define("SYNC_MIMETRUNCATION_4096", 1); -define("SYNC_MIMETRUNCATION_5120", 2); -define("SYNC_MIMETRUNCATION_7168", 3); -define("SYNC_MIMETRUNCATION_10240", 4); -define("SYNC_MIMETRUNCATION_20480", 5); -define("SYNC_MIMETRUNCATION_51200", 6); -define("SYNC_MIMETRUNCATION_102400", 7); -define("SYNC_MIMETRUNCATION_COMPLETE", 8); +const SYNC_MIMETRUNCATION_ALL = 0; +const SYNC_MIMETRUNCATION_4096 = 1; +const SYNC_MIMETRUNCATION_5120 = 2; +const SYNC_MIMETRUNCATION_7168 = 3; +const SYNC_MIMETRUNCATION_10240 = 4; +const SYNC_MIMETRUNCATION_20480 = 5; +const SYNC_MIMETRUNCATION_51200 = 6; +const SYNC_MIMETRUNCATION_102400 = 7; +const SYNC_MIMETRUNCATION_COMPLETE = 8; -define("SYNC_MIMESUPPORT_NEVER", 0); -define("SYNC_MIMESUPPORT_SMIME", 1); -define("SYNC_MIMESUPPORT_ALWAYS", 2); +const SYNC_MIMESUPPORT_NEVER = 0; +const SYNC_MIMESUPPORT_SMIME = 1; +const SYNC_MIMESUPPORT_ALWAYS = 2; -define("SYNC_VALIDATECERTSTATUS_SUCCESS", 1); -define("SYNC_VALIDATECERTSTATUS_PROTOCOLLERROR", 2); -define("SYNC_VALIDATECERTSTATUS_CANTVALIDATESIG", 3); -define("SYNC_VALIDATECERTSTATUS_DIGIDUNTRUSTED", 4); -define("SYNC_VALIDATECERTSTATUS_CERTCHAINNOTCORRECT", 5); -define("SYNC_VALIDATECERTSTATUS_DIGIDNOTVALIDFORSIGN", 6); -define("SYNC_VALIDATECERTSTATUS_DIGIDNOTVALID", 7); -define("SYNC_VALIDATECERTSTATUS_INVALIDCHAINCERTSTIME", 8); -define("SYNC_VALIDATECERTSTATUS_DIGIDUSEDINCORRECTLY", 9); -define("SYNC_VALIDATECERTSTATUS_INCORRECTDIGIDINFO", 10); -define("SYNC_VALIDATECERTSTATUS_INCORRECTUSEOFDIGIDINCHAIN", 11); -define("SYNC_VALIDATECERTSTATUS_DIGIDDOESNOTMATCHEMAIL", 12); -define("SYNC_VALIDATECERTSTATUS_DIGIDREVOKED", 13); -define("SYNC_VALIDATECERTSTATUS_DIGIDSERVERUNAVAILABLE", 14); -define("SYNC_VALIDATECERTSTATUS_DIGIDINCHAINREVOKED", 15); -define("SYNC_VALIDATECERTSTATUS_DIGIDREVSTATUSUNVALIDATED", 16); -define("SYNC_VALIDATECERTSTATUS_SERVERERROR", 17); +const SYNC_VALIDATECERTSTATUS_SUCCESS = 1; +const SYNC_VALIDATECERTSTATUS_PROTOCOLLERROR = 2; +const SYNC_VALIDATECERTSTATUS_CANTVALIDATESIG = 3; +const SYNC_VALIDATECERTSTATUS_DIGIDUNTRUSTED = 4; +const SYNC_VALIDATECERTSTATUS_CERTCHAINNOTCORRECT = 5; +const SYNC_VALIDATECERTSTATUS_DIGIDNOTVALIDFORSIGN = 6; +const SYNC_VALIDATECERTSTATUS_DIGIDNOTVALID = 7; +const SYNC_VALIDATECERTSTATUS_INVALIDCHAINCERTSTIME = 8; +const SYNC_VALIDATECERTSTATUS_DIGIDUSEDINCORRECTLY = 9; +const SYNC_VALIDATECERTSTATUS_INCORRECTDIGIDINFO = 10; +const SYNC_VALIDATECERTSTATUS_INCORRECTUSEOFDIGIDINCHAIN = 11; +const SYNC_VALIDATECERTSTATUS_DIGIDDOESNOTMATCHEMAIL = 12; +const SYNC_VALIDATECERTSTATUS_DIGIDREVOKED = 13; +const SYNC_VALIDATECERTSTATUS_DIGIDSERVERUNAVAILABLE = 14; +const SYNC_VALIDATECERTSTATUS_DIGIDINCHAINREVOKED = 15; +const SYNC_VALIDATECERTSTATUS_DIGIDREVSTATUSUNVALIDATED = 16; +const SYNC_VALIDATECERTSTATUS_SERVERERROR = 17; -define("SYNC_COMMONSTATUS_SUCCESS", 1); -define("SYNC_COMMONSTATUS_INVALIDCONTENT", 101); -define("SYNC_COMMONSTATUS_INVALIDWBXML", 102); -define("SYNC_COMMONSTATUS_INVALIDXML", 103); -define("SYNC_COMMONSTATUS_INVALIDDATETIME", 104); -define("SYNC_COMMONSTATUS_INVALIDCOMBINATIONOFIDS", 105); -define("SYNC_COMMONSTATUS_INVALIDIDS", 106); -define("SYNC_COMMONSTATUS_INVALIDMIME", 107); -define("SYNC_COMMONSTATUS_DEVIDMISSINGORINVALID", 108); -define("SYNC_COMMONSTATUS_DEVTYPEMISSINGORINVALID", 109); -define("SYNC_COMMONSTATUS_SERVERERROR", 110); -define("SYNC_COMMONSTATUS_SERVERERRORRETRYLATER", 111); -define("SYNC_COMMONSTATUS_ADACCESSDENIED", 112); -define("SYNC_COMMONSTATUS_MAILBOXQUOTAEXCEEDED", 113); -define("SYNC_COMMONSTATUS_MAILBOXSERVEROFFLINE", 114); -define("SYNC_COMMONSTATUS_SENDQUOTAEXCEEDED", 115); -define("SYNC_COMMONSTATUS_MESSRECIPUNRESOLVED", 116); -define("SYNC_COMMONSTATUS_MESSREPLYNOTALLOWED", 117); -define("SYNC_COMMONSTATUS_MESSPREVSENT", 118); -define("SYNC_COMMONSTATUS_MESSHASNORECIP", 119); -define("SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED", 120); -define("SYNC_COMMONSTATUS_MESSREPLYFAILED", 121); -define("SYNC_COMMONSTATUS_ATTTOOLARGE", 122); -define("SYNC_COMMONSTATUS_USERHASNOMAILBOX", 123); -define("SYNC_COMMONSTATUS_USERCANTBEANONYMOUS", 124); -define("SYNC_COMMONSTATUS_USERPRINCIPALNOTFOUND", 125); -define("SYNC_COMMONSTATUS_USERDISABLEDFORSYNC", 126); -define("SYNC_COMMONSTATUS_USERONNEWMAILBOXCANTSYNC", 127); -define("SYNC_COMMONSTATUS_USERONLEGACYMAILBOXCANTSYNC", 128); -define("SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER", 129); -define("SYNC_COMMONSTATUS_ACCESSDENIED", 130); -define("SYNC_COMMONSTATUS_ACCOUNTDISABLED", 131); -define("SYNC_COMMONSTATUS_SYNCSTATENOTFOUND", 132); -define("SYNC_COMMONSTATUS_SYNCSTATELOCKED", 133); -define("SYNC_COMMONSTATUS_SYNCSTATECORRUPT", 134); -define("SYNC_COMMONSTATUS_SYNCSTATEEXISTS", 135); -define("SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID", 136); -define("SYNC_COMMONSTATUS_COMMANDONOTSUPPORTED", 137); -define("SYNC_COMMONSTATUS_VERSIONNOTSUPPORTED", 138); -define("SYNC_COMMONSTATUS_DEVNOTFULLYPROVISIONABLE", 139); -define("SYNC_COMMONSTATUS_REMWIPEREQUESTED", 140); -define("SYNC_COMMONSTATUS_LEGACYDEVONSTRICTPOLICY", 141); -define("SYNC_COMMONSTATUS_DEVICENOTPROVISIONED", 142); -define("SYNC_COMMONSTATUS_POLICYREFRESH", 143); -define("SYNC_COMMONSTATUS_INVALIDPOLICYKEY", 144); -define("SYNC_COMMONSTATUS_EXTMANDEVICESNOTALLOWED", 145); -define("SYNC_COMMONSTATUS_NORECURRINCAL", 146); -define("SYNC_COMMONSTATUS_UNEXPECTEDITEMCLASS", 147); -define("SYNC_COMMONSTATUS_REMSERVERHASNOSSL", 148); -define("SYNC_COMMONSTATUS_INVALIDSTOREDREQ", 149); -define("SYNC_COMMONSTATUS_ITEMNOTFOUND", 150); -define("SYNC_COMMONSTATUS_TOOMANYFOLDERS", 151); -define("SYNC_COMMONSTATUS_NOFOLDERSFOUND", 152); -define("SYNC_COMMONSTATUS_ITEMLOSTAFTERMOVE", 153); -define("SYNC_COMMONSTATUS_FAILUREINMOVE", 154); -define("SYNC_COMMONSTATUS_NONPERSISTANTMOVEDISALLOWED", 155); -define("SYNC_COMMONSTATUS_MOVEINVALIDDESTFOLDER", 156); -define("SYNC_COMMONSTATUS_INVALIDACCOUNTID", 166); -define("SYNC_COMMONSTATUS_ACCOUNTSENDDISABLED", 167); -define("SYNC_COMMONSTATUS_IRMFEATUREDISABLED", 168); -define("SYNC_COMMONSTATUS_IRMTRANSIENTERROR", 169); -define("SYNC_COMMONSTATUS_IRMPERMANENTERROR", 170); -define("SYNC_COMMONSTATUS_IRMINVALIDTEMPLATEID", 171); -define("SYNC_COMMONSTATUS_IRMOPERATIONNOTPERMITTED", 172); -define("SYNC_COMMONSTATUS_NOPICTURE", 173); -define("SYNC_COMMONSTATUS_PICTURETOOLARGE", 174); -define("SYNC_COMMONSTATUS_PICTURELIMITREACHED", 175); -define("SYNC_COMMONSTATUS_BODYPARTCONVERSATIONTOOLARGE", 176); -define("SYNC_COMMONSTATUS_MAXDEVICESREACHED", 177); - -define("HTTP_CODE_200", 200); -define("HTTP_CODE_401", 401); -define("HTTP_CODE_449", 449); -define("HTTP_CODE_500", 500); +const SYNC_COMMONSTATUS_SUCCESS = 1; +const SYNC_COMMONSTATUS_INVALIDCONTENT = 101; +const SYNC_COMMONSTATUS_INVALIDWBXML = 102; +const SYNC_COMMONSTATUS_INVALIDXML = 103; +const SYNC_COMMONSTATUS_INVALIDDATETIME = 104; +const SYNC_COMMONSTATUS_INVALIDCOMBINATIONOFIDS = 105; +const SYNC_COMMONSTATUS_INVALIDIDS = 106; +const SYNC_COMMONSTATUS_INVALIDMIME = 107; +const SYNC_COMMONSTATUS_DEVIDMISSINGORINVALID = 108; +const SYNC_COMMONSTATUS_DEVTYPEMISSINGORINVALID = 109; +const SYNC_COMMONSTATUS_SERVERERROR = 110; +const SYNC_COMMONSTATUS_SERVERERRORRETRYLATER = 111; +const SYNC_COMMONSTATUS_ADACCESSDENIED = 112; +const SYNC_COMMONSTATUS_MAILBOXQUOTAEXCEEDED = 113; +const SYNC_COMMONSTATUS_MAILBOXSERVEROFFLINE = 114; +const SYNC_COMMONSTATUS_SENDQUOTAEXCEEDED = 115; +const SYNC_COMMONSTATUS_MESSRECIPUNRESOLVED = 116; +const SYNC_COMMONSTATUS_MESSREPLYNOTALLOWED = 117; +const SYNC_COMMONSTATUS_MESSPREVSENT = 118; +const SYNC_COMMONSTATUS_MESSHASNORECIP = 119; +const SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED = 120; +const SYNC_COMMONSTATUS_MESSREPLYFAILED = 121; +const SYNC_COMMONSTATUS_ATTTOOLARGE = 122; +const SYNC_COMMONSTATUS_USERHASNOMAILBOX = 123; +const SYNC_COMMONSTATUS_USERCANTBEANONYMOUS = 124; +const SYNC_COMMONSTATUS_USERPRINCIPALNOTFOUND = 125; +const SYNC_COMMONSTATUS_USERDISABLEDFORSYNC = 126; +const SYNC_COMMONSTATUS_USERONNEWMAILBOXCANTSYNC = 127; +const SYNC_COMMONSTATUS_USERONLEGACYMAILBOXCANTSYNC = 128; +const SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER = 129; +const SYNC_COMMONSTATUS_ACCESSDENIED = 130; +const SYNC_COMMONSTATUS_ACCOUNTDISABLED = 131; +const SYNC_COMMONSTATUS_SYNCSTATENOTFOUND = 132; +const SYNC_COMMONSTATUS_SYNCSTATELOCKED = 133; +const SYNC_COMMONSTATUS_SYNCSTATECORRUPT = 134; +const SYNC_COMMONSTATUS_SYNCSTATEEXISTS = 135; +const SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID = 136; +const SYNC_COMMONSTATUS_COMMANDONOTSUPPORTED = 137; +const SYNC_COMMONSTATUS_VERSIONNOTSUPPORTED = 138; +const SYNC_COMMONSTATUS_DEVNOTFULLYPROVISIONABLE = 139; +const SYNC_COMMONSTATUS_REMWIPEREQUESTED = 140; +const SYNC_COMMONSTATUS_LEGACYDEVONSTRICTPOLICY = 141; +const SYNC_COMMONSTATUS_DEVICENOTPROVISIONED = 142; +const SYNC_COMMONSTATUS_POLICYREFRESH = 143; +const SYNC_COMMONSTATUS_INVALIDPOLICYKEY = 144; +const SYNC_COMMONSTATUS_EXTMANDEVICESNOTALLOWED = 145; +const SYNC_COMMONSTATUS_NORECURRINCAL = 146; +const SYNC_COMMONSTATUS_UNEXPECTEDITEMCLASS = 147; +const SYNC_COMMONSTATUS_REMSERVERHASNOSSL = 148; +const SYNC_COMMONSTATUS_INVALIDSTOREDREQ = 149; +const SYNC_COMMONSTATUS_ITEMNOTFOUND = 150; +const SYNC_COMMONSTATUS_TOOMANYFOLDERS = 151; +const SYNC_COMMONSTATUS_NOFOLDERSFOUND = 152; +const SYNC_COMMONSTATUS_ITEMLOSTAFTERMOVE = 153; +const SYNC_COMMONSTATUS_FAILUREINMOVE = 154; +const SYNC_COMMONSTATUS_NONPERSISTANTMOVEDISALLOWED = 155; +const SYNC_COMMONSTATUS_MOVEINVALIDDESTFOLDER = 156; +const SYNC_COMMONSTATUS_INVALIDACCOUNTID = 166; +const SYNC_COMMONSTATUS_ACCOUNTSENDDISABLED = 167; +const SYNC_COMMONSTATUS_IRMFEATUREDISABLED = 168; +const SYNC_COMMONSTATUS_IRMTRANSIENTERROR = 169; +const SYNC_COMMONSTATUS_IRMPERMANENTERROR = 170; +const SYNC_COMMONSTATUS_IRMINVALIDTEMPLATEID = 171; +const SYNC_COMMONSTATUS_IRMOPERATIONNOTPERMITTED = 172; +const SYNC_COMMONSTATUS_NOPICTURE = 173; +const SYNC_COMMONSTATUS_PICTURETOOLARGE = 174; +const SYNC_COMMONSTATUS_PICTURELIMITREACHED = 175; +const SYNC_COMMONSTATUS_BODYPARTCONVERSATIONTOOLARGE = 176; +const SYNC_COMMONSTATUS_MAXDEVICESREACHED = 177; +const HTTP_CODE_200 = 200; +const HTTP_CODE_401 = 401; +const HTTP_CODE_449 = 449; +const HTTP_CODE_500 = 500; //logging defs -define("LOGLEVEL_OFF", 0); -define("LOGLEVEL_FATAL", 1); -define("LOGLEVEL_ERROR", 2); -define("LOGLEVEL_WARN", 4); -define("LOGLEVEL_INFO", 8); -define("LOGLEVEL_DEBUG", 16); -define("LOGLEVEL_WBXML", 32); -define("LOGLEVEL_DEVICEID", 64); -define("LOGLEVEL_WBXMLSTACK", 128); +const LOGLEVEL_OFF = 0; +const LOGLEVEL_FATAL = 1; +const LOGLEVEL_ERROR = 2; +const LOGLEVEL_WARN = 4; +const LOGLEVEL_INFO = 8; +const LOGLEVEL_DEBUG = 16; +const LOGLEVEL_WBXML = 32; +const LOGLEVEL_DEVICEID = 64; +const LOGLEVEL_WBXMLSTACK = 128; -define("LOGLEVEL_ALL", LOGLEVEL_FATAL | LOGLEVEL_ERROR | LOGLEVEL_WARN | LOGLEVEL_INFO | LOGLEVEL_DEBUG | LOGLEVEL_WBXML); +//LOGLEVEL_ALL = LOGLEVEL_FATAL | LOGLEVEL_ERROR | LOGLEVEL_WARN | LOGLEVEL_INFO | LOGLEVEL_DEBUG | LOGLEVEL_WBXML +const LOGLEVEL_ALL = 63; -define("BACKEND_DISCARD_DATA", 1); +const BACKEND_DISCARD_DATA = 1; -define("SYNC_BODYPREFERENCE_UNDEFINED", 0); -define("SYNC_BODYPREFERENCE_PLAIN", 1); -define("SYNC_BODYPREFERENCE_HTML", 2); -define("SYNC_BODYPREFERENCE_RTF", 3); -define("SYNC_BODYPREFERENCE_MIME", 4); +const SYNC_BODYPREFERENCE_UNDEFINED = 0; +const SYNC_BODYPREFERENCE_PLAIN = 1; +const SYNC_BODYPREFERENCE_HTML = 2; +const SYNC_BODYPREFERENCE_RTF = 3; +const SYNC_BODYPREFERENCE_MIME = 4; -define("SYNC_FLAGSTATUS_CLEAR", 0); -define("SYNC_FLAGSTATUS_COMPLETE", 1); -define("SYNC_FLAGSTATUS_ACTIVE", 2); +const SYNC_FLAGSTATUS_CLEAR = 0; +const SYNC_FLAGSTATUS_COMPLETE = 1; +const SYNC_FLAGSTATUS_ACTIVE = 2; -define("DEFAULT_EMAIL_CONTENTCLASS", "urn:content-classes:message"); -define("DEFAULT_CALENDAR_CONTENTCLASS", "urn:content-classes:calendarmessage"); +const DEFAULT_EMAIL_CONTENTCLASS = "urn:content-classes:message"; +const DEFAULT_CALENDAR_CONTENTCLASS = "urn:content-classes:calendarmessage"; -define("SYNC_MAIL_LASTVERB_UNKNOWN", 0); -define("SYNC_MAIL_LASTVERB_REPLYSENDER", 1); -define("SYNC_MAIL_LASTVERB_REPLYALL", 2); -define("SYNC_MAIL_LASTVERB_FORWARD", 3); +const SYNC_MAIL_LASTVERB_UNKNOWN = 0; +const SYNC_MAIL_LASTVERB_REPLYSENDER = 1; +const SYNC_MAIL_LASTVERB_REPLYALL = 2; +const SYNC_MAIL_LASTVERB_FORWARD = 3; -define("INTERNET_CPID_WINDOWS1252", 1252); -define("INTERNET_CPID_UTF8", 65001); +const INTERNET_CPID_WINDOWS1252 = 1252; +const INTERNET_CPID_UTF8 = 65001; -define("MAPI_E_NOT_ENOUGH_MEMORY_32BIT", -2147024882); -define("MAPI_E_NOT_ENOUGH_MEMORY_64BIT", 2147942414); +const MAPI_E_NOT_ENOUGH_MEMORY_32BIT = -2147024882; +const MAPI_E_NOT_ENOUGH_MEMORY_64BIT = 2147942414; -define("SYNC_SETTINGSOOF_BODYTYPE_HTML", "HTML"); -define("SYNC_SETTINGSOOF_BODYTYPE_TEXT", "TEXT"); +const SYNC_SETTINGSOOF_BODYTYPE_HTML = "HTML"; +const SYNC_SETTINGSOOF_BODYTYPE_TEXT = "TEXT"; -define("SYNC_FILEAS_FIRSTLAST", 1); -define("SYNC_FILEAS_LASTFIRST", 2); -define("SYNC_FILEAS_COMPANYONLY", 3); -define("SYNC_FILEAS_COMPANYLAST", 4); -define("SYNC_FILEAS_COMPANYFIRST", 5); -define("SYNC_FILEAS_LASTCOMPANY", 6); -define("SYNC_FILEAS_FIRSTCOMPANY", 7); +const SYNC_FILEAS_FIRSTLAST = 1; +const SYNC_FILEAS_LASTFIRST = 2; +const SYNC_FILEAS_COMPANYONLY = 3; +const SYNC_FILEAS_COMPANYLAST = 4; +const SYNC_FILEAS_COMPANYFIRST = 5; +const SYNC_FILEAS_LASTCOMPANY = 6; +const SYNC_FILEAS_FIRSTCOMPANY = 7; -define ("SYNC_RESOLVERECIPIENTS_TYPE_GAL", 1); -define ("SYNC_RESOLVERECIPIENTS_TYPE_CONTACT", 2); +const SYNC_RESOLVERECIPIENTS_TYPE_GAL = 1; +const SYNC_RESOLVERECIPIENTS_TYPE_CONTACT = 2; -define("SYNC_RESOLVERECIPIENTS_CERTRETRIEVE_NO", 1); -define("SYNC_RESOLVERECIPIENTS_CERTRETRIEVE_FULL", 2); -define("SYNC_RESOLVERECIPIENTS_CERTRETRIEVE_MINI", 3); +const SYNC_RESOLVERECIPIENTS_CERTRETRIEVE_NO = 1; +const SYNC_RESOLVERECIPIENTS_CERTRETRIEVE_FULL = 2; +const SYNC_RESOLVERECIPIENTS_CERTRETRIEVE_MINI = 3; -define("NOTEIVERB_REPLYTOSENDER", 102); -define("NOTEIVERB_REPLYTOALL", 103); -define("NOTEIVERB_FORWARD", 104); +const NOTEIVERB_REPLYTOSENDER = 102; +const NOTEIVERB_REPLYTOALL = 103; +const NOTEIVERB_FORWARD = 104; -define("AS_REPLYTOSENDER", 1); -define("AS_REPLYTOALL", 2); -define("AS_FORWARD", 3); - -?> \ No newline at end of file +const AS_REPLYTOSENDER = 1; +const AS_REPLYTOALL = 2; +const AS_FORWARD = 3; diff --git a/sources/lib/core/zsyslog.php b/sources/lib/core/zsyslog.php new file mode 100644 index 0000000..8396818 --- /dev/null +++ b/sources/lib/core/zsyslog.php @@ -0,0 +1,81 @@ + 0) { + $syslog_message = "<{$pri}>" . date('M d H:i:s ') . self::$program . ': ' . $line; + socket_sendto($sock, $syslog_message, strlen($syslog_message), 0, self::$hostname, self::$port); + } + } + socket_close($sock); + } + + return true; + } + + /** + * Converts the ZLog level to SYSLOG level. + * + * @params int $loglevel Z-Push LogLevel + * + * @access private + * @return SYSLOG_LEVEL or false + */ + private static function zlogLevel2SyslogLevel($loglevel) { + switch($loglevel) { + case LOGLEVEL_OFF: return false; break; + case LOGLEVEL_FATAL: return LOG_ALERT; break; + case LOGLEVEL_ERROR: return LOG_ERR; break; + case LOGLEVEL_WARN: return LOG_WARNING; break; + case LOGLEVEL_INFO: return LOG_INFO; break; + case LOGLEVEL_DEBUG: return LOG_DEBUG; break; + case LOGLEVEL_WBXML: return LOG_DEBUG; break; + case LOGLEVEL_DEVICEID: return LOG_DEBUG; break; + case LOGLEVEL_WBXMLSTACK: return LOG_DEBUG; break; + } + } +} \ No newline at end of file diff --git a/sources/lib/default/backend.php b/sources/lib/default/backend.php index 99c5b39..ad92517 100644 --- a/sources/lib/default/backend.php +++ b/sources/lib/default/backend.php @@ -58,6 +58,7 @@ abstract class Backend implements IBackend { protected $permanentStorage; protected $stateStorage; + protected $originalUsername; /** * Constructor @@ -215,6 +216,16 @@ abstract class Backend implements IBackend { return array('emailaddress' => $username, 'fullname' => $username); } + /** + * Returns the username and store of the currently active user + * + * @access public + * @return Array + */ + public function GetCurrentUsername() { + return $this->GetUserDetails(Request::GetAuthUser()); + } + /**---------------------------------------------------------------------------------------------------------- * Protected methods for BackendStorage * @@ -294,12 +305,24 @@ abstract class Backend implements IBackend { } if (isset($this->stateStorage)) { try { - $this->storage_state = ZPush::GetDeviceManager()->GetStateManager()->SetBackendStorage($this->stateStorage, StateManager::BACKENDSTORAGE_STATE); + ZPush::GetDeviceManager()->GetStateManager()->SetBackendStorage($this->stateStorage, StateManager::BACKENDSTORAGE_STATE); } catch (StateNotYetAvailableException $snyae) { } catch(StateNotFoundException $snfe) { } } } -} -?> \ No newline at end of file + /** + * Sets the username originally specified by the user to connect with Z-Push. This can be different from the + * username used for this backend; for example, BackendCombined could have applied a username mapping. + * + * This information can be used by backends to communicate the right username; for example, calendar events + * without an organizer need to supply the original username in order for the device to understand that the + * user owns the event. + * + * @param string $originalUsername The original username + */ + public function SetOriginalUsername($originalUsername) { + $this->originalUsername = $originalUsername; + } +} \ No newline at end of file diff --git a/sources/lib/default/diffbackend/diffbackend.php b/sources/lib/default/diffbackend/diffbackend.php index c41f46b..7fd87dc 100644 --- a/sources/lib/default/diffbackend/diffbackend.php +++ b/sources/lib/default/diffbackend/diffbackend.php @@ -51,15 +51,6 @@ * Consult LICENSE file for details ************************************************/ -// default backend -include_once('lib/default/backend.php'); - -// DiffBackend components -include_once('diffstate.php'); -include_once('importchangesdiff.php'); -include_once('exportchangesdiff.php'); - - abstract class BackendDiff extends Backend { protected $store; @@ -340,25 +331,6 @@ abstract class BackendDiff extends Backend { */ public abstract function SetReadFlag($folderid, $id, $flags, $contentParameters); - /** - * Changes the 'star' flag of a message on disk. The $flags - * parameter can only be '2' (starred) or '0' (unstarred). After a call to - * SetStarFlag(), GetMessageList() should return the message with the - * new 'flags' but should not modify the 'mod' parameter. If you do - * change 'mod', simply setting the message to 'starred' on the mobile will trigger - * a full resync of the item from the server. - * - * @param string $folderid id of the folder - * @param string $id id of the message - * @param int $flags star flag of the message - * @param ContentParameters $contentParameters - * - * @access public - * @return boolean status of the operation - * @throws StatusException could throw specific SYNC_STATUS_* exceptions - */ - public abstract function SetStarFlag($folderid, $id, $flags, $contentParameters); - /** * Called when the user has requested to delete (really delete) a message. Usually * this means just unlinking the file its in or somesuch. After this call has succeeded, a call to @@ -394,4 +366,3 @@ abstract class BackendDiff extends Backend { public abstract function MoveMessage($folderid, $id, $newfolderid, $contentParameters); } -?> \ No newline at end of file diff --git a/sources/lib/default/diffbackend/diffstate.php b/sources/lib/default/diffbackend/diffstate.php index fabe98c..07f1731 100644 --- a/sources/lib/default/diffbackend/diffstate.php +++ b/sources/lib/default/diffbackend/diffstate.php @@ -132,12 +132,7 @@ class DiffState implements IChanges { * @return boolean */ static public function RowCmp($a, $b) { - if (is_numeric($a["id"]) && is_numeric($b["id"])) { - return $a["id"] < $b["id"] ? 1 : -1; - } - else { - return strcmp($a["id"], $b["id"]) < 0 ? 1 : -1; - } + return strcmp($b['id'], $a['id']); } /** @@ -158,34 +153,29 @@ class DiffState implements IChanges { $inew = 0; $iold = 0; + $cntstate = count($this->syncstate); + $cntnew = count($new); // Get changes by comparing our list of messages with // our previous state - while(1) { - $change = array(); - - if($iold >= count($this->syncstate) || $inew >= count($new)) + while(true) { + if($iold >= $cntstate || $inew >= $cntnew) break; - if($this->syncstate[$iold]["id"] == $new[$inew]["id"]) { - // Both messages are still available, compare flags, star and mod + $cmp = strcmp($this->syncstate[$iold]["id"], $new[$inew]["id"]); + if ($cmp == 0) { + // Both messages are still available, compare flags and mod if(isset($this->syncstate[$iold]["flags"]) && isset($new[$inew]["flags"]) && $this->syncstate[$iold]["flags"] != $new[$inew]["flags"]) { // Flags changed + $change = array(); $change["type"] = "flags"; $change["id"] = $new[$inew]["id"]; $change["flags"] = $new[$inew]["flags"]; $changes[] = $change; } - if(isset($this->syncstate[$iold]["star"]) && isset($new[$inew]["star"]) && $this->syncstate[$iold]["star"] != $new[$inew]["star"]) { - // Star changed - $change["type"] = "star"; - $change["id"] = $new[$inew]["id"]; - $change["star"] = $new[$inew]["star"]; - $changes[] = $change; - } - - if($this->syncstate[$iold]["mod"] != $new[$inew]["mod"]) { + if(isset($this->syncstate[$iold]["mod"]) && isset($new[$inew]["mod"]) && $this->syncstate[$iold]["mod"] != $new[$inew]["mod"]) { + $change = array(); $change["type"] = "change"; $change["id"] = $new[$inew]["id"]; $changes[] = $change; @@ -193,38 +183,38 @@ class DiffState implements IChanges { $inew++; $iold++; + } elseif ($cmp > 0) { + // Message in state seems to have disappeared (delete) + $change = array(); + $change["type"] = "delete"; + $change["id"] = $this->syncstate[$iold]["id"]; + $changes[] = $change; + $iold++; } else { - if($this->syncstate[$iold]["id"] > $new[$inew]["id"]) { - // Message in state seems to have disappeared (delete) - $change["type"] = "delete"; - $change["id"] = $this->syncstate[$iold]["id"]; - $changes[] = $change; - $iold++; - } else { - // Message in new seems to be new (add) - $change["type"] = "change"; - $change["flags"] = SYNC_NEWMESSAGE; - $change["star"] = SYNC_NEWMESSAGE; - $change["id"] = $new[$inew]["id"]; - $changes[] = $change; - $inew++; - } + // Message in new seems to be new (add) + $change = array(); + $change["type"] = "change"; + $change["flags"] = SYNC_NEWMESSAGE; + $change["id"] = $new[$inew]["id"]; + $changes[] = $change; + $inew++; } } - while($iold < count($this->syncstate)) { + while($iold < $cntstate) { // All data left in 'syncstate' have been deleted + $change = array(); $change["type"] = "delete"; $change["id"] = $this->syncstate[$iold]["id"]; $changes[] = $change; $iold++; } - while($inew < count($new)) { + while($inew < $cntnew) { // All data left in new have been added + $change = array(); $change["type"] = "change"; $change["flags"] = SYNC_NEWMESSAGE; - $change["star"] = SYNC_NEWMESSAGE; $change["id"] = $new[$inew]["id"]; $changes[] = $change; $inew++; @@ -245,32 +235,33 @@ class DiffState implements IChanges { */ protected function updateState($type, $change) { // Change can be a change or an add - if($type == "change") { - for($i=0; $i < count($this->syncstate); $i++) { - if($this->syncstate[$i]["id"] == $change["id"]) { + $change_id = $change['id']; + foreach ($this->syncstate as $i => &$state) { + if($this->syncstate[$i]["id"] == $change["id"]) { + if ($state['id'] == $change_id) { $this->syncstate[$i] = $change; - return; + switch ($type) { + case 'change': + $state = $change; + return; + case 'flags': + $state['flags'] = $change['flags']; + return; + case 'delete': + array_splice($this->syncstate, $i, 1); + return; + default: + throw new Exception(sprintf("updateState: type '%s' is not supported", $type)); + } } } - // Not found, add as new + } + if($type == "change") { $this->syncstate[] = $change; } else { - for($i=0; $i < count($this->syncstate); $i++) { - // Search for the entry for this item - if($this->syncstate[$i]["id"] == $change["id"]) { - if($type == "flags") { - // Update flags - $this->syncstate[$i]["flags"] = $change["flags"]; - } else if($type == "star") { - // Update star - $this->syncstate[$i]["star"] = $change["star"]; - } else if($type == "delete") { - // Delete item - array_splice($this->syncstate, $i, 1); - } - return; - } - } + $flags = empty($change['flags'])?"":$change['flags']; + $mod = empty($change['mod'])?"":$change['mod']; + ZLog::Write(LOGLEVEL_WARN, sprintf("updateState: no state modification!!! %s|%s|%s|%s", $type, $change_id, $flags, $mod)); } } @@ -321,5 +312,3 @@ class DiffState implements IChanges { } } - -?> \ No newline at end of file diff --git a/sources/lib/default/diffbackend/exportchangesdiff.php b/sources/lib/default/diffbackend/exportchangesdiff.php index c9ad12d..c3a3a89 100644 --- a/sources/lib/default/diffbackend/exportchangesdiff.php +++ b/sources/lib/default/diffbackend/exportchangesdiff.php @@ -110,7 +110,7 @@ class ExportChangesDiff extends DiffState implements IExportChanges{ $this->changes = $this->getDiffTo($folderlist); } - ZLog::Write(LOGLEVEL_INFO, sprintf("ExportChangesDiff->InitializeExporter(): Found '%d' changes", count($this->changes) )); + ZLog::Write(LOGLEVEL_INFO, sprintf("ExportChangesDiff->InitializeExporter(): Found %d changes", count($this->changes))); } /** @@ -195,10 +195,6 @@ class ExportChangesDiff extends DiffState implements IExportChanges{ if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageReadFlag($change["id"], $change["flags"]) == true) $this->updateState("flags", $change); break; - case "star": - if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageStarFlag($change["id"], $change["star"]) == true) - $this->updateState("star", $change); - break; case "move": if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageMove($change["id"], $change["parent"]) == true) $this->updateState("move", $change); @@ -218,5 +214,3 @@ class ExportChangesDiff extends DiffState implements IExportChanges{ } } } - -?> \ No newline at end of file diff --git a/sources/lib/default/diffbackend/importchangesdiff.php b/sources/lib/default/diffbackend/importchangesdiff.php index 3145593..733fc8c 100644 --- a/sources/lib/default/diffbackend/importchangesdiff.php +++ b/sources/lib/default/diffbackend/importchangesdiff.php @@ -100,7 +100,6 @@ class ImportChangesDiff extends DiffState implements IImportChanges { $change["mod"] = 0; // dummy, will be updated later if the change succeeds $change["parent"] = $this->folderid; $change["flags"] = (isset($message->read)) ? $message->read : 0; - $change["star"] = (isset($message->flag) && isset($message->flag->flagstatus)) ? $message->flag->flagstatus : 0; $this->updateState("change", $change); if($conflict && $this->flags == SYNC_CONFLICT_OVERWRITE_PIM) @@ -185,43 +184,14 @@ class ImportChangesDiff extends DiffState implements IImportChanges { return true; } - /** - * Imports a change in 'star' flag - * This can never conflict - * - * @param string $id - * @param int $flags - flagged/unflagged - * - * @access public - * @return boolean - * @throws StatusException - */ - public function ImportMessageStarFlag($id, $flags) { - //do nothing if it is a dummy folder - if ($this->folderid == SYNC_FOLDER_TYPE_DUMMY) - throw new StatusException(sprintf("ImportChangesDiff->ImportMessageStarFlag('%s','%s'): can not be done on a dummy folder", $id, $flags), SYNC_STATUS_SYNCCANNOTBECOMPLETED); - - // Update client state - $change = array(); - $change["id"] = $id; - $change["star"] = $flags; - $this->updateState("star", $change); - - $stat = $this->backend->SetStarFlag($this->folderid, $id, $flags, $this->contentparameters); - if (!$stat) - throw new StatusException(sprintf("ImportChangesDiff->ImportMessageStarFlag('%s','%s'): Error, unable retrieve message from backend", $id, $flags), SYNC_STATUS_OBJECTNOTFOUND); - - return true; - } - /** * Imports a move of a message. This occurs when a user moves an item to another folder * * @param string $id - * @param int $flags - read/unread + * @param string $newfolder * * @access public - * @return boolean + * @return string * @throws StatusException */ public function ImportMessageMove($id, $newfolder) { @@ -229,7 +199,12 @@ class ImportChangesDiff extends DiffState implements IImportChanges { if ($this->folderid == SYNC_FOLDER_TYPE_DUMMY || $newfolder == SYNC_FOLDER_TYPE_DUMMY) throw new StatusException(sprintf("ImportChangesDiff->ImportMessageMove('%s'): can not be done on a dummy folder", $id), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); - return $this->backend->MoveMessage($this->folderid, $id, $newfolder, $this->contentparameters); + $newid = $this->backend->MoveMessage($this->folderid, $id, $newfolder, $this->contentparameters); + if ($newid === false) + throw new StatusException("ImportChangesDiff->ImportMessageMove($id, $newfolder): MoveMessage failed (false)", SYNC_MOVEITEMSSTATUS_CANNOTMOVE); + + // Don't resync the folder here, since this can be called from the combined backed and $newfolder will not exist (backend prefix is missing) + return $newid; } @@ -301,5 +276,3 @@ class ImportChangesDiff extends DiffState implements IImportChanges { return true; } } - -?> \ No newline at end of file diff --git a/sources/lib/default/filestatemachine.php b/sources/lib/default/filestatemachine.php index f8dbec0..8322ae3 100644 --- a/sources/lib/default/filestatemachine.php +++ b/sources/lib/default/filestatemachine.php @@ -51,6 +51,7 @@ class FileStateMachine implements IStateMachine { private $userfilename; private $settingsfilename; + private $usermapfilename; /** * Constructor @@ -73,6 +74,7 @@ class FileStateMachine implements IStateMachine { $this->getDirectoryForDevice(Request::GetDeviceID()); $this->userfilename = STATE_DIR . 'users'; $this->settingsfilename = STATE_DIR . 'settings'; + $this->usermapfilename = STATE_DIR . 'usermap'; if ((!file_exists($this->userfilename) && !touch($this->userfilename)) || !is_writable($this->userfilename)) throw new FatalMisconfigurationException("Not possible to write to the configured state directory."); @@ -153,7 +155,7 @@ class FileStateMachine implements IStateMachine { $state = serialize($state); $filename = $this->getFullFilePath($devid, $type, $key, $counter); - if (($bytes = file_put_contents($filename, $state)) === false) + if (($bytes = Utils::safe_put_contents($filename, $state)) === false) throw new FatalMisconfigurationException(sprintf("FileStateMachine->SetState(): Could not write state '%s'",$filename)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->SetState() written %d bytes on file: '%s'", $bytes, $filename)); @@ -208,8 +210,7 @@ class FileStateMachine implements IStateMachine { * @return boolean indicating if the user was added or not (existed already) */ public function LinkUserDevice($username, $devid) { - include_once("simplemutex.php"); - $mutex = new SimpleMutex(); + $mutex = new SimpleMutex(__FILE__); $changed = false; // exclusive block @@ -232,7 +233,7 @@ class FileStateMachine implements IStateMachine { } if ($changed) { - $bytes = file_put_contents($this->userfilename, serialize($users)); + $bytes = Utils::safe_put_contents($this->userfilename, serialize($users)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->LinkUserDevice(): wrote %d bytes to users file", $bytes)); } else @@ -253,8 +254,7 @@ class FileStateMachine implements IStateMachine { * @return boolean */ public function UnLinkUserDevice($username, $devid) { - include_once("simplemutex.php"); - $mutex = new SimpleMutex(); + $mutex = new SimpleMutex(__FILE__); $changed = false; // exclusive block @@ -281,7 +281,7 @@ class FileStateMachine implements IStateMachine { } if ($changed) { - $bytes = file_put_contents($this->userfilename, serialize($users)); + $bytes = Utils::safe_put_contents($this->userfilename, serialize($users)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->UnLinkUserDevice(): wrote %d bytes to users file", $bytes)); } else @@ -292,6 +292,16 @@ class FileStateMachine implements IStateMachine { return $changed; } + /** + * Get all UserDevice mapping + * + * @access public + * @return array + */ + public function GetAllUserDevice() { + return unserialize(file_get_contents($this->userfilename))?:array(); + } + /** * Returns an array with all device ids for a user. * If no user is set, all device ids should be returned @@ -369,7 +379,7 @@ class FileStateMachine implements IStateMachine { $settings[self::VERSION] = $version; ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->SetStateVersion() saving supported state version, value '%d'", $version)); - $status = file_put_contents($this->settingsfilename, serialize($settings)); + $status = Utils::safe_put_contents($this->settingsfilename, serialize($settings)); Utils::FixFileOwner($this->settingsfilename); return $status; } @@ -383,42 +393,25 @@ class FileStateMachine implements IStateMachine { * @return array(mixed) */ public function GetAllStatesForDevice($devid) { + $types = array(IStateMachine::DEVICEDATA, IStateMachine::FOLDERDATA, IStateMachine::FAILSAVE, IStateMachine::HIERARCHY, IStateMachine::BACKENDSTORAGE); + $typematch = implode("|", $types); $out = array(); $devdir = $this->getDirectoryForDevice($devid) . "/$devid-"; foreach (glob($devdir . "*", GLOB_NOSORT) as $devdata) { - // cut the device dir away and split into parts - $parts = explode("-", substr($devdata, strlen($devdir))); + $str = substr($devdata, strlen($devdir)-1); + $matches = array(); - $state = array('type' => false, 'counter' => false, 'uuid' => false); + if (!preg_match("/^(?:-(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}))?(?:-($typematch))?(?:-(\d+))?$/", $str, $matches)) + throw new Exception(sprintf("GetAllStatesForDevice(): didn't match the regexp !!!: %s", $str)); - if (isset($parts[0]) && $parts[0] == IStateMachine::DEVICEDATA) - $state['type'] = IStateMachine::DEVICEDATA; - - if (isset($parts[0]) && strlen($parts[0]) == 8 && - isset($parts[1]) && strlen($parts[1]) == 4 && - isset($parts[2]) && strlen($parts[2]) == 4 && - isset($parts[3]) && strlen($parts[3]) == 4 && - isset($parts[4]) && strlen($parts[4]) == 12) - $state['uuid'] = $parts[0]."-".$parts[1]."-".$parts[2]."-".$parts[3]."-".$parts[4]; - - if (isset($parts[5]) && is_numeric($parts[5])) { - $state['counter'] = $parts[5]; - $state['type'] = ""; // default - } - - if (isset($parts[5])) { - if (is_int($parts[5])) - $state['counter'] = $parts[5]; - - else if (in_array($parts[5], array(IStateMachine::FOLDERDATA, IStateMachine::FAILSAVE, IStateMachine::HIERARCHY, IStateMachine::BACKENDSTORAGE))) - $state['type'] = $parts[5]; - } - if (isset($parts[6]) && is_numeric($parts[6])) - $state['counter'] = $parts[6]; - - $out[] = $state; + $out[] = array( + 'uuid' => (isset($matches[1]) ? $matches[1] : false), + 'type' => (isset($matches[2]) ? $matches[2] : false), + 'counter' => (isset($matches[3]) ? $matches[3] : false), + ); } + return $out; } @@ -433,7 +426,6 @@ class FileStateMachine implements IStateMachine { * @return integer */ public function GetUserDevicePermission($user, $devid) { - include_once("simplemutex.php"); $mutex = new SimpleMutex(); $status = SYNC_COMMONSTATUS_SUCCESS; @@ -449,7 +441,8 @@ class FileStateMachine implements IStateMachine { } // Android PROVISIONING initial step - if ($devid != "validate") { + // LG-D802 is sending an empty deviceid + if ($devid != "validate" && $devid != "") { $changed = false; if (array_key_exists($user, $userList)) { @@ -601,5 +594,112 @@ class FileStateMachine implements IStateMachine { return false; } -} -?> \ No newline at end of file + /** + * Retrieves the mapped username for a specific username and backend. + * + * @param string $username The username to lookup + * @param string $backend Name of the backend to lookup + * + * @return string The mapped username or null if none found + */ + public function GetMappedUsername($username, $backend) { + $mutex = new SimpleMutex(); + + // exclusive block + if ($mutex->Block()) { + // Read current mapping + $filecontents = @file_get_contents($this->usermapfilename); + if ($filecontents) + $mapping = unserialize($filecontents); + else + $mapping = array(); + $mutex->Release(); + } + + // Find mapping + $key = $username . '/' . $backend; + if (isset($mapping[$key])) { + return $mapping[$key]; + } + return null; + } + + /** + * Maps a username for a specific backend to another username. + * + * @param string $username The username to map + * @param string $backend Name of the backend + * @param string $mappedname The mappend username + * + * @return boolean + */ + public function MapUsername($username, $backend, $mappedname) { + $mutex = new SimpleMutex(); + + // exclusive block + if ($mutex->Block()) { + // Read current mapping + $filecontents = @file_get_contents($this->usermapfilename); + if ($filecontents) + $mapping = unserialize($filecontents); + else + $mapping = array(); + + // Map username + backend to the mapped username + $key = $username . '/' . $backend; + $mapping[$key] = $mappedname; + + // Write mapping file + $bytes = file_put_contents($this->usermapfilename, serialize($mapping)); + if ($bytes === false) { + ZLog::Write(LOGLEVEL_ERROR, "Unable to write to mapping file"); + return false; + } + ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->MapUsername(): wrote %d bytes to mapping file", $bytes)); + + $mutex->Release(); + } + return true; + } + + /** + * Unmaps a username for a specific backend. + * + * @param string $username The username to unmap + * @param string $backend Name of the backend + * + * @return boolean + */ + public function UnmapUsername($username, $backend) { + $mutex = new SimpleMutex(); + + // exclusive block + if ($mutex->Block()) { + // Read current mapping + $filecontents = @file_get_contents($this->usermapfilename); + if ($filecontents) + $mapping = unserialize($filecontents); + else + $mapping = array(); + + // Unmap username + backend + $key = $username . '/' . $backend; + if (!isset($mapping[$key])) { + ZLog::Write(LOGLEVEL_INFO, "Username and backend not found in mapping file"); + return false; + } + unset($mapping[$key]); + + // Write mapping file + $bytes = file_put_contents($this->usermapfilename, serialize($mapping)); + if ($bytes === false) { + ZLog::Write(LOGLEVEL_ERROR, "Unable to write to mapping file"); + return false; + } + ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->UnmapUsername(): wrote %d bytes to mapping file", $bytes)); + + $mutex->Release(); + } + return true; + } +} \ No newline at end of file diff --git a/sources/lib/default/searchprovider.php b/sources/lib/default/searchprovider.php index e30187b..8072e85 100644 --- a/sources/lib/default/searchprovider.php +++ b/sources/lib/default/searchprovider.php @@ -122,4 +122,3 @@ class SearchProvider implements ISearchProvider{ return true; } } -?> \ No newline at end of file diff --git a/sources/lib/default/simplemutex.php b/sources/lib/default/simplemutex.php index 98c74c2..5280109 100644 --- a/sources/lib/default/simplemutex.php +++ b/sources/lib/default/simplemutex.php @@ -41,19 +41,13 @@ * Consult LICENSE file for details ************************************************/ -class SimpleMutex extends InterProcessData { - /** - * Constructor - */ - public function SimpleMutex() { - // initialize super parameters - $this->allocate = 64; - $this->type = 5173; - parent::__construct(); +class SimpleMutex { - if (!$this->IsActive()) { - ZLog::Write(LOGLEVEL_ERROR, "SimpleMutex not available as InterProcessData is not available. This is not recommended on duty systems and may result in corrupt user/device linking."); - } + private $file; + private $fp; + + public function __construct($file = __FILE__) { + $this->file = $file; } /** @@ -65,11 +59,8 @@ class SimpleMutex extends InterProcessData { * @return boolean */ public function Block() { - if ($this->IsActive()) - return $this->blockMutex(); - - ZLog::Write(LOGLEVEL_WARN, "Could not enter mutex as InterProcessData is not available. This is not recommended on duty systems and may result in corrupt user/device linking!"); - return true; + $this->fp = fopen($this->file, 'r'); + return flock($this->fp, LOCK_EX); } /** @@ -80,10 +71,6 @@ class SimpleMutex extends InterProcessData { * @return boolean */ public function Release() { - if ($this->IsActive()) - return $this->releaseMutex(); - - return true; + return flock($this->fp, LOCK_UN) && fclose($this->fp) && $this->fp = null; } } -?> \ No newline at end of file diff --git a/sources/lib/default/sqlstatemachine.php b/sources/lib/default/sqlstatemachine.php index c98c22d..1561b47 100644 --- a/sources/lib/default/sqlstatemachine.php +++ b/sources/lib/default/sqlstatemachine.php @@ -178,7 +178,13 @@ class SqlStateMachine implements IStateMachine { } } else { - $data = unserialize($record["state_data"]); + if (is_string($record["state_data"])) { + // MySQL-PDO returns a string for LOB objects + $data = unserialize($record["state_data"]); + } + else { + $data = unserialize(stream_get_contents($record["state_data"])); + } } } catch(PDOException $ex) { @@ -220,16 +226,13 @@ class SqlStateMachine implements IStateMachine { $sth = $this->dbh->prepare($sql); $sth->execute($params); - $params[":data"] = serialize($state); - $params[":updated_at"] = $this->getNow(); - $record = $sth->fetch(PDO::FETCH_ASSOC); if (!$record) { // New record $sql = "insert into zpush_states (device_id, state_type, uuid, counter, state_data, created_at, updated_at) values (:devid, :type, :key, :counter, :data, :created_at, :updated_at)"; - $params[":created_at"] = $params[":updated_at"]; $sth = $this->dbh->prepare($sql); + $sth->bindValue(":created_at", $this->getNow(), PDO::PARAM_STR); } else { // Existing record, we update it @@ -238,12 +241,19 @@ class SqlStateMachine implements IStateMachine { $sth = $this->dbh->prepare($sql); } - if (!$sth->execute($params) ) { + $sth->bindParam(":devid", $devid, PDO::PARAM_STR); + $sth->bindParam(":type", $type, PDO::PARAM_STR); + $sth->bindParam(":key", $key, PDO::PARAM_STR); + $sth->bindValue(":counter", ($counter === false ? -1 : $counter), PDO::PARAM_INT); + $sth->bindValue(":data", serialize($state), PDO::PARAM_LOB); + $sth->bindValue(":updated_at", $this->getNow(), PDO::PARAM_STR); + + if (!$sth->execute() ) { $this->clearConnection($this->dbh, $sth); throw new FatalMisconfigurationException(sprintf("SqlStateMachine->SetState(): Could not write state")); } else { - $bytes = strlen($params[":data"]); + $bytes = strlen(serialize($state)); } } catch(PDOException $ex) { @@ -386,6 +396,41 @@ class SqlStateMachine implements IStateMachine { return $changed; } + /** + * Get all UserDevice mapping + * + * @access public + * @return array + */ + public function GetAllUserDevice() { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetAllUserDevice(): '%s'", $username)); + + $sth = null; + $record = null; + $out = array(); + try { + $this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options); + + $sql = "select device_id, username from zpush_users order by username"; + $sth = $this->dbh->prepare($sql); + $sth->execute(); + + while ($record = $sth->fetch(PDO::FETCH_ASSOC)) { + if (!array_key_exists($record["username"], $out)) { + $out[$record["username"]] = array(); + } + $out[$record["username"]][] = $record["device_id"]; + } + } + catch(PDOException $ex) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetAllUserDevice(): Error listing devices: %s", $ex->getMessage())); + } + + $this->clearConnection($this->dbh, $sth, $record); + + return $out; + } + /** * Returns an array with all device ids for a user. * If no user is set, all device ids should be returned @@ -584,6 +629,8 @@ class SqlStateMachine implements IStateMachine { * @return integer */ public function GetUserDevicePermission($user, $devid) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetUserDevicePermission('%s', '%s')", $user, $devid)); + $status = SYNC_COMMONSTATUS_SUCCESS; $userExist = false; @@ -592,13 +639,27 @@ class SqlStateMachine implements IStateMachine { $deviceBlocked = false; // Android PROVISIONING initial step - if ($devid != "validate") { + // LG-D802 is sending an empty deviceid + if ($devid != "validate" && $devid != "") { $sth = null; $record = null; try { $this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options); + $sql = "select count(*) as pcount from zpush_preauth_users where username = :user and device_id != 'authorized' and authorized = 1"; + $params = array(":user" => $user); + + // Get number of authorized devices for user + $num_devid_user = 0; + $sth = $this->dbh->prepare($sql); + $sth->execute($params); + if ($record = $sth->fetch(PDO::FETCH_ASSOC)) { + $num_devid_user = $record["pcount"]; + } + $record = null; + $sth = null; + $sql = "select authorized from zpush_preauth_users where username = :user and device_id = :devid"; $params = array(":user" => $user, ":devid" => "authorized"); $paramsNewDevid = array(); @@ -614,7 +675,7 @@ class SqlStateMachine implements IStateMachine { $sth = null; if ($userExist) { - // User already pre-authorized + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetUserDevicePermission(): User '%s', already pre-authorized", $user)); // User could be blocked if a "authorized" device exist and it's false if ($userBlocked) { @@ -648,7 +709,7 @@ class SqlStateMachine implements IStateMachine { // Device not pre-authorized if (defined('PRE_AUTHORIZE_NEW_DEVICES') && PRE_AUTHORIZE_NEW_DEVICES === true) { - if (defined('PRE_AUTHORIZE_MAX_DEVICES') && PRE_AUTHORIZE_MAX_DEVICES >= count($userList[$user])) { + if (defined('PRE_AUTHORIZE_MAX_DEVICES') && PRE_AUTHORIZE_MAX_DEVICES > $num_devid_user) { $paramsNewDevid[":auth"] = true; ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Pre-authorized new device '%s' for user '%s'", $devid, $user)); } @@ -666,15 +727,19 @@ class SqlStateMachine implements IStateMachine { } } else { - // User not pre-authorized + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetUserDevicePermission(): User '%s', not pre-authorized", $user)); if (defined('PRE_AUTHORIZE_NEW_USERS') && PRE_AUTHORIZE_NEW_USERS === true) { $paramsNewUser[":auth"] = true; if (defined('PRE_AUTHORIZE_NEW_DEVICES') && PRE_AUTHORIZE_NEW_DEVICES === true) { - if (defined('PRE_AUTHORIZE_MAX_DEVICES') && PRE_AUTHORIZE_MAX_DEVICES >= count($userList[$user])) { + if (defined('PRE_AUTHORIZE_MAX_DEVICES') && PRE_AUTHORIZE_MAX_DEVICES > $num_devid_user) { $paramsNewDevid[":auth"] = true; ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Pre-authorized new device '%s' for new user '%s'", $devid, $user)); } + else { + $status = SYNC_COMMONSTATUS_MAXDEVICESREACHED; + ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Max number of devices reached for user '%s', tried '%s'", $user, $devid)); + } } else { $status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER; @@ -691,6 +756,8 @@ class SqlStateMachine implements IStateMachine { } if (count($paramsNewUser) > 0) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetUserDevicePermission(): Creating new user '%s'", $user)); + $sql = "insert into zpush_preauth_users (username, device_id, authorized, created_at, updated_at) values (:user, :devid, :auth, :created_at, :updated_at)"; $paramsNewUser[":user"] = $user; $paramsNewUser[":devid"] = "authorized"; @@ -704,6 +771,8 @@ class SqlStateMachine implements IStateMachine { } if (count($paramsNewDevid) > 0) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetUserDevicePermission(): Creating new device '%s' for user '%s'", $devid, $user)); + $sql = "insert into zpush_preauth_users (username, device_id, authorized, created_at, updated_at) values (:user, :devid, :auth, :created_at, :updated_at)"; $paramsNewDevid[":user"] = $user; $paramsNewDevid[":devid"] = $devid; @@ -727,6 +796,86 @@ class SqlStateMachine implements IStateMachine { return $status; } + /** + * Retrieves the mapped username for a specific username and backend. + * + * @param string $username The username to lookup + * @param string $backend Name of the backend to lookup + * + * @return string The mapped username or null if none found + */ + public function GetMappedUsername($username, $backend) { + $result = null; + + $this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options); + + $sql = "SELECT `mappedname` FROM `zpush_combined_usermap` WHERE `username` = :user AND `backend` = :backend"; + $params = array("user" => $username, "backend" => $backend); + $sth = $this->dbh->prepare($sql); + if ($sth->execute($params) === false) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetMappedUsername(): Failed to execute query")); + } else if ($record = $sth->fetch(PDO::FETCH_ASSOC)) { + $result = $record["mappedname"]; + } + + $this->clearConnection($this->dbh, $sth, $record); + + return $result; + } + + /** + * Maps a username for a specific backend to another username. + * + * @param string $username The username to map + * @param string $backend Name of the backend + * @param string $mappedname The mappend username + * + * @return boolean + */ + public function MapUsername($username, $backend, $mappedname) { + $this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options); + + $sql = " + INSERT INTO `zpush_combined_usermap` (`username`, `backend`, `mappedname`, `created_at`, `updated_at`) + VALUES (:user, :backend, :mappedname, NOW(), NOW()) + ON DUPLICATE KEY UPDATE `mappedname` = :mappedname2, `updated_at` = NOW() + "; + $params = array("user" => $username, "backend" => $backend, "mappedname" => $mappedname, "mappedname2" => $mappedname); + $sth = $this->dbh->prepare($sql); + if ($sth->execute($params) === false) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->MapUsername(): Failed to execute query")); + return false; + } + + $this->clearConnection($this->dbh, $sth); + return true; + } + + /** + * Unmaps a username for a specific backend. + * + * @param string $username The username to unmap + * @param string $backend Name of the backend + * + * @return boolean + */ + public function UnmapUsername($username, $backend) { + $this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options); + + $sql = "DELETE FROM `zpush_combined_usermap` WHERE `username` = :user AND `backend` = :backend"; + $params = array("user" => $username, "backend" => $backend); + $sth = $this->dbh->prepare($sql); + if ($sth->execute($params) === false) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->UnmapUsername(): Failed to execute query")); + return false; + } else if ($sth->rowCount() !== 1) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->MapUsername(): Invalid mapping of username and backend")); + return false; + } + + $this->clearConnection($this->dbh, $sth); + return true; + } /**---------------------------------------------------------------------------------------------------------- * Private SqlStateMachine stuff @@ -777,5 +926,4 @@ class SqlStateMachine implements IStateMachine { } } -} -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/lib/exceptions/authenticationrequiredexception.php b/sources/lib/exceptions/authenticationrequiredexception.php index 87fa134..f66509c 100644 --- a/sources/lib/exceptions/authenticationrequiredexception.php +++ b/sources/lib/exceptions/authenticationrequiredexception.php @@ -48,5 +48,3 @@ class AuthenticationRequiredException extends HTTPReturnCodeException { protected $httpHeaders = array('WWW-Authenticate: Basic realm="ZPush"'); protected $showLegal = true; } - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/fatalexception.php b/sources/lib/exceptions/fatalexception.php index 3a4b393..8a5f661 100644 --- a/sources/lib/exceptions/fatalexception.php +++ b/sources/lib/exceptions/fatalexception.php @@ -43,5 +43,3 @@ ************************************************/ class FatalException extends ZPushException {} - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/fatalmisconfigurationexception.php b/sources/lib/exceptions/fatalmisconfigurationexception.php index 6ddeae9..93b876a 100644 --- a/sources/lib/exceptions/fatalmisconfigurationexception.php +++ b/sources/lib/exceptions/fatalmisconfigurationexception.php @@ -42,5 +42,3 @@ ************************************************/ class FatalMisconfigurationException extends FatalException {} - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/fatalnotimplementedexception.php b/sources/lib/exceptions/fatalnotimplementedexception.php index 61b3f82..cb49f9d 100644 --- a/sources/lib/exceptions/fatalnotimplementedexception.php +++ b/sources/lib/exceptions/fatalnotimplementedexception.php @@ -43,5 +43,3 @@ ************************************************/ class FatalNotImplementedException extends FatalException {} - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/httpreturncodeexception.php b/sources/lib/exceptions/httpreturncodeexception.php index 9228d01..80fa85f 100644 --- a/sources/lib/exceptions/httpreturncodeexception.php +++ b/sources/lib/exceptions/httpreturncodeexception.php @@ -52,5 +52,3 @@ class HTTPReturnCodeException extends FatalException { parent::__construct($message, (int) $code, $previous, $logLevel); } } - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/nohierarchycacheavailableexception.php b/sources/lib/exceptions/nohierarchycacheavailableexception.php index f7fc6dd..5a6f1be 100644 --- a/sources/lib/exceptions/nohierarchycacheavailableexception.php +++ b/sources/lib/exceptions/nohierarchycacheavailableexception.php @@ -42,5 +42,3 @@ ************************************************/ class NoHierarchyCacheAvailableException extends StateNotFoundException {} - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/nopostrequestexception.php b/sources/lib/exceptions/nopostrequestexception.php index 1130733..08eb06a 100644 --- a/sources/lib/exceptions/nopostrequestexception.php +++ b/sources/lib/exceptions/nopostrequestexception.php @@ -47,5 +47,3 @@ class NoPostRequestException extends FatalException { const GET_REQUEST = 2; protected $defaultLogLevel = LOGLEVEL_DEBUG; } - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/notimplementedexception.php b/sources/lib/exceptions/notimplementedexception.php index 8377729..36fee4b 100644 --- a/sources/lib/exceptions/notimplementedexception.php +++ b/sources/lib/exceptions/notimplementedexception.php @@ -45,5 +45,3 @@ class NotImplementedException extends ZPushException { protected $defaultLogLevel = LOGLEVEL_ERROR; } - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/provisioningrequiredexception.php b/sources/lib/exceptions/provisioningrequiredexception.php index 4a58d8c..b861ef9 100644 --- a/sources/lib/exceptions/provisioningrequiredexception.php +++ b/sources/lib/exceptions/provisioningrequiredexception.php @@ -47,5 +47,3 @@ class ProvisioningRequiredException extends HTTPReturnCodeException { protected $httpReturnCode = HTTP_CODE_449; protected $httpReturnMessage = "Retry after sending a PROVISION command"; } - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/stateinvalidexception.php b/sources/lib/exceptions/stateinvalidexception.php index 720c345..b410aad 100644 --- a/sources/lib/exceptions/stateinvalidexception.php +++ b/sources/lib/exceptions/stateinvalidexception.php @@ -42,5 +42,3 @@ ************************************************/ class StateInvalidException extends StatusException {} - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/statenotfoundexception.php b/sources/lib/exceptions/statenotfoundexception.php index b15bcfc..f75e09d 100644 --- a/sources/lib/exceptions/statenotfoundexception.php +++ b/sources/lib/exceptions/statenotfoundexception.php @@ -43,5 +43,3 @@ ************************************************/ class StateNotFoundException extends StatusException {} - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/statenotyetavailableexception.php b/sources/lib/exceptions/statenotyetavailableexception.php index 0910185..998fd66 100644 --- a/sources/lib/exceptions/statenotyetavailableexception.php +++ b/sources/lib/exceptions/statenotyetavailableexception.php @@ -42,5 +42,3 @@ ************************************************/ class StateNotYetAvailableException extends StatusException {} - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/statusexception.php b/sources/lib/exceptions/statusexception.php index b2d4f17..164f6e1 100644 --- a/sources/lib/exceptions/statusexception.php +++ b/sources/lib/exceptions/statusexception.php @@ -44,5 +44,3 @@ class StatusException extends ZPushException { protected $defaultLogLevel = LOGLEVEL_INFO; } - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/syncobjectbrokenexception.php b/sources/lib/exceptions/syncobjectbrokenexception.php index 0f014f0..2072a0f 100644 --- a/sources/lib/exceptions/syncobjectbrokenexception.php +++ b/sources/lib/exceptions/syncobjectbrokenexception.php @@ -69,5 +69,3 @@ class SyncObjectBrokenException extends ZPushException { return true; } } - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/wbxmlexception.php b/sources/lib/exceptions/wbxmlexception.php index 418da3f..11808d4 100644 --- a/sources/lib/exceptions/wbxmlexception.php +++ b/sources/lib/exceptions/wbxmlexception.php @@ -42,5 +42,3 @@ ************************************************/ class WBXMLException extends FatalNotImplementedException {} - -?> \ No newline at end of file diff --git a/sources/lib/exceptions/zpushexception.php b/sources/lib/exceptions/zpushexception.php index f6009e8..6832cd9 100644 --- a/sources/lib/exceptions/zpushexception.php +++ b/sources/lib/exceptions/zpushexception.php @@ -55,7 +55,7 @@ class ZPushException extends Exception { if (!$logLevel) $logLevel = $this->defaultLogLevel; - ZLog::Write($logLevel, get_class($this) .': '. $message . ' - code: '.$code); + ZLog::Write($logLevel, sprintf("%s: %s - code: %s - file: %s:%s", get_class($this), $message, $code, $this->getFile(), $this->getLine()), false); parent::__construct($message, (int) $code); } @@ -70,5 +70,4 @@ class ZPushException extends Exception { public function showLegalNotice() { return $this->showLegal; } -} -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/lib/interface/ibackend.php b/sources/lib/interface/ibackend.php index d4b36f3..74b069c 100644 --- a/sources/lib/interface/ibackend.php +++ b/sources/lib/interface/ibackend.php @@ -299,6 +299,12 @@ interface IBackend { * @return Array */ public function GetUserDetails($username); -} -?> \ No newline at end of file + /** + * Returns the username of the currently active user + * + * @access public + * @return Array + */ + public function GetCurrentUsername(); +} diff --git a/sources/lib/interface/ichanges.php b/sources/lib/interface/ichanges.php index 3ef95fd..9087bb6 100644 --- a/sources/lib/interface/ichanges.php +++ b/sources/lib/interface/ichanges.php @@ -82,5 +82,3 @@ interface IChanges { */ public function GetState(); } - -?> \ No newline at end of file diff --git a/sources/lib/interface/iexportchanges.php b/sources/lib/interface/iexportchanges.php index dc323d1..51a6202 100644 --- a/sources/lib/interface/iexportchanges.php +++ b/sources/lib/interface/iexportchanges.php @@ -72,5 +72,3 @@ interface IExportChanges extends IChanges { */ public function Synchronize(); } - -?> \ No newline at end of file diff --git a/sources/lib/interface/iimportchanges.php b/sources/lib/interface/iimportchanges.php index 3ce5629..321c56b 100644 --- a/sources/lib/interface/iimportchanges.php +++ b/sources/lib/interface/iimportchanges.php @@ -98,19 +98,6 @@ interface IImportChanges extends IChanges { */ public function ImportMessageReadFlag($id, $flags); - /** - * Imports a change in 'star' flag - * This can never conflict - * - * @param string $id - * @param int $flags - * - * @access public - * @return boolean - * @throws StatusException - */ - public function ImportMessageStarFlag($id, $flags); - /** * Imports a move of a message. This occurs when a user moves an item to another folder * @@ -152,5 +139,3 @@ interface IImportChanges extends IChanges { public function ImportFolderDeletion($id, $parent = false); } - -?> \ No newline at end of file diff --git a/sources/lib/interface/iloopdetection.php b/sources/lib/interface/iloopdetection.php new file mode 100644 index 0000000..3a7fc05 --- /dev/null +++ b/sources/lib/interface/iloopdetection.php @@ -0,0 +1,221 @@ +FolderSync->Sync->FolderSync => ReSync) + * Ticket: https://jira.zarafa.com/browse/ZP-5 + * + * @access public + * @return boolean + * + */ + public function ProcessLoopDetectionIsHierarchyResyncRequired(); + + /** + * Indicates if a previous process could not be terminated + * + * Checks if there is an end time for the last entry on the stack + * + * @access public + * @return boolean + * + */ + public function ProcessLoopDetectionPreviousConnectionFailed(); + + /** + * Gets the PID of an outdated search process + * + * Returns false if there isn't any process + * + * @access public + * @return boolean + * + */ + public function ProcessLoopDetectionGetOutdatedSearchPID(); + + /** + * TRACKING OF BROKEN MESSAGES + * if a previousily ignored message is streamed again to the device it's tracked here + * + * There are two outcomes: + * - next uuid counter is higher than current -> message is fixed and successfully synchronized + * - next uuid counter is the same or uuid changed -> message is still broken + */ + + /** + * Adds a message to the tracking of broken messages + * Being tracked means that a broken message was streamed to the device. + * We save the latest uuid and counter so if on the next sync the counter is higher + * the message was accepted by the device. + * + * @param string $folderid the parent folder of the message + * @param string $id the id of the message + * + * @access public + * @return boolean + */ + public function SetBrokenMessage($folderid, $id); + + /** + * Gets a list of all ids of a folder which were tracked and which were + * accepted by the device from the last sync. + * + * @param string $folderid the parent folder of the message + * @param string $id the id of the message + * + * @access public + * @return array + */ + public function GetSyncedButBeforeIgnoredMessages($folderid); + + /** + * Marks a SyncState as "already used", e.g. when an import process started. + * This is most critical for DiffBackends, as an imported message would be exported again + * in the heartbeat if the notification is triggered before the import is complete. + * + * @param string $folderid folder id + * @param string $uuid synkkey + * @param string $counter synckey counter + * + * @access public + * @return boolean + */ + public function SetSyncStateUsage($folderid, $uuid, $counter); + + /** + * Checks if the given counter for a certain uuid+folderid was exported before. + * Returns also true if the counter are the same but previously there were + * changes to be exported. + * + * @param string $folderid folder id + * @param string $uuid synkkey + * @param string $counter synckey counter + * + * @access public + * @return boolean indicating if an uuid+counter were exported (with changes) before + */ + public function IsSyncStateObsolete($folderid, $uuid, $counter); + + /** + * MESSAGE LOOP DETECTION + */ + + /** + * Loop detection mechanism + * + * 1. request counter is higher than the previous counter (somehow default) + * 1.1) standard situation -> do nothing + * 1.2) loop information exists + * 1.2.1) request counter < maxCounter AND no ignored data -> continue in loop mode + * 1.2.2) request counter < maxCounter AND ignored data -> we have already encountered issue, return to normal + * + * 2. request counter is the same as the previous, but no data was sent on the last request (standard situation) + * + * 3. request counter is the same as the previous and last time objects were sent (loop!) + * 3.1) no loop was detected before, entereing loop mode -> save loop data, loopcount = 1 + * 3.2) loop was detected before, but are gone -> loop resolved + * 3.3) loop was detected before, continuing in loop mode -> this is probably the broken element,loopcount++, + * 3.3.1) item identified, loopcount >= 3 -> ignore item, set ignoredata flag + * + * @param string $folderid the current folder id to be worked on + * @param string $type the type of that folder (Email, Calendar, Contact, Task) + * @param string $uuid the synkkey + * @param string $counter the synckey counter + * @param string $maxItems the current amount of items to be sent to the mobile + * @param string $queuedMessages the amount of messages which were found by the exporter + * + * @access public + * @return boolean when returning true if a loop has been identified + */ + public function Detect($folderid, $type, $uuid, $counter, $maxItems, $queuedMessages); + + /** + * Indicates if the next messages should be ignored (not be sent to the mobile!) + * + * @param string $messageid (opt) id of the message which is to be exported next + * @param string $folderid (opt) parent id of the message + * @param boolean $markAsIgnored (opt) to peek without setting the next message to be + * ignored, set this value to false + * @access public + * @return boolean + */ + public function IgnoreNextMessage($markAsIgnored = true, $messageid = false, $folderid = false); + + /** + * Clears loop detection data + * + * @param string $user (opt) user which data should be removed - user can not be specified without + * @param string $devid (opt) device id which data to be removed + * + * @return boolean + * @access public + */ + public function ClearData($user = false, $devid = false); + + /** + * Returns loop detection data for a user and device + * + * @param string $user + * @param string $devid + * + * @return array/boolean returns false if data not available + * @access public + */ + public function GetCachedData($user, $devid); +} diff --git a/sources/lib/interface/ipingtracking.php b/sources/lib/interface/ipingtracking.php new file mode 100644 index 0000000..69086a9 --- /dev/null +++ b/sources/lib/interface/ipingtracking.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/sources/lib/interface/istatemachine.php b/sources/lib/interface/istatemachine.php index 55c3986..1fc22a4 100644 --- a/sources/lib/interface/istatemachine.php +++ b/sources/lib/interface/istatemachine.php @@ -156,6 +156,14 @@ interface IStateMachine { */ public function UnLinkUserDevice($username, $devid); + /** + * Get all UserDevice mapping + * + * @access public + * @return array + */ + public function GetAllUserDevice(); + /** * Returns an array with all device ids for a user. * If no user is set, all device ids should be returned @@ -194,6 +202,35 @@ interface IStateMachine { * @return array(mixed) */ public function GetAllStatesForDevice($devid); -} -?> \ No newline at end of file + /** + * Retrieves the mapped username for a specific username and backend. + * + * @param string $username The username to lookup + * @param string $backend Name of the backend to lookup + * + * @return string The mapped username or null if none found + */ + public function GetMappedUsername($username, $backend); + + /** + * Maps a username for a specific backend to another username. + * + * @param string $username The username to map + * @param string $backend Name of the backend + * @param string $mappedname The mappend username + * + * @return boolean + */ + public function MapUsername($username, $backend, $mappedname); + + /** + * Unmaps a username for a specific backend. + * + * @param string $username The username to unmap + * @param string $backend Name of the backend + * + * @return boolean + */ + public function UnmapUsername($username, $backend); +} diff --git a/sources/lib/interface/itopcollector.php b/sources/lib/interface/itopcollector.php new file mode 100644 index 0000000..fa5a5d9 --- /dev/null +++ b/sources/lib/interface/itopcollector.php @@ -0,0 +1,66 @@ +connect(IPC_REDIS_IP, IPC_REDIS_PORT); + self::$redis->select(IPC_REDIS_DATABASE); + } + } + + /** + * Indicates if the shared memory is active + * + * @access public + * @return boolean + */ + public function IsActive() { + $str = "Testing connection"; + return strcmp(self::$redis->echo($str), $str) == 0; + } + + /** + * Reinitializes inter-process data storage + * + * @access public + * @return boolean + */ + public function ReInitSharedMem() { + return self::$redis->flushDB(); + } +} diff --git a/sources/lib/ipc/redis/LoopDetectionRedis.php b/sources/lib/ipc/redis/LoopDetectionRedis.php new file mode 100644 index 0000000..c17411d --- /dev/null +++ b/sources/lib/ipc/redis/LoopDetectionRedis.php @@ -0,0 +1,674 @@ +updateProcessStack(); + } + + /** + * Marks the process entry as termineted successfully on the process stack + * + * @access public + * @return boolean + */ + public function ProcessLoopDetectionTerminate() { + self::$processentry['end'] = time(); + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionTerminate()"); + $this->updateProcessStack(); + + return true; + } + + /** + * Adds an Exceptions to the process tracking + * + * @param Exception $exception + * + * @access public + * @return boolean + */ + public function ProcessLoopDetectionAddException($exception) { + if (!isset(self::$processentry['stat'])) + self::$processentry['stat'] = array(); + self::$processentry['stat'][get_class($exception)] = $exception->getCode(); + $this->updateProcessStack(); + + return true; + } + + /** + * Adds a folderid and connected status code to the process tracking + * + * @param string $folderid + * @param int $status + * + * @access public + * @return boolean + */ + public function ProcessLoopDetectionAddStatus($folderid, $status) { + if ($folderid === false) + $folderid = "hierarchy"; + if (!isset(self::$processentry['stat'])) + self::$processentry['stat'] = array(); + self::$processentry['stat'][$folderid] = $status; + $this->updateProcessStack(); + + return true; + } + + /** + * Marks the current process as a PUSH connection + * + * @access public + * @return boolean + */ + public function ProcessLoopDetectionSetAsPush() { + self::$processentry['push'] = true; + $this->updateProcessStack(); + + return true; + } + + /** + * Indicates if a full Hierarchy Resync is necessary + * + * In some occasions the mobile tries to sync a folder with an invalid/not-existing ID. + * In these cases a status exception like SYNC_STATUS_FOLDERHIERARCHYCHANGED is returned + * so the mobile executes a FolderSync expecting that some action is taken on that folder (e.g. remove). + * + * If the FolderSync is not doing anything relevant, then the Sync is attempted again + * resulting in the same error and looping between these two processes. + * + * This method checks if in the last process stack a Sync and FolderSync were triggered to + * catch the loop at the 2nd interaction (Sync->FolderSync->Sync->FolderSync => ReSync) + * Ticket: https://jira.zarafa.com/browse/ZP-5 + * + * @access public + * @return boolean + * + */ + public function ProcessLoopDetectionIsHierarchyResyncRequired() { + $seenFailed = array(); + $seenFolderSync = false; + + $lookback = self::$processentry['time'] - 600; // look at the last 5 min + foreach ($this->getProcessStack() as $se) { + if ($se['time'] > $lookback && $se['time'] < (self::$processentry['time']-1)) { + // look for sync command + if (isset($se['stat']) && ($se['cc'] == ZPush::COMMAND_SYNC || $se['cc'] == ZPush::COMMAND_PING)) { + foreach ($se['stat'] as $key => $value) { + if (!isset($seenFailed[$key])) + $seenFailed[$key] = 0; + $seenFailed[$key]++; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen command with Exception or folderid '%s' and code '%s'", $key, $value )); + } + } + // look for FolderSync command with previous failed commands + if ($se['cc'] == ZPush::COMMAND_FOLDERSYNC && !empty($seenFailed) && $se['id'] != self::$processentry['id']) { + // a full folderresync was already triggered + if (isset($se['stat']) && isset($se['stat']['hierarchy']) && $se['stat']['hierarchy'] == SYNC_FSSTATUS_SYNCKEYERROR) { + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): a full FolderReSync was already requested. Resetting fail counter."); + $seenFailed = array(); + } + else { + $seenFolderSync = true; + if (!empty($seenFailed)) + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen FolderSync after other failing command"); + } + } + } + } + $filtered = array(); + foreach ($seenFailed as $k => $count) { + if ($count>1) + $filtered[] = $k; + } + if ($seenFolderSync && !empty($filtered)) { + ZLog::Write(LOGLEVEL_INFO, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): Potential loop detected. Full hierarchysync indicated."); + + return true; + } + + return false; + } + + /** + * Indicates if a previous process could not be terminated + * + * Checks if there is an end time for the last entry on the stack + * + * @access public + * @return boolean + * + */ + public function ProcessLoopDetectionPreviousConnectionFailed() { + $stack = $this->getProcessStack(); + if (count($stack) > 1) { + $se = $stack[0]; + if (!isset($se['end']) && $se['cc'] != ZPush::COMMAND_PING && !isset($se['push']) ) { + // there is no end time + ZLog::Write(LOGLEVEL_ERROR, sprintf("LoopDetection->ProcessLoopDetectionPreviousConnectionFailed(): Command '%s' at %s with pid '%d' terminated unexpectedly or is still running.", Utils::GetCommandFromCode($se['cc']), Utils::GetFormattedTime($se['time']), $se['pid'])); + ZLog::Write(LOGLEVEL_ERROR, "Please check your logs for this PID and errors like PHP-Fatals or Apache segmentation faults and report your results to the Z-Push dev team."); + } + } + } + + /** + * Gets the PID of an outdated search process + * + * Returns false if there isn't any process + * + * @access public + * @return boolean + * + */ + public function ProcessLoopDetectionGetOutdatedSearchPID() { + $stack = $this->getProcessStack(); + if (count($stack) > 1) { + $se = $stack[0]; + if ($se['cc'] == ZPush::COMMAND_SEARCH) { + return $se['pid']; + } + } + + return false; + } + + /** + * Inserts or updates the current process entry on the stack + * + * @access private + * @return boolean + */ + private function updateProcessStack() { + while (true) { + self::$redis->watch(self::$keystack); + $stack = self::$redis->get(self::$keystack); + if ($stack === false) { + $stack = array(); + } + else { + $stack = unserialize($stack); + } + + // insert/update current process entry + $nstack = array(); + $found = false; + foreach ($stack as $entry) { + if ($entry['id'] != self::$processentry['id']) { + $nstack[] = $entry; + } + else { + $nstack[] = self::$processentry; + $found = true; + } + } + if (!$found) + $nstack[] = self::$processentry; + if (count($nstack) > 10) + $nstack = array_slice($nstack, -10, 10); + // update loop data + if(self::$redis->multi() + ->setex(self::$keystack, self::TTL, serialize($nstack)) + ->exec()) { + return true; + } + else { + ZLog::Write(LOGLEVEL_WARN, "updateProcessStack(): setex just failed (too much concurrency), retrying"); + } + } + } + + /** + * Returns the current process stack + * + * @access private + * @return array + */ + private function getProcessStack() { + $stack = self::$redis->get(self::$keystack); + if ($stack === false) { + return array(); + } + else { + return unserialize($stack); + } + } + + /** + * TRACKING OF BROKEN MESSAGES + * if a previousily ignored message is streamed again to the device it's tracked here + * + * There are two outcomes: + * - next uuid counter is higher than current -> message is fixed and successfully synchronized + * - next uuid counter is the same or uuid changed -> message is still broken + */ + + /** + * Adds a message to the tracking of broken messages + * Being tracked means that a broken message was streamed to the device. + * We save the latest uuid and counter so if on the next sync the counter is higher + * the message was accepted by the device. + * + * @param string $folderid the parent folder of the message + * @param string $id the id of the message + * + * @access public + * @return boolean + */ + public function SetBrokenMessage($folderid, $id) { + if ($folderid == false || !isset($this->broken_message_uuid) || !isset($this->broken_message_counter) || $this->broken_message_uuid == false || $this->broken_message_counter == false) + return false; + + $brokenmsg = array('uuid' => $this->broken_message_uuid, 'counter' => $this->broken_message_counter); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->SetBrokenMessage('%s', '%s'): tracking broken message", $folderid, $id)); + self::$redis->multi()->hSet(self::$keybroken . $folderid, $id, serialize($brokenmsg))->expire(self::$keybroken . $folderid, self::TTL)->exec(); + } + + private function RemoveBrokenMessage($folderid, $id) { + $exec = self::$redis->multi()->hDel(self::$keybroken . $folderid, $id)->expire(self::$keybroken . $folderid, self::TTL)->exec(); + + return $exec[0] === 1; + } + + /** + * Gets a list of all ids of a folder which were tracked and which were + * accepted by the device from the last sync. + * + * @param string $folderid the parent folder of the message + * @param string $id the id of the message + * + * @access public + * @return array + */ + public function GetSyncedButBeforeIgnoredMessages($folderid) { + if ($folderid == false || !isset($this->broken_message_uuid) || !isset($this->broken_message_counter) || $this->broken_message_uuid == false || $this->broken_message_counter == false) + return array(); + + $removeIds = array(); + $okIds = array(); + $brokenmsgs = self::$redis->hGetAll(self::$keybroken . $folderid); + if (!empty($brokenmsgs)) { + foreach ($brokenmsgs as $id => $data) { + $data = unserialize($data); + // previously broken message was sucessfully synced! + if ($data['uuid'] == $this->broken_message_uuid && $data['counter'] < $this->broken_message_counter) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): message '%s' was successfully synchronized", $folderid, $id)); + $okIds[] = $id; + } + // if the uuid has changed this is old data which should also be removed + if ($data['uuid'] != $this->broken_message_uuid) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): stored message id '%s' for uuid '%s' is obsolete", $folderid, $id, $data['uuid'])); + $removeIds[] = $id; + } + } + // remove data + $arg = array_merge(array(self::$keybroken . $folderid), $okIds, $removeIds); + if (count($arg) > 1) + call_user_func_array(array(self::$redis, 'hDel'), $arg); + + //we want to increase the ttl and test if the hash is still there + if (!self::$redis->expire(self::$keybroken . $folderid, self::TTL)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): removed folder from tracking of ignored messages", $folderid)); + } + } + + return $okIds; + } + + /** + * Marks a SyncState as "already used", e.g. when an import process started. + * This is most critical for DiffBackends, as an imported message would be exported again + * in the heartbeat if the notification is triggered before the import is complete. + * + * @param string $folderid folder id + * @param string $uuid synkkey + * @param string $counter synckey counter + * + * @access public + * @return boolean + */ + public function SetSyncStateUsage($folderid, $uuid, $counter) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->SetSyncStateUsage(): uuid: %s counter: %d", $uuid, $counter)); + + return self::$redis->hSet(self::$keyfolder . $folderid, 'usage', $counter) !== false; + } + + /** + * Checks if the given counter for a certain uuid+folderid was exported before. + * Returns also true if the counter are the same but previously there were + * changes to be exported. + * + * @param string $folderid folder id + * @param string $uuid synkkey + * @param string $counter synckey counter + * + * @access public + * @return boolean indicating if an uuid+counter were exported (with changes) before + */ + public function IsSyncStateObsolete($folderid, $uuid, $counter) { + $current = self::$redis->hGetAll(self::$keyfolder . $folderid); + if (empty($current)) + return false; + $current = unserialize($current); + if (!empty($current)) { + if (!isset($current["uuid"]) || $current["uuid"] != $uuid) { + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, uuid changed or not set"); + + return true; + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IsSyncStateObsolete(): check uuid counter: %d - last known counter: %d with %d queued objects", $counter, $current["count"], $current["queued"])); + + if ($current["uuid"] == $uuid && ($current["count"] > $counter || ($current["count"] == $counter && $current["queued"] > 0) || (isset($current["usage"]) && $current["usage"] >= $counter))) { + $usage = isset($current["usage"]) ? sprintf(" - counter %d already expired",$current["usage"]) : ""; + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, counter already processed". $usage); + + return true; + } + } + } + + return false; + } + + /** + * MESSAGE LOOP DETECTION + */ + + /** + * Loop detection mechanism + * + * 1. request counter is higher than the previous counter (somehow default) + * 1.1) standard situation -> do nothing + * 1.2) loop information exists + * 1.2.1) request counter < maxCounter AND no ignored data -> continue in loop mode + * 1.2.2) request counter < maxCounter AND ignored data -> we have already encountered issue, return to normal + * + * 2. request counter is the same as the previous, but no data was sent on the last request (standard situation) + * + * 3. request counter is the same as the previous and last time objects were sent (loop!) + * 3.1) no loop was detected before, entereing loop mode -> save loop data, loopcount = 1 + * 3.2) loop was detected before, but are gone -> loop resolved + * 3.3) loop was detected before, continuing in loop mode -> this is probably the broken element,loopcount++, + * 3.3.1) item identified, loopcount >= 3 -> ignore item, set ignoredata flag + * + * @param string $folderid the current folder id to be worked on + * @param string $type the type of that folder (Email, Calendar, Contact, Task) + * @param string $uuid the synkkey + * @param string $counter the synckey counter + * @param string $maxItems the current amount of items to be sent to the mobile + * @param string $queuedMessages the amount of messages which were found by the exporter + * + * @access public + * @return boolean when returning true if a loop has been identified + */ + public function Detect($folderid, $type, $uuid, $counter, $maxItems, $queuedMessages) { + $this->broken_message_uuid = $uuid; + $this->broken_message_counter = $counter; + + // if an incoming loop is already detected, do nothing + if ($maxItems === 0 && $queuedMessages > 0) { + ZPush::GetTopCollector()->AnnounceInformation("Incoming loop!", true); + + return true; + } + + $loop = false; + + while (true) { + self::$redis->watch(self::$keyfolder . $folderid); + $current = self::$redis->hGetAll(self::$keyfolder . $folderid); + + // completely new/unknown UUID + if (empty($current)) { + $current = array("type" => $type, "uuid" => $uuid, "count" => $counter-1, "queued" => $queuedMessages); + } + // old UUID in cache - the device requested a new state!! + elseif (isset($current['type']) && $current['type'] == $type && isset($current['uuid']) && $current['uuid'] != $uuid ) { + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed for folder"); + // some devices (iPhones) may request new UUIDs after broken items were sent several times + if (isset($current['queued']) && $current['queued'] > 0 && (isset($current['maxCount']) && $current['count']+1 < $current['maxCount'] || $counter == 1)) { + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed and while items where sent to device - forcing loop mode"); + $loop = true; // force loop mode + $current['queued'] = $queuedMessages; + } + else { + $current['queued'] = 0; + } + // set new data, unset old loop information + $current["uuid"] = $uuid; + $current['count'] = $counter; + unset($current['loopcount']); + unset($current['ignored']); + unset($current['maxCount']); + unset($current['potential']); + } + + // see if there are values + if (isset($current['uuid']) && $current['uuid'] == $uuid && isset($current['type']) && $current['type'] == $type && isset($current['count'])) { + + // case 1 - standard, during loop-resolving & resolving + if ($current['count'] < $counter) { + + // case 1.1 + $current['count'] = $counter; + $current['queued'] = $queuedMessages; + if (isset($current["usage"]) && $current["usage"] < $current['count']) + unset($current["usage"]); + // case 1.2 + if (isset($current['maxCount'])) { + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2 detected"); + // case 1.2.1 + // broken item not identified yet + if (!isset($current['ignored']) && $counter < $current['maxCount']) { + $loop = true; // continue in loop-resolving + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.1 detected"); + } + // case 1.2.2 - if there were any broken items they should be gone, return to normal + else { + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.2 detected"); + unset($current['loopcount']); + unset($current['ignored']); + unset($current['maxCount']); + unset($current['potential']); + } + } + } + // case 2 - same counter, but there were no changes before and are there now + elseif ($current['count'] == $counter && $current['queued'] == 0 && $queuedMessages > 0) { + $current['queued'] = $queuedMessages; + if (isset($current["usage"]) && $current["usage"] < $current['count']) + unset($current["usage"]); + } + // case 3 - same counter, changes sent before, hanging loop and ignoring + elseif ($current['count'] == $counter && $current['queued'] > 0) { + if (!isset($current['loopcount'])) { + // case 3.1) we have just encountered a loop! + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.1 detected - loop detected, init loop mode"); + $current['loopcount'] = 1; + // the MaxCount is the max number of messages exported before + $current['maxCount'] = $counter + (($maxItems < $queuedMessages) ? $maxItems : $queuedMessages); + $loop = true; // loop mode!! + } + elseif ($queuedMessages == 0) { + // case 3.2) there was a loop before but now the changes are GONE + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.2 detected - changes gone - clearing loop data"); + $current['queued'] = 0; + unset($current['loopcount']); + unset($current['ignored']); + unset($current['maxCount']); + unset($current['potential']); + } + else { + // case 3.3) still looping the same message! Increase counter + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.3 detected - in loop mode, increase loop counter"); + $current['loopcount']++; + // case 3.3.1 - we got our broken item! + if ($current['loopcount'] >= 3 && isset($current['potential'])) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): case 3.3.1 detected - broken item should be next, attempt to ignore it - id '%s'", $current['potential'])); + $this->ignore_messageid = $current['potential']; + } + $current['maxCount'] = $counter + $queuedMessages; + $loop = true; // loop mode!! + } + } + } + if (isset($current['loopcount'])) + ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): loop data: loopcount(%d), maxCount(%d), queued(%d), ignored(%s)", $current['loopcount'], $current['maxCount'], $current['queued'], (isset($current['ignored']) ? $current['ignored'] : 'false'))); + + $exec = self::$redis->multi() + ->del(self::$keyfolder . $folderid) + ->hMset(self::$keyfolder . $folderid, $current) + ->expire(self::$keyfolder . $folderid, self::TTL) + ->exec(); + if ($exec[1]) { + break; + } + else { + ZLog::Write(LOGLEVEL_WARN, "Detect($folderid, $type, $uuid, $counter, $maxItems, $queuedMessages): setex just failed (too much concurrency), retrying"); + } + } + + if ($loop == true && $this->ignore_messageid == false) { + ZPush::GetTopCollector()->AnnounceInformation("Loop detection", true); + } + + return $loop; + } + + /** + * Indicates if the next messages should be ignored (not be sent to the mobile!) + * + * @param string $messageid (opt) id of the message which is to be exported next + * @param string $folderid (opt) parent id of the message + * @param boolean $markAsIgnored (opt) to peek without setting the next message to be + * ignored, set this value to false + * @access public + * @return boolean + */ + public function IgnoreNextMessage($markAsIgnored = true, $messageid = false, $folderid = false) { + // as the next message id is not available at all point this method is called, we use different indicators. + // potentialbroken indicates that we know that the broken message should be exported next, + // alltho we do not know for sure as it's export message orders can change + // if the $messageid is available and matches then we are sure and only then really ignore it + + $potentialBroken = false; + $realBroken = false; + if (Request::GetCommandCode() == ZPush::COMMAND_SYNC && $this->ignore_messageid !== false) + $potentialBroken = true; + + if ($messageid !== false && $this->ignore_messageid == $messageid) + $realBroken = true; + + // this call is just to know what should be happening + // no further actions necessary + if ($markAsIgnored === false) { + return $potentialBroken; + } + + // we should really do something here + + // first we check if we are in the loop mode, if so, + // we update the potential broken id message so we loop count the same message + + // we found our broken message! + if ($realBroken) { + $this->ignore_messageid = false; + self::$redis->hSet(self::$keyfolder . $folderid, 'ignored', $messageid); + // check if this message was broken before - here we know that it still is and remove it from the tracking + if($this->RemoveBrokenMessage($folderid, $messageid)) + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IgnoreNextMessage(): previously broken message '$messageid' is still broken and will not be tracked anymore (folder = $folderid)"); + ZPush::GetTopCollector()->AnnounceInformation("Broken message ignored", true); + } + // not the broken message yet + else { + // update potential id if looping on an item + if (self::$redis->hGet(self::$keyfolder . $folderid, 'loopcount') !== false) { + // this message should be the broken one, but is not!! + // we should reset the loop count because this is certainly not the broken one + if ($potentialBroken) { + self::$redis->hMset(self::$keyfolder . $folderid, array('potential' => $messageid, 'loopcount' => 1)); + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IgnoreNextMessage(): this should be the broken one, but is not! Resetting loop count."); + } + else { + self::$redis->hSet(self::$keyfolder . $folderid, 'potential', $messageid); + } + ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): Loop mode, potential broken message id '%s'", $messageid)); + } + } + + return $realBroken; + } + + /** + * Clears loop detection data + * + * @param string $user (opt) user which data should be removed + * @param string $devid (opt) device id which data to be removed + * + * @return boolean + * @access public + */ + public function ClearData($user = false, $devid = false) { + if ($user == false && $devid == false) + self::$redis->del(self::$redis->keys('ZP-LOOP*')); + elseif ($user == false && $devid != false) + self::$redis->del(self::$redis->keys('ZP-LOOP[^|]*|' . $devid . '|*')); + elseif ($user != false && $devid != false) + self::$redis->del(self::$redis->keys('ZP-LOOP[^|]*|' . $devid . '|' . $user . '*')); + elseif ($user != false && $devid == false) { + self::$redis->del(self::$redis->keys('ZP-LOOP[^|]*|[^|]*|' . $user . '*')); + } + + return true; + } + + /** + * Returns loop detection data for a user and device + * + * @param string $user + * @param string $devid + * + * @return array/boolean returns false if data not available + * @access public + */ + public function GetCachedData($user, $devid) { + //nobody really use it ... + return false; + } +} diff --git a/sources/lib/ipc/redis/PingTrackingRedis.php b/sources/lib/ipc/redis/PingTrackingRedis.php new file mode 100644 index 0000000..7f62233 --- /dev/null +++ b/sources/lib/ipc/redis/PingTrackingRedis.php @@ -0,0 +1,47 @@ +key = "ZP-PING|" . self::$devid . '|' . self::$user . '|' . Request::GetAuthDomain(); + } + + /** + * Checks if there are newer ping requests for the same device & user so + * the current process could be terminated + * + * @access public + * @return boolean true if the current process is obsolete + */ + public function DoForcePingTimeout() { + while (true) { + self::$redis->watch($this->key); + $savedtime = self::$redis->get($this->key); + if ($savedtime === false || $savedtime < $_SERVER['REQUEST_TIME']) { + $res = self::$redis->multi()->setex($this->key, self::TTL, $_SERVER['REQUEST_TIME'])->exec(); + if ($res === false) { + ZLog::Write(LOGLEVEL_DEBUG, "DoForcePingTimeout(): set just failed, retrying"); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("DoForcePingTimeout() key: '%s' reqtime: '%s' last_error: '%s'", $this->key, $_SERVER['REQUEST_TIME'], self::$redis->getLastError())); + continue; + } + else { + return false; + } + } + // Don't compare types, only values + if ($savedtime == $_SERVER['REQUEST_TIME']) { + self::$redis->unwatch(); + + return false; + } + if ($savedtime > $_SERVER['REQUEST_TIME']) { + self::$redis->unwatch(); + + return true; + } + } + } +} diff --git a/sources/lib/ipc/redis/REQUIREMENTS b/sources/lib/ipc/redis/REQUIREMENTS new file mode 100644 index 0000000..9b12a73 --- /dev/null +++ b/sources/lib/ipc/redis/REQUIREMENTS @@ -0,0 +1,3 @@ +You will need the PHP Redis client in your include_path + +https://github.com/phpredis/phpredis \ No newline at end of file diff --git a/sources/lib/ipc/redis/TopCollectorRedis.php b/sources/lib/ipc/redis/TopCollectorRedis.php new file mode 100644 index 0000000..3d644cf --- /dev/null +++ b/sources/lib/ipc/redis/TopCollectorRedis.php @@ -0,0 +1,161 @@ +preserved = array(); + // static vars come from the parent class + $this->latest = array( "pid" => self::$pid, + "ip" => Request::GetRemoteAddr(), + "user" => self::$user, + "start" => $_SERVER['REQUEST_TIME'], + "devtype" => Request::GetDeviceType(), + "devid" => self::$devid, + "devagent" => Request::GetUserAgent(), + "command" => Request::GetCommandCode(), + "ended" => 0, + "push" => false, + ); + $this->key = self::PREFIX . self::$devid . '|' . self::$user . '|' . self::$pid; + $this->AnnounceInformation("initializing"); + } + + /** + * Destructor + * indicates that the process is shutting down + * + * @access public + */ + public function __destruct() { + $this->AnnounceInformation("OK", false, true); + } + + /** + * Advices all other processes that they should start/stop + * collecting data. The data saved is a timestamp. It has to be + * reactivated every couple of seconds + * + * @param boolean $stop (opt) default false (do collect) + * + * @access public + * @return boolean indicating if it was set to collect before + */ + public function CollectData($stop = false) { + //for now we always collect top data + return true; + } + + /** + * Indicates if the TopCollector is active + * + * @access public + * @return boolean + */ + public function IsActive() { + //for now we always collect top data + return true; + } + + /** + * Announces a string to the TopCollector + * + * @param string $info + * @param boolean $preserve info should be displayed when process terminates + * @param boolean $terminating indicates if the process is terminating + * + * @access public + * @return boolean + */ + public function AnnounceInformation($addinfo, $preserve = false, $terminating = false) { + $this->latest["addinfo"] = $addinfo; + $this->latest["update"] = time(); + + if ($terminating) { + $this->latest["ended"] = time(); + foreach ($this->preserved as $p) + $this->latest["addinfo"] .= " : " . $p; + } + + if ($preserve) + $this->preserved[] = $addinfo; + + $exp = $terminating ? 20 : 120; + self::$redis->setex($this->key,$exp,serialize($this->latest)); + + return true; + } + + /** + * Returns all available top data + * + * @access public + * @return array + */ + public function ReadLatest() { + $topdata = array(); + $keys = self::$redis->keys(self::PREFIX . '*'); + if (!empty($keys)) { + $values = self::$redis->mGet($keys); + $array = array_combine($keys, $values); + foreach ($array as $key => $value) { + if ($value === false) + continue; + $key = explode('|',$key); + if (!array_key_exists($key[1], $topdata)) + $topdata[$key[1]] = array(); + if (!array_key_exists($key[2], $topdata[$key[1]])) + $topdata[$key[1]][$key[2]] = array(); + $topdata[$key[1]][$key[2]][$key[3]] = unserialize($value); + } + } + + return $topdata; + } + + /** + * Cleans up data collected so far + * + * @param boolean $all (optional) if set all data independently from the age is removed + * + * @access public + * @return boolean status + */ + public function ClearLatest($all = false) { + if ($all) + self::$redis->del(self::$redis->keys(self::PREFIX . '*')); + return true; + } + + /** + * Sets a different UserAgent for this connection + * + * @param string $agent + * + * @access public + * @return boolean + */ + public function SetUserAgent($agent) { + $this->latest["devagent"] = $agent; + + return true; + } + + /** + * Marks this process as push connection + * + * @param string $agent + * + * @access public + * @return boolean + */ + public function SetAsPushConnection() { + $this->latest["push"] = true; + + return true; + } +} diff --git a/sources/lib/core/interprocessdata.php b/sources/lib/ipc/shm/interprocessdata.php similarity index 94% rename from sources/lib/core/interprocessdata.php rename to sources/lib/ipc/shm/interprocessdata.php index 66ba699..23e0ee2 100644 --- a/sources/lib/core/interprocessdata.php +++ b/sources/lib/ipc/shm/interprocessdata.php @@ -42,13 +42,9 @@ * Consult LICENSE file for details ************************************************/ -abstract class InterProcessData { +abstract class InterProcessData extends InterProcessStorage { const CLEANUPTIME = 1; - static protected $devid; - static protected $pid; - static protected $user; - static protected $start; protected $type; protected $allocate; private $mutexid; @@ -68,19 +64,23 @@ abstract class InterProcessData { } /** - * Initializes internal parameters + * Indicates if the shared memory is active * * @access public * @return boolean */ - public function InitializeParams() { - if (!isset(self::$devid)) { - self::$devid = Request::GetDeviceID(); - self::$pid = @getmypid(); - self::$user = Request::GetAuthUser(); - self::$start = time(); - } - return true; + public function IsActive() { + return ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)); + } + + /** + * Reinitializes shared memory by removing, detaching and re-allocating it + * + * @access public + * @return boolean + */ + public function ReInitSharedMem() { + return ($this->RemoveSharedMem() && $this->InitSharedMem()); } /** @@ -147,16 +147,6 @@ abstract class InterProcessData { return false; } - /** - * Reinitializes shared memory by removing, detaching and re-allocating it - * - * @access public - * @return boolean - */ - public function ReInitSharedMem() { - return ($this->RemoveSharedMem() && $this->InitSharedMem()); - } - /** * Cleans up the shared memory block * @@ -179,16 +169,6 @@ abstract class InterProcessData { return $stat; } - /** - * Indicates if the shared memory is active - * - * @access public - * @return boolean - */ - public function IsActive() { - return ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)); - } - /** * Blocks the class mutex * Method blocks until mutex is available! @@ -293,5 +273,3 @@ abstract class InterProcessData { } } - -?> diff --git a/sources/lib/core/loopdetection.php b/sources/lib/ipc/shm/loopdetection.php similarity index 99% rename from sources/lib/core/loopdetection.php rename to sources/lib/ipc/shm/loopdetection.php index 225d853..18cc3c5 100644 --- a/sources/lib/core/loopdetection.php +++ b/sources/lib/ipc/shm/loopdetection.php @@ -47,7 +47,7 @@ ************************************************/ -class LoopDetection extends InterProcessData { +class LoopDetection extends InterProcessData implements ILoopDetection { const INTERPROCESSLD = "ipldkey"; const BROKENMSGS = "bromsgs"; static private $processident; @@ -106,9 +106,9 @@ class LoopDetection extends InterProcessData { * @access public * @return string */ - public static function GetProcessIdentifier() { + private static function GetProcessIdentifier() { if (!isset(self::$processident)) - self::$processident = sprintf('%04x%04', mt_rand(0, 0xffff), mt_rand(0, 0xffff)); + self::$processident = sprintf('%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff)); return self::$processident; } @@ -119,7 +119,7 @@ class LoopDetection extends InterProcessData { * @access public * @return array */ - public static function GetProcessEntry() { + private static function GetProcessEntry() { if (!isset(self::$processentry)) { self::$processentry = array(); self::$processentry['id'] = self::GetProcessIdentifier(); @@ -932,5 +932,3 @@ class LoopDetection extends InterProcessData { $loopdata[self::$devid][self::$user][$folderid] = array(); } } - -?> \ No newline at end of file diff --git a/sources/lib/core/pingtracking.php b/sources/lib/ipc/shm/pingtracking.php similarity index 98% rename from sources/lib/core/pingtracking.php rename to sources/lib/ipc/shm/pingtracking.php index 47e751f..5cd8c05 100644 --- a/sources/lib/core/pingtracking.php +++ b/sources/lib/ipc/shm/pingtracking.php @@ -41,7 +41,7 @@ * Consult LICENSE file for details ************************************************/ -class PingTracking extends InterProcessData { +class PingTracking extends InterProcessData implements IPingTracking { /** * Constructor @@ -152,5 +152,3 @@ class PingTracking extends InterProcessData { } } - -?> \ No newline at end of file diff --git a/sources/lib/core/topcollector.php b/sources/lib/ipc/shm/topcollector.php similarity index 98% rename from sources/lib/core/topcollector.php rename to sources/lib/ipc/shm/topcollector.php index ae1b247..ec9416c 100644 --- a/sources/lib/core/topcollector.php +++ b/sources/lib/ipc/shm/topcollector.php @@ -44,7 +44,7 @@ * Consult LICENSE file for details ************************************************/ -class TopCollector extends InterProcessData { +class TopCollector extends InterProcessData implements ITopCollector { const ENABLEDAT = 2; const TOPDATA = 3; @@ -145,6 +145,7 @@ class TopCollector extends InterProcessData { $this->preserved[] = $addinfo; // exclusive block + $ok = true; if ($this->blockMutex()) { if ($this->isEnabled()) { @@ -160,7 +161,7 @@ class TopCollector extends InterProcessData { } // end exclusive block - if ($this->isEnabled() === true && !$ok) { + if (!$ok) { ZLog::Write(LOGLEVEL_WARN, "TopCollector::AnnounceInformation(): could not write to shared memory. Z-Push top will not display this data."); return false; } @@ -295,5 +296,3 @@ class TopCollector extends InterProcessData { $topdata[self::$devid][self::$user][self::$pid] = array(); } } - -?> \ No newline at end of file diff --git a/sources/lib/request/folderchange.php b/sources/lib/request/folderchange.php index 253c3f4..47be4c9 100644 --- a/sources/lib/request/folderchange.php +++ b/sources/lib/request/folderchange.php @@ -244,4 +244,3 @@ class FolderChange extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/foldersync.php b/sources/lib/request/foldersync.php index 653e6dc..18c6db0 100644 --- a/sources/lib/request/foldersync.php +++ b/sources/lib/request/foldersync.php @@ -52,9 +52,6 @@ class FolderSync extends RequestProcessor { * @return boolean */ public function Handle ($commandCode) { - // Maps serverid -> clientid for items that are received from the PIM - $map = array(); - // Parse input if(!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) return false; @@ -137,10 +134,6 @@ class FolderSync extends RequestProcessor { $serverid = $changesMem->ImportFolderDeletion($folder); break; } - - // TODO what does $map?? - if($serverid) - $map[$serverid] = $folder->clientid; } else { ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): ignoring incoming folderchange for folder '%s' as status indicates problem.", $folder->displayname)); @@ -215,7 +208,7 @@ class FolderSync extends RequestProcessor { // say that we are done with partial synching self::$deviceManager->SetFolderSyncComplete(true); // reset the loop data to prevent any loop detection to kick in now - self::$deviceManager->ClearLoopDetectionData(Request::GetAuthUser(), Request::GetDeviceId()); + self::$deviceManager->ClearLoopDetectionData(Request::GetAuthUser(), Request::GetDeviceID()); ZLog::Write(LOGLEVEL_INFO, "Request->HandleFolderSync(): Chunked exporting of folders completed successfully"); } @@ -265,4 +258,3 @@ class FolderSync extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/getattachment.php b/sources/lib/request/getattachment.php index 912eb69..39679eb 100644 --- a/sources/lib/request/getattachment.php +++ b/sources/lib/request/getattachment.php @@ -63,22 +63,16 @@ class GetAttachment extends RequestProcessor { if ($stream == null) throw new StatusException(sprintf("HandleGetAttachment(): No stream resource returned by backend for attachment: %s", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); + if (!is_resource($stream)) + throw new StatusException(sprintf("HandleGetAttachment(): is_resource(stream) == false for attachment: %s", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); header("Content-Type: application/octet-stream"); - $l = 0; - while (!feof($stream)) { - $d = fgets($stream, 4096); - $l += strlen($d); - echo $d; - - // announce an update every 100K - if (($l/1024) % 100 == 0) - self::$topCollector->AnnounceInformation(sprintf("Streaming attachment: %d KB sent", round($l/1024))); - } + $l = fpassthru($stream); fclose($stream); - self::$topCollector->AnnounceInformation(sprintf("Streamed %d KB attachment", $l/1024), true); - ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleGetAttachment(): attachment with %d KB sent to mobile", $l/1024)); - + if ($l === false) + throw new Exception("HandleGetAttachment(): fpassthru === false !!!"); + self::$topCollector->AnnounceInformation(sprintf("Streamed %d KB attachment", round($l/1024)), true); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleGetAttachment(): attachment with %d KB sent to mobile", round($l/1024))); } catch (StatusException $s) { // StatusException already logged so we just need to pass it upwards to send a HTTP error @@ -88,4 +82,3 @@ class GetAttachment extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/gethierarchy.php b/sources/lib/request/gethierarchy.php index 152062d..c081cf8 100644 --- a/sources/lib/request/gethierarchy.php +++ b/sources/lib/request/gethierarchy.php @@ -78,4 +78,3 @@ class GetHierarchy extends RequestProcessor { return self::$deviceManager->InitializeFolderCache($folders); } } -?> \ No newline at end of file diff --git a/sources/lib/request/getitemestimate.php b/sources/lib/request/getitemestimate.php index 6f00587..683d481 100644 --- a/sources/lib/request/getitemestimate.php +++ b/sources/lib/request/getitemestimate.php @@ -91,7 +91,7 @@ class GetItemEstimate extends RequestProcessor { elseif(self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) { $spa->SetConversationMode(true); if(($conversationmode = self::$decoder->getElementContent()) !== false) { - $spa->SetConversationMode((boolean)$conversationmode); + $spa->SetConversationMode((bool)$conversationmode); if(!self::$decoder->getElementEndTag()) return false; } @@ -284,4 +284,3 @@ class GetItemEstimate extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/itemoperations.php b/sources/lib/request/itemoperations.php index c68c838..66f519b 100644 --- a/sources/lib/request/itemoperations.php +++ b/sources/lib/request/itemoperations.php @@ -211,7 +211,7 @@ class ItemOperations extends RequestProcessor { if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_DELETESUBFOLDERS)) { $operation['deletesubfolders'] = true; if (($dsf = self::$decoder->getElementContent()) !== false) { - $operation['deletesubfolders'] = (boolean)$dsf; + $operation['deletesubfolders'] = (bool)$dsf; if(!self::$decoder->getElementEndTag()) return false; } @@ -330,6 +330,9 @@ class ItemOperations extends RequestProcessor { } if (isset($data)) { + if (!is_object($data)) + throw new StatusException("ItemOperations->Handle(): data isn't an object !!!"); + self::$topCollector->AnnounceInformation("Streaming data"); self::$encoder->startTag(SYNC_ITEMOPERATIONS_PROPERTIES); @@ -384,4 +387,3 @@ class ItemOperations extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/meetingresponse.php b/sources/lib/request/meetingresponse.php index 64115a8..68de1e3 100644 --- a/sources/lib/request/meetingresponse.php +++ b/sources/lib/request/meetingresponse.php @@ -129,4 +129,3 @@ class MeetingResponse extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/moveitems.php b/sources/lib/request/moveitems.php index 7fe5691..b2a028d 100644 --- a/sources/lib/request/moveitems.php +++ b/sources/lib/request/moveitems.php @@ -135,4 +135,3 @@ class MoveItems extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/notify.php b/sources/lib/request/notify.php index 66a8058..0337f6a 100644 --- a/sources/lib/request/notify.php +++ b/sources/lib/request/notify.php @@ -80,4 +80,3 @@ class Notify extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/ping.php b/sources/lib/request/ping.php index 5cfacff..8d4a071 100644 --- a/sources/lib/request/ping.php +++ b/sources/lib/request/ping.php @@ -60,13 +60,23 @@ class Ping extends RequestProcessor { // Contains all requested folders (containers) $sc = new SyncCollections(); + // read from stream to see if the symc params are being sent + $params_present = self::$decoder->getElementStartTag(SYNC_PING_PING); + // Load all collections - do load states and check permissions try { $sc->LoadAllCollections(true, true, true); } catch (StateNotFoundException $snfex) { - $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED; - self::$topCollector->AnnounceInformation("StateNotFoundException: require HierarchySync", true); + // if no params are present, indicate to send params, else do hierarchy sync + if (!$params_present) { + $pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS; + self::$topCollector->AnnounceInformation("StateNotFoundException: require PingParameters", true); + } + else { + $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED; + self::$topCollector->AnnounceInformation("StateNotFoundException: require HierarchySync", true); + } } catch (StateInvalidException $snfex) { // we do not have a ping status for this, but SyncCollections should have generated fake changes for the folders which are broken @@ -83,7 +93,7 @@ class Ping extends RequestProcessor { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): reference PolicyKey for PING: %s", $sc->GetReferencePolicyKey())); // receive PING initialization data - if(self::$decoder->getElementStartTag(SYNC_PING_PING)) { + if($params_present) { self::$topCollector->AnnounceInformation("Processing PING data"); ZLog::Write(LOGLEVEL_DEBUG, "HandlePing(): initialization data received"); @@ -171,9 +181,12 @@ class Ping extends RequestProcessor { $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED; self::$deviceManager->AnnounceProcessStatus(false, $pingstatus); break; - case SyncCollections::OBSOLETE_CONNECTION: + case SyncCollections::OBSOLETE_CONNECTION: $foundchanges = false; break; + case SyncCollections::HIERARCHY_CHANGED: + $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED; + break; } } @@ -212,4 +225,3 @@ class Ping extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/provisioning.php b/sources/lib/request/provisioning.php index fe2df6e..5bff21e 100644 --- a/sources/lib/request/provisioning.php +++ b/sources/lib/request/provisioning.php @@ -227,4 +227,3 @@ class Provisioning extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/request.php b/sources/lib/request/request.php index 6511aa5..8954453 100644 --- a/sources/lib/request/request.php +++ b/sources/lib/request/request.php @@ -73,7 +73,6 @@ class Request { static private $input; static private $output; - static private $headers; static private $getparameters; static private $command; static private $device; @@ -195,37 +194,27 @@ class Request { * @return */ static public function ProcessHeaders() { - self::$headers = array_change_key_case(apache_request_headers(), CASE_LOWER); - self::$useragent = (isset(self::$headers["user-agent"]))? self::$headers["user-agent"] : self::UNKNOWN; + self::$useragent = (isset($_SERVER['HTTP_USER_AGENT']))? $_SERVER['HTTP_USER_AGENT'] : self::UNKNOWN; if (!isset(self::$asProtocolVersion)) - self::$asProtocolVersion = (isset(self::$headers["ms-asprotocolversion"]))? self::filterEvilInput(self::$headers["ms-asprotocolversion"], self::NUMBERSDOT_ONLY) : ZPush::GetLatestSupportedASVersion(); + self::$asProtocolVersion = (isset($_SERVER['HTTP_MS_ASPROTOCOLVERSION']))? self::filterEvilInput($_SERVER['HTTP_MS_ASPROTOCOLVERSION'], self::NUMBERSDOT_ONLY) : ZPush::GetLatestSupportedASVersion(); //if policykey is not yet set, try to set it from the header //the policy key might be set in Request::Initialize from the base64 encoded query if (!isset(self::$policykey)) { - if (isset(self::$headers["x-ms-policykey"])) - self::$policykey = (int) self::filterEvilInput(self::$headers["x-ms-policykey"], self::NUMBERS_ONLY); + if (isset($_SERVER['HTTP_X_MS_POLICYKEY'])) + self::$policykey = (int) self::filterEvilInput($_SERVER['HTTP_X_MS_POLICYKEY'], self::NUMBERS_ONLY); else self::$policykey = 0; } - if (!empty($_SERVER['QUERY_STRING']) && Utils::IsBase64String($_SERVER['QUERY_STRING'])) { - ZLog::Write(LOGLEVEL_DEBUG, "Using data from base64 encoded query string"); - if (isset(self::$policykey)) - self::$headers["x-ms-policykey"] = self::$policykey; - - if (isset(self::$asProtocolVersion)) - self::$headers["ms-asprotocolversion"] = self::$asProtocolVersion; - } - - if (!isset(self::$acceptMultipart) && isset(self::$headers["ms-asacceptmultipart"]) && strtoupper(self::$headers["ms-asacceptmultipart"]) == "T") { + if (!isset(self::$acceptMultipart) && isset($_SERVER['HTTP_MS_ASACCEPTMULTIPART']) && strtoupper($_SERVER['HTTP_MS_ASACCEPTMULTIPART']) == "T") { self::$acceptMultipart = true; } ZLog::Write(LOGLEVEL_DEBUG, sprintf("Request::ProcessHeaders() ASVersion: %s", self::$asProtocolVersion)); - if (defined('USE_X_FORWARDED_FOR_HEADER') && USE_X_FORWARDED_FOR_HEADER == true && isset(self::$headers["x-forwarded-for"])) { - $forwardedIP = self::filterEvilInput(self::$headers["x-forwarded-for"], self::NUMBERSDOT_ONLY); + if (defined('USE_X_FORWARDED_FOR_HEADER') && USE_X_FORWARDED_FOR_HEADER == true && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $forwardedIP = self::filterEvilInput($_SERVER['HTTP_X_FORWARDED_FOR'], self::NUMBERSDOT_ONLY); if ($forwardedIP) { self::$remoteAddr = $forwardedIP; ZLog::Write(LOGLEVEL_INFO, sprintf("'X-Forwarded-for' indicates remote IP: %s", self::$remoteAddr)); @@ -524,7 +513,7 @@ class Request { * @return boolean */ static public function WasPolicyKeySent() { - return isset(self::$headers["x-ms-policykey"]); + return isset($_SERVER['HTTP_X_MS_POLICYKEY']); } /** @@ -578,7 +567,7 @@ class Request { * @return int */ static public function GetContentLength() { - return (isset(self::$headers["content-length"]))? (int) self::$headers["content-length"] : 0; + return (isset($_SERVER['HTTP_CONTENT_LENGTH']))? (int) $_SERVER['HTTP_CONTENT_LENGTH'] : 0; } /** @@ -634,4 +623,3 @@ class Request { return ($re) ? preg_replace($re, $replacevalue, $input) : ''; } } -?> \ No newline at end of file diff --git a/sources/lib/request/requestprocessor.php b/sources/lib/request/requestprocessor.php index c3a898e..a4d8b8c 100644 --- a/sources/lib/request/requestprocessor.php +++ b/sources/lib/request/requestprocessor.php @@ -127,8 +127,20 @@ abstract class RequestProcessor { static public function HandleRequest() { $handler = ZPush::GetRequestHandlerForCommand(Request::GetCommandCode()); - // TODO handle WBXML exceptions here and print stack - return $handler->Handle(Request::GetCommandCode()); + // if there is an error decoding wbxml, consume remaining data and include it in the WBXMLException + if (!$handler->Handle(Request::GetCommandCode())) { + $wbxmlLog = "no decoder"; + if (self::$decoder) { + self::$decoder->readRemainingData(); + $wbxmlLog = self::$decoder->getWBXMLLog(); + } + throw new WBXMLException("Debug data: " . $wbxmlLog); + } + + // also log WBXML in happy case + if (self::$decoder && @constant('WBXML_DEBUG') === true) { + ZLog::Write(LOGLEVEL_WBXML, "WBXML-IN : ". self::$decoder->getWBXMLLog(), false); + } } /** @@ -154,4 +166,3 @@ abstract class RequestProcessor { */ abstract public function Handle($commandCode); } -?> \ No newline at end of file diff --git a/sources/lib/request/resolverecipients.php b/sources/lib/request/resolverecipients.php index 9c19d5b..75fe9d9 100755 --- a/sources/lib/request/resolverecipients.php +++ b/sources/lib/request/resolverecipients.php @@ -101,4 +101,3 @@ class ResolveRecipients extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/search.php b/sources/lib/request/search.php index d365fc8..a3f7dc1 100644 --- a/sources/lib/request/search.php +++ b/sources/lib/request/search.php @@ -445,4 +445,3 @@ class Search extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/sendmail.php b/sources/lib/request/sendmail.php index fbdb8b3..cda6edb 100644 --- a/sources/lib/request/sendmail.php +++ b/sources/lib/request/sendmail.php @@ -69,12 +69,12 @@ class SendMail extends RequestProcessor { if($el[EN_TYPE] != EN_TYPE_STARTTAG) return false; - - if($el[EN_TAG] == SYNC_COMPOSEMAIL_SENDMAIL) + $commandTag = $el[EN_TAG]; + if($commandTag == SYNC_COMPOSEMAIL_SENDMAIL) $sendmail = true; - else if($el[EN_TAG] == SYNC_COMPOSEMAIL_SMARTREPLY) + else if($commandTag == SYNC_COMPOSEMAIL_SMARTREPLY) $smartreply = true; - else if($el[EN_TAG] == SYNC_COMPOSEMAIL_SMARTFORWARD) + else if($commandTag == SYNC_COMPOSEMAIL_SMARTFORWARD) $smartforward = true; if(!$sendmail && !$smartreply && !$smartforward) @@ -121,11 +121,10 @@ class SendMail extends RequestProcessor { if ($status != SYNC_COMMONSTATUS_SUCCESS) { if (self::$decoder->IsWBXML()) { - // TODO check no WBXML on SmartReply and SmartForward self::$encoder->StartWBXML(); - self::$encoder->startTag(SYNC_COMPOSEMAIL_SENDMAIL); + self::$encoder->startTag($commandTag); self::$encoder->startTag(SYNC_COMPOSEMAIL_STATUS); - self::$encoder->content($status); //TODO return the correct status + self::$encoder->content($status); self::$encoder->endTag(); self::$encoder->endTag(); } @@ -136,4 +135,3 @@ class SendMail extends RequestProcessor { return $status; } } -?> \ No newline at end of file diff --git a/sources/lib/request/settings.php b/sources/lib/request/settings.php index 1266b4c..7db79b9 100644 --- a/sources/lib/request/settings.php +++ b/sources/lib/request/settings.php @@ -229,4 +229,3 @@ class Settings extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/request/sync.php b/sources/lib/request/sync.php index b71e5cf..34990d3 100644 --- a/sources/lib/request/sync.php +++ b/sources/lib/request/sync.php @@ -209,7 +209,7 @@ class Sync extends RequestProcessor { if(self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) { $spa->SetDeletesAsMoves(true); if (($dam = self::$decoder->getElementContent()) !== false) { - $spa->SetDeletesAsMoves((boolean)$dam); + $spa->SetDeletesAsMoves((bool)$dam); if(!self::$decoder->getElementEndTag()) { return false; } @@ -247,7 +247,7 @@ class Sync extends RequestProcessor { if(self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) { $spa->SetConversationMode(true); if(($conversationmode = self::$decoder->getElementContent()) !== false) { - $spa->SetConversationMode((boolean)$conversationmode); + $spa->SetConversationMode((bool)$conversationmode); if(!self::$decoder->getElementEndTag()) return false; } @@ -465,7 +465,7 @@ class Sync extends RequestProcessor { } if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) { - ZLog::Write(LOGLEVEL_INFO, sprintf("Processed '%d' incoming changes", $nchanges)); + ZLog::Write(LOGLEVEL_INFO, sprintf("Sync->Handle(): Processed %d incoming changes", $nchanges)); if (!$actiondata["fetchids"]) self::$topCollector->AnnounceInformation(sprintf("%d incoming", $nchanges), true); @@ -623,12 +623,25 @@ class Sync extends RequestProcessor { } } - // in case there are no changes, we can reply with an empty response - if (!$foundchanges && $status == SYNC_STATUS_SUCCESS){ - ZLog::Write(LOGLEVEL_DEBUG, "No changes found. Replying with empty response and closing connection."); - self::$specialHeaders = array(); - self::$specialHeaders[] = "Connection: close"; - return true; + // in case there are no changes and no other request has synchronized while we waited, we can reply with an empty response + if (!$foundchanges && $status == SYNC_STATUS_SUCCESS) { + // if there were changes to the SPA or CPOs we need to save this before we terminate + // only save if the state was not modified by some other request, if so, return state invalid status + foreach($sc as $folderid => $spa) { + if (self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) { + $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; + } + else { + $sc->SaveCollection($spa); + } + } + + if ($status == SYNC_STATUS_SUCCESS) { + ZLog::Write(LOGLEVEL_DEBUG, "No changes found and no other process changed states. Replying with empty response and closing connection."); + self::$specialHeaders = array(); + self::$specialHeaders[] = "Connection: close"; + return true; + } } if ($foundchanges) { @@ -846,7 +859,7 @@ class Sync extends RequestProcessor { // check if the message is broken if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) { - ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager.", $id)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager, id = %s", $id)); $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR; } } @@ -912,6 +925,15 @@ class Sync extends RequestProcessor { self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO); } } + // something really bad happened while exporting changes + catch (StatusException $stex) { + $status = $stex->getCode(); + // during export we found out that the states should be thrown away (ZP-623) + if ($status == SYNC_STATUS_INVALIDSYNCKEY) { + self::$deviceManager->ForceFolderResync($spa->GetFolderId()); + break; + } + } if($n >= $windowSize) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount)); @@ -928,8 +950,10 @@ class Sync extends RequestProcessor { self::$encoder->endTag(); self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, ($n >= $windowSize)?" of ".$changecount:""), true); - // update folder status - $spa->SetFolderSyncRemaining($changecount); + // update folder status, if there is something set + if ($spa->GetFolderSyncRemaining() && $changecount > 0) { + $spa->SetFolderSyncRemaining($changecount); + } // changecount is initialized with 'false', so 0 means no changes! if ($changecount === 0 || ($changecount !== false && $changecount <= $windowSize)) self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_COMPLETED); @@ -966,13 +990,14 @@ class Sync extends RequestProcessor { ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey())); } - // reset status for the next folder - $status = SYNC_STATUS_SUCCESS; - // save SyncParameters if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"])) $sc->SaveCollection($spa); + // reset status for the next folder + $status = SYNC_STATUS_SUCCESS; + + } // END foreach collection } self::$encoder->endTag(); //SYNC_FOLDERS @@ -1090,7 +1115,7 @@ class Sync extends RequestProcessor { private function importMessage($spa, &$actiondata, $todo, $message, $clientid, $serverid, $foldertype, $messageCount) { // the importer needs to be available! if ($this->importer == false) - throw StatusException(sprintf("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR)); + throw StatusException("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR); // mark this state as used, e.g. for HeartBeat self::$deviceManager->SetHeartbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter()); @@ -1230,5 +1255,3 @@ class Sync extends RequestProcessor { } } } - -?> \ No newline at end of file diff --git a/sources/lib/request/validatecert.php b/sources/lib/request/validatecert.php index 4a827ed..4215837 100755 --- a/sources/lib/request/validatecert.php +++ b/sources/lib/request/validatecert.php @@ -87,4 +87,3 @@ class ValidateCert extends RequestProcessor { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncappointment.php b/sources/lib/syncobjects/syncappointment.php index 6f929e5..755c1a5 100644 --- a/sources/lib/syncobjects/syncappointment.php +++ b/sources/lib/syncobjects/syncappointment.php @@ -123,6 +123,7 @@ class SyncAppointment extends SyncObject { // 1 = Tentative // 2 = Busy // 3 = Out of office + // 4 = Working Elsewhere SYNC_POOMCAL_BUSYSTATUS => array ( self::STREAMER_VAR => "busystatus", self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETTWO, self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3,4) )), @@ -218,5 +219,3 @@ class SyncAppointment extends SyncObject { return true; } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncappointmentexception.php b/sources/lib/syncobjects/syncappointmentexception.php index e8d95aa..35c0a91 100644 --- a/sources/lib/syncobjects/syncappointmentexception.php +++ b/sources/lib/syncobjects/syncappointmentexception.php @@ -68,11 +68,9 @@ class SyncAppointmentException extends SyncAppointment { $this->mapping[SYNC_POOMCAL_STARTTIME][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_CMPLOWER => SYNC_POOMCAL_ENDTIME); $this->mapping[SYNC_POOMCAL_SUBJECT][self::STREAMER_CHECKS] = array(); $this->mapping[SYNC_POOMCAL_ENDTIME][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_CMPHIGHER => SYNC_POOMCAL_STARTTIME); - $this->mapping[SYNC_POOMCAL_BUSYSTATUS][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) ); + $this->mapping[SYNC_POOMCAL_BUSYSTATUS][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3,4) ); $this->mapping[SYNC_POOMCAL_REMINDER][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_CMPHIGHER => -1); $this->mapping[SYNC_POOMCAL_EXCEPTIONS][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_NOTALLOWED => true); } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncattachment.php b/sources/lib/syncobjects/syncattachment.php index 653d42d..0c25e00 100644 --- a/sources/lib/syncobjects/syncattachment.php +++ b/sources/lib/syncobjects/syncattachment.php @@ -74,4 +74,3 @@ class SyncAttachment extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncattendee.php b/sources/lib/syncobjects/syncattendee.php index 15f44cd..f8b4c84 100644 --- a/sources/lib/syncobjects/syncattendee.php +++ b/sources/lib/syncobjects/syncattendee.php @@ -67,5 +67,3 @@ class SyncAttendee extends SyncObject { parent::SyncObject($mapping); } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncbaseattachment.php b/sources/lib/syncobjects/syncbaseattachment.php index 61c7667..9c0e128 100644 --- a/sources/lib/syncobjects/syncbaseattachment.php +++ b/sources/lib/syncobjects/syncbaseattachment.php @@ -68,4 +68,3 @@ class SyncBaseAttachment extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncbasebody.php b/sources/lib/syncobjects/syncbasebody.php index 833d752..d19166f 100644 --- a/sources/lib/syncobjects/syncbasebody.php +++ b/sources/lib/syncobjects/syncbasebody.php @@ -65,4 +65,3 @@ class SyncBaseBody extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/synccontact.php b/sources/lib/syncobjects/synccontact.php index 454953f..c20596b 100644 --- a/sources/lib/syncobjects/synccontact.php +++ b/sources/lib/syncobjects/synccontact.php @@ -113,7 +113,10 @@ class SyncContact extends SyncObject { public $nickname; public $mms; - function SyncContact() { + // AS 12 props + public $asbody; + + public function __construct() { $mapping = array ( SYNC_POOMCONTACTS_ANNIVERSARY => array ( self::STREAMER_VAR => "anniversary", self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES ), @@ -123,9 +126,6 @@ class SyncContact extends SyncObject { SYNC_POOMCONTACTS_BIRTHDAY => array ( self::STREAMER_VAR => "birthday", self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES ), - SYNC_POOMCONTACTS_BODY => array ( self::STREAMER_VAR => "body"), - SYNC_POOMCONTACTS_BODYSIZE => array ( self::STREAMER_VAR => "bodysize"), - SYNC_POOMCONTACTS_BODYTRUNCATED => array ( self::STREAMER_VAR => "bodytruncated"), SYNC_POOMCONTACTS_BUSINESS2PHONENUMBER => array ( self::STREAMER_VAR => "business2phonenumber"), SYNC_POOMCONTACTS_BUSINESSCITY => array ( self::STREAMER_VAR => "businesscity"), SYNC_POOMCONTACTS_BUSINESSCOUNTRY => array ( self::STREAMER_VAR => "businesscountry"), @@ -178,31 +178,28 @@ class SyncContact extends SyncObject { SYNC_POOMCONTACTS_CATEGORIES => array ( self::STREAMER_VAR => "categories", self::STREAMER_ARRAY => SYNC_POOMCONTACTS_CATEGORY ), - ); - if (Request::GetProtocolVersion() >= 2.5) { - $mapping[SYNC_POOMCONTACTS2_CUSTOMERID] = array ( self::STREAMER_VAR => "customerid"); - $mapping[SYNC_POOMCONTACTS2_GOVERNMENTID] = array ( self::STREAMER_VAR => "governmentid"); - $mapping[SYNC_POOMCONTACTS2_IMADDRESS] = array ( self::STREAMER_VAR => "imaddress"); - $mapping[SYNC_POOMCONTACTS2_IMADDRESS2] = array ( self::STREAMER_VAR => "imaddress2"); - $mapping[SYNC_POOMCONTACTS2_IMADDRESS3] = array ( self::STREAMER_VAR => "imaddress3"); - $mapping[SYNC_POOMCONTACTS2_MANAGERNAME] = array ( self::STREAMER_VAR => "managername"); - $mapping[SYNC_POOMCONTACTS2_COMPANYMAINPHONE] = array ( self::STREAMER_VAR => "companymainphone"); - $mapping[SYNC_POOMCONTACTS2_ACCOUNTNAME] = array ( self::STREAMER_VAR => "accountname"); - $mapping[SYNC_POOMCONTACTS2_NICKNAME] = array ( self::STREAMER_VAR => "nickname"); - $mapping[SYNC_POOMCONTACTS2_MMS] = array ( self::STREAMER_VAR => "mms"); - } + SYNC_POOMCONTACTS2_CUSTOMERID => array ( self::STREAMER_VAR => "customerid"), + SYNC_POOMCONTACTS2_GOVERNMENTID => array ( self::STREAMER_VAR => "governmentid"), + SYNC_POOMCONTACTS2_IMADDRESS => array ( self::STREAMER_VAR => "imaddress"), + SYNC_POOMCONTACTS2_IMADDRESS2 => array ( self::STREAMER_VAR => "imaddress2"), + SYNC_POOMCONTACTS2_IMADDRESS3 => array ( self::STREAMER_VAR => "imaddress3"), + SYNC_POOMCONTACTS2_MANAGERNAME => array ( self::STREAMER_VAR => "managername"), + SYNC_POOMCONTACTS2_COMPANYMAINPHONE => array ( self::STREAMER_VAR => "companymainphone"), + SYNC_POOMCONTACTS2_ACCOUNTNAME => array ( self::STREAMER_VAR => "accountname"), + SYNC_POOMCONTACTS2_NICKNAME => array ( self::STREAMER_VAR => "nickname"), + SYNC_POOMCONTACTS2_MMS => array ( self::STREAMER_VAR => "mms"), + ); if (Request::GetProtocolVersion() >= 12.0) { $mapping[SYNC_AIRSYNCBASE_BODY] = array ( self::STREAMER_VAR => "asbody", self::STREAMER_TYPE => "SyncBaseBody"); - - //unset these properties because airsyncbase body and attachments will be used instead - unset($mapping[SYNC_POOMCONTACTS_BODY], $mapping[SYNC_POOMCONTACTS_BODYTRUNCATED]); + } else { + $mapping[SYNC_POOMCONTACTS_BODY] = array ( self::STREAMER_VAR => "body"); + $mapping[SYNC_POOMCONTACTS_BODYSIZE] = array ( self::STREAMER_VAR => "bodysize"); + $mapping[SYNC_POOMCONTACTS_BODYTRUNCATED] = array ( self::STREAMER_VAR => "bodytruncated"); } parent::SyncObject($mapping); } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncdeviceinformation.php b/sources/lib/syncobjects/syncdeviceinformation.php index 9e2b412..02308e5 100644 --- a/sources/lib/syncobjects/syncdeviceinformation.php +++ b/sources/lib/syncobjects/syncdeviceinformation.php @@ -82,4 +82,3 @@ class SyncDeviceInformation extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncdevicepassword.php b/sources/lib/syncobjects/syncdevicepassword.php index 4ac1b88..21b257a 100644 --- a/sources/lib/syncobjects/syncdevicepassword.php +++ b/sources/lib/syncobjects/syncdevicepassword.php @@ -60,4 +60,3 @@ class SyncDevicePassword extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncfolder.php b/sources/lib/syncobjects/syncfolder.php index ae71c12..a3e23c4 100644 --- a/sources/lib/syncobjects/syncfolder.php +++ b/sources/lib/syncobjects/syncfolder.php @@ -62,7 +62,7 @@ class SyncFolder extends SyncObject { self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO)), SYNC_FOLDERHIERARCHY_DISPLAYNAME => array ( self::STREAMER_VAR => "displayname", - self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETEMPTY)), + self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => "Unknown")), SYNC_FOLDERHIERARCHY_TYPE => array ( self::STREAMER_VAR => "type", self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => 18, @@ -76,4 +76,3 @@ class SyncFolder extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncitemoperationsattachment.php b/sources/lib/syncobjects/syncitemoperationsattachment.php index 6f2e8da..6a520ad 100644 --- a/sources/lib/syncobjects/syncitemoperationsattachment.php +++ b/sources/lib/syncobjects/syncitemoperationsattachment.php @@ -59,4 +59,3 @@ class SyncItemOperationsAttachment extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncmail.php b/sources/lib/syncobjects/syncmail.php index d694396..ef70b75 100644 --- a/sources/lib/syncobjects/syncmail.php +++ b/sources/lib/syncobjects/syncmail.php @@ -86,6 +86,7 @@ class SyncMail extends SyncObject { public $lastverbexectime; public $receivedasbcc; public $sender; + public $categories; function SyncMail() { $mapping = array ( @@ -196,5 +197,3 @@ class SyncMail extends SyncObject { parent::SyncObject($mapping); } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncmailflags.php b/sources/lib/syncobjects/syncmailflags.php index fa08a84..6811470 100644 --- a/sources/lib/syncobjects/syncmailflags.php +++ b/sources/lib/syncobjects/syncmailflags.php @@ -96,4 +96,3 @@ class SyncMailFlags extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncmeetingrequest.php b/sources/lib/syncobjects/syncmeetingrequest.php index 66167a2..f7c5aa4 100644 --- a/sources/lib/syncobjects/syncmeetingrequest.php +++ b/sources/lib/syncobjects/syncmeetingrequest.php @@ -119,9 +119,10 @@ class SyncMeetingRequest extends SyncObject { // 1 = Tentative // 2 = Busy // 3 = Out of office + // 4 = Working Elsewhere SYNC_POOMMAIL_BUSYSTATUS => array ( self::STREAMER_VAR => "busystatus", self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETTWO, - self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) )), + self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3,4) )), SYNC_POOMMAIL_TIMEZONE => array ( self::STREAMER_VAR => "timezone", self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => base64_encode(pack("la64vvvvvvvv"."la64vvvvvvvv"."l",0,"",0,0,0,0,0,0,0,0,0,"",0,0,0,0,0,0,0,0,0)) )), @@ -136,4 +137,3 @@ class SyncMeetingRequest extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncmeetingrequestrecurrence.php b/sources/lib/syncobjects/syncmeetingrequestrecurrence.php index 25ba05d..6b86237 100644 --- a/sources/lib/syncobjects/syncmeetingrequestrecurrence.php +++ b/sources/lib/syncobjects/syncmeetingrequestrecurrence.php @@ -117,5 +117,3 @@ class SyncMeetingRequestRecurrence extends SyncObject { parent::SyncObject($mapping); } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncnote.php b/sources/lib/syncobjects/syncnote.php index 66db246..64bdfc2 100644 --- a/sources/lib/syncobjects/syncnote.php +++ b/sources/lib/syncobjects/syncnote.php @@ -72,4 +72,3 @@ class SyncNote extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncobject.php b/sources/lib/syncobjects/syncobject.php index 4a571e9..a68aba1 100644 --- a/sources/lib/syncobjects/syncobject.php +++ b/sources/lib/syncobjects/syncobject.php @@ -82,7 +82,7 @@ abstract class SyncObject extends Streamer { public function emptySupported($supportedFields) { // Some devices do not send supported tag. In such a case remove all not set properties. if (($supportedFields === false || !is_array($supportedFields) || (empty($supportedFields)))) { - if (defined('UNSET_UNDEFINED_PROPERTIES') && UNSET_UNDEFINED_PROPERTIES && ($this instanceOf SyncContact || $this instanceOf SyncAppointment)) { + if (defined('UNSET_UNDEFINED_PROPERTIES') && UNSET_UNDEFINED_PROPERTIES && ($this instanceof SyncContact || $this instanceof SyncAppointment)) { ZLog::Write(LOGLEVEL_INFO, sprintf("%s->emptySupported(): no supported list available, emptying all not set parameters", get_class($this))); $supportedFields = array_keys($this->mapping); } @@ -389,7 +389,7 @@ abstract class SyncObject extends Streamer { $output = array(); foreach ($mails as $mail) { - if (! Utils::CheckEmail($mail)) { + if (!Utils::CheckEmail($mail) && !Utils::CheckEmailEmptyGroup($mail)) { ZLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): object from type %s: parameter '%s' contains an invalid email address '%s'. Address is removed.", $objClass, $v[self::STREAMER_VAR], $mail)); } else @@ -419,5 +419,3 @@ abstract class SyncObject extends Streamer { return true; } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncoof.php b/sources/lib/syncobjects/syncoof.php index 163a2b7..9becfc0 100644 --- a/sources/lib/syncobjects/syncoof.php +++ b/sources/lib/syncobjects/syncoof.php @@ -80,4 +80,3 @@ class SyncOOF extends SyncObject { } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncoofmessage.php b/sources/lib/syncobjects/syncoofmessage.php index c0ec349..88d48a5 100644 --- a/sources/lib/syncobjects/syncoofmessage.php +++ b/sources/lib/syncobjects/syncoofmessage.php @@ -78,4 +78,3 @@ class SyncOOFMessage extends SyncObject { } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncprovisioning.php b/sources/lib/syncobjects/syncprovisioning.php index 7d05150..a859a90 100644 --- a/sources/lib/syncobjects/syncprovisioning.php +++ b/sources/lib/syncobjects/syncprovisioning.php @@ -300,5 +300,3 @@ class SyncProvisioning extends SyncObject { $this->approvedapplist = array(); } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncrecurrence.php b/sources/lib/syncobjects/syncrecurrence.php index 665abaf..cc2c0a4 100644 --- a/sources/lib/syncobjects/syncrecurrence.php +++ b/sources/lib/syncobjects/syncrecurrence.php @@ -117,4 +117,3 @@ class SyncRecurrence extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncresolverecipient.php b/sources/lib/syncobjects/syncresolverecipient.php index b8f6926..81b6ae3 100755 --- a/sources/lib/syncobjects/syncresolverecipient.php +++ b/sources/lib/syncobjects/syncresolverecipient.php @@ -74,4 +74,3 @@ class SyncResolveRecipient extends SyncObject { } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncresolverecipients.php b/sources/lib/syncobjects/syncresolverecipients.php index 9339545..12cb30d 100755 --- a/sources/lib/syncobjects/syncresolverecipients.php +++ b/sources/lib/syncobjects/syncresolverecipients.php @@ -72,4 +72,3 @@ class SyncResolveRecipients extends SyncObject { } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncresolverecipientsavailability.php b/sources/lib/syncobjects/syncresolverecipientsavailability.php index d4f3642..5dae42e 100755 --- a/sources/lib/syncobjects/syncresolverecipientsavailability.php +++ b/sources/lib/syncobjects/syncresolverecipientsavailability.php @@ -63,4 +63,3 @@ class SyncRRAvailability extends SyncObject { } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncresolverecipientscertificates.php b/sources/lib/syncobjects/syncresolverecipientscertificates.php index 52ce8f0..6496f65 100755 --- a/sources/lib/syncobjects/syncresolverecipientscertificates.php +++ b/sources/lib/syncobjects/syncresolverecipientscertificates.php @@ -71,4 +71,3 @@ class SyncRRCertificates extends SyncObject { } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncresolverecipientsoptions.php b/sources/lib/syncobjects/syncresolverecipientsoptions.php index 09b5393..726350f 100755 --- a/sources/lib/syncobjects/syncresolverecipientsoptions.php +++ b/sources/lib/syncobjects/syncresolverecipientsoptions.php @@ -69,4 +69,3 @@ class SyncRROptions extends SyncObject { } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncresolverecipientspicture.php b/sources/lib/syncobjects/syncresolverecipientspicture.php index d241184..7c18a63 100755 --- a/sources/lib/syncobjects/syncresolverecipientspicture.php +++ b/sources/lib/syncobjects/syncresolverecipientspicture.php @@ -63,4 +63,3 @@ class SyncRRPicture extends SyncObject { } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncsendmail.php b/sources/lib/syncobjects/syncsendmail.php index cfe109c..c884af0 100644 --- a/sources/lib/syncobjects/syncsendmail.php +++ b/sources/lib/syncobjects/syncsendmail.php @@ -83,4 +83,3 @@ class SyncSendMail extends SyncObject { parent::SyncObject($mapping); } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncsendmailsource.php b/sources/lib/syncobjects/syncsendmailsource.php index b75f2b5..a0d3d00 100644 --- a/sources/lib/syncobjects/syncsendmailsource.php +++ b/sources/lib/syncobjects/syncsendmailsource.php @@ -64,4 +64,3 @@ class SyncSendMailSource extends SyncObject { } } -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/synctask.php b/sources/lib/syncobjects/synctask.php index daf3e69..3a52670 100644 --- a/sources/lib/syncobjects/synctask.php +++ b/sources/lib/syncobjects/synctask.php @@ -47,7 +47,11 @@ class SyncTask extends SyncObject { + // AS 2.5 props public $body; + public $bodysize; + public $bodytruncated; + // public $complete; public $datecompleted; public $duedate; @@ -64,10 +68,11 @@ class SyncTask extends SyncObject { public $subject; public $rtf; public $categories; + // AS 12.0 props + public $asbody; function SyncTask() { $mapping = array ( - SYNC_POOMTASKS_BODY => array ( self::STREAMER_VAR => "body"), SYNC_POOMTASKS_COMPLETE => array ( self::STREAMER_VAR => "complete", self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO, self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO )), @@ -122,12 +127,15 @@ class SyncTask extends SyncObject { self::STREAMER_ARRAY => SYNC_POOMTASKS_CATEGORY), ); - if (Request::GetProtocolVersion() >= 12.0) { + if (Request::GetProtocolVersion() >= 12.0) { $mapping[SYNC_AIRSYNCBASE_BODY] = array ( self::STREAMER_VAR => "asbody", self::STREAMER_TYPE => "SyncBaseBody"); - - //unset these properties because airsyncbase body and attachments will be used instead - unset($mapping[SYNC_POOMTASKS_BODY]); + } else { + $mapping[SYNC_POOMTASKS_BODY] = array ( self::STREAMER_VAR => "body"); + $mapping[SYNC_POOMTASKS_BODYTRUNCATED] = array ( self::STREAMER_VAR => "bodytruncated", + self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO)); + $mapping[SYNC_POOMTASKS_BODYSIZE] = array ( self::STREAMER_VAR => "bodysize", + self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1)); } parent::SyncObject($mapping); @@ -176,5 +184,3 @@ class SyncTask extends SyncObject { return true; } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/synctaskrecurrence.php b/sources/lib/syncobjects/synctaskrecurrence.php index e4a0cd7..63a1074 100644 --- a/sources/lib/syncobjects/synctaskrecurrence.php +++ b/sources/lib/syncobjects/synctaskrecurrence.php @@ -57,6 +57,7 @@ class SyncTaskRecurrence extends SyncObject { public $dayofmonth; public $weekofmonth; public $monthofyear; + public $regenerate; public $deadoccur; function SyncTaskRecurrence() { @@ -88,6 +89,7 @@ class SyncTaskRecurrence extends SyncObject { //TODO: check iOS5 sends deadoccur inside of the recurrence SYNC_POOMTASKS_DEADOCCUR => array ( self::STREAMER_VAR => "deadoccur"), + SYNC_POOMTASKS_REGENERATE => array ( self::STREAMER_VAR => "regenerate"), // DayOfWeek values // 1 = Sunday @@ -153,5 +155,3 @@ class SyncTaskRecurrence extends SyncObject { return true; } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncuserinformation.php b/sources/lib/syncobjects/syncuserinformation.php index a9fae8c..2fac953 100644 --- a/sources/lib/syncobjects/syncuserinformation.php +++ b/sources/lib/syncobjects/syncuserinformation.php @@ -75,5 +75,3 @@ class SyncUserInformation extends SyncObject { parent::SyncObject($mapping); } } - -?> \ No newline at end of file diff --git a/sources/lib/syncobjects/syncvalidatecert.php b/sources/lib/syncobjects/syncvalidatecert.php index e23c4a3..08ece86 100755 --- a/sources/lib/syncobjects/syncvalidatecert.php +++ b/sources/lib/syncobjects/syncvalidatecert.php @@ -69,4 +69,3 @@ class SyncValidateCert extends SyncObject { } } -?> \ No newline at end of file diff --git a/sources/lib/utils/compat.php b/sources/lib/utils/compat.php index 528dd97..9a68868 100644 --- a/sources/lib/utils/compat.php +++ b/sources/lib/utils/compat.php @@ -41,82 +41,6 @@ * Consult LICENSE file for details ************************************************/ -if (!function_exists("quoted_printable_encode")) { - /** - * Process a string to fit the requirements of RFC2045 section 6.7. Note that - * this works, but replaces more characters than the minimum set. For readability - * the spaces and CRLF pairs aren't encoded though. - * - * @param string $string string to be encoded - * - * @see http://www.php.net/manual/en/function.quoted-printable-encode.php#106078 - */ - function quoted_printable_encode($string) { - $lp = 0; - $ret = ''; - $hex = "0123456789ABCDEF"; - $length = strlen($str); - $str_index = 0; - - while ($length--) { - if ((($c = $str[$str_index++]) == "\015") && ($str[$str_index] == "\012") && $length > 0) { - $ret .= "\015"; - $ret .= $str[$str_index++]; - $length--; - $lp = 0; - } else { - if (ctype_cntrl($c) - || (ord($c) == 0x7f) - || (ord($c) & 0x80) - || ($c == '=') - || (($c == ' ') && ($str[$str_index] == "\015"))) - { - if (($lp += 3) > PHP_QPRINT_MAXL) - { - $ret .= '='; - $ret .= "\015"; - $ret .= "\012"; - $lp = 3; - } - $ret .= '='; - $ret .= $hex[ord($c) >> 4]; - $ret .= $hex[ord($c) & 0xf]; - } - else - { - if ((++$lp) > PHP_QPRINT_MAXL) - { - $ret .= '='; - $ret .= "\015"; - $ret .= "\012"; - $lp = 1; - } - $ret .= $c; - } - } - } - - return $ret; - } -} - -if (!function_exists("apache_request_headers")) { - /** - * When using other webservers or using php as cgi in apache - * the function apache_request_headers() is not available. - * This function parses the environment variables to extract - * the necessary headers for Z-Push - */ - function apache_request_headers() { - $headers = array(); - foreach ($_SERVER as $key => $value) - if (substr($key, 0, 5) == 'HTTP_') - $headers[strtr(substr($key, 5), '_', '-')] = $value; - - return $headers; - } -} - if (!function_exists("hex2bin")) { /** * Complementary function to bin2hex() which converts a hex entryid to a binary entryid. @@ -130,4 +54,65 @@ if (!function_exists("hex2bin")) { return pack("H*", $data); } } -?> \ No newline at end of file + +if (!function_exists('http_response_code')) { + /** + * http_response_code does not exists in PHP < 5.4 + * http://php.net/manual/en/function.http-response-code.php + */ + function http_response_code($code = NULL) { + if ($code !== NULL) { + switch ($code) { + case 100: $text = 'Continue'; break; + case 101: $text = 'Switching Protocols'; break; + case 200: $text = 'OK'; break; + case 201: $text = 'Created'; break; + case 202: $text = 'Accepted'; break; + case 203: $text = 'Non-Authoritative Information'; break; + case 204: $text = 'No Content'; break; + case 205: $text = 'Reset Content'; break; + case 206: $text = 'Partial Content'; break; + case 300: $text = 'Multiple Choices'; break; + case 301: $text = 'Moved Permanently'; break; + case 302: $text = 'Moved Temporarily'; break; + case 303: $text = 'See Other'; break; + case 304: $text = 'Not Modified'; break; + case 305: $text = 'Use Proxy'; break; + case 400: $text = 'Bad Request'; break; + case 401: $text = 'Unauthorized'; break; + case 402: $text = 'Payment Required'; break; + case 403: $text = 'Forbidden'; break; + case 404: $text = 'Not Found'; break; + case 405: $text = 'Method Not Allowed'; break; + case 406: $text = 'Not Acceptable'; break; + case 407: $text = 'Proxy Authentication Required'; break; + case 408: $text = 'Request Time-out'; break; + case 409: $text = 'Conflict'; break; + case 410: $text = 'Gone'; break; + case 411: $text = 'Length Required'; break; + case 412: $text = 'Precondition Failed'; break; + case 413: $text = 'Request Entity Too Large'; break; + case 414: $text = 'Request-URI Too Large'; break; + case 415: $text = 'Unsupported Media Type'; break; + case 500: $text = 'Internal Server Error'; break; + case 501: $text = 'Not Implemented'; break; + case 502: $text = 'Bad Gateway'; break; + case 503: $text = 'Service Unavailable'; break; + case 504: $text = 'Gateway Time-out'; break; + case 505: $text = 'HTTP Version not supported'; break; + default: + exit('Unknown http status code "' . htmlentities($code) . '"'); + break; + } + + $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); + header($protocol . ' ' . $code . ' ' . $text); + + $GLOBALS['http_response_code'] = $code; + } else { + $code = (isset($GLOBALS['http_response_code']) ? $GLOBALS['http_response_code'] : 200); + } + + return $code; + } +} \ No newline at end of file diff --git a/sources/lib/exceptions/exceptions.php b/sources/lib/utils/stringstreamwrapper.php similarity index 68% rename from sources/lib/exceptions/exceptions.php rename to sources/lib/utils/stringstreamwrapper.php index 19c3f48..64a7d80 100644 --- a/sources/lib/exceptions/exceptions.php +++ b/sources/lib/utils/stringstreamwrapper.php @@ -1,10 +1,10 @@ \ No newline at end of file +class StringStreamWrapper { + /** + * Instantiates a stream + * + * @param string $string The string to be wrapped + * @access public + * @return stream + */ + public static function Open($string) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("StringStreamWrapper::Open(): len = %d", strlen($string))); + if (defined('BUG68532FIXED') && BUG68532FIXED === true) { + ZLog::Write(LOGLEVEL_DEBUG, "StringStreamWrapper::Open(): Using php://temp"); + $stream = fopen('php://temp', 'r+'); + } else { + ZLog::Write(LOGLEVEL_DEBUG, "StringStreamWrapper::Open(): Using tmpfile()"); + $stream = tmpfile(); + } + fwrite($stream, $string); + rewind($stream); + return $stream; + } +} diff --git a/sources/lib/utils/timezoneutil.php b/sources/lib/utils/timezoneutil.php index d994587..0926ed8 100644 --- a/sources/lib/utils/timezoneutil.php +++ b/sources/lib/utils/timezoneutil.php @@ -1119,8 +1119,8 @@ class TimezoneUtil { "tzname" => self::encodeTZName(self::getMSTZnameFromTZName($tzname)), "dstendyear" => $offset[3], "dstendmonth" => $offset[4], - "dstendday" => $offset[6], - "dstendweek" => $offset[5], + "dstendday" => $offset[5], + "dstendweek" => $offset[6], "dstendhour" => $offset[7], "dstendminute" => $offset[8], "dstendsecond" => $offset[9], @@ -1129,8 +1129,8 @@ class TimezoneUtil { "tznamedst" => self::encodeTZName(self::getMSTZnameFromTZName($tzname)), "dststartyear" => $offset[11], "dststartmonth" => $offset[12], - "dststartday" => $offset[14], - "dststartweek" => $offset[13], + "dststartday" => $offset[13], + "dststartweek" => $offset[14], "dststarthour" => $offset[15], "dststartminute" => $offset[16], "dststartsecond" => $offset[17], @@ -1222,7 +1222,7 @@ class TimezoneUtil { * @access public * @return string */ - static private function getMSTZnameFromTZName($name) { + static public function getMSTZnameFromTZName($name) { foreach (self::$mstzones as $mskey => $msdefs) { if ($name == $msdefs[0]) return $msdefs[1]; @@ -1291,5 +1291,3 @@ class TimezoneUtil { return $packed; } } - -?> \ No newline at end of file diff --git a/sources/lib/utils/utils.php b/sources/lib/utils/utils.php index 296b54c..0c47871 100644 --- a/sources/lib/utils/utils.php +++ b/sources/lib/utils/utils.php @@ -337,14 +337,10 @@ class Utils { $back = 60 * 60 * 24 * 31 * 6; break; default: - break; + return 0; // unlimited } - if(isset($back)) { - $date = time() - $back; - return $date; - } else - return 0; // unlimited + return time() - $back; } /** @@ -441,18 +437,18 @@ class Utils { $len = strlen($string) - 1; while ($len > 0) { //look for '&-' sequence and replace it with '&' - if ($len > 0 && $string{($len-1)} == '&' && $string{$len} == '-') { + if ($len > 0 && $string[$len-1] == '&' && $string[$len] == '-') { $string = substr_replace($string, '&', $len - 1, 2); $len--; //decrease $len as this char has alreasy been processed } //search for '&' which weren't found in if clause above and //replace them with '+' as they mark an utf7-encoded char - if ($len > 0 && $string{($len-1)} == '&') { + if ($len > 0 && $string[($len-1)] == '&') { $string = substr_replace($string, '+', $len - 1, 1); $len--; //decrease $len as this char has alreasy been processed } //finally "escape" all remaining '+' chars - if ($len > 0 && $string{($len-1)} == '+') { + if ($len > 0 && $string[$len-1] == '+') { $string = substr_replace($string, '+-', $len - 1, 1); } $len--; @@ -479,18 +475,18 @@ class Utils { $len = strlen($string) - 1; while ($len > 0) { //look for '&-' sequence and replace it with '&' - if ($len > 0 && $string{($len-1)} == '+' && $string{$len} == '-') { + if ($len > 0 && $string[$len-1] == '+' && $string[$len] == '-') { $string = substr_replace($string, '+', $len - 1, 2); $len--; //decrease $len as this char has alreasy been processed } //search for '&' which weren't found in if clause above and //replace them with '+' as they mark an utf7-encoded char - if ($len > 0 && $string{($len-1)} == '+') { + if ($len > 0 && $string[$len-1] == '+') { $string = substr_replace($string, '&', $len - 1, 1); $len--; //decrease $len as this char has alreasy been processed } //finally "escape" all remaining '+' chars - if ($len > 0 && $string{($len-1)} == '&') { + if ($len > 0 && $string[$len-1] == '&') { $string = substr_replace($string, '&-', $len - 1, 1); } $len--; @@ -548,6 +544,19 @@ class Utils { return (bool) preg_match('#([a-zA-Z0-9_\-])+(\.([a-zA-Z0-9_\-])+)*@((\[(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5])))\.(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5])))\.(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5])))\.(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5]))\]))|((([a-zA-Z0-9])+(([\-])+([a-zA-Z0-9])+)*\.)+([a-zA-Z])+(([\-])+([a-zA-Z0-9])+)*)|localhost)#', $email); } + /** + * Checks for valid empty group of email + * e.g.: undisclosed-recipients:; + * + * @param string $email + * + * @access public + * @return boolean + */ + static public function CheckEmailEmptyGroup($email) { + return (bool) preg_match('/.*:;/', $email); + } + /** * Checks if a string is base64 encoded * @@ -606,6 +615,45 @@ class Utils { return $unpackedQuery; } + /** + * Encode a string or a stream into a base64 string + * + * @param string or stream $input + * + * @access public + * @return string + */ + public static function EncodeBase64($input) { + if (is_resource($input)) { + ZLog::Write(LOGLEVEL_DEBUG, "Utils::EncodeBase64(): is_resource"); + if (defined('BUG68532FIXED') && BUG68532FIXED === true) { + ZLog::Write(LOGLEVEL_DEBUG, "Utils::EncodeBase64(): BUG68532FIXED"); + $stream = $input; + } elseif ( ($meta = stream_get_meta_data($input)) && $meta['stream_type'] === 'STDIO') { + ZLog::Write(LOGLEVEL_DEBUG, "Utils::EncodeBase64(): STDIO"); + $stream = $input; + } else { + ZLog::Write(LOGLEVEL_DEBUG, "Utils::EncodeBase64(): else"); + //because of bug #68532, we can't work with memory stream, + //so we copy input to a tmpfile + $stream = tmpfile(); + stream_copy_to_stream($input, $stream); + fclose($input); + rewind($stream); + } + $base64filter = stream_filter_append($stream, 'convert.base64-encode'); + $base64 = stream_get_contents($stream); + stream_filter_remove($base64filter); + fclose($stream); + return $base64; + } elseif (is_string($input)) { + ZLog::Write(LOGLEVEL_DEBUG, "Utils::EncodeBase64(): is_string"); + return base64_encode($input); + } else { + throw new Exception("unsupported type : ".gettype($input)); + } + } + /** * Returns a command string for a given command code. * @@ -636,12 +684,8 @@ class Utils { case ZPush::COMMAND_RESOLVERECIPIENTS: return 'ResolveRecipients'; case ZPush::COMMAND_VALIDATECERT: return 'ValidateCert'; - // Deprecated commands + // Deprecated commands (AS >= 14) case ZPush::COMMAND_GETHIERARCHY: return 'GetHierarchy'; - case ZPush::COMMAND_CREATECOLLECTION: return 'CreateCollection'; - case ZPush::COMMAND_DELETECOLLECTION: return 'DeleteCollection'; - case ZPush::COMMAND_MOVECOLLECTION: return 'MoveCollection'; - case ZPush::COMMAND_NOTIFY: return 'Notify'; // Webservice commands case ZPush::COMMAND_WEBSERVICE_DEVICE: return 'WebserviceDevice'; @@ -680,12 +724,8 @@ class Utils { case 'ResolveRecipients': return ZPush::COMMAND_RESOLVERECIPIENTS; case 'ValidateCert': return ZPush::COMMAND_VALIDATECERT; - // Deprecated commands + // Deprecated commands (AS >= 14) case 'GetHierarchy': return ZPush::COMMAND_GETHIERARCHY; - case 'CreateCollection': return ZPush::COMMAND_CREATECOLLECTION; - case 'DeleteCollection': return ZPush::COMMAND_DELETECOLLECTION; - case 'MoveCollection': return ZPush::COMMAND_MOVECOLLECTION; - case 'Notify': return ZPush::COMMAND_NOTIFY; // Webservice commands case 'WebserviceDevice': return ZPush::COMMAND_WEBSERVICE_DEVICE; @@ -722,6 +762,22 @@ class Utils { return @strftime("%d/%m/%Y %H:%M:%S", $timestamp); } + /** + * Returns a formatted string output from an optional timestamp with microseconds. + * If no timestamp is sent, NOW is used. + * + * @param float $timestamp + * + * @access public + * @return string + */ + public static function GetFormattedMicroTime($timestamp = false) { + if(!$timestamp) + $timestamp = microtime(true); + + $t = explode('.',number_format($timestamp,6,'.',''),2); + return strftime("%Y-%m-%dT%H:%M:%S", $t[0]).'.'.$t[1]; + } /** * Get charset name from a codepage @@ -987,43 +1043,27 @@ class Utils { if (preg_match('/\/[.[:word:]]+\/\w+\/(\w+)\/([\w\/]+)/', $timezone, $matches)) { return $matches[1] . "/" . $matches[2]; } - return trim($timezone, '"'); + return TimezoneUtil::getMSTZnameFromTZName(trim($timezone, '"')); } -} + /** + * Safely write data to disk, using an unique tmp file (concurrent write), + * and using rename for atomicity + * + * If you use safe_put_contents, you can safely use file_get_contents + * (you will always read a fully written file) + * + * @param string $filename + * @param string $data + * @return boolean|int + */ + public static function safe_put_contents($filename, $data) { + //put the 'tmp' as a prefix (and not suffix) so all glob call will not see temp files + $tmp = dirname($filename) . DIRECTORY_SEPARATOR . 'tmp-' . getmypid() . '-' . basename($filename); + if (($res = file_put_contents($tmp, $data)) !== false) + if (rename($tmp, $filename) !== true) + $res = false; - -// TODO Win1252/UTF8 functions are deprecated and will be removed sometime -//if the ICS backend is loaded in CombinedBackend and Zarafa > 7 -//STORE_SUPPORTS_UNICODE is true and the convertion will not be done -//for other backends. -function utf8_to_windows1252($string, $option = "", $force_convert = false) { - //if the store supports unicode return the string without converting it - if (!$force_convert && defined('STORE_SUPPORTS_UNICODE') && STORE_SUPPORTS_UNICODE == true) return $string; - - if (function_exists("iconv")){ - return @iconv("UTF-8", "Windows-1252" . $option, $string); - }else{ - return utf8_decode($string); // no euro support here + return $res; } -} - -function windows1252_to_utf8($string, $option = "", $force_convert = false) { - //if the store supports unicode return the string without converting it - if (!$force_convert && defined('STORE_SUPPORTS_UNICODE') && STORE_SUPPORTS_UNICODE == true) return $string; - - if (function_exists("iconv")){ - return @iconv("Windows-1252", "UTF-8" . $option, $string); - }else{ - return utf8_encode($string); // no euro support here - } -} - -function w2u($string) { return windows1252_to_utf8($string); } -function u2w($string) { return utf8_to_windows1252($string); } - -function w2ui($string) { return windows1252_to_utf8($string, "//TRANSLIT"); } -function u2wi($string) { return utf8_to_windows1252($string, "//TRANSLIT"); } - - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/lib/utils/zpushadmin.php b/sources/lib/utils/zpushadmin.php index 17ba954..e78ad2b 100644 --- a/sources/lib/utils/zpushadmin.php +++ b/sources/lib/utils/zpushadmin.php @@ -439,8 +439,7 @@ class ZPushAdmin { * @access public */ static public function ClearLoopDetectionData($user = false, $devid = false) { - $loopdetection = new LoopDetection(); - return $loopdetection->ClearData($user, $devid); + return ZPush::GetLoopDetection()->ClearData($user, $devid); } /** @@ -453,8 +452,49 @@ class ZPushAdmin { * @access public */ static public function GetLoopDetectionData($user, $devid) { - $loopdetection = new LoopDetection(); - return $loopdetection->GetCachedData($user, $devid); + return ZPush::GetLoopDetection()->GetCachedData($user, $devid); + } + + /** + * Remove users that doesn't belong to this devicedata state + * + * @return boolean + * @access public + */ + public static function FixStatesWrongDevicedata() { + $statesfixed = 0; + $statesok = 0; + $usersremoved = 0; + $usersok = 0; + $devices = ZPush::GetStateMachine()->GetAllDevices(false); + foreach ($devices as $devid) { + $changed = false; + $devicedata = ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA); + if (!($devicedata instanceof StateObject && isset($devicedata->devices) && is_array($devicedata->devices))) + continue; + + $asdevices = $devicedata->devices; + foreach ($asdevices as $asuser => $asdev) { + $asdevid = $asdev->GetDeviceId(); + if (strcasecmp($asdevid, $devid) !== 0) { + $usersremoved++; + $changed = true; + unset($asdevices[$asuser]); + ZPush::GetStateMachine()->UnLinkUserDevice($asuser, $devid); + ZLog::Write(LOGLEVEL_WARN, sprintf("Removed from %s devicedata state user '%s' (devid = '%s')", $devid, $asuser, $asdevid)); + } else { + $usersok++; + } + } + if ($changed) { + $statesfixed++; + $devicedata->devices = $asdevices; + ZPush::GetStateMachine()->SetState($devicedata, $devid, IStateMachine::DEVICEDATA); + } else { + $statesok++; + } + } + return array($statesfixed, $statesok, $usersremoved, $usersok); } /** @@ -532,26 +572,46 @@ class ZPushAdmin { /** * Fixes states of available device data to the user linking * - * @return int + * @return array * @access public */ - static public function FixStatesDeviceToUserLinking() { - $seen = 0; - $fixed = 0; - $devices = ZPush::GetStateMachine()->GetAllDevices(false); - ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::FixStatesDeviceToUserLinking(): found %d devices", count($devices))); - - foreach ($devices as $devid) { - $users = self::ListUsers($devid); - foreach ($users as $username) { - $seen++; - ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::FixStatesDeviceToUserLinking(): linking user '%s' to device '%d'", $username, $devid)); - - if (ZPush::GetStateMachine()->LinkUserDevice($username, $devid)) - $fixed++; + public static function FixStatesDeviceToUserLinking() { + //users to devices mapping + $usersdevs = ZPush::GetStateMachine()->GetAllUserDevice(); + $devsusers = array(); + foreach ($usersdevs as $user => $devs) { + foreach (array_keys($devs) as $dev) { + if (empty($devsusers[$dev])) + $devsusers[$dev] = array(); + $devsusers[$dev][] = $user; } } - return array($seen, $fixed); + unset($usersdevs); + + $linked = 0; + $unlinked = 0; + //devices to users mapping + $statedevices = ZPush::GetStateMachine()->GetAllDevices(false); + $alldevices = array_merge($statedevices, array_keys($devsusers)); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::FixStatesDeviceToUserLinking(): found %d devices (%d|%d)", count($alldevices), count($statedevices), count($devsusers))); + + foreach ($alldevices as $devid) { + $stateusers = self::ListUsers($devid); + $mapusers = isset($devsusers[$devid]) ? $devsusers[$devid] : array(); + $links = array_diff($stateusers, $mapusers); + foreach ($links as $user) { + ZLog::Write(LOGLEVEL_INFO, sprintf("ZPushAdmin::FixStatesDeviceToUserLinking(): linking user '%s' to device '%s'", $user, $devid)); + ZPush::GetStateMachine()->LinkUserDevice($user, $devid); + $linked++; + } + $unlinks = array_diff($stateusers, $mapusers); + foreach ($unlinks as $user) { + ZLog::Write(LOGLEVEL_INFO, sprintf("ZPushAdmin::FixStatesDeviceToUserLinking(): unlinking user '%s' to device '%s'", $user, $devid)); + ZPush::GetStateMachine()->UnLinkUserDevice($user, $devid); + $unlinked++; + } + } + return array($unlinked, $linked); } /** @@ -573,8 +633,13 @@ class ZPushAdmin { $devicedata = ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA); $knownUuids = array(); + // == self::ListUsers (no need to GetState 2 times) + if ($devicedata instanceof StateObject && isset($devicedata->devices) && is_array($devicedata->devices)) + $usernames = array_keys($devicedata->devices); + else + $usernames = array(); // get all known UUIDs for this device - foreach (self::ListUsers($devid) as $username) { + foreach ($usernames as $username) { $device = new ASDevice($devid, ASDevice::UNDEFINED, $username, ASDevice::UNDEFINED); $device->SetData($devicedata, false); @@ -617,6 +682,40 @@ class ZPushAdmin { return array($processed, $deleted); } -} + /** + * Maps a username for a specific backend to another username. + * + * @param string $user The username + * @param string $backend Name of the backend to map + * @param string $targetUsername The username to actually use for this backend + * + * @return boolean + */ + static public function AddUsernameMapping($user, $backend, $targetUsername) { + if (!ZPush::GetStateMachine()->MapUsername($user, $backend, $targetUsername)) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::AddUsernameMapping(): unable to add mapping for user %s and backend %s", $user, $backend)); + return false; + } -?> \ No newline at end of file + ZLog::Write(LOGLEVEL_INFO, sprintf("ZPushAdmin::AddUsernameMapping(): successfully mapped user %s and backend %s to username %s", $user, $backend, $targetUsername)); + return true; + } + + /** + * Unmaps a username for a specific backend. + * + * @param string $user The username + * @param string $backend Name of the backend to unmap + * + * @return boolean + */ + static public function RemoveUsernameMapping($user, $backend) { + if (!ZPush::GetStateMachine()->UnmapUsername($user, $backend)) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::RemoveUsernameMapping(): unable to remove mapping for user %s and backend %s", $user, $backend)); + return false; + } + + ZLog::Write(LOGLEVEL_INFO, sprintf("ZPushAdmin::RemoveUsernameMapping(): successfully unmapped user %s and backend %s", $user, $backend)); + return true; + } +} diff --git a/sources/lib/wbxml/wbxmldecoder.php b/sources/lib/wbxml/wbxmldecoder.php index ea8a703..5c22148 100644 --- a/sources/lib/wbxml/wbxmldecoder.php +++ b/sources/lib/wbxml/wbxmldecoder.php @@ -44,20 +44,11 @@ class WBXMLDecoder extends WBXMLDefs { private $in; - - private $version; - private $publicid; - private $publicstringid; - private $charsetid; - private $stringtable; - + private $inLog; private $tagcp = 0; - private $attrcp = 0; - private $ungetbuffer; - + private $log = false; private $logStack = array(); - private $inputBuffer = ""; private $isWBXML = true; @@ -65,30 +56,36 @@ class WBXMLDecoder extends WBXMLDefs { /** * WBXML Decode Constructor + * We only handle ActiveSync WBXML, which is a subset of WBXML * * @param stream $input the incoming data stream * * @access public */ public function WBXMLDecoder($input) { - // make sure WBXML_DEBUG is defined. It should be at this point - if (!defined('WBXML_DEBUG')) define('WBXML_DEBUG', false); + $this->log = defined('WBXML_DEBUG') && WBXML_DEBUG; $this->in = $input; + $this->inLog = StringStreamWrapper::Open(""); - $this->readVersion(); - if (isset($this->version) && $this->version != self::VERSION) { + $version = $this->getByte(); + if($version != self::VERSION) { + $this->inputBuffer .= chr($version); $this->isWBXML = false; return; } - $this->publicid = $this->getMBUInt(); - if($this->publicid == 0) { - $this->publicstringid = $this->getMBUInt(); - } + $publicid = $this->getMBUInt(); + if($publicid !== 1) + throw new WBXMLException("Wrong publicid : ".$publicid); - $this->charsetid = $this->getMBUInt(); - $this->stringtable = $this->getStringTable(); + $charsetid = $this->getMBUInt(); + if ($charsetid !== 106) + throw new WBXMLException("Wrong charset : ".$charsetid); + + $stringtablesize = $this->getMBUInt(); + if ($stringtablesize !== 0) + throw new WBXMLException("Wrong string table size : ".$stringtablesize); } /** @@ -223,11 +220,7 @@ class WBXMLDecoder extends WBXMLDefs { * @return string */ public function GetPlainInputStream() { - $plain = $this->inputBuffer; - while($data = fread($this->in, 4096)) - $plain .= $data; - - return $plain; + return $this->inputBuffer . stream_get_contents($this->in); } /** @@ -241,6 +234,19 @@ class WBXMLDecoder extends WBXMLDefs { } + /** + * Returns the WBXML data read from the stream + * + * @access public + * @return string - base64 encoded wbxml + */ + public function getWBXMLLog() { + $out = ""; + if ($this->inLog) { + $out = base64_encode(stream_get_contents($this->inLog, -1,0)); + } + return $out; + } /**---------------------------------------------------------------------------------------------------------- * Private WBXMLDecoder stuff @@ -261,7 +267,8 @@ class WBXMLDecoder extends WBXMLDefs { } $el = $this->_getToken(); - $this->logToken($el); + if($this->log) + $this->logToken($el); return $el; } @@ -275,9 +282,6 @@ class WBXMLDecoder extends WBXMLDefs { * @return */ private function logToken($el) { - if(!WBXML_DEBUG) - return; - $spaces = str_repeat(" ", count($this->logStack)); switch($el[EN_TYPE]) { @@ -310,216 +314,60 @@ class WBXMLDecoder extends WBXMLDefs { $element = array(); while(1) { - $byte = $this->getByte(); - - if(!isset($byte)) + $byte = fread($this->in, 1); + if($byte === "" || $byte === false) break; + $byte = ord($byte); switch($byte) { - case WBXML_SWITCH_PAGE: + case self::WBXML_SWITCH_PAGE: $this->tagcp = $this->getByte(); - continue; + break; - case WBXML_END: + case self::WBXML_END: $element[EN_TYPE] = EN_TYPE_ENDTAG; return $element; - case WBXML_ENTITY: - $entity = $this->getMBUInt(); - $element[EN_TYPE] = EN_TYPE_CONTENT; - $element[EN_CONTENT] = $this->entityToCharset($entity); - return $element; - - case WBXML_STR_I: + case self::WBXML_STR_I: $element[EN_TYPE] = EN_TYPE_CONTENT; $element[EN_CONTENT] = $this->getTermStr(); return $element; - case WBXML_LITERAL: - $element[EN_TYPE] = EN_TYPE_STARTTAG; - $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt()); - $element[EN_FLAGS] = 0; - return $element; - - case WBXML_EXT_I_0: - case WBXML_EXT_I_1: - case WBXML_EXT_I_2: - $this->getTermStr(); - // Ignore extensions - continue; - - case WBXML_PI: - // Ignore PI - $this->getAttributes(); - continue; - - case WBXML_LITERAL_C: - $element[EN_TYPE] = EN_TYPE_STARTTAG; - $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt()); - $element[EN_FLAGS] = EN_FLAGS_CONTENT; - return $element; - - case WBXML_EXT_T_0: - case WBXML_EXT_T_1: - case WBXML_EXT_T_2: - $this->getMBUInt(); - // Ingore extensions; - continue; - - case WBXML_STR_T: - $element[EN_TYPE] = EN_TYPE_CONTENT; - $element[EN_CONTENT] = $this->getStringTableEntry($this->getMBUInt()); - return $element; - - case WBXML_LITERAL_A: - $element[EN_TYPE] = EN_TYPE_STARTTAG; - $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt()); - $element[EN_ATTRIBUTES] = $this->getAttributes(); - $element[EN_FLAGS] = EN_FLAGS_ATTRIBUTES; - return $element; - case WBXML_EXT_0: - case WBXML_EXT_1: - case WBXML_EXT_2: - continue; - - case WBXML_OPAQUE: + case self::WBXML_OPAQUE: $length = $this->getMBUInt(); $element[EN_TYPE] = EN_TYPE_CONTENT; $element[EN_CONTENT] = $this->getOpaque($length); return $element; - case WBXML_LITERAL_AC: - $element[EN_TYPE] = EN_TYPE_STARTTAG; - $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt()); - $element[EN_ATTRIBUTES] = $this->getAttributes(); - $element[EN_FLAGS] = EN_FLAGS_ATTRIBUTES | EN_FLAGS_CONTENT; - return $element; + case self::WBXML_ENTITY: + case self::WBXML_LITERAL: + case self::WBXML_EXT_I_0: + case self::WBXML_EXT_I_1: + case self::WBXML_EXT_I_2: + case self::WBXML_PI: + case self::WBXML_LITERAL_C: + case self::WBXML_EXT_T_0: + case self::WBXML_EXT_T_1: + case self::WBXML_EXT_T_2: + case self::WBXML_STR_T: + case self::WBXML_LITERAL_A: + case self::WBXML_EXT_0: + case self::WBXML_EXT_1: + case self::WBXML_EXT_2: + case self::WBXML_LITERAL_AC: + throw new WBXMLException("Invalid token :".$byte); default: + if($byte & self::WBXML_WITH_ATTRIBUTES) + throw new WBXMLException("Attributes are not allowed :".$byte); $element[EN_TYPE] = EN_TYPE_STARTTAG; $element[EN_TAG] = $this->getMapping($this->tagcp, $byte & 0x3f); - $element[EN_FLAGS] = ($byte & 0x80 ? EN_FLAGS_ATTRIBUTES : 0) | ($byte & 0x40 ? EN_FLAGS_CONTENT : 0); - if($byte & 0x80) - $element[EN_ATTRIBUTES] = $this->getAttributes(); + $element[EN_FLAGS] = ($byte & self::WBXML_WITH_CONTENT ? EN_FLAGS_CONTENT : 0); return $element; } } } - /** - * Gets attributes - * - * @access private - * @return - */ - private function getAttributes() { - $attributes = array(); - $attr = ""; - - while(1) { - $byte = $this->getByte(); - - if(count($byte) == 0) - break; - - switch($byte) { - case WBXML_SWITCH_PAGE: - $this->attrcp = $this->getByte(); - break; - - case WBXML_END: - if($attr != "") - $attributes += $this->splitAttribute($attr); - - return $attributes; - - case WBXML_ENTITY: - $entity = $this->getMBUInt(); - $attr .= $this->entityToCharset($entity); - return $attr; /* fmbiete's contribution r1534, ZP-324 */ - - case WBXML_STR_I: - $attr .= $this->getTermStr(); - return $attr; /* fmbiete's contribution r1534, ZP-324 */ - - case WBXML_LITERAL: - if($attr != "") - $attributes += $this->splitAttribute($attr); - - $attr = $this->getStringTableEntry($this->getMBUInt()); - return $attr; /* fmbiete's contribution r1534, ZP-324 */ - - case WBXML_EXT_I_0: - case WBXML_EXT_I_1: - case WBXML_EXT_I_2: - $this->getTermStr(); - continue; - - case WBXML_PI: - case WBXML_LITERAL_C: - // Invalid - return false; - - case WBXML_EXT_T_0: - case WBXML_EXT_T_1: - case WBXML_EXT_T_2: - $this->getMBUInt(); - continue; - - case WBXML_STR_T: - $attr .= $this->getStringTableEntry($this->getMBUInt()); - return $attr; /* fmbiete's contribution r1534, ZP-324 */ - - case WBXML_LITERAL_A: - return false; - - case WBXML_EXT_0: - case WBXML_EXT_1: - case WBXML_EXT_2: - continue; - - case WBXML_OPAQUE: - $length = $this->getMBUInt(); - $attr .= $this->getOpaque($length); - return $attr; /* fmbiete's contribution r1534, ZP-324 */ - - case WBXML_LITERAL_AC: - return false; - - default: - if($byte < 128) { - if($attr != "") { - $attributes += $this->splitAttribute($attr); - $attr = ""; - } - } - $attr .= $this->getMapping($this->attrcp, $byte); - break; - } - } - } - - /** - * Splits an attribute - * - * @param string $attr attribute to be splitted - * - * @access private - * @return array - */ - private function splitAttribute($attr) { - $attributes = array(); - - $pos = strpos($attr,chr(61)); // equals sign - - if($pos) - $attributes[substr($attr, 0, $pos)] = substr($attr, $pos+1); - else - $attributes[$attr] = null; - - return $attributes; - } - /** * Reads from the stream until getting a string terminator * @@ -529,12 +377,11 @@ class WBXMLDecoder extends WBXMLDefs { private function getTermStr() { $str = ""; while(1) { - $in = $this->getByte(); - - if($in == 0) + $in = fread($this->in, 1); + //this is faster than ord($in) == 0 + if($in === "\0" || $in === false || $in === "") break; - else - $str .= chr($in); + $str .= $in; } return $str; @@ -549,30 +396,12 @@ class WBXMLDecoder extends WBXMLDefs { * @return string */ private function getOpaque($len) { - // TODO check if it's possible to do it other way - // fread stops reading because the following condition is true (from php.net): - // if the stream is read buffered and it does not represent a plain file, - // at most one read of up to a number of bytes equal to the chunk size - // (usually 8192) is made; depending on the previously buffered data, - // the size of the returned data may be larger than the chunk size. - - // using only return fread it will return only a part of stream if chunk is smaller - // than $len. Read from stream in a loop until the $len is reached. - $d = ""; - $l = 0; - while (1) { - $l = (($len - strlen($d)) > 8192) ? 8192 : ($len - strlen($d)); - if ($l > 0) { - $data = fread($this->in, $l); - - // Stream ends prematurely on instable connections and big mails - if ($data === false || feof($this->in)) - throw new HTTPReturnCodeException(sprintf("WBXMLDecoder->getOpaque() connection unavailable while trying to read %d bytes from stream. Aborting after %d bytes read.", $len, strlen($d)), HTTP_CODE_500, null, LOGLEVEL_WARN); - else - $d .= $data; - } - if (strlen($d) >= $len) break; - } + $d = stream_get_contents($this->in, $len); + if ($d === false) + throw new HTTPReturnCodeException("WBXMLDecoder->getOpaque(): stream_get_contents === false", HTTP_CODE_500, null, LOGLEVEL_WARN); + $l = strlen($d); + if ($l !== $len) + throw new HTTPReturnCodeException("WBXMLDecoder->getOpaque(): only $l byte read instead of $len", HTTP_CODE_500, null, LOGLEVEL_WARN); return $d; } @@ -584,8 +413,10 @@ class WBXMLDecoder extends WBXMLDefs { */ private function getByte() { $ch = fread($this->in, 1); - if(strlen($ch) > 0) + if (strlen($ch) > 0) { + fwrite($this->inLog, $ch); return ord($ch); + } else return; } @@ -613,22 +444,6 @@ class WBXMLDecoder extends WBXMLDefs { return $uint; } - /** - * Reads string table from the input stream - * - * @access private - * @return int - */ - private function getStringTable() { - $stringtable = ""; - - $length = $this->getMBUInt(); - if($length > 0) - $stringtable = fread($this->in, $length); - - return $stringtable; - } - /** * Returns the mapping for a specified codepage and id * @@ -648,21 +463,4 @@ class WBXMLDecoder extends WBXMLDefs { return $this->dtd["codes"][$cp][$id]; } } - - /** - * Reads one byte from the input stream - * - * @access private - * @return void - */ - private function readVersion() { - $ch = $this->getByte(); - - if($ch != NULL) { - $this->inputBuffer .= chr($ch); - $this->version = $ch; - } - } } - -?> \ No newline at end of file diff --git a/sources/lib/wbxml/wbxmldefs.php b/sources/lib/wbxml/wbxmldefs.php index dd616b1..c3a80e0 100644 --- a/sources/lib/wbxml/wbxmldefs.php +++ b/sources/lib/wbxml/wbxmldefs.php @@ -41,42 +41,32 @@ * Consult LICENSE file for details ************************************************/ - -define('WBXML_SWITCH_PAGE', 0x00); -define('WBXML_END', 0x01); -define('WBXML_ENTITY', 0x02); -define('WBXML_STR_I', 0x03); -define('WBXML_LITERAL', 0x04); -define('WBXML_EXT_I_0', 0x40); -define('WBXML_EXT_I_1', 0x41); -define('WBXML_EXT_I_2', 0x42); -define('WBXML_PI', 0x43); -define('WBXML_LITERAL_C', 0x44); -define('WBXML_EXT_T_0', 0x80); -define('WBXML_EXT_T_1', 0x81); -define('WBXML_EXT_T_2', 0x82); -define('WBXML_STR_T', 0x83); -define('WBXML_LITERAL_A', 0x84); -define('WBXML_EXT_0', 0xC0); -define('WBXML_EXT_1', 0xC1); -define('WBXML_EXT_2', 0xC2); -define('WBXML_OPAQUE', 0xC3); -define('WBXML_LITERAL_AC', 0xC4); - -define('EN_TYPE', 1); -define('EN_TAG', 2); -define('EN_CONTENT', 3); -define('EN_FLAGS', 4); -define('EN_ATTRIBUTES', 5); - -define('EN_TYPE_STARTTAG', 1); -define('EN_TYPE_ENDTAG', 2); -define('EN_TYPE_CONTENT', 3); - -define('EN_FLAGS_CONTENT', 1); -define('EN_FLAGS_ATTRIBUTES', 2); - class WBXMLDefs { + + const WBXML_SWITCH_PAGE = 0x00; + const WBXML_END = 0x01; + const WBXML_ENTITY = 0x02; //not used in ActiveSync + const WBXML_STR_I = 0x03; + const WBXML_LITERAL = 0x04; //not used in ActiveSync + const WBXML_EXT_I_0 = 0x40; //not used in ActiveSync + const WBXML_EXT_I_1 = 0x41; //not used in ActiveSync + const WBXML_EXT_I_2 = 0x42; //not used in ActiveSync + const WBXML_PI = 0x43; //not used in ActiveSync + const WBXML_LITERAL_C = 0x44; //not used in ActiveSync + const WBXML_EXT_T_0 = 0x80; //not used in ActiveSync + const WBXML_EXT_T_1 = 0x81; //not used in ActiveSync + const WBXML_EXT_T_2 = 0x82; //not used in ActiveSync + const WBXML_STR_T = 0x83; //not used in ActiveSync + const WBXML_LITERAL_A = 0x84; //not used in ActiveSync + const WBXML_EXT_0 = 0xC0; //not used in ActiveSync + const WBXML_EXT_1 = 0xC1; //not used in ActiveSync + const WBXML_EXT_2 = 0xC2; //not used in ActiveSync + const WBXML_OPAQUE = 0xC3; + const WBXML_LITERAL_AC = 0xC4; //not used in ActiveSync + + const WBXML_WITH_ATTRIBUTES = 0x80; //not used in ActiveSync + const WBXML_WITH_CONTENT = 0x40; + /** * The WBXML DTDs */ @@ -765,5 +755,3 @@ class WBXMLDefs { ) ); } - -?> \ No newline at end of file diff --git a/sources/lib/wbxml/wbxmlencoder.php b/sources/lib/wbxml/wbxmlencoder.php index cf36ec3..07e4ad5 100644 --- a/sources/lib/wbxml/wbxmlencoder.php +++ b/sources/lib/wbxml/wbxmlencoder.php @@ -45,10 +45,11 @@ class WBXMLEncoder extends WBXMLDefs { private $_dtd; private $_out; + private $_outLog; - private $_tagcp; - private $_attrcp; + private $_tagcp = 0; + private $log = false; private $logStack = array(); // We use a delayed output mechanism in which we only output a tag when it actually has something @@ -63,13 +64,10 @@ class WBXMLEncoder extends WBXMLDefs { private $bodyparts; public function WBXMLEncoder($output, $multipart = false) { - // make sure WBXML_DEBUG is defined. It should be at this point - if (!defined('WBXML_DEBUG')) define('WBXML_DEBUG', false); + $this->log = defined('WBXML_DEBUG') && WBXML_DEBUG; $this->_out = $output; - - $this->_tagcp = 0; - $this->_attrcp = 0; + $this->_outLog = StringStreamWrapper::Open(""); // reverse-map the DTD foreach($this->dtd["namespaces"] as $nsid => $nsname) { @@ -124,7 +122,6 @@ class WBXMLEncoder extends WBXMLDefs { if(!$nocontent) { $stackelem['tag'] = $tag; - $stackelem['attributes'] = $attributes; $stackelem['nocontent'] = $nocontent; $stackelem['sent'] = false; @@ -134,7 +131,7 @@ class WBXMLEncoder extends WBXMLDefs { // output of an empty tag, and we therefore output the stack here } else { $this->_outputStack(); - $this->_startTag($tag, $attributes, $nocontent); + $this->_startTag($tag, $nocontent); } } @@ -157,6 +154,8 @@ class WBXMLEncoder extends WBXMLDefs { if(count($this->_stack) == 0 && $this->multipart == true) { $this->processMultipart(); } + if(count($this->_stack) == 0) + $this->writeLog(); } } @@ -198,6 +197,8 @@ class WBXMLEncoder extends WBXMLDefs { * @return void */ public function addBodypartStream($bp) { + if (!is_resource($bp)) + throw new Exception("WBXMLEncoder->addBodypartStream(): trying to add a ".gettype($bp)." instead off a stream"); if ($this->multipart) $this->bodyparts[] = $bp; } @@ -225,7 +226,7 @@ class WBXMLEncoder extends WBXMLDefs { private function _outputStack() { for($i=0;$i_stack);$i++) { if(!$this->_stack[$i]['sent']) { - $this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['attributes'], $this->_stack[$i]['nocontent']); + $this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['nocontent']); $this->_stack[$i]['sent'] = true; } } @@ -237,8 +238,9 @@ class WBXMLEncoder extends WBXMLDefs { * @access private * @return */ - private function _startTag($tag, $attributes = false, $nocontent = false) { - $this->logStartTag($tag, $attributes, $nocontent); + private function _startTag($tag, $nocontent = false) { + if ($this->log) + $this->logStartTag($tag, $nocontent); $mapping = $this->getMapping($tag); @@ -251,17 +253,11 @@ class WBXMLEncoder extends WBXMLDefs { } $code = $mapping["code"]; - if(isset($attributes) && is_array($attributes) && count($attributes) > 0) { - $code |= 0x80; - } if(!isset($nocontent) || !$nocontent) $code |= 0x40; $this->outByte($code); - - if($code & 0x80) - $this->outAttributes($attributes); } /** @@ -271,8 +267,9 @@ class WBXMLEncoder extends WBXMLDefs { * @return */ private function _content($content) { - $this->logContent($content); - $this->outByte(WBXML_STR_I); + if ($this->log) + $this->logContent($content); + $this->outByte(self::WBXML_STR_I); $this->outTermStr($content); } @@ -283,8 +280,9 @@ class WBXMLEncoder extends WBXMLDefs { * @return */ private function _endTag() { - $this->logEndTag(); - $this->outByte(WBXML_END); + if ($this->log) + $this->logEndTag(); + $this->outByte(self::WBXML_END); } /** @@ -297,6 +295,7 @@ class WBXMLEncoder extends WBXMLDefs { */ private function outByte($byte) { fwrite($this->_out, chr($byte)); + fwrite($this->_outLog, chr($byte)); } /** @@ -331,20 +330,8 @@ class WBXMLEncoder extends WBXMLDefs { private function outTermStr($content) { fwrite($this->_out, $content); fwrite($this->_out, chr(0)); - } - - /** - * Output attributes - * We don't actually support this, because to do so, we would have - * to build a string table before sending the data (but we can't - * because we're streaming), so we'll just send an END, which just - * terminates the attribute list with 0 attributes. - * - * @access private - * @return - */ - private function outAttributes() { - $this->outByte(WBXML_END); + fwrite($this->_outLog, $content); + fwrite($this->_outLog, chr(0)); } /** @@ -356,7 +343,7 @@ class WBXMLEncoder extends WBXMLDefs { * @return */ private function outSwitchPage($page) { - $this->outByte(WBXML_SWITCH_PAGE); + $this->outByte(self::WBXML_SWITCH_PAGE); $this->outByte($page); } @@ -420,16 +407,12 @@ class WBXMLEncoder extends WBXMLDefs { * Logs a StartTag to ZLog * * @param $tag - * @param $attr * @param $nocontent * * @access private * @return */ - private function logStartTag($tag, $attr, $nocontent) { - if(!WBXML_DEBUG) - return; - + private function logStartTag($tag, $nocontent) { $spaces = str_repeat(" ", count($this->logStack)); if($nocontent) ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . " <$tag/>"); @@ -446,9 +429,6 @@ class WBXMLEncoder extends WBXMLDefs { * @return */ private function logEndTag() { - if(!WBXML_DEBUG) - return; - $spaces = str_repeat(" ", count($this->logStack)); $tag = array_pop($this->logStack); ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . ""); @@ -463,9 +443,6 @@ class WBXMLEncoder extends WBXMLDefs { * @return */ private function logContent($content) { - if(!WBXML_DEBUG) - return; - $spaces = str_repeat(" ", count($this->logStack)); ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . $content); } @@ -479,29 +456,58 @@ class WBXMLEncoder extends WBXMLDefs { private function processMultipart() { ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXMLEncoder->processMultipart() with %d parts to be processed", $this->getBodypartsCount())); $len = ob_get_length(); + // #190 - KD 2015-06-08 Replace ob_get_flush with ob_get_clean; because we don't want to disable the buffering $buffer = ob_get_clean(); $nrBodyparts = $this->getBodypartsCount(); $blockstart = (($nrBodyparts + 1) * 2) * 4 + 4; $data = pack("iii", ($nrBodyparts + 1), $blockstart, $len); - ob_start(null, 1048576); - foreach ($this->bodyparts as $bp) { $blockstart = $blockstart + $len; - $len = fstat($bp); - $len = (isset($len['size'])) ? $len['size'] : 0; + if (is_resource($bp)) { + $len = fstat($bp); + $len = (isset($len['size'])) ? $len['size'] : 0; + } elseif (is_string($bp)) { + $len = strlen($bp); + } else { + throw new Exception("bp is a ".gettype($bp)."!?!"); + } $data .= pack("ii", $blockstart, $len); } fwrite($this->_out, $data); fwrite($this->_out, $buffer); + fwrite($this->_outLog, $data); + fwrite($this->_outLog, $buffer); foreach($this->bodyparts as $bp) { - while (!feof($bp)) { - fwrite($this->_out, fread($bp, 4096)); + if (is_resource($bp)) { + stream_copy_to_stream($bp, $this->_out); + stream_copy_to_stream($bp, $this->_outLog); + fclose($bp); + } elseif (is_string($bp)) { + fwrite($this->_out, $bp); + fwrite($this->_outLog, $bp); + } else { + throw new Exception("bp is a ".gettype($bp)."!?!"); } } } -} -?> \ No newline at end of file + /** + * Writes the sent WBXML data to the log if it is not bigger than 512K. + * + * @access private + * @return void + */ + private function writeLog() { + $stat = fstat($this->_outLog); + if ($stat['size'] < 524288) { + $data = base64_encode(stream_get_contents($this->_outLog, -1,0)); + } + else { + $data = "more than 512K of data"; + } + ZLog::Write(LOGLEVEL_WBXML, "WBXML-OUT: ". $data, false); + } +} diff --git a/sources/lib/webservice/webservice.php b/sources/lib/webservice/webservice.php index 36040a5..fc4dd5e 100644 --- a/sources/lib/webservice/webservice.php +++ b/sources/lib/webservice/webservice.php @@ -67,23 +67,20 @@ class Webservice { // the webservice command is handled by its class if ($commandCode == ZPush::COMMAND_WEBSERVICE_DEVICE) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): executing WebserviceDevice service", $commandCode)); - - include_once('webservicedevice.php'); $this->server->setClass("WebserviceDevice"); } - // the webservice command is handled by its class - if ($commandCode == ZPush::COMMAND_WEBSERVICE_USERS) { + // the webservice command is handled by its class + if ($commandCode == ZPush::COMMAND_WEBSERVICE_USERS) { if (!defined("ALLOW_WEBSERVICE_USERS_ACCESS") || ALLOW_WEBSERVICE_USERS_ACCESS !== true) - throw new HTTPReturnCodeException(sprintf("Access to the WebserviceUsers service is disabled in configuration. Enable setting ALLOW_WEBSERVICE_USERS_ACCESS.", Request::GetAuthUser()), 403); + throw new HTTPReturnCodeException("Access to the WebserviceUsers service is disabled in configuration. Enable setting ALLOW_WEBSERVICE_USERS_ACCESS", 403); ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): executing WebserviceUsers service", $commandCode)); - if(ZPush::GetBackend()->Setup("SYSTEM", true) == false) + if(ZPush::GetBackend()->Setup("SYSTEM", true) == false) throw new AuthenticationRequiredException(sprintf("User '%s' has no admin privileges", Request::GetAuthUser())); - - include_once('webserviceusers.php'); - $this->server->setClass("WebserviceUsers"); + + $this->server->setClass("WebserviceUsers"); } $this->server->handle(); @@ -92,4 +89,3 @@ class Webservice { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/webservice/webservicedevice.php b/sources/lib/webservice/webservicedevice.php index 6c4566d..050ec2c 100644 --- a/sources/lib/webservice/webservicedevice.php +++ b/sources/lib/webservice/webservicedevice.php @@ -42,7 +42,6 @@ * * Consult LICENSE file for details ************************************************/ -include ('lib/utils/zpushadmin.php'); class WebserviceDevice { @@ -132,4 +131,3 @@ class WebserviceDevice { return true; } } -?> \ No newline at end of file diff --git a/sources/lib/webservice/webserviceusers.php b/sources/lib/webservice/webserviceusers.php index 1ce0eb6..24d901b 100644 --- a/sources/lib/webservice/webserviceusers.php +++ b/sources/lib/webservice/webserviceusers.php @@ -41,19 +41,18 @@ * * Consult LICENSE file for details ************************************************/ -include ('lib/utils/zpushadmin.php'); class WebserviceUsers { - /** - * Returns a list of all known devices - * - * @access public - * @return array - */ - public function ListDevices() { - return ZPushAdmin::ListDevices(false); - } + /** + * Returns a list of all known devices + * + * @access public + * @return array + */ + public function ListDevices() { + return ZPushAdmin::ListDevices(false); + } /** * Returns a list of all known devices of the users @@ -74,29 +73,28 @@ class WebserviceUsers { return $output; } - /** - * Returns a list of all known devices with users and when they synchronized for the first time - * - * @access public - * @return array - */ - public function ListDevicesDetails() { - $devices = ZPushAdmin::ListDevices(false); - $output = array(); - - ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceUsers::ListLastSync(): found %d devices", count($devices))); - ZPush::GetTopCollector()->AnnounceInformation(sprintf("Retrieved details of %d devices and getting users", count($devices)), true); + /** + * Returns a list of all known devices with users and when they synchronized for the first time + * + * @access public + * @return array + */ + public function ListDevicesDetails() { + $devices = ZPushAdmin::ListDevices(false); + $output = array(); + + ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceUsers::ListLastSync(): found %d devices", count($devices))); + ZPush::GetTopCollector()->AnnounceInformation(sprintf("Retrieved details of %d devices and getting users", count($devices)), true); foreach ($devices as $deviceId) { - $output[$deviceId] = array(); + $output[$deviceId] = array(); $users = ZPushAdmin::ListUsers($deviceId); - foreach ($users as $user) { + foreach ($users as $user) { $output[$deviceId][$user] = ZPushAdmin::GetDeviceDetails($deviceId, $user); } } - - - return $output; + + + return $output; } } -?> \ No newline at end of file diff --git a/sources/sql/mysql.sql b/sources/sql/mysql.sql index 84904d4..36c9766 100644 --- a/sources/sql/mysql.sql +++ b/sources/sql/mysql.sql @@ -2,7 +2,7 @@ create table zpush_settings (key_name varchar(50) not null, key_value varchar(50 create table zpush_users (username varchar(50) not null, device_id varchar(50) not null, created_at datetime not null, updated_at datetime not null, primary key (username, device_id)); -create table zpush_states (id_state integer auto_increment, device_id varchar(50) not null, uuid varchar(50), state_type varchar(50), counter integer, state_data mediumtext not null, +create table zpush_states (id_state integer auto_increment, device_id varchar(50) not null, uuid varchar(50), state_type varchar(50), counter integer, state_data mediumblob, created_at datetime not null, updated_at datetime not null, primary key (id_state)); create unique index idx_zpush_states_unique on zpush_states (device_id, uuid, state_type, counter); @@ -16,4 +16,13 @@ alter table zpush_states engine=InnoDB row_format=compressed key_block_size=16; create table zpush_preauth_users (id integer auto_increment, username varchar(50) not null, device_id varchar(50) not null, authorized boolean not null, created_at datetime not null, updated_at datetime not null, primary key (id)); -create unique index index_zpush_preauth_users_on_username_and_device_id on zpush_preauth_users (username, device_id); \ No newline at end of file +create unique index index_zpush_preauth_users_on_username_and_device_id on zpush_preauth_users (username, device_id); + +create table zpush_combined_usermap ( + username varchar(50) not null, + backend varchar(32) not null, + mappedname varchar(200) not null, + created_at datetime not null, + updated_at datetime not null, + primary key (username, backend) +); diff --git a/sources/testing/samples/meeting_request.txt b/sources/testing/samples/meeting_request.txt index d8df4ba..302c189 100644 --- a/sources/testing/samples/meeting_request.txt +++ b/sources/testing/samples/meeting_request.txt @@ -19,8 +19,10 @@ END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT ORGANIZER;CN=Pablo Marmol:MAILTO:pablo.marmol@zpush.org -ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=pedro.pica - piedra@zpush.org:MAILTO:pedro.picapiedra@zpush.org +ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE;CN=Name1 Sur + name2:MAILTO:user1@zpush.org +ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Name1 + Surname2:MAILTO:user2@zpush.org DESCRIPTION;LANGUAGE=es-ES:Texto de la segunda cita\n\n SUMMARY;LANGUAGE=es-ES:Segunda cita DTSTART;TZID=Romance Standard Time:20140519T090000 diff --git a/sources/testing/samples/messages/m0019.txt b/sources/testing/samples/messages/m0019.txt new file mode 100644 index 0000000..013b125 --- /dev/null +++ b/sources/testing/samples/messages/m0019.txt @@ -0,0 +1,3 @@ +Date: Sat, 28 Feb 2015 22:32:35 +0100 +MIME-Version: 1.0 +Subject: =?windows-1252?Q?Te=E9st?= diff --git a/sources/testing/testing-bug68532fixed.php b/sources/testing/testing-bug68532fixed.php new file mode 100644 index 0000000..d0acf71 --- /dev/null +++ b/sources/testing/testing-bug68532fixed.php @@ -0,0 +1,22 @@ +CheckConnection()); + // Show options supported by server $options = $caldav->DoOptionsRequest(); print_r($options); @@ -31,7 +29,7 @@ print_r($options); $calendars = $caldav->FindCalendars(); print_r($calendars); -$path = $caldav_path . "personal" . "/"; +$path = $caldav_path . CALDAV_PERSONAL . "/"; $val = $caldav->GetCalendarDetails($path); print_r($val); @@ -48,5 +46,3 @@ sleep(60); $results = $caldav->GetSync($path, false, CALDAV_SUPPORTS_SYNC); print_r($results); - -?> \ No newline at end of file diff --git a/sources/testing/testing-caldav_search.php b/sources/testing/testing-caldav_search.php new file mode 100644 index 0000000..2a0b0b0 --- /dev/null +++ b/sources/testing/testing-caldav_search.php @@ -0,0 +1,39 @@ +CheckConnection()); + +$path = $caldav_path . CALDAV_PERSONAL . "/"; + +$filter =<< + + + + 040000008200E00074C5B7101A82E00800000000B72BEB1EF3CCD00100000000000000001000000067299FC1A8990B4EB88510710DB15426 + + + + +EOFFILTER; +$val = $caldav->DoCalendarQuery($filter, $path); +print_r($val); diff --git a/sources/testing/testing-carddav.php b/sources/testing/testing-carddav.php index 40e3df7..0840961 100644 --- a/sources/testing/testing-carddav.php +++ b/sources/testing/testing-carddav.php @@ -3,7 +3,7 @@ // Test CardDAV server // This code will do an addressbook discover, an initial sync and will get a vcard. -include_once('include/z_carddav.php'); +require_once 'vendor/autoload.php'; define('CARDDAV_PROTOCOL', 'http'); define('CARDDAV_SERVER', 'sogo-demo.inverse.ca'); @@ -60,6 +60,4 @@ echo "-----------\n"; // TODO: set to an existing vcard ID (you will get a list with the do_sync operation $xml = $server->get_xml_vcard('131-52C19B00-7-7A512880'); //var_dump($server->get_debug()); -echo "$xml\n"; - -?> +echo "$xml\n"; \ No newline at end of file diff --git a/sources/testing/testing-forward.php b/sources/testing/testing-forward.php index 9463277..ac645cb 100644 --- a/sources/testing/testing-forward.php +++ b/sources/testing/testing-forward.php @@ -1,10 +1,7 @@ \ No newline at end of file + } \ No newline at end of file diff --git a/sources/testing/testing-imap.php b/sources/testing/testing-imap.php index 4b8be57..c144e9f 100644 --- a/sources/testing/testing-imap.php +++ b/sources/testing/testing-imap.php @@ -5,6 +5,4 @@ $id = null; $mail = @imap_fetchheader($mbox, $id, FT_UID) . @imap_body($mbox, $id, FT_PEEK | FT_UID); printf("MAIL <%s>\n", $mail); -printf("EMPTY <%b>\n", empty($mail)); - -?> +printf("EMPTY <%b>\n", empty($mail)); \ No newline at end of file diff --git a/sources/testing/testing-imap_date.php b/sources/testing/testing-imap_date.php index d7dbf7f..2628439 100644 --- a/sources/testing/testing-imap_date.php +++ b/sources/testing/testing-imap_date.php @@ -48,6 +48,4 @@ function inside_cutoffdate($cutoffdate, $id, $mbox) { } return $is_inside; -} - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/testing/testing-imap_folder_list.php b/sources/testing/testing-imap_folder_list.php new file mode 100644 index 0000000..182562d --- /dev/null +++ b/sources/testing/testing-imap_folder_list.php @@ -0,0 +1,188 @@ +name, strlen($server)); + printf("Evaluating %s\n", $imapid); + + GetFolder($imapid); + + $fhir = explode($val->delimiter, $imapid); + if (count($fhir) > 1) { + if (defined('IMAP_FOLDER_PREFIX') && strlen(IMAP_FOLDER_PREFIX) > 0) { + if (strcasecmp($fhir[0], IMAP_FOLDER_PREFIX) == 0) { +// printf("Removing prefix\n"); + // Discard prefix + array_shift($fhir); + } + } + + if (count($fhir) == 1) { + $box["mod"] = $fhir[0]; + $box["parent"] = "0"; + } + else { +// printf("Subfolder\n"); + getModAndParentNames($fhir, $box["mod"], $imapparent); + if ($imapparent === null) { + $box["parent"] = "0"; + } + else { + $box["parent"] = $imapparent; + } + } + } + else { + $box["mod"] = $imapid; + $box["parent"] = "0"; + } + $folders[] = $box; + } + +// print_r($folders); +} +else { + printf("imap_list failed: %s\n", imap_last_error()); +} + +imap_close($mbox); + + +function getModAndParentNames($fhir, &$displayname, &$parent) { + // if mod is already set add the previous part to it as it might be a folder which has delimiter in its name + $displayname = (isset($displayname) && strlen($displayname) > 0) ? $displayname = array_pop($fhir) . getServerDelimiter() . $displayname : array_pop($fhir); + $parent = implode(getServerDelimiter(), $fhir); + + if (count($fhir) == 1 || checkIfIMAPFolder($parent)) { + return; + } + //recursion magic + getModAndParentNames($fhir, $displayname, $parent); +} + +function getServerDelimiter() { + $list = @imap_getmailboxes($mbox, $server, "*"); + if (is_array($list) && count($list) > 0) { + // get the delimiter from the first folder + $delimiter = $list[0]->delimiter; + } else { + // default + $delimiter = "."; + } + return $delimiter; +} + +function checkIfIMAPFolder($folderName) { + $folder_name = $folderName; + if (defined(IMAP_FOLDER_PREFIX) && strlen(IMAP_FOLDER_PREFIX) > 0) { + // TODO: We don't care about the inbox exception with the prefix, because we won't check inbox + $folder_name = IMAP_FOLDER_PREFIX . getServerDelimiter() . $folder_name; + } + $list_subfolders = @imap_list($mbox, $server, $folder_name); + return is_array($list_subfolders); +} + + +function GetFolder($imapid) { + $folder = array(); + + // explode hierarchy + $fhir = explode(getServerDelimiter(), $imapid); + + if (strcasecmp($imapid, create_name_folder(IMAP_FOLDER_INBOX)) == 0) { + $folder["parentid"] = "0"; + $folder["displayname"] = "Inbox"; + $folder["type"] = "SYNC_FOLDER_TYPE_INBOX"; + } + else if (strcasecmp($imapid, create_name_folder(IMAP_FOLDER_DRAFT)) == 0) { + $folder["parentid"] = "0"; + $folder["displayname"] = "Drafts"; + $folder["type"] = "SYNC_FOLDER_TYPE_DRAFTS"; + } + else if (strcasecmp($imapid, create_name_folder(IMAP_FOLDER_SENT)) == 0) { + $folder["parentid"] = "0"; + $folder["displayname"] = "Sent"; + $folder["type"] = "SYNC_FOLDER_TYPE_SENTMAIL"; + } + else if (strcasecmp($imapid, create_name_folder(IMAP_FOLDER_TRASH)) == 0) { + $folder["parentid"] = "0"; + $folder["displayname"] = "Trash"; + $folder["type"] = "SYNC_FOLDER_TYPE_WASTEBASKET"; + } + else { + if (defined('IMAP_FOLDER_PREFIX') && strlen(IMAP_FOLDER_PREFIX) > 0) { + if (strcasecmp($fhir[0], IMAP_FOLDER_PREFIX) == 0) { + // Discard prefix + array_shift($fhir); + } + else { + printf("GetFolder('%s'): '%s'; using server delimiter '%s', first part '%s' is not equal to the prefix defined '%s'. Something is wrong with your config.\n", $id, $imapid, getServerDelimiter(), $fhir[0], IMAP_FOLDER_PREFIX); + } + } + + if (count($fhir) == 1) { + $folder["displayname"] = $fhir[0]; + $folder["parentid"] = "0"; + } + else { + getModAndParentNames($fhir, $folder["displayname"], $imapparent); + $folder["displayname"] = $folder["displayname"]; + if ($imapparent === null) { + printf("GetFolder('%s'): '%s'; we didn't found a valid parent name for the folder, but we should... contact the developers for further info\n", $id, $imapid); + $folder["parentid"] = "0"; // We put the folder as root folder, so we see it + } + else { + $folder["parentid"] = $imapparent; + } + } + $folder["type"] = "SYNC_FOLDER_TYPE_USER_MAIL"; + } + +// print_r($folder); +} + +function create_name_folder($folder_name) { + $foldername = $folder_name; + // If we have defined a folder prefix, and it's not empty + if (defined('IMAP_FOLDER_PREFIX') && IMAP_FOLDER_PREFIX != "") { + // If inbox uses prefix or we are not evaluating inbox + if (IMAP_FOLDER_PREFIX_IN_INBOX == true || strcasecmp($foldername, IMAP_FOLDER_INBOX) != 0) { + $foldername = IMAP_FOLDER_PREFIX . getServerDelimiter() . $foldername; + } + } + + return $foldername; +} \ No newline at end of file diff --git a/sources/testing/testing-imap_from.php b/sources/testing/testing-imap_from.php index f5d7da8..c146367 100644 --- a/sources/testing/testing-imap_from.php +++ b/sources/testing/testing-imap_from.php @@ -126,10 +126,8 @@ function parseAddr($ad) { return $addr_string; } -include_once('include/z_RFC822.php'); +require_once 'vendor/autoload.php'; $Mail_RFC822 = new Mail_RFC822(); $fromaddr = parseAddr($Mail_RFC822->parseAddressList($encoded_from)); -printf("%s\n", $fromaddr); - -?> \ No newline at end of file +printf("%s\n", $fromaddr); \ No newline at end of file diff --git a/sources/testing/testing-imap_issue_120.php b/sources/testing/testing-imap_issue_120.php new file mode 100644 index 0000000..b161818 --- /dev/null +++ b/sources/testing/testing-imap_issue_120.php @@ -0,0 +1,70 @@ + 0) ? $displayname = array_pop($fhir).$serverdelimiter.$displayname : array_pop($fhir); + $parent = implode($serverdelimiter, $fhir); + + if (count($fhir) == 1 || checkIfIMAPFolder($parent)) { + return; + } + //recursion magic + getModAndParentNames($fhir, $displayname, $parent); +} + +function getModAndParentNamesWithSharedSupport($fhir, &$displayname, &$parent) { + // PUT IN HERE YOUR SHARED FOLDER PREFIXES + $shared_prefix = [ "#Users", "#Public" ]; + + $serverdelimiter = "/"; + + if (!isset($displayname) || strlen($displayname) == 0) { + if (count($fhir) > 1) { + foreach($shared_prefix as $prefix) { + if (strcasecmp($fhir[0], $prefix) == 0) { + printf("Found shared prefix\n"); + // Remove first element, it's the shared prefix + array_shift($fhir); + $displayname = "SHARED " . $fhir[count($fhir) - 1]; + $parent = implode($serverdelimiter, $fhir); + return; + } + } + } + } + + printf("displayname is: %s\n", $displayname); + // if mod is already set add the previous part to it as it might be a folder which has + // delimiter in its name + $displayname = (isset($displayname) && strlen($displayname) > 0) ? $displayname = array_pop($fhir).$serverdelimiter.$displayname : array_pop($fhir); + $parent = implode($serverdelimiter, $fhir); + + printf("displayname after is: %s\n", $displayname); + + if (count($fhir) == 1 || checkIfIMAPFolder($parent)) { + return; + } + //recursion magic + getModAndParentNames($fhir, $displayname, $parent); +} + +function checkIfIMAPFolder($folderName) { + return true; +} \ No newline at end of file diff --git a/sources/testing/testing-imap_list_155.php b/sources/testing/testing-imap_list_155.php new file mode 100644 index 0000000..d45dc4b --- /dev/null +++ b/sources/testing/testing-imap_list_155.php @@ -0,0 +1,18 @@ +name) . "\n"; + } +} else { + echo "imap_list failed: " . imap_last_error() . "\n"; +} + +imap_close($mbox); \ No newline at end of file diff --git a/sources/testing/testing-imap_meeting.php b/sources/testing/testing-imap_meeting.php index 6d3926f..22006ad 100644 --- a/sources/testing/testing-imap_meeting.php +++ b/sources/testing/testing-imap_meeting.php @@ -1,32 +1,44 @@ ParseFrom($body); - -$props = $ical->GetPropertiesByPath('!VTIMEZONE/ATTENDEE'); -if (count($props) == 1) { - if (isset($props[0]->Parameters()["PARTSTAT"])) { - printf("DOES THIS CAUSE ERROR? %s\n", $props[0]->Parameters()["PARTSTAT"]); - } +$props = $ical->GetPropertiesByPath("VEVENT/UID"); +if (count($props) > 0) { + $uid = $props[0]->Value(); + printf("UID: %s\n", $uid); } +else { + printf("NO UID\n"); +} + +$new_attendees = array(); +$props = $ical->GetPropertiesByPath('VEVENT/ATTENDEE'); +for ($i = 0; $i < count($props); $i++) { + printf("Attendee Mailto '%s' Status '%s'\n", str_ireplace("MAILTO:", "", $props[$i]->Value()), $props[$i]->Parameters()["PARTSTAT"]); +} + +$ical->SetCPParameterValue("VEVENT", "ATTENDEE", "RSVP", null); // MODIFICATIONS // METHOD $ical->SetPValue("METHOD", "REPLY"); //ATTENDEE $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", "ACCEPTED"); +$ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", "TENTATIVE", "MAILTO:user2@zpush.org"); + printf("%s\n", $ical->Render()); -include_once('include/mimePart.php'); - -$mail = new Mail_mimepart(); +$mail = new Mail_mimePart(); $headers = array("MIME-version" => "1.0", "From" => $mail->encodeHeader("from", "Pedro Picapiedra ", "UTF-8"), "To" => $mail->encodeHeader("to", "Pablo Marmol ", "UTF-8"), @@ -34,7 +46,7 @@ $headers = array("MIME-version" => "1.0", "Subject" => $mail->encodeHeader("subject", "This is a subject", "UTF-8"), "Content-class" => "urn:content-classes:calendarmessage", "Content-transfer-encoding" => "8BIT"); -$mail = new Mail_mimepart($ical->Render(), array("content_type" => "text/calendar; method=REPLY; charset=UTF-8", "headers" => $headers)); +$mail = new Mail_mimePart($ical->Render(), array("content_type" => "text/calendar; method=REPLY; charset=UTF-8", "headers" => $headers)); $message = ""; $encoded_mail = $mail->encode(); @@ -46,22 +58,9 @@ $message .= "\r\n" . $encoded_mail["body"] . "\r\n"; printf("%s\n", $message); -include_once('lib/utils/utils.php'); -include_once('lib/core/zpushdefs.php'); -include_once('lib/core/zlog.php'); -include_once('lib/utils/timezoneutil.php'); - -define('LOGLEVEL', LOGLEVEL_DEBUG); -define('LOGUSERLEVEL', LOGLEVEL_DEVICEID); - $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID"); if (count($props) > 0) { $tzid = $props[0]->Value(); -// printf("TZID %s\n", $props[0]->Value()); + printf("TZID %s\n", $props[0]->Value()); } -// print_r(TimezoneUtil::GetFullTZFromTZName($tzid)); - - - - -?> \ No newline at end of file +print_r(TimezoneUtil::GetFullTZFromTZName($tzid)); \ No newline at end of file diff --git a/sources/testing/testing-imap_overview.php b/sources/testing/testing-imap_overview.php new file mode 100644 index 0000000..230d2ac --- /dev/null +++ b/sources/testing/testing-imap_overview.php @@ -0,0 +1,35 @@ +date)) { + printf("%s\n", $overview->date); + printf("%s\n", cleanupDate($overview->date)); + } + if (isset($overview->udate)) { + printf("%s\n", $overview->udate); + } +} +imap_close($mbox); + + +function cleanupDate($receiveddate) { + if (is_array($receiveddate)) { + // Header Date could be repeated in the message, we only check the first + $receiveddate = $receiveddate[0]; + } + printf("%s\n", preg_replace("/\(.*\)/", "", $receiveddate)); + printf("%s\n", preg_replace('/\(.*\)/', "", $receiveddate)); + printf("%s\n", preg_replace("/\\(.*\\)/", "", $receiveddate)); + $receiveddate = strtotime(preg_replace("/\(.*\)/", "", $receiveddate)); + if ($receiveddate == false || $receiveddate == -1) { + printf("cleanupDate() : Received date is false. Message might be broken."); + return null; + } + + return $receiveddate; +} \ No newline at end of file diff --git a/sources/testing/testing-imap_smtp.php b/sources/testing/testing-imap_smtp.php index 5a21acc..485a64b 100644 --- a/sources/testing/testing-imap_smtp.php +++ b/sources/testing/testing-imap_smtp.php @@ -1,9 +1,7 @@ 'smtp.zpush.org', 'port' => 25, 'auth' => true, "username" => "fmbiete", "password" => "password_account", "debug" => true, "pipelining" => true); $toaddr = "fmbiete@zpush.org"; -$headers = array('Subject' => 'Testing SMTP', 'From' => 'fmbiete@zpush.org', 'Return-Path' => 'fmbiete@zpush.org', 'To' => 'fmbiete@zpush.org', 'Cc' => 'fmbiete@zpush.org,fmbiete@zpush.net'); +$headers = array('Subject' => 'Testing SMTP', 'From' => 'fmbiete@zpush.org', 'Return-Path' => 'fmbiete@zpush.org', 'To' => 'fmbiete@zpush.org', 'Cc' => 'fmbiete@zpush.org,fmbiete@zpush.net', 'Bcc' => array('fmbiete2@zpush.org', 'fmbiete2@zpush.net', 'fmbiete@zpush.org')); $body = "This is a test"; +if (is_array($toaddr)) { + $recipients = $toaddr; +} +else { + $recipients = array($toaddr); +} + +// Cc and Bcc headers are sent, but we need to make sure that the recipient list contains them +foreach (array("CC", "cc", "Cc", "BCC", "Bcc", "bcc") as $key) { + if (!empty($headers[$key])) { + if (is_array($headers[$key])) { + $recipients = array_merge($recipients, $headers[$key]); + } + else { + $recipients[] = $headers[$key]; + } + } +} + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->sendMessage(): SendingMail with %s", "smtp")); $mail =& Mail::factory("smtp", $imap_smtp_params); -$send = $mail->send($toaddr, $headers, $body); +$send = $mail->send($recipients, $headers, $body); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->sendMessage(): send return value %s", $send)); if ($send !== true) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->SendMail(): The email could not be sent")); -} - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/testing/testing-issue_164.php b/sources/testing/testing-issue_164.php new file mode 100644 index 0000000..24256d5 --- /dev/null +++ b/sources/testing/testing-issue_164.php @@ -0,0 +1,27 @@ +'; + +printf("Encoded from [%s]\n", $encoded_from); + +$mimeDecode = new Mail_mimeDecode(); +$decoded_from = $mimeDecode->_decodeHeader($encoded_from); +printf("Decoded from [%s]\n", $decoded_from); + +$Mail_RFC822 = new Mail_RFC822(); +$parsed = $Mail_RFC822->parseAddressList($decoded_from); + +printf("Empty if wrong email address\n"); +print_r($parsed); \ No newline at end of file diff --git a/sources/testing/testing-mime-mail-parse.php b/sources/testing/testing-mime-mail-parse.php index a614db7..6a8bda4 100644 --- a/sources/testing/testing-mime-mail-parse.php +++ b/sources/testing/testing-mime-mail-parse.php @@ -1,5 +1,7 @@ \n", $text); printf("HTML Body <%s>\n", $html); -} -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/testing/testing-mime-split.php b/sources/testing/testing-mime-split.php index 25a1096..66256e7 100644 --- a/sources/testing/testing-mime-split.php +++ b/sources/testing/testing-mime-split.php @@ -1,6 +1,6 @@ send($recipents,$headers,$body); -} - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/sources/testing/testing-mime.php b/sources/testing/testing-mime.php index ae4a20e..cad341f 100644 --- a/sources/testing/testing-mime.php +++ b/sources/testing/testing-mime.php @@ -5,10 +5,7 @@ // RUN FROM Z-Push-contrib ROOT FOLDER (NOT FROM TESTING FOLDER) -include_once('include/Mail.php'); -include_once('include/mimeDecode.php'); -include_once('include/mimePart.php'); -include_once('include/z_RFC822.php'); +require_once 'vendor/autoload.php'; define('IMAP_MBCONVERT', "UTF-16, UTF-8, ISO-8859-15, ISO-8859-1, Windows-1252"); @@ -179,6 +176,4 @@ unset($mimeHeaders); unset($finalEmail); unset($message); unset($mobj); -unset($mail); - -?> +unset($mail); \ No newline at end of file diff --git a/sources/testing/testing-mimetype.php b/sources/testing/testing-mimetype.php index 8e435fa..cbf13dc 100644 --- a/sources/testing/testing-mimetype.php +++ b/sources/testing/testing-mimetype.php @@ -36,5 +36,3 @@ if (isset($list[$mime_type])) { else { echo "NOT FOUND!\n"; } - -?> diff --git a/sources/testing/testing-preg_split.php b/sources/testing/testing-preg_split.php index 9b02fb0..9251fdc 100644 --- a/sources/testing/testing-preg_split.php +++ b/sources/testing/testing-preg_split.php @@ -8,5 +8,4 @@ function testing($text) { testing('Test A,Test'); testing('Test A , Test'); testing('Test A , Test B, Test C'); -testing('Test A \, Test B'); -?> +testing('Test A \, Test B'); \ No newline at end of file diff --git a/sources/testing/testing-redis.php b/sources/testing/testing-redis.php new file mode 100644 index 0000000..fd55c2c --- /dev/null +++ b/sources/testing/testing-redis.php @@ -0,0 +1,16 @@ +connect(IPC_REDIS_IP, IPC_REDIS_PORT); +printf("Connected? %s\n", $connected); + +$keys = $redis->keys("ZP_TOP|" . "*"); +print_r($keys); +printf("Keys is_array? %d, Count %d\n", is_array($keys), count($keys)); + +$values = $redis->mGet($keys); +print_r($values); +printf("Values is_array? %d, Count %d\n", is_array($values), count($values)); diff --git a/sources/testing/testing-rfc822_199.php b/sources/testing/testing-rfc822_199.php new file mode 100644 index 0000000..6b210fb --- /dev/null +++ b/sources/testing/testing-rfc822_199.php @@ -0,0 +1,15 @@ + (A comment), ted@example.com (Ted Bloggs), Barney;'; +$structure = Mail_RFC822::parseAddressList($address_string, 'example.com', true); +// print_r($structure); + +$address_string = 'undisclosed-recipients:;'; +$structure = Mail_RFC822::parseAddressList($address_string); +print_r($structure); + +$address_string = 'fmbiete@zpush.org'; +$structure = Mail_RFC822::parseAddressList($address_string); +// print_r($structure); diff --git a/sources/testing/testing-ternary.php b/sources/testing/testing-ternary.php index f616e22..fcc04b7 100644 --- a/sources/testing/testing-ternary.php +++ b/sources/testing/testing-ternary.php @@ -25,5 +25,4 @@ if ($nullset === null) { } else { test_function($nullset); -} -?> +} \ No newline at end of file diff --git a/sources/tools/migrate-2.0.x-2.1.0.php b/sources/tools/migrate-2.0.x-2.1.0.php old mode 100755 new mode 100644 index e9c7606..6eaa140 --- a/sources/tools/migrate-2.0.x-2.1.0.php +++ b/sources/tools/migrate-2.0.x-2.1.0.php @@ -52,8 +52,8 @@ define('ZPUSH_BASE_PATH', "../src"); * MAIN */ try { - if (!isset($_SERVER["TERM"]) || !isset($_SERVER["LOGNAME"])) - die("This script should not be called in a browser."); + if (php_sapi_name() != "cli") + die(sprintf("This script should not be called in a browser. Called from: %s", php_sapi_name())); if (!defined('ZPUSH_BASE_PATH') || !file_exists(ZPUSH_BASE_PATH . "/config.php")) die("ZPUSH_BASE_PATH not set correctly or no config.php file found\n"); @@ -213,5 +213,3 @@ class StateMigrator20xto210 { return true; } } - -?> \ No newline at end of file diff --git a/sources/tools/printwbxml.php b/sources/tools/printwbxml.php new file mode 100644 index 0000000..f430070 --- /dev/null +++ b/sources/tools/printwbxml.php @@ -0,0 +1,84 @@ +. + * + * Consult LICENSE file for details + ************************************************/ + +if (count($argv) < 2) { + die("\tUsage: printwbmxl.php WBXML-INPUT-HERE\n\n"); +} +$wbxml64 = $argv[1]; + +// include the stuff we need +include_once('../../src/lib/utils/stringstreamwrapper.php'); +include_once('../../src/lib/wbxml/wbxmldefs.php'); +include_once('../../src/lib/wbxml/wbxmldecoder.php'); +include_once('../../src/lib/wbxml/wbxmlencoder.php'); + +// minimal definitions & log to stdout overwrite +define('WBXML_DEBUG', true); +define("LOGLEVEL_WBXML", "wbxml"); +define("LOGLEVEL_DEBUG", "debug"); +class ZLog { + static public function Write($level, $msg, $truncate = false) { + // we only care about the wbxml + if ($level == "wbxml") { + if (substr($msg,0,1) == "I") { + echo substr($msg,1) . "\n"; + } + else { + echo $msg . "\n"; + } + } + } +} + +// setup +$wxbml = StringStreamWrapper::Open($wbxml64); +$base64filter = stream_filter_append($wxbml, 'convert.base64-decode'); +$decoder = new WBXMLDecoder($wxbml); +if (! $decoder->IsWBXML()) { + die("input is not WBXML as base64\n\n"); +} + +echo "\n"; +// read everything and log it +$decoder->readRemainingData(); +echo "\n"; diff --git a/sources/vendor/autoload.php b/sources/vendor/autoload.php new file mode 100644 index 0000000..c1e7b20 --- /dev/null +++ b/sources/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0 class loader + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + + private $classMapAuthoritative = false; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-0 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/sources/vendor/composer/autoload_classmap.php b/sources/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..67716a6 --- /dev/null +++ b/sources/vendor/composer/autoload_classmap.php @@ -0,0 +1,155 @@ + $baseDir . '/lib/core/asdevice.php', + 'Auth_SASL' => $baseDir . '/include/Auth/SASL.php', + 'Auth_SASL_Anonymous' => $baseDir . '/include/Auth/SASL/Anonymous.php', + 'Auth_SASL_Common' => $baseDir . '/include/Auth/SASL/Common.php', + 'Auth_SASL_CramMD5' => $baseDir . '/include/Auth/SASL/CramMD5.php', + 'Auth_SASL_DigestMD5' => $baseDir . '/include/Auth/SASL/DigestMD5.php', + 'Auth_SASL_External' => $baseDir . '/include/Auth/SASL/External.php', + 'Auth_SASL_Login' => $baseDir . '/include/Auth/SASL/Login.php', + 'Auth_SASL_Plain' => $baseDir . '/include/Auth/SASL/Plain.php', + 'Auth_SASL_SCRAM' => $baseDir . '/include/Auth/SASL/SCRAM.php', + 'AuthenticationRequiredException' => $baseDir . '/lib/exceptions/authenticationrequiredexception.php', + 'Backend' => $baseDir . '/lib/default/backend.php', + 'BackendDiff' => $baseDir . '/lib/default/diffbackend/diffbackend.php', + 'BodyPreference' => $baseDir . '/lib/core/bodypreference.php', + 'CalDAVClient' => $baseDir . '/include/z_caldav.php', + 'CalendarInfo' => $baseDir . '/include/z_caldav.php', + 'ChangesMemoryWrapper' => $baseDir . '/lib/core/changesmemorywrapper.php', + 'ContentParameters' => $baseDir . '/lib/core/contentparameters.php', + 'DeviceManager' => $baseDir . '/lib/core/devicemanager.php', + 'DiffState' => $baseDir . '/lib/default/diffbackend/diffstate.php', + 'ExportChangesDiff' => $baseDir . '/lib/default/diffbackend/exportchangesdiff.php', + 'FatalException' => $baseDir . '/lib/exceptions/fatalexception.php', + 'FatalMisconfigurationException' => $baseDir . '/lib/exceptions/fatalmisconfigurationexception.php', + 'FatalNotImplementedException' => $baseDir . '/lib/exceptions/fatalnotimplementedexception.php', + 'FileStateMachine' => $baseDir . '/lib/default/filestatemachine.php', + 'FolderChange' => $baseDir . '/lib/request/folderchange.php', + 'FolderSync' => $baseDir . '/lib/request/foldersync.php', + 'GetAttachment' => $baseDir . '/lib/request/getattachment.php', + 'GetHierarchy' => $baseDir . '/lib/request/gethierarchy.php', + 'GetItemEstimate' => $baseDir . '/lib/request/getitemestimate.php', + 'HTTPReturnCodeException' => $baseDir . '/lib/exceptions/httpreturncodeexception.php', + 'HierarchyCache' => $baseDir . '/lib/core/hierarchycache.php', + 'IBackend' => $baseDir . '/lib/interface/ibackend.php', + 'IChanges' => $baseDir . '/lib/interface/ichanges.php', + 'IExportChanges' => $baseDir . '/lib/interface/iexportchanges.php', + 'IImportChanges' => $baseDir . '/lib/interface/iimportchanges.php', + 'ILoopDetection' => $baseDir . '/lib/interface/iloopdetection.php', + 'IPingTracking' => $baseDir . '/lib/interface/ipingtracking.php', + 'ISearchProvider' => $baseDir . '/lib/interface/isearchprovider.php', + 'IStateMachine' => $baseDir . '/lib/interface/istatemachine.php', + 'ITopCollector' => $baseDir . '/lib/interface/itopcollector.php', + 'ImportChangesDiff' => $baseDir . '/lib/default/diffbackend/importchangesdiff.php', + 'ImportChangesStream' => $baseDir . '/lib/core/streamimporter.php', + 'InterProcessData' => $baseDir . '/lib/ipc/shm/interprocessdata.php', + 'InterProcessRedis' => $baseDir . '/lib/ipc/redis/InterProcessRedis.php', + 'InterProcessStorage' => $baseDir . '/lib/ipc/interprocessstorage.php', + 'ItemOperations' => $baseDir . '/lib/request/itemoperations.php', + 'LoopDetection' => $baseDir . '/lib/ipc/shm/loopdetection.php', + 'LoopDetectionRedis' => $baseDir . '/lib/ipc/redis/LoopDetectionRedis.php', + 'Mail' => $baseDir . '/include/Mail.php', + 'Mail_RFC822' => $baseDir . '/include/z_RFC822.php', + 'Mail_mail' => $baseDir . '/include/Mail/mail.php', + 'Mail_mimeDecode' => $baseDir . '/include/mimeDecode.php', + 'Mail_mimePart' => $baseDir . '/include/mimePart.php', + 'Mail_sendmail' => $baseDir . '/include/Mail/sendmail.php', + 'Mail_smtp' => $baseDir . '/include/Mail/smtp.php', + 'MeetingResponse' => $baseDir . '/lib/request/meetingresponse.php', + 'MoveItems' => $baseDir . '/lib/request/moveitems.php', + 'Net_SMTP' => $baseDir . '/include/Net/SMTP.php', + 'Net_Socket' => $baseDir . '/include/Net/Socket.php', + 'NoHierarchyCacheAvailableException' => $baseDir . '/lib/exceptions/nohierarchycacheavailableexception.php', + 'NoPostRequestException' => $baseDir . '/lib/exceptions/nopostrequestexception.php', + 'NotImplementedException' => $baseDir . '/lib/exceptions/notimplementedexception.php', + 'Notify' => $baseDir . '/lib/request/notify.php', + 'Ping' => $baseDir . '/lib/request/ping.php', + 'PingTracking' => $baseDir . '/lib/ipc/shm/pingtracking.php', + 'PingTrackingRedis' => $baseDir . '/lib/ipc/redis/PingTrackingRedis.php', + 'Provisioning' => $baseDir . '/lib/request/provisioning.php', + 'ProvisioningRequiredException' => $baseDir . '/lib/exceptions/provisioningrequiredexception.php', + 'Request' => $baseDir . '/lib/request/request.php', + 'RequestProcessor' => $baseDir . '/lib/request/requestprocessor.php', + 'ResolveRecipients' => $baseDir . '/lib/request/resolverecipients.php', + 'Search' => $baseDir . '/lib/request/search.php', + 'SearchProvider' => $baseDir . '/lib/default/searchprovider.php', + 'SendMail' => $baseDir . '/lib/request/sendmail.php', + 'Settings' => $baseDir . '/lib/request/settings.php', + 'SimpleMutex' => $baseDir . '/lib/default/simplemutex.php', + 'SqlStateMachine' => $baseDir . '/lib/default/sqlstatemachine.php', + 'StateInvalidException' => $baseDir . '/lib/exceptions/stateinvalidexception.php', + 'StateManager' => $baseDir . '/lib/core/statemanager.php', + 'StateNotFoundException' => $baseDir . '/lib/exceptions/statenotfoundexception.php', + 'StateNotYetAvailableException' => $baseDir . '/lib/exceptions/statenotyetavailableexception.php', + 'StateObject' => $baseDir . '/lib/core/stateobject.php', + 'StatusException' => $baseDir . '/lib/exceptions/statusexception.php', + 'Streamer' => $baseDir . '/lib/core/streamer.php', + 'StringStreamWrapper' => $baseDir . '/lib/utils/stringstreamwrapper.php', + 'Sync' => $baseDir . '/lib/request/sync.php', + 'SyncAppointment' => $baseDir . '/lib/syncobjects/syncappointment.php', + 'SyncAppointmentException' => $baseDir . '/lib/syncobjects/syncappointmentexception.php', + 'SyncAttachment' => $baseDir . '/lib/syncobjects/syncattachment.php', + 'SyncAttendee' => $baseDir . '/lib/syncobjects/syncattendee.php', + 'SyncBaseAttachment' => $baseDir . '/lib/syncobjects/syncbaseattachment.php', + 'SyncBaseBody' => $baseDir . '/lib/syncobjects/syncbasebody.php', + 'SyncCollections' => $baseDir . '/lib/core/synccollections.php', + 'SyncContact' => $baseDir . '/lib/syncobjects/synccontact.php', + 'SyncDeviceInformation' => $baseDir . '/lib/syncobjects/syncdeviceinformation.php', + 'SyncDevicePassword' => $baseDir . '/lib/syncobjects/syncdevicepassword.php', + 'SyncFolder' => $baseDir . '/lib/syncobjects/syncfolder.php', + 'SyncItemOperationsAttachment' => $baseDir . '/lib/syncobjects/syncitemoperationsattachment.php', + 'SyncMail' => $baseDir . '/lib/syncobjects/syncmail.php', + 'SyncMailFlags' => $baseDir . '/lib/syncobjects/syncmailflags.php', + 'SyncMeetingRequest' => $baseDir . '/lib/syncobjects/syncmeetingrequest.php', + 'SyncMeetingRequestRecurrence' => $baseDir . '/lib/syncobjects/syncmeetingrequestrecurrence.php', + 'SyncNote' => $baseDir . '/lib/syncobjects/syncnote.php', + 'SyncOOF' => $baseDir . '/lib/syncobjects/syncoof.php', + 'SyncOOFMessage' => $baseDir . '/lib/syncobjects/syncoofmessage.php', + 'SyncObject' => $baseDir . '/lib/syncobjects/syncobject.php', + 'SyncObjectBrokenException' => $baseDir . '/lib/exceptions/syncobjectbrokenexception.php', + 'SyncParameters' => $baseDir . '/lib/core/syncparameters.php', + 'SyncProvisioning' => $baseDir . '/lib/syncobjects/syncprovisioning.php', + 'SyncRRAvailability' => $baseDir . '/lib/syncobjects/syncresolverecipientsavailability.php', + 'SyncRRCertificates' => $baseDir . '/lib/syncobjects/syncresolverecipientscertificates.php', + 'SyncRROptions' => $baseDir . '/lib/syncobjects/syncresolverecipientsoptions.php', + 'SyncRRPicture' => $baseDir . '/lib/syncobjects/syncresolverecipientspicture.php', + 'SyncRecurrence' => $baseDir . '/lib/syncobjects/syncrecurrence.php', + 'SyncResolveRecipient' => $baseDir . '/lib/syncobjects/syncresolverecipient.php', + 'SyncResolveRecipients' => $baseDir . '/lib/syncobjects/syncresolverecipients.php', + 'SyncSendMail' => $baseDir . '/lib/syncobjects/syncsendmail.php', + 'SyncSendMailSource' => $baseDir . '/lib/syncobjects/syncsendmailsource.php', + 'SyncTask' => $baseDir . '/lib/syncobjects/synctask.php', + 'SyncTaskRecurrence' => $baseDir . '/lib/syncobjects/synctaskrecurrence.php', + 'SyncUserInformation' => $baseDir . '/lib/syncobjects/syncuserinformation.php', + 'SyncValidateCert' => $baseDir . '/lib/syncobjects/syncvalidatecert.php', + 'TimezoneUtil' => $baseDir . '/lib/utils/timezoneutil.php', + 'TopCollector' => $baseDir . '/lib/ipc/shm/topcollector.php', + 'TopCollectorRedis' => $baseDir . '/lib/ipc/redis/TopCollectorRedis.php', + 'Utils' => $baseDir . '/lib/utils/utils.php', + 'ValidateCert' => $baseDir . '/lib/request/validatecert.php', + 'WBXMLDecoder' => $baseDir . '/lib/wbxml/wbxmldecoder.php', + 'WBXMLDefs' => $baseDir . '/lib/wbxml/wbxmldefs.php', + 'WBXMLEncoder' => $baseDir . '/lib/wbxml/wbxmlencoder.php', + 'WBXMLException' => $baseDir . '/lib/exceptions/wbxmlexception.php', + 'Webservice' => $baseDir . '/lib/webservice/webservice.php', + 'WebserviceDevice' => $baseDir . '/lib/webservice/webservicedevice.php', + 'WebserviceUsers' => $baseDir . '/lib/webservice/webserviceusers.php', + 'ZLog' => $baseDir . '/lib/core/zlog.php', + 'ZPush' => $baseDir . '/lib/core/zpush.php', + 'ZPushAdmin' => $baseDir . '/lib/utils/zpushadmin.php', + 'ZPushAutodiscover' => $baseDir . '/autodiscover/autodiscover.php', + 'ZPushException' => $baseDir . '/lib/exceptions/zpushexception.php', + 'ZSyslog' => $baseDir . '/lib/core/zsyslog.php', + 'carddav_backend' => $baseDir . '/include/z_carddav.php', + 'iCalComponent' => $baseDir . '/include/iCalendar.php', + 'iCalProp' => $baseDir . '/include/iCalendar.php', + 'iCalendar' => $baseDir . '/include/iCalendar.php', + 'rtf' => $baseDir . '/include/z_RTF.php', +); diff --git a/sources/vendor/composer/autoload_files.php b/sources/vendor/composer/autoload_files.php new file mode 100644 index 0000000..0017dfe --- /dev/null +++ b/sources/vendor/composer/autoload_files.php @@ -0,0 +1,13 @@ + $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + $includeFiles = require __DIR__ . '/autoload_files.php'; + foreach ($includeFiles as $file) { + composerRequire690cc3270d214f53d3ddd43de3041343($file); + } + + return $loader; + } +} + +function composerRequire690cc3270d214f53d3ddd43de3041343($file) +{ + require $file; +} diff --git a/sources/version.php b/sources/version.php index 9802487..9af2e33 100644 --- a/sources/version.php +++ b/sources/version.php @@ -42,6 +42,4 @@ ************************************************/ -define("ZPUSH_VERSION", "SVN-trunk-r1928"); - -?> +define("ZPUSH_VERSION", "SVN-trunk-r1975"); diff --git a/sources/z-push-admin.php b/sources/z-push-admin.php index fc42374..a677e93 100755 --- a/sources/z-push-admin.php +++ b/sources/z-push-admin.php @@ -44,56 +44,8 @@ * Consult LICENSE file for details ************************************************/ -include('lib/core/zpushdefs.php'); -include('lib/core/zpush.php'); -include('lib/core/stateobject.php'); -include('lib/core/syncparameters.php'); -include('lib/core/bodypreference.php'); -include('lib/core/contentparameters.php'); -include('lib/core/synccollections.php'); -include('lib/core/zlog.php'); -include('lib/core/statemanager.php'); -include('lib/core/streamer.php'); -include('lib/core/asdevice.php'); -include('lib/core/interprocessdata.php'); -include('lib/core/loopdetection.php'); -include('lib/exceptions/exceptions.php'); -include('lib/utils/utils.php'); -include('lib/utils/zpushadmin.php'); -include('lib/request/request.php'); -include('lib/request/requestprocessor.php'); -include('lib/interface/ibackend.php'); -include('lib/interface/ichanges.php'); -include('lib/interface/iexportchanges.php'); -include('lib/interface/iimportchanges.php'); -include('lib/interface/isearchprovider.php'); -include('lib/interface/istatemachine.php'); -include('lib/syncobjects/syncobject.php'); -include('lib/syncobjects/syncbasebody.php'); -include('lib/syncobjects/syncbaseattachment.php'); -include('lib/syncobjects/syncmailflags.php'); -include('lib/syncobjects/syncrecurrence.php'); -include('lib/syncobjects/syncappointment.php'); -include('lib/syncobjects/syncappointmentexception.php'); -include('lib/syncobjects/syncattachment.php'); -include('lib/syncobjects/syncattendee.php'); -include('lib/syncobjects/syncmeetingrequestrecurrence.php'); -include('lib/syncobjects/syncmeetingrequest.php'); -include('lib/syncobjects/syncmail.php'); -include('lib/syncobjects/syncnote.php'); -include('lib/syncobjects/synccontact.php'); -include('lib/syncobjects/syncfolder.php'); -include('lib/syncobjects/syncprovisioning.php'); -include('lib/syncobjects/synctaskrecurrence.php'); -include('lib/syncobjects/synctask.php'); -include('lib/syncobjects/syncoofmessage.php'); -include('lib/syncobjects/syncoof.php'); -include('lib/syncobjects/syncuserinformation.php'); -include('lib/syncobjects/syncdeviceinformation.php'); -include('lib/syncobjects/syncdevicepassword.php'); -include('lib/syncobjects/syncitemoperationsattachment.php'); -include('config.php'); -include('version.php'); +require_once 'vendor/autoload.php'; +require_once 'config.php'; /** * //TODO resync of single folders of a users device @@ -106,6 +58,7 @@ include('version.php'); set_include_path(get_include_path() . PATH_SEPARATOR . BASE_PATH_CLI); try { ZPush::CheckConfig(); + ZLog::Initialize(); ZPushAdminCLI::CheckEnv(); ZPushAdminCLI::CheckOptions(); @@ -139,11 +92,20 @@ class ZPushAdminCLI { const COMMAND_SHOWLASTSYNC = 8; const COMMAND_RESYNCFOLDER = 9; const COMMAND_FIXSTATES = 10; + const COMMAND_MAP = 11; + const COMMAND_UNMAP = 12; + + const TYPE_OPTION_EMAIL = "email"; + const TYPE_OPTION_CALENDAR = "calendar"; + const TYPE_OPTION_CONTACT = "contact"; + const TYPE_OPTION_TASK = "task"; + const TYPE_OPTION_NOTE = "note"; static private $command; static private $user = false; static private $device = false; static private $type = false; + static private $backend = false; static private $errormessage; /** @@ -153,8 +115,12 @@ class ZPushAdminCLI { * @access public */ static public function UsageInstructions() { + $types = "'".self::TYPE_OPTION_EMAIL."', '".self::TYPE_OPTION_CALENDAR."', '".self::TYPE_OPTION_CONTACT."', '".self::TYPE_OPTION_TASK."' or '".self::TYPE_OPTION_NOTE."'"; return "Usage:\n\tz-push-admin.php -a ACTION [options]\n\n" . - "Parameters:\n\t-a list/wipe/remove/resync/clearloop\n\t[-u] username\n\t[-d] deviceid\n\n" . + "Parameters:\n\t-a list/wipe/remove/resync/clearloop/fixstates/map/unmap\n" . + "\t[-u] username\n" . + "\t[-d] deviceid\n" . + "\t[-b] backend\n\n" . "Actions:\n" . "\tlist\t\t\t\t Lists all devices and synchronized users\n" . "\tlist -u USER\t\t\t Lists all devices of user USER\n" . @@ -167,13 +133,15 @@ class ZPushAdminCLI { "\tremove -d DEVICE\t\t Removes all state data of all users synchronized on device DEVICE\n" . "\tremove -u USER -d DEVICE\t Removes all related state data of device DEVICE of user USER\n" . "\tresync -u USER -d DEVICE\t Resynchronizes all data of device DEVICE of user USER\n" . - "\tresync -t TYPE \t\t\t Resynchronizes all folders of type 'email', 'calendar', 'contact', 'task' or 'note' for all devices and users.\n" . - "\tresync -t TYPE -u USER \t\t Resynchronizes all folders of type 'email', 'calendar', 'contact', 'task' or 'note' for the user USER.\n" . - "\tresync -t TYPE -u USER -d DEVICE Resynchronizes all folders of type 'email', 'calendar', 'contact', 'task' or 'note' for a specified device and user.\n" . - "\tresync -t FOLDERID -u USER\t Resynchronize the specified folder id only. The USER should be specified.\n" . + "\tresync -t TYPE \t\t\t Resynchronizes all folders of type $types for all devices and users.\n" . + "\tresync -t TYPE -u USER \t\t Resynchronizes all folders of type $types for the user USER.\n" . + "\tresync -t TYPE -u USER -d DEVICE Resynchronizes all folders of type $types for a specified device and user.\n" . + "\tresync -t FOLDERID -u USER\t Resynchronize the specified folder id only. The USER should be specified for better performance.\n" . "\tclearloop\t\t\t Clears system wide loop detection data\n" . "\tclearloop -d DEVICE -u USER\t Clears all loop detection data of a device DEVICE and an optional user USER\n" . "\tfixstates\t\t\t Checks the states for integrity and fixes potential issues\n" . + "\tmap -u USER -b BACKEND -t USER2\t Maps USER for BACKEND to username USER2 (when using 'combined' backend)\n" . + "\tunmap -u USER -b BACKEND\t Removes the mapping for USER and BACKEND (when using 'combined' backend)\n" . "\n"; } @@ -184,8 +152,8 @@ class ZPushAdminCLI { * @access public */ static public function CheckEnv() { - if (!isset($_SERVER["TERM"]) || !isset($_SERVER["LOGNAME"])) - self::$errormessage = "This script should not be called in a browser."; + if (php_sapi_name() != "cli") + self::$errormessage = sprintf("This script should not be called in a browser. Called from: %s", php_sapi_name()); if (!function_exists("getopt")) self::$errormessage = "PHP Function getopt not found. Please check your PHP version and settings."; @@ -201,7 +169,7 @@ class ZPushAdminCLI { if (self::$errormessage) return; - $options = getopt("u:d:a:t:"); + $options = getopt("u:d:a:t:b:"); // get 'user' if (isset($options['u']) && !empty($options['u'])) @@ -228,6 +196,27 @@ class ZPushAdminCLI { elseif (isset($options['type']) && !empty($options['type'])) self::$type = strtolower(trim($options['type'])); + // if type is set, it must be one of known types or a 44 byte long folder id + if (self::$type !== false) { + if (self::$type !== self::TYPE_OPTION_EMAIL && + self::$type !== self::TYPE_OPTION_CALENDAR && + self::$type !== self::TYPE_OPTION_CONTACT && + self::$type !== self::TYPE_OPTION_TASK && + self::$type !== self::TYPE_OPTION_NOTE && + strlen(self::$type) !== 44) { + self::$errormessage = "Wrong 'type'. Possible values are: ". + "'".self::TYPE_OPTION_EMAIL."', '".self::TYPE_OPTION_CALENDAR."', '".self::TYPE_OPTION_CONTACT."', '".self::TYPE_OPTION_TASK."', '".self::TYPE_OPTION_NOTE."' ". + "or a 44 byte long folder id (as hex)."; + return; + } + } + + // get 'backend' + if (isset($options['b']) && !empty($options['b'])) + self::$backend = strtolower(trim($options['b'])); + elseif (isset($options['backend']) && !empty($options['backend'])) + self::$backend = strtolower(trim($options['backend'])); + // get a command for the requested action switch ($action) { // list data @@ -294,6 +283,21 @@ class ZPushAdminCLI { self::$command = self::COMMAND_FIXSTATES; break; + // map users for 'combined' backend + case "map": + if (self::$user === false || self::$backend === false || self::$type === false) + self::$errormessage = "Not possible to map. User, backend and target user must be specified."; + else + self::$command = self::COMMAND_MAP; + break; + + // unmap users for 'combined' backend + case "unmap": + if (self::$user === false || self::$backend === false) + self::$errormessage = "Not possible to unmap. User and backend must be specified."; + else + self::$command = self::COMMAND_UNMAP; + break; default: self::UsageInstructions(); @@ -395,6 +399,13 @@ class ZPushAdminCLI { self::CommandFixStates(); break; + case self::COMMAND_MAP: + self::CommandMap(); + break; + + case self::COMMAND_UNMAP: + self::CommandUnmap(); + break; } echo "\n"; } @@ -442,7 +453,7 @@ class ZPushAdminCLI { echo "\tno devices found\n"; else { echo "All known devices and users and their last synchronization time\n\n"; - echo str_pad("Device id", 36). str_pad("Synchronized user", 30)."Last sync time\n"; + echo str_pad("Device id", 36). str_pad("Synchronized user", 31)."Last sync time\n"; echo "-----------------------------------------------------------------------------------------------------\n"; } @@ -450,7 +461,7 @@ class ZPushAdminCLI { $users = ZPushAdmin::ListUsers($deviceId); foreach ($users as $user) { $device = ZPushAdmin::GetDeviceDetails($deviceId, $user); - echo str_pad($deviceId, 36) . str_pad($user, 30). ($device->GetLastSyncTime() ? strftime("%Y-%m-%d %H:%M", $device->GetLastSyncTime()) : "never") . "\n"; + echo str_pad($deviceId, 36) . str_pad($user, 30) . " " . ($device->GetLastSyncTime() ? strftime("%Y-%m-%d %H:%M", $device->GetLastSyncTime()) : "never") . "\n"; } } } @@ -465,10 +476,18 @@ class ZPushAdminCLI { static public function CommandDeviceUsers() { $users = ZPushAdmin::ListUsers(self::$device); - if (empty($users)) + if (empty($users)) { echo "\tno user data synchronized to device\n"; + } + // if a user is specified, we only want to see the devices of this one + else if (self::$user !== false && !in_array(self::$user, $users)) { + printf("\tuser '%s' not known in device data '%s'\n", self::$user, self::$device); + } foreach ($users as $user) { + if (self::$user !== false && strtolower($user) !== self::$user) { + continue; + } echo "Synchronized by user: ". $user. "\n"; self::printDeviceData(self::$device, $user); } @@ -648,6 +667,12 @@ class ZPushAdminCLI { static private function CommandFixStates() { echo "Validating and fixing states (this can take some time):\n"; + echo "\tChecking devicedata states: "; + if ($stat = ZPushAdmin::FixStatesWrongDevicedata()) + printf("Devices: fixed %d - ok %d Users: removed %d - ok %d\n",$stat[0], $stat[1], $stat[2], $stat[3]); + else + echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n"; + echo "\tChecking username casings: "; if ($stat = ZPushAdmin::FixStatesDifferentUsernameCases()) printf("Processed: %d - Converted: %d - Removed: %d\n", $stat[0], $stat[1], $stat[2]); @@ -657,7 +682,7 @@ class ZPushAdminCLI { // fixes ZP-339 echo "\tChecking available devicedata & user linking: "; if ($stat = ZPushAdmin::FixStatesDeviceToUserLinking()) - printf("Processed: %d - Fixed: %d\n", $stat[0], $stat[1]); + printf("Unlinked: %d - Linked: %d\n", $stat[0], $stat[1]); else echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n"; @@ -668,6 +693,32 @@ class ZPushAdminCLI { echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n"; } + /** + * Maps a username for a specific backend to another username + * + * @return + * @access private + */ + static private function CommandMap() { + if (ZPushAdmin::AddUsernameMapping(self::$user, self::$backend, self::$type)) + printf("Successfully mapped username.\n"); + else + echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n"; + } + + /** + * Deletes the mapping for a username and a specific bakkend + * + * @return + * @access private + */ + static private function CommandUnmap() { + if (ZPushAdmin::RemoveUsernameMapping(self::$user, self::$backend)) + printf("Successfully unmapped username.\n"); + else + echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n"; + } + /** * Prints detailed informations about a device * @@ -839,6 +890,3 @@ class ZPushAdminCLI { } } - - -?> \ No newline at end of file diff --git a/sources/z-push-top.php b/sources/z-push-top.php index 9b71e67..32dafe0 100755 --- a/sources/z-push-top.php +++ b/sources/z-push-top.php @@ -44,17 +44,8 @@ * Consult LICENSE file for details ************************************************/ -include('lib/exceptions/exceptions.php'); -include('lib/core/zpushdefs.php'); -include('lib/core/zpush.php'); -include('lib/core/zlog.php'); -include('lib/core/interprocessdata.php'); -include('lib/core/topcollector.php'); -include('lib/utils/utils.php'); -include('lib/request/request.php'); -include('lib/request/requestprocessor.php'); -include('config.php'); -include('version.php'); +require_once 'vendor/autoload.php'; +require_once 'config.php'; /************************************************ * MAIN @@ -64,6 +55,7 @@ include('version.php'); try { ZPush::CheckConfig(); + ZLog::Initialize(); if (!function_exists("pcntl_signal")) throw new FatalException("Function pcntl_signal() is not available. Please install package 'php5-pcntl' (or similar) on your system."); @@ -148,7 +140,7 @@ class ZPushTop { $this->pingInterval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 12; // get a TopCollector - $this->topCollector = new TopCollector(); + $this->topCollector = ZPush::GetTopCollector(); } /** @@ -178,6 +170,9 @@ class ZPushTop { public function run() { $this->initialize(); + // Non-blocking read from stdin + stream_set_blocking(STDIN, FALSE); + do { $this->currenttime = time(); @@ -322,7 +317,7 @@ class ZPushTop { $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", $this->pushConn, count($this->activeHosts))); $lc++; + $this->scrPrintAt($lc,0, sprintf(" Hosts:\t %d", count($this->activeHosts))); $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++; @@ -451,7 +446,7 @@ class ZPushTop { } $this->scrPrintAt(5,0, $str); - $this->scrPrintAt(4,0,"Action: \033[01m".$this->action . "\033[0m"); + $this->scrPrintAt(4,0,"Action: \033[01m" . $this->action . "\033[0m"); } /** @@ -461,18 +456,21 @@ class ZPushTop { * @return */ private function readLineProcess() { - $ans = explode("^^", `bash -c "read -n 1 -t 1 ANS ; echo \\\$?^^\\\$ANS;"`); + //$ans = explode("^^", shell_exec('bash -c "read -n 1 -t 1 ANS ; echo \\\$?^^\\\$ANS;"')); + sleep(1); + $ans = false; + $ans = fread(STDIN, 1); - if ($ans[0] < 128) { - if (isset($ans[1]) && bin2hex(trim($ans[1])) == "7f") { - $this->action = substr($this->action,0,-1); + if ($ans !== false) { + if (bin2hex($ans) == "7f") { + $this->action = substr($this->action, 0, -1); } - if (isset($ans[1]) && $ans[1] != "" ){ - $this->action .= trim(preg_replace("/[^A-Za-z0-9:]/","",$ans[1])); + if (trim($ans) != "" ){ + $this->action .= trim(preg_replace("/[^A-Za-z0-9:]/", "", $ans)); } - if (bin2hex($ans[0]) == "30" && bin2hex($ans[1]) == "0a") { + if (bin2hex($ans) == "0a") { $cmds = explode(':', $this->action); if ($cmds[0] == "quit" || $cmds[0] == "q" || (isset($cmds[1]) && $cmds[0] == "" && $cmds[1] == "q")) { $this->topCollector->CollectData(true); @@ -498,7 +496,7 @@ class ZPushTop { } else if ($cmds[0] == "option" || $cmds[0] == "o") { if (!isset($cmds[1]) || $cmds[1] == "") { - $this->status = sprintf("Option value needs to be specified. See 'help' or 'h' for instructions", $cmds[1]); + $this->status = "Option value needs to be specified. See 'help' or 'h' for instructions"; $this->statusexpire = $this->currenttime+5; } else if ($cmds[1] == "p" || $cmds[1] == "push" || $cmds[1] == "ping") @@ -707,10 +705,6 @@ class ZPushTop { * @return string */ private function getVersion() { - if (ZPUSH_VERSION == "SVN checkout" && file_exists(REAL_BASE_PATH.".svn/entries")) { - $svn = file(REAL_BASE_PATH.".svn/entries"); - return "SVN " . substr(trim($svn[4]),stripos($svn[4],"z-push")+7) ." r".trim($svn[3]); - } return ZPUSH_VERSION; } @@ -765,5 +759,3 @@ class ZPushTop { } } - -?>