mirror of
https://github.com/YunoHost-Apps/z-push_ynh.git
synced 2024-09-03 18:05:58 +02:00
use Z-Push-contrib
This commit is contained in:
parent
bd0487c071
commit
918ef6c246
170 changed files with 26073 additions and 1003 deletions
112
sources/README.md
Normal file
112
sources/README.md
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
Z-Push-contrib
|
||||||
|
==============
|
||||||
|
|
||||||
|
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:
|
||||||
|
For them to get into the official Z-Push, you must release the code under AGPLv3. Add this text to your commits "Released under the Affero GNU General Public License (AGPL) version 3" before merging.
|
||||||
|
|
||||||
|
If you see some changes here, and you are the author, I will not contrib them, as I have no rights over them.
|
||||||
|
But I will try to reimplement with different code/approach so I can contribute them. When a sustitution is ready I will remove your changes from this repo.
|
||||||
|
If you want to help the community, contribute them yourself.
|
||||||
|
|
||||||
|
|
||||||
|
IMPORTANT 2:
|
||||||
|
All the code is AGPL licensed (or compatible, like the "include" files). So you can get a copy, modify it, use... your only obligation it's to publish your changes somewhere.
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------------------------------
|
||||||
|
Original Z-Push
|
||||||
|
|
||||||
|
URL: http://www.zpush.org
|
||||||
|
|
||||||
|
Z-Push is an implementation of the ActiveSync protocol, which is used 'over-the-air' for multi platform ActiveSync devices, including Windows Mobile, Ericsson and Nokia phones. With Z-Push any groupware can be connected and synced with these devices.
|
||||||
|
|
||||||
|
License: GNU Affero Genaral Public License v3.0 (AGPLv3)
|
||||||
|
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
=============
|
||||||
|
You can find some configuration guidelines in the Wiki https://github.com/fmbiete/Z-Push-contrib/wiki
|
||||||
|
|
||||||
|
Requisites
|
||||||
|
==========
|
||||||
|
- PHP 5.5 (5.3 should also work, 5.4 it's fine, but 5.5 is better)
|
||||||
|
- NGINX or APACHE
|
||||||
|
- PHP-FPM or MOD_PHP
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
|
NGINX, 1.4 at least or you will need to enable chunkin mode (Use google for Apache configuration)
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443;
|
||||||
|
server_name zpush.domain.com;
|
||||||
|
|
||||||
|
ssl on;
|
||||||
|
ssl_certificate /etc/ssl/certs/zpush.pem;
|
||||||
|
ssl_certificate_key /etc/ssl/private/zpush.key;
|
||||||
|
|
||||||
|
root /usr/share/www/z-push-contrib;
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/zpush-error.log;
|
||||||
|
access_log /var/log/nginx/zpush-access.log;
|
||||||
|
|
||||||
|
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 unix:/var/run/php5-fpm.sock;
|
||||||
|
# Z-Push Ping command will be alive for 470s, but be safe
|
||||||
|
fastcgi_read_timeout 630;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PHP-FPM
|
||||||
|
|
||||||
|
max_execution_time=600
|
||||||
|
short_open_tag=On
|
||||||
|
|
||||||
|
And configure enough php-fpm processes, as a rough estimation you will need 1.5 x number users.
|
||||||
|
|
||||||
|
|
||||||
|
Backends
|
||||||
|
========
|
||||||
|
Each backend has a README file, or comments in their config.php file. Look at them to configure correctly. Also you can look here https://github.com/fmbiete/Z-Push-contrib/wiki
|
||||||
|
|
||||||
|
|
||||||
|
StateMachine
|
||||||
|
============
|
||||||
|
You have 2 StateMachine methods.
|
||||||
|
|
||||||
|
- FILE - FileStateMachine : will store state info into files. You will use it in Active-Pasive setups
|
||||||
|
- SQL - SqlStateMachine: will store state info into a database. It uses PHP-PDO, so you will need to install the required packages for your database flavour and create the database. You will use it in Active-Active setups.
|
||||||
|
|
||||||
|
|
||||||
|
User-Device Permissions
|
||||||
|
=======================
|
||||||
|
Disabled by default, when enabled will limit what users and device can sync against your Z-Push installation.
|
||||||
|
It can auto-accept users, users and device until a maximum number of devices is reached.
|
||||||
|
|
||||||
|
If using with FileStateMachine, edit the file STATE_DIR/PreAuthUserDevices to modificate the behaivour. That file is JSON formatted and it's filled each time a new user connect.
|
||||||
|
|
||||||
|
If using with SqlStateMachine, look at the zpush_preauth_users table.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Links
|
||||||
|
=====
|
||||||
|
Microsoft ActiveSync Specification
|
||||||
|
http://msdn.microsoft.com/en-us/library/cc425499%28v=exchg.80%29.aspx
|
208
sources/autodiscover/INSTALL
Normal file
208
sources/autodiscover/INSTALL
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
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/<backend>/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! :)
|
268
sources/autodiscover/autodiscover.php
Normal file
268
sources/autodiscover/autodiscover.php
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
<?php
|
||||||
|
/***********************************************
|
||||||
|
* File : autodiscover.php
|
||||||
|
* Project : Z-Push
|
||||||
|
* Descr : The autodiscover service for Z-Push.
|
||||||
|
*
|
||||||
|
* Created : 14.05.2014
|
||||||
|
*
|
||||||
|
* Copyright 2007 - 2014 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,
|
||||||
|
* as published by the Free Software Foundation with the following additional
|
||||||
|
* term according to sec. 7:
|
||||||
|
*
|
||||||
|
* According to sec. 7 of the GNU Affero General Public License, version 3,
|
||||||
|
* the terms of the AGPL are supplemented with the following terms:
|
||||||
|
*
|
||||||
|
* "Zarafa" is a registered trademark of Zarafa B.V.
|
||||||
|
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
|
||||||
|
* The licensing of the Program under the AGPL does not imply a trademark license.
|
||||||
|
* Therefore any rights, title and interest in our trademarks remain entirely with us.
|
||||||
|
*
|
||||||
|
* However, if you propagate an unmodified version of the Program you are
|
||||||
|
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
|
||||||
|
* Furthermore you may use our trademarks where it is necessary to indicate
|
||||||
|
* the intended purpose of a product or service provided you use it in accordance
|
||||||
|
* with honest practices in industrial or commercial matters.
|
||||||
|
* If you want to propagate modified versions of the Program under the name "Z-Push",
|
||||||
|
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
|
||||||
|
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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');
|
||||||
|
|
||||||
|
class ZPushAutodiscover {
|
||||||
|
const ACCEPTABLERESPONSESCHEMA = 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006';
|
||||||
|
const MAXINPUTSIZE = 8192; // Bytes, the autodiscover request shouldn't exceed that value
|
||||||
|
|
||||||
|
private static $instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static method to start the autodiscover process.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function DoZPushAutodiscover() {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, '-------- Start ZPushAutodiscover');
|
||||||
|
// TODO use filterevilinput?
|
||||||
|
if (stripos($_SERVER["REQUEST_METHOD"], "GET") !== false) {
|
||||||
|
ZLog::Write(LOGLEVEL_WARN, "GET request for autodiscover. Exiting.");
|
||||||
|
if (!headers_sent()) {
|
||||||
|
ZPush::PrintZPushLegal('GET not supported');
|
||||||
|
}
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, '-------- End ZPushAutodiscover');
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (!isset(self::$instance)) {
|
||||||
|
self::$instance = new ZPushAutodiscover();
|
||||||
|
}
|
||||||
|
self::$instance->DoAutodiscover();
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, '-------- End ZPushAutodiscover');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the complete autodiscover.
|
||||||
|
* @access public
|
||||||
|
* @throws AuthenticationRequiredException if login to the backend failed.
|
||||||
|
* @throws ZPushException if the incoming XML is invalid..
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function DoAutodiscover() {
|
||||||
|
if (!defined('REAL_BASE_PATH')) {
|
||||||
|
define('REAL_BASE_PATH', str_replace('autodiscover/', '', BASE_PATH));
|
||||||
|
}
|
||||||
|
set_include_path(get_include_path() . PATH_SEPARATOR . REAL_BASE_PATH);
|
||||||
|
$response = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$incomingXml = $this->getIncomingXml();
|
||||||
|
$backend = ZPush::GetBackend();
|
||||||
|
$username = $this->login($backend, $incomingXml);
|
||||||
|
$userDetails = $backend->GetUserDetails($username);
|
||||||
|
$email = ($this->getAttribFromUserDetails($userDetails, 'emailaddress')) ? $this->getAttribFromUserDetails($userDetails, 'emailaddress') : $incomingXml->Request->EMailAddress;
|
||||||
|
$userFullname = ($this->getAttribFromUserDetails($userDetails, 'fullname')) ? $this->getAttribFromUserDetails($userDetails, 'fullname') : $email;
|
||||||
|
ZLog::Write(LOGLEVEL_WBXML, sprintf("Resolved user's '%s' fullname to '%s'", $username, $userFullname));
|
||||||
|
$response = $this->createResponse($email, $userFullname);
|
||||||
|
setcookie("membername", $username);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (AuthenticationRequiredException $ex) {
|
||||||
|
if (isset($incomingXml)) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover because login failed for user with email '%s'", $incomingXml->Request->EMailAddress));
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
catch (ZPushException $ex) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover because of ZPushException. Error: %s", $ex->getMessage()));
|
||||||
|
if(!headers_sent()) {
|
||||||
|
header('HTTP/1.1 '. $ex->getHTTPCodeString());
|
||||||
|
foreach ($ex->getHTTPHeaders() as $h) {
|
||||||
|
header($h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->sendResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the incoming XML request and parses it to a SimpleXMLElement.
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @throws ZPushException if the XML is invalid.
|
||||||
|
* @throws AuthenticationRequiredException if no login data was sent.
|
||||||
|
*
|
||||||
|
* @return SimpleXMLElement
|
||||||
|
*/
|
||||||
|
private function getIncomingXml() {
|
||||||
|
if ($_SERVER['CONTENT_LENGTH'] > ZPushAutodiscover::MAXINPUTSIZE) {
|
||||||
|
throw new ZPushException('The request input size exceeds 8kb.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) {
|
||||||
|
throw new AuthenticationRequiredException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = @file_get_contents('php://input');
|
||||||
|
$xml = simplexml_load_string($input);
|
||||||
|
|
||||||
|
if (LOGLEVEL >= LOGLEVEL_WBXML) {
|
||||||
|
ZLog::Write(LOGLEVEL_WBXML, sprintf("ZPushAutodiscover->getIncomingXml() incoming XML data:%s%s", PHP_EOL, $xml->asXML()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($xml->Request->EMailAddress)) {
|
||||||
|
throw new FatalException('Invalid input XML: no email address.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Utils::GetLocalPartFromEmail($xml->Request->EMailAddress) != Utils::GetLocalPartFromEmail($_SERVER['PHP_AUTH_USER'])) {
|
||||||
|
ZLog::Write(LOGLEVEL_WARN, sprintf("The local part of the server auth user is different from the local part in the XML request ('%s' != '%s')",
|
||||||
|
Utils::GetLocalPartFromEmail($xml->Request->EMailAddress), Utils::GetLocalPartFromEmail($_SERVER['PHP_AUTH_USER'])));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($xml->Request->AcceptableResponseSchema)) {
|
||||||
|
throw new FatalException('Invalid input XML: no AcceptableResponseSchema.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($xml->Request->AcceptableResponseSchema != ZPushAutodiscover::ACCEPTABLERESPONSESCHEMA) {
|
||||||
|
throw new FatalException('Invalid input XML: not a mobilesync responseschema.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logins using the backend's Logon function.
|
||||||
|
*
|
||||||
|
* @param IBackend $backend
|
||||||
|
* @param String $incomingXml
|
||||||
|
* @access private
|
||||||
|
* @throws AuthenticationRequiredException if no login data was sent.
|
||||||
|
*
|
||||||
|
* @return string $username
|
||||||
|
*/
|
||||||
|
private function login($backend, $incomingXml) {
|
||||||
|
// Determine the login name depending on the configuration: complete email address or
|
||||||
|
// the local part only.
|
||||||
|
if (USE_FULLEMAIL_FOR_LOGIN) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Using the complete email address for login."));
|
||||||
|
$username = $incomingXml->Request->EMailAddress;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Using the username only for login."));
|
||||||
|
$username = Utils::GetLocalPartFromEmail($incomingXml->Request->EMailAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($backend->Logon($username, "", $_SERVER['PHP_AUTH_PW']) == false) {
|
||||||
|
throw new AuthenticationRequiredException("Access denied. Username or password incorrect.");
|
||||||
|
}
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAutodiscover->login() Using '%s' as the username.", $username));
|
||||||
|
return $username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the XML response.
|
||||||
|
*
|
||||||
|
* @param string $email
|
||||||
|
* @param string $userFullname
|
||||||
|
* @access private
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function createResponse($email, $userFullname) {
|
||||||
|
$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->asXML();
|
||||||
|
ZLog::Write(LOGLEVEL_WBXML, sprintf("ZPushAutodiscover->createResponse() XML response:%s%s", PHP_EOL, $response));
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the response to the device.
|
||||||
|
* @param string $response
|
||||||
|
* @access private
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function sendResponse($response) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, "ZPushAutodiscover->sendResponse() sending response...");
|
||||||
|
header("Content-type: text/html");
|
||||||
|
$output = fopen("php://output", "w+");
|
||||||
|
fwrite($output, $response);
|
||||||
|
fclose($output);
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, "ZPushAutodiscover->sendResponse() response sent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an attribute from user details.
|
||||||
|
* @param Array $userDetails
|
||||||
|
* @param String $attrib
|
||||||
|
* @access private
|
||||||
|
*
|
||||||
|
* @return String or false on error.
|
||||||
|
*/
|
||||||
|
private function getAttribFromUserDetails($userDetails, $attrib) {
|
||||||
|
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."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ZPushAutodiscover::DoZPushAutodiscover();
|
||||||
|
?>
|
89
sources/autodiscover/config.php
Normal file
89
sources/autodiscover/config.php
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
/***********************************************
|
||||||
|
* File : config.php
|
||||||
|
* Project : Z-Push
|
||||||
|
* Descr : Autodiscover configuration file
|
||||||
|
*
|
||||||
|
* Created : 30.07.2014
|
||||||
|
*
|
||||||
|
* Copyright 2007 - 2014 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,
|
||||||
|
* as published by the Free Software Foundation with the following additional
|
||||||
|
* term according to sec. 7:
|
||||||
|
*
|
||||||
|
* According to sec. 7 of the GNU Affero General Public License, version 3,
|
||||||
|
* the terms of the AGPL are supplemented with the following terms:
|
||||||
|
*
|
||||||
|
* "Zarafa" is a registered trademark of Zarafa B.V.
|
||||||
|
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
|
||||||
|
* The licensing of the Program under the AGPL does not imply a trademark license.
|
||||||
|
* Therefore any rights, title and interest in our trademarks remain entirely with us.
|
||||||
|
*
|
||||||
|
* However, if you propagate an unmodified version of the Program you are
|
||||||
|
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
|
||||||
|
* Furthermore you may use our trademarks where it is necessary to indicate
|
||||||
|
* the intended purpose of a product or service provided you use it in accordance
|
||||||
|
* with honest practices in industrial or commercial matters.
|
||||||
|
* If you want to propagate modified versions of the Program under the name "Z-Push",
|
||||||
|
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
|
||||||
|
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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', '');
|
||||||
|
?>
|
19
sources/autodiscover/response.xml
Normal file
19
sources/autodiscover/response.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006">
|
||||||
|
<Culture>en:us</Culture>
|
||||||
|
<User>
|
||||||
|
<DisplayName></DisplayName>
|
||||||
|
<EMailAddress></EMailAddress>
|
||||||
|
</User>
|
||||||
|
<Action>
|
||||||
|
<Settings>
|
||||||
|
<Server>
|
||||||
|
<Type>MobileSync</Type>
|
||||||
|
<Url></Url>
|
||||||
|
<Name></Name>
|
||||||
|
</Server>
|
||||||
|
</Settings>
|
||||||
|
</Action>
|
||||||
|
</Response>
|
||||||
|
</Autodiscover>
|
3
sources/backend/caldav/AUTHOR
Normal file
3
sources/backend/caldav/AUTHOR
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
The Author of this backend is dupondje, I could have modified it.
|
||||||
|
You can found the original code here:
|
||||||
|
https://github.com/dupondje/PHP-Push-2
|
5
sources/backend/caldav/REQUIREMENTS
Normal file
5
sources/backend/caldav/REQUIREMENTS
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
REQUIREMENTS:
|
||||||
|
php-curl
|
||||||
|
libawl-php
|
||||||
|
|
||||||
|
CalDAV server (DAViCal, Sabredav, Sogo, Owncloud...)
|
1466
sources/backend/caldav/caldav.php
Normal file
1466
sources/backend/caldav/caldav.php
Normal file
File diff suppressed because it is too large
Load diff
67
sources/backend/caldav/config.php
Normal file
67
sources/backend/caldav/config.php
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
/***********************************************
|
||||||
|
* File : config.php
|
||||||
|
* Project : Z-Push
|
||||||
|
* Descr : CalDAV backend configuration file
|
||||||
|
*
|
||||||
|
* Created : 27.11.2012
|
||||||
|
*
|
||||||
|
* Copyright 2007 - 2013 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,
|
||||||
|
* as published by the Free Software Foundation with the following additional
|
||||||
|
* term according to sec. 7:
|
||||||
|
*
|
||||||
|
* According to sec. 7 of the GNU Affero General Public License, version 3,
|
||||||
|
* the terms of the AGPL are supplemented with the following terms:
|
||||||
|
*
|
||||||
|
* "Zarafa" is a registered trademark of Zarafa B.V.
|
||||||
|
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
|
||||||
|
* The licensing of the Program under the AGPL does not imply a trademark license.
|
||||||
|
* Therefore any rights, title and interest in our trademarks remain entirely with us.
|
||||||
|
*
|
||||||
|
* However, if you propagate an unmodified version of the Program you are
|
||||||
|
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
|
||||||
|
* Furthermore you may use our trademarks where it is necessary to indicate
|
||||||
|
* the intended purpose of a product or service provided you use it in accordance
|
||||||
|
* with honest practices in industrial or commercial matters.
|
||||||
|
* If you want to propagate modified versions of the Program under the name "Z-Push",
|
||||||
|
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
|
||||||
|
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Consult LICENSE file for details
|
||||||
|
************************************************/
|
||||||
|
|
||||||
|
// ************************
|
||||||
|
// BackendCalDAV settings
|
||||||
|
// ************************
|
||||||
|
|
||||||
|
// Server address
|
||||||
|
define('CALDAV_SERVER', 'http://calendar.domain.com');
|
||||||
|
|
||||||
|
// Port
|
||||||
|
define('CALDAV_PORT', '80');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
12
sources/backend/carddav/README
Normal file
12
sources/backend/carddav/README
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
This is a CardDAV backend based in the vcarddir backend.
|
||||||
|
|
||||||
|
It supports DAViCal, Sogo, OwnCloud, SabreDav... and should works with any carddav server. So if it doesn't work with your server, please open a issue.
|
||||||
|
|
||||||
|
It supports ChangesSink method that will detect and send faster changes to your device.
|
||||||
|
|
||||||
|
DAViCal implements the SYNC operation, it's a very fast method to detect changes in your vcards.
|
||||||
|
The others servers don't implement it, so the code will fallback to a slower method (suggest your carddav server developers to implement it!!).
|
||||||
|
|
||||||
|
This is controlled with a flag in the config.php file.
|
||||||
|
|
||||||
|
Also, it can autodetect multiple addressbooks and will present them to the mobile device as an unique addressbook (only iOS supports multiple addressbook).
|
5
sources/backend/carddav/REQUIREMENTS
Normal file
5
sources/backend/carddav/REQUIREMENTS
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
REQUIREMENTS:
|
||||||
|
php-curl
|
||||||
|
php-xsl
|
||||||
|
|
||||||
|
CardDAV server (DAViCal, Sabredav, Sogo, Owncloud...)
|
8
sources/backend/carddav/THANKS
Normal file
8
sources/backend/carddav/THANKS
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
*Drenalina SRL (www.drenalina.com)* sponsored the development of the following features in the BackendCardDAV, any existing bug it's my fault not theirs ;-)
|
||||||
|
Thank you very much for helping to improve it!!
|
||||||
|
|
||||||
|
|
||||||
|
- Autodetecting addressbooks within a DAV principal.
|
||||||
|
- Merging multiple addressbooks so the device will see a unique one. Only iOS based devices support multiple addressbooks, so we will merge them for now.
|
||||||
|
- Selecting default addressbook to store new contacts created from the device.
|
||||||
|
- GAL addressbook and GAL search.
|
1452
sources/backend/carddav/carddav.php
Normal file
1452
sources/backend/carddav/carddav.php
Normal file
File diff suppressed because it is too large
Load diff
109
sources/backend/carddav/config.php
Normal file
109
sources/backend/carddav/config.php
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<?php
|
||||||
|
/***********************************************
|
||||||
|
* File : config.php
|
||||||
|
* Project : Z-Push
|
||||||
|
* Descr : CardDAV backend configuration file
|
||||||
|
*
|
||||||
|
* Created : 16.03.2013
|
||||||
|
*
|
||||||
|
* Copyright 2007 - 2013 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,
|
||||||
|
* as published by the Free Software Foundation with the following additional
|
||||||
|
* term according to sec. 7:
|
||||||
|
*
|
||||||
|
* According to sec. 7 of the GNU Affero General Public License, version 3,
|
||||||
|
* the terms of the AGPL are supplemented with the following terms:
|
||||||
|
*
|
||||||
|
* "Zarafa" is a registered trademark of Zarafa B.V.
|
||||||
|
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
|
||||||
|
* The licensing of the Program under the AGPL does not imply a trademark license.
|
||||||
|
* Therefore any rights, title and interest in our trademarks remain entirely with us.
|
||||||
|
*
|
||||||
|
* However, if you propagate an unmodified version of the Program you are
|
||||||
|
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
|
||||||
|
* Furthermore you may use our trademarks where it is necessary to indicate
|
||||||
|
* the intended purpose of a product or service provided you use it in accordance
|
||||||
|
* with honest practices in industrial or commercial matters.
|
||||||
|
* If you want to propagate modified versions of the Program under the name "Z-Push",
|
||||||
|
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
|
||||||
|
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Consult LICENSE file for details
|
||||||
|
************************************************/
|
||||||
|
|
||||||
|
// ************************
|
||||||
|
// BackendCardDAV settings
|
||||||
|
// ************************
|
||||||
|
|
||||||
|
// Server protocol: http or https
|
||||||
|
define('CARDDAV_PROTOCOL', 'https');
|
||||||
|
|
||||||
|
// Server name
|
||||||
|
define('CARDDAV_SERVER', 'localhost');
|
||||||
|
|
||||||
|
// Server port
|
||||||
|
define('CARDDAV_PORT', '443');
|
||||||
|
|
||||||
|
// Server path to the addressbook, or the principal with the addressbooks
|
||||||
|
// If your user has more than 1 addressbook point it to the principal.
|
||||||
|
// Example: user test@domain.com will have 2 addressbooks
|
||||||
|
// http://localhost/caldav.php/test@domain.com/addresses/personal
|
||||||
|
// http://localhost/caldav.php/test@domain.com/addresses/work
|
||||||
|
// You set the CARDDAV_PATH to '/caldav.php/%u/addresses/' and personal and work will be autodiscovered
|
||||||
|
// %u: replaced with the username
|
||||||
|
// %d: replaced with the domain
|
||||||
|
// Add the trailing /
|
||||||
|
define('CARDDAV_PATH', '/caldav.php/%u/');
|
||||||
|
|
||||||
|
|
||||||
|
// Server path to the default addressbook
|
||||||
|
// Mobile device will create new contacts here. It must be under CARDDAV_PATH
|
||||||
|
// %u: replaced with the username
|
||||||
|
// %d: replaced with the domain
|
||||||
|
// Add the trailing /
|
||||||
|
define('CARDDAV_DEFAULT_PATH', '/caldav.php/%u/addresses/');
|
||||||
|
|
||||||
|
// Server path to the GAL addressbook. This addressbook is readonly and searchable by the user, but it will NOT be synced.
|
||||||
|
// If you don't want GAL, comment it
|
||||||
|
// %u: replaced with the username
|
||||||
|
// %d: replaced with the domain
|
||||||
|
// Add the trailing /
|
||||||
|
define('CARDDAV_GAL_PATH', '/caldav.php/%d/GAL/');
|
||||||
|
|
||||||
|
// Minimal length for the search pattern to do the real search.
|
||||||
|
define('CARDDAV_GAL_MIN_LENGTH', 5);
|
||||||
|
|
||||||
|
// Addressbook display name, the name showed in the mobile device
|
||||||
|
// %u: replaced with the username
|
||||||
|
// %d: replaced with the domain
|
||||||
|
define('CARDDAV_CONTACTS_FOLDER_NAME', '%u Addressbook');
|
||||||
|
|
||||||
|
|
||||||
|
// If the CardDAV server supports the sync-collection operation
|
||||||
|
// DAViCal and SabreDav support it, but Owncloud, SOGo don't
|
||||||
|
// 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: 1 petition for the href of vcards, and 1 petition for each vcard
|
||||||
|
define('CARDDAV_SUPPORTS_SYNC', false);
|
||||||
|
|
||||||
|
|
||||||
|
// If the CardDAV server supports the FN attribute for searches
|
||||||
|
// DAViCal supports it, but SabreDav, Owncloud and SOGo don't
|
||||||
|
// Setting this to true will search by FN. If false will search by sn, givenName and email
|
||||||
|
// It's safe to leave it as false
|
||||||
|
define('CARDDAV_SUPPORTS_FN_SEARCH', false);
|
||||||
|
|
||||||
|
|
||||||
|
// If your carddav server needs to use file extension to recover a vcard.
|
||||||
|
// Davical needs it
|
||||||
|
// SOGo official demo online needs it, but some SOGo installation don't need it, so test it
|
||||||
|
define('CARDDAV_URL_VCARD_EXTENSION', '.vcf');
|
|
@ -57,11 +57,12 @@ require_once("backend/combined/config.php");
|
||||||
require_once("backend/combined/importer.php");
|
require_once("backend/combined/importer.php");
|
||||||
require_once("backend/combined/exporter.php");
|
require_once("backend/combined/exporter.php");
|
||||||
|
|
||||||
class BackendCombined extends Backend {
|
class BackendCombined extends Backend implements ISearchProvider {
|
||||||
public $config;
|
public $config;
|
||||||
public $backends;
|
public $backends;
|
||||||
private $activeBackend;
|
private $activeBackend;
|
||||||
private $activeBackendID;
|
private $activeBackendID;
|
||||||
|
private $numberChangesSink;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor of the combined backend
|
* Constructor of the combined backend
|
||||||
|
@ -72,12 +73,12 @@ class BackendCombined extends Backend {
|
||||||
parent::Backend();
|
parent::Backend();
|
||||||
$this->config = BackendCombinedConfig::GetBackendCombinedConfig();
|
$this->config = BackendCombinedConfig::GetBackendCombinedConfig();
|
||||||
|
|
||||||
foreach ($this->config['backends'] as $i => $b){
|
$backend_values = array_unique(array_values($this->config['folderbackend']));
|
||||||
// load and instatiate backend
|
foreach ($backend_values as $i) {
|
||||||
ZPush::IncludeBackend($b['name']);
|
ZPush::IncludeBackend($this->config['backends'][$i]['name']);
|
||||||
$this->backends[$i] = new $b['name']();
|
$this->backends[$i] = new $this->config['backends'][$i]['name']();
|
||||||
}
|
}
|
||||||
ZLog::Write(LOGLEVEL_INFO, sprintf("Combined %d backends loaded.", count($this->backends)));
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined %d backends loaded.", count($this->backends)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,7 +117,8 @@ class BackendCombined extends Backend {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ZLog::Write(LOGLEVEL_INFO, "Combined->Logon() success");
|
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, "Combined->Logon() success");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +156,7 @@ class BackendCombined extends Backend {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ZLog::Write(LOGLEVEL_INFO, "Combined->Setup() success");
|
ZLog::Write(LOGLEVEL_DEBUG, "Combined->Setup() success");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,6 +276,10 @@ class BackendCombined extends Backend {
|
||||||
*/
|
*/
|
||||||
public function SendMail($sm) {
|
public function SendMail($sm) {
|
||||||
ZLog::Write(LOGLEVEL_DEBUG, "Combined->SendMail()");
|
ZLog::Write(LOGLEVEL_DEBUG, "Combined->SendMail()");
|
||||||
|
// Convert source folderid
|
||||||
|
if (isset($sm->source->folderid)) {
|
||||||
|
$sm->source->folderid = $this->GetBackendFolder($sm->source->folderid);
|
||||||
|
}
|
||||||
foreach ($this->backends as $i => $b){
|
foreach ($this->backends as $i => $b){
|
||||||
if($this->backends[$i]->SendMail($sm) == true){
|
if($this->backends[$i]->SendMail($sm) == true){
|
||||||
return true;
|
return true;
|
||||||
|
@ -358,11 +364,125 @@ class BackendCombined extends Backend {
|
||||||
* @return string id of the created/updated calendar obj
|
* @return string id of the created/updated calendar obj
|
||||||
* @throws StatusException
|
* @throws StatusException
|
||||||
*/
|
*/
|
||||||
public function MeetingResponse($requestid, $folderid, $error) {
|
public function MeetingResponse($requestid, $folderid, $response) {
|
||||||
$backend = $this->GetBackend($folderid);
|
$backend = $this->GetBackend($folderid);
|
||||||
if($backend === false)
|
if($backend === false)
|
||||||
return false;
|
return false;
|
||||||
return $backend->MeetingResponse($requestid, $this->GetBackendFolder($folderid), $error);
|
return $backend->MeetingResponse($requestid, $this->GetBackendFolder($folderid), $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
$backend = $this->GetBackend($folderid);
|
||||||
|
if($backend === false)
|
||||||
|
return false;
|
||||||
|
return $backend->EmptyFolder($this->GetBackendFolder($folderid), $includeSubfolders);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the backend has a ChangesSink.
|
||||||
|
* A sink is an active notification mechanism which does not need polling.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function HasChangesSink() {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->HasChangesSink()"));
|
||||||
|
|
||||||
|
$this->numberChangesSink = 0;
|
||||||
|
|
||||||
|
foreach ($this->backends as $i => $b) {
|
||||||
|
if ($this->backends[$i]->HasChangesSink()) {
|
||||||
|
$this->numberChangesSink++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->HasChangesSink - Number ChangesSink found: %d", $this->numberChangesSink));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The folder should be considered by the sink.
|
||||||
|
* Folders which were not initialized should not result in a notification
|
||||||
|
* of IBacken->ChangesSink().
|
||||||
|
*
|
||||||
|
* @param string $folderid
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean false if there is any problem with that folder
|
||||||
|
*/
|
||||||
|
public function ChangesSinkInitialize($folderid) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->ChangesSinkInitialize('%s')", $folderid));
|
||||||
|
|
||||||
|
$backend = $this->GetBackend($folderid);
|
||||||
|
if($backend === false) {
|
||||||
|
// if not backend is found we return true, we don't want this to never cause an error
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($backend->HasChangesSink()) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->ChangesSinkInitialize('%s') is supported, initializing", $folderid));
|
||||||
|
return $backend->ChangesSinkInitialize($this->GetBackendFolder($folderid));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if the backend doesn't support ChangesSink, we also return true so we don't get an error
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual ChangesSink.
|
||||||
|
* For max. the $timeout value this method should block and if no changes
|
||||||
|
* are available return an empty array.
|
||||||
|
* If changes are available a list of folderids is expected.
|
||||||
|
*
|
||||||
|
* @param int $timeout max. amount of seconds to block
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function ChangesSink($timeout = 30) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->ChangesSink(%d)", $timeout));
|
||||||
|
|
||||||
|
$notifications = array();
|
||||||
|
if ($this->numberChangesSink == 0) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("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));
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while($stopat > time() && empty($notifications));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -413,7 +533,159 @@ class BackendCombined extends Backend {
|
||||||
$pos = strpos($folderid, $this->config['delimiter']);
|
$pos = strpos($folderid, $this->config['delimiter']);
|
||||||
if($pos === false)
|
if($pos === false)
|
||||||
return false;
|
return false;
|
||||||
return substr($folderid,0,$pos);
|
return substr($folderid, 0, $pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates which AS version is supported by the backend.
|
||||||
|
* Return the lowest version supported by the backends used.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string AS version constant
|
||||||
|
*/
|
||||||
|
public function GetSupportedASVersion() {
|
||||||
|
$version = ZPush::ASV_14;
|
||||||
|
foreach ($this->backends as $i => $b) {
|
||||||
|
$subversion = $this->backends[$i]->GetSupportedASVersion();
|
||||||
|
if ($subversion < $version) {
|
||||||
|
$version = $subversion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the BackendCombined as it implements the ISearchProvider interface
|
||||||
|
* This could be overwritten by the global configuration
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return object Implementation of ISearchProvider
|
||||||
|
*/
|
||||||
|
public function GetSearchProvider() {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------------------------------------
|
||||||
|
-- ISearchProvider
|
||||||
|
------------------------------------------------------------------------------------------*/
|
||||||
|
/**
|
||||||
|
* Indicates if a search type is supported by this SearchProvider
|
||||||
|
* It supports all the search types, searches are delegated.
|
||||||
|
*
|
||||||
|
* @param string $searchtype
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function SupportsType($searchtype) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->SupportsType('%s')", $searchtype));
|
||||||
|
$i = $this->getSearchBackend($searchtype);
|
||||||
|
|
||||||
|
return $i !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries the LDAP backend
|
||||||
|
*
|
||||||
|
* @param string $searchquery string to be searched for
|
||||||
|
* @param string $searchrange specified searchrange
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return array search results
|
||||||
|
*/
|
||||||
|
public function GetGALSearchResults($searchquery, $searchrange) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetGALSearchResults()");
|
||||||
|
$i = $this->getSearchBackend(ISearchProvider::SEARCH_GAL);
|
||||||
|
|
||||||
|
$result = false;
|
||||||
|
if ($i !== false) {
|
||||||
|
$result = $this->backends[$i]->GetGALSearchResults($searchquery, $searchrange);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for the emails on the server
|
||||||
|
*
|
||||||
|
* @param ContentParameter $cpo
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function GetMailboxSearchResults($cpo) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetMailboxSearchResults()");
|
||||||
|
$i = $this->getSearchBackend(ISearchProvider::SEARCH_MAILBOX);
|
||||||
|
|
||||||
|
$result = false;
|
||||||
|
if ($i !== false) {
|
||||||
|
//Convert $cpo GetSearchFolderid
|
||||||
|
$cpo->SetSearchFolderid($this->GetBackendFolder($cpo->GetSearchFolderid()));
|
||||||
|
$result = $this->backends[$i]->GetMailboxSearchResults($cpo, $i . $this->config['delimiter']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates a search for a given PID
|
||||||
|
*
|
||||||
|
* @param int $pid
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function TerminateSearch($pid) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, "Combined->TerminateSearch()");
|
||||||
|
foreach ($this->backends as $i => $b) {
|
||||||
|
if ($this->backends[$i] instanceof ISearchProvider) {
|
||||||
|
$this->backends[$i]->TerminateSearch($pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects backends
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function Disconnect() {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, "Combined->Disconnect()");
|
||||||
|
foreach ($this->backends as $i => $b) {
|
||||||
|
if ($this->backends[$i] instanceof ISearchProvider) {
|
||||||
|
$this->backends[$i]->Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first backend that support a search type
|
||||||
|
*
|
||||||
|
* @param string $searchtype
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getSearchBackend($searchtype) {
|
||||||
|
foreach ($this->backends as $i => $b) {
|
||||||
|
if ($this->backends[$i] instanceof ISearchProvider) {
|
||||||
|
if ($this->backends[$i]->SupportsType($searchtype)) {
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->getSearchBackend('%s') No support found!", $searchtype));
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
|
@ -74,6 +74,15 @@ class BackendCombinedConfig {
|
||||||
'v' => array(
|
'v' => array(
|
||||||
'name' => 'BackendVCardDir',
|
'name' => 'BackendVCardDir',
|
||||||
),
|
),
|
||||||
|
'c' => array(
|
||||||
|
'name' => 'BackendCalDAV',
|
||||||
|
),
|
||||||
|
'l' => array(
|
||||||
|
'name' => 'BackendLDAP',
|
||||||
|
),
|
||||||
|
'd' => array(
|
||||||
|
'name' => 'BackendCardDAV',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
'delimiter' => '/',
|
'delimiter' => '/',
|
||||||
//force one type of folder to one backend
|
//force one type of folder to one backend
|
||||||
|
|
|
@ -173,7 +173,7 @@ class ExportChangesCombined implements IExportChanges {
|
||||||
public function InitializeExporter(&$importer) {
|
public function InitializeExporter(&$importer) {
|
||||||
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->InitializeExporter(...)");
|
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->InitializeExporter(...)");
|
||||||
foreach ($this->exporters as $i => $e) {
|
foreach ($this->exporters as $i => $e) {
|
||||||
if(!isset($this->_importwraps[$i])){
|
if(!isset($this->importwraps[$i])){
|
||||||
$this->importwraps[$i] = new ImportHierarchyChangesCombinedWrap($i, $this->backend, $importer);
|
$this->importwraps[$i] = new ImportHierarchyChangesCombinedWrap($i, $this->backend, $importer);
|
||||||
}
|
}
|
||||||
$e->InitializeExporter($this->importwraps[$i]);
|
$e->InitializeExporter($this->importwraps[$i]);
|
||||||
|
|
|
@ -131,6 +131,24 @@ class ImportChangesCombined implements IImportChanges {
|
||||||
return $this->icc->ImportMessageReadFlag($id, $flags);
|
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
|
* Imports a move of a message. This occurs when a user moves an item to another folder
|
||||||
*
|
*
|
||||||
|
|
28
sources/backend/imap/README
Normal file
28
sources/backend/imap/README
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
BackendIMAP - NOTES
|
||||||
|
===================
|
||||||
|
|
||||||
|
This backend support the Search operation in the mailbox.
|
||||||
|
Since the IMAP search operation is pretty slow, with a medium/big mailbox, or with a lots of folders,
|
||||||
|
the mobile device will timeout the operation before this is completed on server.
|
||||||
|
|
||||||
|
I'm using Dovecot + FTS-SOLR plugin so the real search is done against an Apache SOLR server.
|
||||||
|
It reduces a 1-2 minutes search to 1-5 seconds, and the response is given to the mobile device in time.
|
||||||
|
|
||||||
|
|
||||||
|
CHANGESSINK
|
||||||
|
===========
|
||||||
|
It supports ChangesSink method that will detect and send faster changes to your device.
|
||||||
|
|
||||||
|
|
||||||
|
SMTP
|
||||||
|
====
|
||||||
|
You can choice between 3 methods for send mails: mail (php mail), sendmail (native binary), smtp (php smtp direct connection).
|
||||||
|
Remember to configure it in the config.php
|
||||||
|
|
||||||
|
"mail" is a sendmail wrapper in Linux.
|
||||||
|
|
||||||
|
|
||||||
|
MBCONVERT
|
||||||
|
=========
|
||||||
|
A lot of messages come with wrong encoding, to them to look better with any device you can pre-convert them to UTF-8.
|
||||||
|
You will need to install the php-mbstring module
|
7
sources/backend/imap/REQUIREMENTS
Normal file
7
sources/backend/imap/REQUIREMENTS
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
REQUIREMENTS:
|
||||||
|
php-imap
|
||||||
|
php-mbstring (optional but recommended)
|
||||||
|
libawl-php
|
||||||
|
|
||||||
|
|
||||||
|
IMAP server (Dovecot, Courier...)
|
|
@ -54,25 +54,107 @@ define('IMAP_PORT', 143);
|
||||||
// best cross-platform compatibility (see http://php.net/imap_open for options)
|
// best cross-platform compatibility (see http://php.net/imap_open for options)
|
||||||
define('IMAP_OPTIONS', '/notls/norsh');
|
define('IMAP_OPTIONS', '/notls/norsh');
|
||||||
|
|
||||||
// overwrite the "from" header if it isn't set when sending emails
|
// overwrite the "from" header with some value
|
||||||
// options: 'username' - the username will be set (usefull if your login is equal to your emailaddress)
|
// options:
|
||||||
// 'domain' - the value of the "domain" field is used
|
// '' - do nothing, use the From header
|
||||||
|
// 'username' - the username will be set (usefull if your login is equal to your emailaddress)
|
||||||
|
// 'domain' - the value of the "domain" field is used
|
||||||
|
// 'sql' - the username will be the result of a sql query. REMEMBER TO INSTALL PHP-PDO AND PHP-DATABASE
|
||||||
|
// 'ldap' - the username will be the result of a ldap query. REMEMBER TO INSTALL PHP-LDAP!!
|
||||||
// '@mydomain.com' - the username is used and the given string will be appended
|
// '@mydomain.com' - the username is used and the given string will be appended
|
||||||
define('IMAP_DEFAULTFROM', '');
|
define('IMAP_DEFAULTFROM', '');
|
||||||
|
|
||||||
|
// DSN: formatted PDO connection string
|
||||||
|
// mysql:host=xxx;port=xxx;dbname=xxx
|
||||||
|
// USER: username to DB
|
||||||
|
// PASSWORD: password to DB
|
||||||
|
// OPTIONS: array with options needed
|
||||||
|
// QUERY: query to execute
|
||||||
|
// FIELDS: columns in the query
|
||||||
|
// FROM: string that will be the from, replacing the column names with the values
|
||||||
|
define('IMAP_FROM_SQL_DSN', '');
|
||||||
|
define('IMAP_FROM_SQL_USER', '');
|
||||||
|
define('IMAP_FROM_SQL_PASSWORD', '');
|
||||||
|
define('IMAP_FROM_SQL_OPTIONS', serialize(array(PDO::ATTR_PERSISTENT => true)));
|
||||||
|
define('IMAP_FROM_SQL_QUERY', "select first_name, last_name, mail_address from users where mail_address = '#username@#domain'");
|
||||||
|
define('IMAP_FROM_SQL_FIELDS', serialize(array('first_name', 'last_name', 'mail_address')));
|
||||||
|
define('IMAP_FROM_SQL_FROM', '#first_name #last_name <#mail_address>');
|
||||||
|
define('IMAP_FROM_SQL_FULLNAME', '#first_name #last_name');
|
||||||
|
|
||||||
|
// SERVER: ldap server
|
||||||
|
// SERVER_PORT: ldap port
|
||||||
|
// USER: dn to use for connecting
|
||||||
|
// PASSWORD: password
|
||||||
|
// QUERY: query to execute
|
||||||
|
// FIELDS: columns in the query
|
||||||
|
// FROM: string that will be the from, replacing the field names with the values
|
||||||
|
define('IMAP_FROM_LDAP_SERVER', 'localhost');
|
||||||
|
define('IMAP_FROM_LDAP_SERVER_PORT', '389');
|
||||||
|
define('IMAP_FROM_LDAP_USER', 'cn=zpush,ou=servers,dc=zpush,dc=org');
|
||||||
|
define('IMAP_FROM_LDAP_PASSWORD', 'password');
|
||||||
|
define('IMAP_FROM_LDAP_BASE', 'dc=zpush,dc=org');
|
||||||
|
define('IMAP_FROM_LDAP_QUERY', '(mail=#username@#domain)');
|
||||||
|
define('IMAP_FROM_LDAP_FIELDS', serialize(array('givenname', 'sn', 'mail')));
|
||||||
|
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
|
// copy outgoing mail to this folder. If not set z-push will try the default folders
|
||||||
define('IMAP_SENTFOLDER', '');
|
// You can use the real case and the full path (INBOX.Sent)
|
||||||
|
define('IMAP_FOLDER_SENT', '');
|
||||||
|
|
||||||
// forward messages inline (default false - as attachment)
|
// Draft folder
|
||||||
define('IMAP_INLINE_FORWARD', false);
|
// You can use the real case and the full path (INBOX.Draft)
|
||||||
|
define('IMAP_FOLDER_DRAFT', '');
|
||||||
|
|
||||||
// use imap_mail() to send emails (default) - if false mail() is used
|
// Trash folder
|
||||||
define('IMAP_USE_IMAPMAIL', true);
|
// 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);
|
||||||
|
|
||||||
/* BEGIN fmbiete's contribution r1527, ZP-319 */
|
|
||||||
// list of folders we want to exclude from sync. Names, or part of it, separated by |
|
// list of folders we want to exclude from sync. Names, or part of it, separated by |
|
||||||
// example: dovecot.sieve|archive|spam
|
// example: dovecot.sieve|archive|spam
|
||||||
define('IMAP_EXCLUDED_FOLDERS', '');
|
define('IMAP_EXCLUDED_FOLDERS', '');
|
||||||
/* END fmbiete's contribution r1527, ZP-319 */
|
|
||||||
|
|
||||||
?>
|
|
||||||
|
// Method used for sending mail
|
||||||
|
// mail => mail() php function
|
||||||
|
// sendmail => sendmail executable
|
||||||
|
// smtp => direct connection against SMTP
|
||||||
|
define('IMAP_SMTP_METHOD', 'mail');
|
||||||
|
|
||||||
|
global $imap_smtp_params;
|
||||||
|
// SMTP Parameters
|
||||||
|
// mail : no params
|
||||||
|
$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.
|
||||||
|
//$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
|
||||||
|
//$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");
|
||||||
|
|
||||||
|
?>
|
File diff suppressed because it is too large
Load diff
295
sources/backend/imap/mime_encode.php
Normal file
295
sources/backend/imap/mime_encode.php
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add extra parts (not text; inlined or attached parts) to a mimepart object.
|
||||||
|
*
|
||||||
|
* @param Mail_mimePart $email reference to the object
|
||||||
|
* @param array $parts array of parts
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function add_extra_sub_parts(&$email, $parts) {
|
||||||
|
if (isset($parts)) {
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$new_part = null;
|
||||||
|
// Only if it's an attachment we will add the text parts, because all the inline/no disposition have been already added
|
||||||
|
if (isset($part->disposition) && $part->disposition == "attachment") {
|
||||||
|
// it's an attachment
|
||||||
|
$new_part = add_sub_part($email, $part);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isset($part->ctype_primary) && $part->ctype_primary != "text" && $part->ctype_primary != "multipart") {
|
||||||
|
// it's not a text part or a multipart
|
||||||
|
$new_part = add_sub_part($email, $part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($part->parts)) {
|
||||||
|
// We add sub-parts to the new part (if any), not to the main message. Recursive calling
|
||||||
|
if ($new_part === null) {
|
||||||
|
add_extra_sub_parts($email, $part->parts);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
add_extra_sub_parts($new_part, $part->parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a subpart to a mimepart object.
|
||||||
|
*
|
||||||
|
* @param Mail_mimePart $email reference to the object
|
||||||
|
* @param object $part message part
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function add_sub_part(&$email, $part) {
|
||||||
|
//http://tools.ietf.org/html/rfc4021
|
||||||
|
$new_part = null;
|
||||||
|
$params = array();
|
||||||
|
$params['content_type'] = '';
|
||||||
|
if (isset($part) && isset($email)) {
|
||||||
|
if (isset($part->ctype_primary)) {
|
||||||
|
$params['content_type'] = $part->ctype_primary;
|
||||||
|
}
|
||||||
|
if (isset($part->ctype_secondary)) {
|
||||||
|
$params['content_type'] .= '/' . $part->ctype_secondary;
|
||||||
|
}
|
||||||
|
if (isset($part->ctype_parameters)) {
|
||||||
|
foreach ($part->ctype_parameters as $k => $v) {
|
||||||
|
if(strcasecmp($k, 'boundary') != 0) {
|
||||||
|
$params['content_type'] .= '; ' . $k . '=' . $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($part->disposition)) {
|
||||||
|
$params['disposition'] = $part->disposition;
|
||||||
|
}
|
||||||
|
//FIXME: dfilename => filename
|
||||||
|
if (isset($part->d_parameters)) {
|
||||||
|
foreach ($part->d_parameters as $k => $v) {
|
||||||
|
$params[$k] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($part->headers as $k => $v) {
|
||||||
|
switch($k) {
|
||||||
|
case "content-description":
|
||||||
|
$params['description'] = $v;
|
||||||
|
break;
|
||||||
|
case "content-type":
|
||||||
|
case "content-disposition":
|
||||||
|
case "content-transfer-encoding":
|
||||||
|
// Do nothing, we already did
|
||||||
|
break;
|
||||||
|
case "content-id":
|
||||||
|
$params['cid'] = str_replace('<', '', str_replace('>', '', $v));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$params[$k] = $v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not exist body, the part will be multipart/alternative, so we don't add encoding
|
||||||
|
if (!isset($params['encoding']) && isset($part->body)) {
|
||||||
|
$params['encoding'] = 'base64';
|
||||||
|
}
|
||||||
|
// We could not have body; recursive messages
|
||||||
|
$new_part = $email->addSubPart(isset($part->body) ? $part->body : "", $params);
|
||||||
|
unset($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the new part
|
||||||
|
return $new_part;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a subpart to a mimepart object.
|
||||||
|
*
|
||||||
|
* @param Mail_mimePart $email reference to the object
|
||||||
|
* @param object $part message part
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function change_charset_and_add_subparts(&$email, $part) {
|
||||||
|
if (isset($part)) {
|
||||||
|
$new_part = null;
|
||||||
|
if (isset($part->ctype_parameters['charset'])) {
|
||||||
|
$part->ctype_parameters['charset'] = 'UTF-8';
|
||||||
|
$new_part = add_sub_part($email, $part);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We don't add the charset because it could be a non-text part
|
||||||
|
$new_part = add_sub_part($email, $part);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($part->parts)) {
|
||||||
|
foreach ($part->parts as $subpart) {
|
||||||
|
// Subparts are added to the part, not the main message
|
||||||
|
change_charset_and_add_subparts($new_part, $subpart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a MIME message from a decoded MIME message, reencoding and fixing the text.
|
||||||
|
*
|
||||||
|
* @param array $message array returned from Mail_mimeDecode->decode
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string MIME message
|
||||||
|
*/
|
||||||
|
function build_mime_message($message) {
|
||||||
|
$finalEmail = new Mail_mimePart(isset($message->body) ? $message->body : "", array('headers' => $message->headers));
|
||||||
|
if (isset($message->parts)) {
|
||||||
|
foreach ($message->parts as $part) {
|
||||||
|
change_charset_and_add_subparts($finalEmail, $part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mimeHeaders = Array();
|
||||||
|
$mimeHeaders['headers'] = Array();
|
||||||
|
$is_mime = false;
|
||||||
|
foreach ($message->headers as $key => $value) {
|
||||||
|
switch($key) {
|
||||||
|
case 'content-type':
|
||||||
|
$new_value = $message->ctype_primary . "/" . $message->ctype_secondary;
|
||||||
|
$is_mime = (strcasecmp($message->ctype_primary, 'multipart') == 0);
|
||||||
|
|
||||||
|
if (isset($message->ctype_parameters)) {
|
||||||
|
foreach ($message->ctype_parameters as $ckey => $cvalue) {
|
||||||
|
switch($ckey) {
|
||||||
|
case 'charset':
|
||||||
|
$new_value .= '; charset="UTF-8"';
|
||||||
|
break;
|
||||||
|
case 'boundary':
|
||||||
|
// Do nothing, we are encoding also the headers
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$new_value .= '; ' . $ckey . '="' . $cvalue . '"';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mimeHeaders['content_type'] = $new_value;
|
||||||
|
break;
|
||||||
|
case 'content-transfer-encoding':
|
||||||
|
if (strcasecmp($value, "base64") == 0 || strcasecmp($value, "binary") == 0) {
|
||||||
|
$mimeHeaders['encoding'] = "base64";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$mimeHeaders['encoding'] = "8bit";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'content-id':
|
||||||
|
$mimeHeaders['cid'] = $value;
|
||||||
|
break;
|
||||||
|
case 'content-location':
|
||||||
|
$mimeHeaders['location'] = $value;
|
||||||
|
break;
|
||||||
|
case 'content-disposition':
|
||||||
|
$mimeHeaders['disposition'] = $value;
|
||||||
|
break;
|
||||||
|
case 'content-description':
|
||||||
|
$mimeHeaders['description'] = $value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (is_array($value)) {
|
||||||
|
foreach($value as $v) {
|
||||||
|
$mimeHeaders['headers'][$key] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$mimeHeaders['headers'][$key] = $value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$finalEmail = new Mail_mimePart(isset($message->body) ? $message->body : "", $mimeHeaders);
|
||||||
|
unset($mimeHeaders['headers']);
|
||||||
|
unset($mimeHeaders);
|
||||||
|
|
||||||
|
if (isset($message->parts)) {
|
||||||
|
foreach ($message->parts as $part) {
|
||||||
|
change_charset_and_add_subparts($finalEmail, $part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$boundary = '=_' . md5(rand() . microtime());
|
||||||
|
$finalEmail = $finalEmail->encode($boundary);
|
||||||
|
|
||||||
|
$headers = "";
|
||||||
|
$mimePart = new Mail_mimePart();
|
||||||
|
foreach ($finalEmail['headers'] as $key => $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
foreach ($values as $ikey => $ivalue) {
|
||||||
|
$headers .= $key . ": " . $mimePart->encodeHeader($key, $ivalue, "utf-8", "base64") . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$headers .= $key . ": " . $mimePart->encodeHeader($key, $value, "utf-8", "base64") . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($mimePart);
|
||||||
|
|
||||||
|
|
||||||
|
if ($is_mime) {
|
||||||
|
$built_message = "$headers\nThis is a multi-part message in MIME format.\n".$finalEmail['body'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$built_message = "$headers\n".$finalEmail['body'];
|
||||||
|
}
|
||||||
|
unset($headers);
|
||||||
|
unset($finalEmail);
|
||||||
|
|
||||||
|
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
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
3
sources/backend/ldap/AUTHOR
Normal file
3
sources/backend/ldap/AUTHOR
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
The Author of this backend is dupondje, I could have modified it.
|
||||||
|
You can found the original code here:
|
||||||
|
https://github.com/dupondje/PHP-Push-2
|
60
sources/backend/ldap/config.php
Normal file
60
sources/backend/ldap/config.php
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
/***********************************************
|
||||||
|
* File : config.php
|
||||||
|
* Project : Z-Push
|
||||||
|
* Descr : LDAP backend configuration file
|
||||||
|
*
|
||||||
|
* Created : 27.11.2012
|
||||||
|
*
|
||||||
|
* Copyright 2007 - 2012 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,
|
||||||
|
* as published by the Free Software Foundation with the following additional
|
||||||
|
* term according to sec. 7:
|
||||||
|
*
|
||||||
|
* According to sec. 7 of the GNU Affero General Public License, version 3,
|
||||||
|
* the terms of the AGPL are supplemented with the following terms:
|
||||||
|
*
|
||||||
|
* "Zarafa" is a registered trademark of Zarafa B.V.
|
||||||
|
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
|
||||||
|
* The licensing of the Program under the AGPL does not imply a trademark license.
|
||||||
|
* Therefore any rights, title and interest in our trademarks remain entirely with us.
|
||||||
|
*
|
||||||
|
* However, if you propagate an unmodified version of the Program you are
|
||||||
|
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
|
||||||
|
* Furthermore you may use our trademarks where it is necessary to indicate
|
||||||
|
* the intended purpose of a product or service provided you use it in accordance
|
||||||
|
* with honest practices in industrial or commercial matters.
|
||||||
|
* If you want to propagate modified versions of the Program under the name "Z-Push",
|
||||||
|
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
|
||||||
|
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Consult LICENSE file for details
|
||||||
|
************************************************/
|
||||||
|
|
||||||
|
// **********************
|
||||||
|
// BackendLDAP settings
|
||||||
|
// **********************
|
||||||
|
|
||||||
|
// Server address
|
||||||
|
define('LDAP_SERVER', 'localhost');
|
||||||
|
|
||||||
|
// Server Port
|
||||||
|
define('LDAP_SERVER_PORT', '389');
|
||||||
|
|
||||||
|
// LDAP USER DN
|
||||||
|
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 |
|
||||||
|
|
||||||
|
?>
|
584
sources/backend/ldap/ldap.php
Normal file
584
sources/backend/ldap/ldap.php
Normal file
|
@ -0,0 +1,584 @@
|
||||||
|
<?php
|
||||||
|
/***********************************************
|
||||||
|
* File : ldap.php
|
||||||
|
* Project : PHP-Push
|
||||||
|
* Descr : This backend is based on
|
||||||
|
* 'BackendDiff' and implements an
|
||||||
|
* (Open)LDAP interface
|
||||||
|
*
|
||||||
|
* Created : 07.04.2012
|
||||||
|
*
|
||||||
|
* Copyright 2012 - 2014 Jean-Louis Dupond
|
||||||
|
*
|
||||||
|
* Jean-Louis Dupond released this code as AGPLv3 here: https://github.com/dupondje/PHP-Push-2/issues/93
|
||||||
|
*
|
||||||
|
* 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,
|
||||||
|
* as published by the Free Software Foundation with the following additional
|
||||||
|
* term according to sec. 7:
|
||||||
|
*
|
||||||
|
* According to sec. 7 of the GNU Affero General Public License, version 3,
|
||||||
|
* the terms of the AGPL are supplemented with the following terms:
|
||||||
|
*
|
||||||
|
* "Zarafa" is a registered trademark of Zarafa B.V.
|
||||||
|
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
|
||||||
|
* The licensing of the Program under the AGPL does not imply a trademark license.
|
||||||
|
* Therefore any rights, title and interest in our trademarks remain entirely with us.
|
||||||
|
*
|
||||||
|
* However, if you propagate an unmodified version of the Program you are
|
||||||
|
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
|
||||||
|
* Furthermore you may use our trademarks where it is necessary to indicate
|
||||||
|
* the intended purpose of a product or service provided you use it in accordance
|
||||||
|
* with honest practices in industrial or commercial matters.
|
||||||
|
* If you want to propagate modified versions of the Program under the name "Z-Push",
|
||||||
|
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
|
||||||
|
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Consult LICENSE file for details
|
||||||
|
************************************************/
|
||||||
|
|
||||||
|
// config file
|
||||||
|
require_once("backend/ldap/config.php");
|
||||||
|
|
||||||
|
include_once('lib/default/diffbackend/diffbackend.php');
|
||||||
|
|
||||||
|
class BackendLDAP extends BackendDiff {
|
||||||
|
|
||||||
|
private $ldap_link;
|
||||||
|
private $user;
|
||||||
|
|
||||||
|
public function Logon($username, $domain, $password) {
|
||||||
|
$this->user = $username;
|
||||||
|
$user_dn = str_replace('%u', $username, LDAP_USER_DN);
|
||||||
|
$this->ldap_link = ldap_connect(LDAP_SERVER, LDAP_SERVER_PORT);
|
||||||
|
ldap_set_option($this->ldap_link, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||||
|
if (ldap_bind($this->ldap_link, $user_dn, $password)) {
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("BackendLDAP->Logon(): User '%s' is authenticated on LDAP", $username));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("BackendLDAP->Logon(): User '%s' is not authenticated on LDAP. Error: ", $username, ldap_error($this->ldap_link)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Logoff() {
|
||||||
|
if (ldap_unbind($this->ldap_link)) {
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("BackendLDAP->Logoff(): Disconnection successfull."));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("BackendLDAP->Logoff(): Disconnection failed. Error: %s", ldap_error($this->ldap_link)));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SendMail($sm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetAttachmentData($attname) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetWasteBasket() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetFolderList() {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->GetFolderList(): Getting all folders."));
|
||||||
|
$contacts = array();
|
||||||
|
$dns = explode("|", LDAP_BASE_DNS);
|
||||||
|
foreach ($dns as $dn) {
|
||||||
|
$name = explode(":", $dn);
|
||||||
|
$folder = $this->StatFolder($name[0]);
|
||||||
|
$contacts[] = $folder;
|
||||||
|
}
|
||||||
|
return $contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetFolder($id) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->GetFolder('%s')", $id));
|
||||||
|
$folder = new SyncFolder();
|
||||||
|
$folder->serverid = $id;
|
||||||
|
$folder->parentid = "0";
|
||||||
|
$folder->displayname = $id;
|
||||||
|
$folder->type = SYNC_FOLDER_TYPE_CONTACT;
|
||||||
|
return $folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function StatFolder($id) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->StatFolder('%s')", $id));
|
||||||
|
$folder = $this->GetFolder($id);
|
||||||
|
$stat = array();
|
||||||
|
$stat["id"] = $id;
|
||||||
|
$stat["parent"] = $folder->parentid;
|
||||||
|
$stat["mod"] = $folder->displayname;
|
||||||
|
return $stat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ChangeFolder($folderid, $oldid, $displayname, $type) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->ChangeFolder('%s','%s','%s','%s')", $folderid, $oldid, $displayname, $type));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DeleteFolder($id, $parentid) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->DeleteFolder('%s','%s')", $id, $parentid));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetMessageList($folderid, $cutoffdate) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->GetMessageList('%s','%s')", $folderid, $cutoffdate));
|
||||||
|
|
||||||
|
$cutoff = date("YmdHis\Z", $cutoffdate);
|
||||||
|
$filter = sprintf('(modifyTimestamp>=%s)', $cutoff);
|
||||||
|
$attributes = array("entryUUID", "modifyTimestamp");
|
||||||
|
$messages = array();
|
||||||
|
|
||||||
|
$base_dns = explode("|", LDAP_BASE_DNS);
|
||||||
|
foreach ($base_dns as $base_dn) {
|
||||||
|
$folder = explode(":", $base_dn);
|
||||||
|
if ($folder[0] == $folderid) {
|
||||||
|
$base_dn = str_replace('%u', $this->user, $folder[1]);
|
||||||
|
$results = ldap_list($this->ldap_link, $base_dn, $filter, $attributes);
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->GetMessageList(): Got %s contacts in base_dn '%s'.", ldap_count_entries($this->ldap_link, $results), $base_dn));
|
||||||
|
$entries = ldap_get_entries($this->ldap_link, $results);
|
||||||
|
for ($i = 0; $i < $entries["count"]; $i++) {
|
||||||
|
$message = array();
|
||||||
|
$message["id"] = $entries[$i]["entryuuid"][0];
|
||||||
|
$message["mod"] = $entries[$i]["modifytimestamp"][0];
|
||||||
|
$message["flags"] = "1";
|
||||||
|
$messages[] = $message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetMessage($folderid, $id, $contentparameters) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->GetMessage('%s','%s')", $folderid, $id));
|
||||||
|
|
||||||
|
$truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
|
||||||
|
$base_dns = explode("|", LDAP_BASE_DNS);
|
||||||
|
foreach ($base_dns as $base_dn) {
|
||||||
|
$folder = explode(":", $base_dn);
|
||||||
|
if ($folder[0] == $folderid) {
|
||||||
|
$base_dn = str_replace('%u', $this->user, $folder[1]);
|
||||||
|
$result_id = ldap_list($this->ldap_link, $base_dn, "(entryUUID=".$id.")");
|
||||||
|
if ($result_id) {
|
||||||
|
$entry_id = ldap_first_entry($this->ldap_link, $result_id);
|
||||||
|
if ($entry_id) {
|
||||||
|
return $this->_ParseLDAPMessage($result_id, $entry_id, $truncsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _ParseLDAPMessage($result_id, $entry_id, $truncsize = -1) {
|
||||||
|
$contact = new SyncContact();
|
||||||
|
|
||||||
|
$values = ldap_get_attributes($this->ldap_link, $entry_id);
|
||||||
|
for ($i = 0; $i < $values["count"]; $i++) {
|
||||||
|
$name = $values[$i];
|
||||||
|
$value = $values[$name][0];
|
||||||
|
|
||||||
|
switch ($name) {
|
||||||
|
//person
|
||||||
|
case "cn":
|
||||||
|
case "fileAs":
|
||||||
|
$contact->fileas = $value;
|
||||||
|
break;
|
||||||
|
case "sn":
|
||||||
|
$contact->lastname = $value;
|
||||||
|
break;
|
||||||
|
//inetOrgPerson
|
||||||
|
case "departmentNumber":
|
||||||
|
$contact->department = $value;
|
||||||
|
break;
|
||||||
|
case "givenName":
|
||||||
|
$contact->firstname = $value;
|
||||||
|
break;
|
||||||
|
case "homePhone":
|
||||||
|
$contact->homephonenumber = $value;
|
||||||
|
if ($values[$name]["count"] >= 2) {
|
||||||
|
$contact->home2phonenumber = $values[$name][1];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "jpegPhoto":
|
||||||
|
$contact->picture = base64_encode($value);
|
||||||
|
break;
|
||||||
|
case "labeledURI":
|
||||||
|
$contact->webpage = $value;
|
||||||
|
break;
|
||||||
|
case "mail":
|
||||||
|
$contact->email1address = $value;
|
||||||
|
if ($values[$name]["count"] >= 2) {
|
||||||
|
$contact->email2address = $values[$name][1];
|
||||||
|
}
|
||||||
|
if ($values[$name]["count"] >= 3) {
|
||||||
|
$contact->email3address = $values[$name][2];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "mobile":
|
||||||
|
$contact->mobilephonenumber = $value;
|
||||||
|
break;
|
||||||
|
case "o":
|
||||||
|
$contact->companyname = $value;
|
||||||
|
break;
|
||||||
|
case "pager":
|
||||||
|
$contact->pagernumber = $value;
|
||||||
|
break;
|
||||||
|
case "secretary":
|
||||||
|
case "assistantName":
|
||||||
|
$contact->assistantname = $value;
|
||||||
|
break;
|
||||||
|
//organizationalPerson
|
||||||
|
case "l":
|
||||||
|
$contact->businesscity = $value;
|
||||||
|
break;
|
||||||
|
case "ou":
|
||||||
|
$contact->department = $value;
|
||||||
|
break;
|
||||||
|
case "physicalDeliveryOfficeName":
|
||||||
|
$contact->officelocation = $value;
|
||||||
|
break;
|
||||||
|
case "postalCode":
|
||||||
|
$contact->businesspostalcode = $value;
|
||||||
|
break;
|
||||||
|
case "st":
|
||||||
|
$contact->businessstate = $value;
|
||||||
|
break;
|
||||||
|
case "street":
|
||||||
|
$contact->businessstreet = $value;
|
||||||
|
break;
|
||||||
|
case "telephoneNumber":
|
||||||
|
$contact->businessphonenumber = $value;
|
||||||
|
if ($values[$name]["count"] >= 2) {
|
||||||
|
$contact->business2phonenumber = $values[$name][1];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "title":
|
||||||
|
$contact->title = $value;
|
||||||
|
break;
|
||||||
|
case "description":
|
||||||
|
case "note":
|
||||||
|
if (Request::GetProtocolVersion() >= 12.0) {
|
||||||
|
$contact->asbody = new SyncBaseBody();
|
||||||
|
$contact->asbody->type = SYNC_BODYPREFERENCE_PLAIN;
|
||||||
|
$contact->asbody->data = $value;
|
||||||
|
if ($truncsize > 0 && $truncsize < strlen($contact->asbody->data)) {
|
||||||
|
$contact->asbody->truncated = 1;
|
||||||
|
$contact->asbody->data = Utils::Utf8_truncate($contact->asbody->data, $truncsize);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$contact->asbody->truncated = 0;
|
||||||
|
}
|
||||||
|
$contact->asbody->estimatedDataSize = strlen($contact->asbody->data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$contact->body = $value;
|
||||||
|
if ($truncsize > 0 && $truncsize < strlen($contact->body)) {
|
||||||
|
$contact->bodytruncated = 1;
|
||||||
|
$contact->body = Utils::Utf8_truncate($contact->body, $truncsize);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$contact->bodytruncated = 0;
|
||||||
|
}
|
||||||
|
$contact->bodysize = strlen($contact->body);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "assistantPhone":
|
||||||
|
$contact->assistnamephonenumber = $value;
|
||||||
|
break;
|
||||||
|
case "birthDate":
|
||||||
|
$contact->birthday = $value;
|
||||||
|
break;
|
||||||
|
case "anniversary":
|
||||||
|
$contact->anniversary = $value;
|
||||||
|
break;
|
||||||
|
case "businessRole":
|
||||||
|
$contact->jobtitle = $value;
|
||||||
|
break;
|
||||||
|
case "carPhone":
|
||||||
|
$contact->carphonenumber = $value;
|
||||||
|
break;
|
||||||
|
case "facsimileTelephoneNumber":
|
||||||
|
$contact->businessfaxnumber = $value;
|
||||||
|
break;
|
||||||
|
case "homeFacsimileTelephoneNumber":
|
||||||
|
$contact->homefaxnumber = $value;
|
||||||
|
break;
|
||||||
|
case "spouseName":
|
||||||
|
$contact->spouse = $value;
|
||||||
|
break;
|
||||||
|
case "managerName":
|
||||||
|
$contact->managername = $value;
|
||||||
|
break;
|
||||||
|
case "radio":
|
||||||
|
$contact->radiophonenumber = $value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $contact;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function StatMessage($folderid, $id) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->StatMessage('%s','%s')", $folderid, $id));
|
||||||
|
$base_dns = explode("|", LDAP_BASE_DNS);
|
||||||
|
foreach ($base_dns as $base_dn) {
|
||||||
|
$folder = explode(":", $base_dn);
|
||||||
|
if ($folder[0] == $folderid) {
|
||||||
|
$base_dn = str_replace('%u', $this->user, $folder[1]);
|
||||||
|
$result_id = ldap_list($this->ldap_link, $base_dn, "(entryUUID=".$id.")", array("modifyTimestamp"));
|
||||||
|
if ($result_id) {
|
||||||
|
$entry_id = ldap_first_entry($this->ldap_link, $result_id);
|
||||||
|
if ($entry_id) {
|
||||||
|
$mod = ldap_get_values($this->ldap_link, $entry_id, "modifyTimestamp");
|
||||||
|
$message = array();
|
||||||
|
$message["id"] = $id;
|
||||||
|
$message["mod"] = $mod[0];
|
||||||
|
$message["flags"] = "1";
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ChangeMessage($folderid, $id, $message, $contentParameters) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->ChangeMessage('%s','%s')", $folderid, $id));
|
||||||
|
$base_dns = explode("|", LDAP_BASE_DNS);
|
||||||
|
foreach ($base_dns as $base_dn) {
|
||||||
|
$folder = explode(":", $base_dn);
|
||||||
|
if ($folder[0] == $folderid) {
|
||||||
|
$base_dn = str_replace('%u', $this->user, $folder[1]);
|
||||||
|
$ldap_attributes = $this->_GenerateLDAPArray($message);
|
||||||
|
$result_id = ldap_list($this->ldap_link, $base_dn, "(entryUUID=".$id.")", array("modifyTimestamp"));
|
||||||
|
if ($result_id) {
|
||||||
|
$entry_id = ldap_first_entry($this->ldap_link, $result_id);
|
||||||
|
if ($entry_id) {
|
||||||
|
$dn = ldap_get_dn($this->ldap_link, $entry_id);
|
||||||
|
|
||||||
|
// We cannot ldap_modify objectClass, but we can use ldap_mod_replace
|
||||||
|
$ldap_classes = array();
|
||||||
|
$ldap_classes['objectclass'] = Array("top", "person", "inetOrgPerson", "organizationalPerson", "evolutionPerson");
|
||||||
|
$mode = ldap_mod_replace($this->ldap_link, $dn, $ldap_classes);
|
||||||
|
|
||||||
|
$mod = ldap_modify($this->ldap_link, $dn, $ldap_attributes);
|
||||||
|
if (!$mod) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $this->StatMessage($folderid, $id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$uid = time() . mt_rand(100000, 999999);
|
||||||
|
$dn = "uid=" . $uid . "," . $base_dn;
|
||||||
|
$add = ldap_add($this->ldap_link, $dn, $ldap_attributes);
|
||||||
|
if (!$add) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$result = ldap_read($this->ldap_link, $dn, "objectClass=*", array("entryUUID"));
|
||||||
|
$entry = ldap_first_entry($this->ldap_link, $result);
|
||||||
|
$values = ldap_get_values($this->ldap_link, $entry, "entryUUID");
|
||||||
|
$entryuuid = $values[0];
|
||||||
|
return $this->StatMessage($folderid, $entryuuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _GenerateLDAPArray($message) {
|
||||||
|
$ldap = array();
|
||||||
|
//Set the Object Class
|
||||||
|
$ldap["objectClass"] = Array("top", "person", "inetOrgPerson", "organizationalPerson", "evolutionPerson");
|
||||||
|
|
||||||
|
//Parse Data
|
||||||
|
if ($message->fileas) {
|
||||||
|
$ldap["cn"] = $message->fileas;
|
||||||
|
$ldap["fileAs"] = $message->fileas;
|
||||||
|
}
|
||||||
|
if ($message->lastname) {
|
||||||
|
$ldap["sn"] = $message->lastname;
|
||||||
|
}
|
||||||
|
if ($message->department) {
|
||||||
|
$ldap["departmentNumber"] = $message->department;
|
||||||
|
}
|
||||||
|
if ($message->firstname) {
|
||||||
|
$ldap["givenName"] = $message->firstname;
|
||||||
|
}
|
||||||
|
if ($message->homephonenumber) {
|
||||||
|
$ldap["homePhone"][0] = $message->homephonenumber;
|
||||||
|
}
|
||||||
|
if ($message->home2phonenumber) {
|
||||||
|
$ldap["homePhone"][1] = $message->home2phonenumber;
|
||||||
|
}
|
||||||
|
if ($message->picture) {
|
||||||
|
$ldap["jpegPhoto"] = base64_decode($message->picture);
|
||||||
|
}
|
||||||
|
if ($message->webpage) {
|
||||||
|
$ldap["labeledURI"] = $message->webpage;
|
||||||
|
}
|
||||||
|
if ($message->email1address) {
|
||||||
|
$ldap["mail"][] = $message->email1address;
|
||||||
|
}
|
||||||
|
if ($message->email2address) {
|
||||||
|
$ldap["mail"][] = $message->email2address;
|
||||||
|
}
|
||||||
|
if ($message->email3address) {
|
||||||
|
$ldap["mail"][] = $message->email3address;
|
||||||
|
}
|
||||||
|
if ($message->mobilephonenumber) {
|
||||||
|
$ldap["mobile"] = $message->mobilephonenumber;
|
||||||
|
}
|
||||||
|
if ($message->companyname) {
|
||||||
|
$ldap["o"] = $message->companyname;
|
||||||
|
}
|
||||||
|
if ($message->pagernumber) {
|
||||||
|
$ldap["pager"] = $message->pagernumber;
|
||||||
|
}
|
||||||
|
if ($message->assistantname) {
|
||||||
|
$ldap["secretary"] = $message->assistantname;
|
||||||
|
$ldap["assistantName"] = $message->assistantname;
|
||||||
|
}
|
||||||
|
if ($message->businesscity) {
|
||||||
|
$ldap["l"] = $message->businesscity;
|
||||||
|
}
|
||||||
|
if ($message->department) {
|
||||||
|
$ldap["ou"] = $message->department;
|
||||||
|
}
|
||||||
|
if ($message->officelocation) {
|
||||||
|
$ldap["physicalDeliveryOfficeName"] = $message->officelocation;
|
||||||
|
}
|
||||||
|
if ($message->businesspostalcode) {
|
||||||
|
$ldap["postalCode"] = $message->businesspostalcode;
|
||||||
|
}
|
||||||
|
if ($message->businessstate) {
|
||||||
|
$ldap["st"] = $message->businessstate;
|
||||||
|
}
|
||||||
|
if ($message->businessstreet) {
|
||||||
|
$ldap["street"] = $message->businessstreet;
|
||||||
|
}
|
||||||
|
if ($message->businessphonenumber) {
|
||||||
|
$ldap["telephoneNumber"][] = $message->businessphonenumber;
|
||||||
|
}
|
||||||
|
if ($message->business2phonenumber) {
|
||||||
|
$ldap["telephoneNumber"][] = $message->business2phonenumber;
|
||||||
|
}
|
||||||
|
if ($message->title) {
|
||||||
|
$ldap["title"] = $message->title;
|
||||||
|
}
|
||||||
|
if ($message->body) {
|
||||||
|
$ldap["description"] = $message->body;
|
||||||
|
}
|
||||||
|
if ($message->asbody) {
|
||||||
|
$ldap["description"] = $message->asbody->data;
|
||||||
|
}
|
||||||
|
if ($message->assistnamephonenumber) {
|
||||||
|
$ldap["assistantPhone"] = $message->assistnamephonenumber;
|
||||||
|
}
|
||||||
|
if ($message->birthday) {
|
||||||
|
$ldap["birthDate"] = $message->birthday;
|
||||||
|
}
|
||||||
|
if ($message->anniversary) {
|
||||||
|
$ldap["anniversary"] = $message->anniversary;
|
||||||
|
}
|
||||||
|
if ($message->jobtitle) {
|
||||||
|
$ldap["businessRole"] = $message->jobtitle;
|
||||||
|
}
|
||||||
|
if ($message->carphonenumber) {
|
||||||
|
$ldap["carPhone"] = $message->carphonenumber;
|
||||||
|
}
|
||||||
|
if ($message->businessfaxnumber) {
|
||||||
|
$ldap["facsimileTelephoneNumber"] = $message->businessfaxnumber;
|
||||||
|
}
|
||||||
|
if ($message->homefaxnumber) {
|
||||||
|
$ldap["homeFacsimileTelephoneNumber"] = $message->homefaxnumber;
|
||||||
|
}
|
||||||
|
if ($message->spouse) {
|
||||||
|
$ldap["spouseName"] = $message->spouse;
|
||||||
|
}
|
||||||
|
if ($message->managername) {
|
||||||
|
$ldap["managerName"] = $message->managername;
|
||||||
|
}
|
||||||
|
if ($message->radiophonenumber) {
|
||||||
|
$ldap["radio"] = $message->radiophonenumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ldap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SetReadFlag($folderid, $id, $flags, $contentParameters) {
|
||||||
|
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);
|
||||||
|
foreach ($base_dns as $base_dn) {
|
||||||
|
$folder = explode(":", $base_dn);
|
||||||
|
if ($folder[0] == $folderid) {
|
||||||
|
$base_dn = str_replace('%u', $this->user, $folder[1]);
|
||||||
|
$result_id = ldap_list($this->ldap_link, $base_dn, "(entryUUID=".$id.")", array("entryUUID"));
|
||||||
|
if ($result_id) {
|
||||||
|
$entry_id = ldap_first_entry($this->ldap_link, $result_id);
|
||||||
|
if ($entry_id) {
|
||||||
|
$dn = ldap_get_dn($this->ldap_link, $entry_id);
|
||||||
|
return ldap_delete($this->ldap_link, $dn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function MoveMessage($folderid, $id, $newfolderid, $contentParameters) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->MoveMessage('%s','%s', '%s')", $folderid, $id, $newfolderid));
|
||||||
|
$base_dns = explode("|", LDAP_BASE_DNS);
|
||||||
|
$old = "";
|
||||||
|
$new = "";
|
||||||
|
foreach ($base_dns as $base_dn) {
|
||||||
|
$folder = explode(":", $base_dn);
|
||||||
|
if ($folder[0] == $folderid) {
|
||||||
|
$old = str_replace('%u', $this->user, $folder[1]);
|
||||||
|
}
|
||||||
|
if ($folder[0] == $newfolderid) {
|
||||||
|
$new = str_replace('%u', $this->user, $folder[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$result_id = ldap_list($this->ldap_link, $old, "(entryUUID=".$id.")", array("entryUUID"));
|
||||||
|
if ($result_id) {
|
||||||
|
$entry_id = ldap_first_entry($this->ldap_link, $result_id);
|
||||||
|
if ($entry_id) {
|
||||||
|
$dn = ldap_get_dn($this->ldap_link, $entry_id);
|
||||||
|
$newdn = ldap_explode_dn($dn, 0);
|
||||||
|
return ldap_rename($this->ldap_link, $dn, $newdn[0], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates which AS version is supported by the backend.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string AS version constant
|
||||||
|
*/
|
||||||
|
public function GetSupportedASVersion() {
|
||||||
|
return ZPush::ASV_14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
|
@ -57,6 +57,7 @@
|
||||||
require_once("backend/maildir/config.php");
|
require_once("backend/maildir/config.php");
|
||||||
|
|
||||||
include_once('lib/default/diffbackend/diffbackend.php');
|
include_once('lib/default/diffbackend/diffbackend.php');
|
||||||
|
|
||||||
include_once('include/mimeDecode.php');
|
include_once('include/mimeDecode.php');
|
||||||
require_once('include/z_RFC822.php');
|
require_once('include/z_RFC822.php');
|
||||||
|
|
||||||
|
@ -541,6 +542,22 @@ class BackendMaildir extends BackendDiff {
|
||||||
return true;
|
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
|
* Called when the user has requested to delete (really delete) a message
|
||||||
*
|
*
|
||||||
|
|
|
@ -138,7 +138,7 @@ class BackendSearchLDAP implements ISearchProvider {
|
||||||
$querycnt = $searchresult['count'];
|
$querycnt = $searchresult['count'];
|
||||||
//do not return more results as requested in range
|
//do not return more results as requested in range
|
||||||
$querylimit = (($rangeend + 1) < $querycnt) ? ($rangeend + 1) : $querycnt;
|
$querylimit = (($rangeend + 1) < $querycnt) ? ($rangeend + 1) : $querycnt;
|
||||||
$items['range'] = $rangestart.'-'.($querycnt-1);
|
$items['range'] = $rangestart.'-'.($querylimit-1);
|
||||||
$items['searchtotal'] = $querycnt;
|
$items['searchtotal'] = $querycnt;
|
||||||
|
|
||||||
$rc = 0;
|
$rc = 0;
|
||||||
|
|
|
@ -597,6 +597,22 @@ class BackendVCardDir extends BackendDiff {
|
||||||
return false;
|
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
|
* Called when the user has requested to delete (really delete) a message
|
||||||
*
|
*
|
||||||
|
|
|
@ -452,6 +452,21 @@ class ImportChangesICS implements IImportChanges {
|
||||||
|
|
||||||
return true;
|
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
|
* Imports a move of a message. This occurs when a user moves an item to another folder
|
||||||
|
|
|
@ -603,6 +603,7 @@ define('fbFree' ,0);
|
||||||
define('fbTentative' ,1);
|
define('fbTentative' ,1);
|
||||||
define('fbBusy' ,2);
|
define('fbBusy' ,2);
|
||||||
define('fbOutOfOffice' ,3);
|
define('fbOutOfOffice' ,3);
|
||||||
|
define('fbWorkingElsewhere' ,4);
|
||||||
|
|
||||||
/* ICS flags */
|
/* ICS flags */
|
||||||
|
|
||||||
|
|
|
@ -1165,6 +1165,15 @@ define('PR_EC_STATS_SESSION_LOCKED' ,mapi_prop_tag(PT_BOOLEAN,
|
||||||
define('PR_EC_STATS_SESSION_BUSYSTATES' ,mapi_prop_tag(PT_MV_STRING8, PR_EC_BASE+0x47));
|
define('PR_EC_STATS_SESSION_BUSYSTATES' ,mapi_prop_tag(PT_MV_STRING8, PR_EC_BASE+0x47));
|
||||||
define('PR_EC_COMPANY_NAME' ,mapi_prop_tag(PT_STRING8, PR_EC_BASE+0x48));
|
define('PR_EC_COMPANY_NAME' ,mapi_prop_tag(PT_STRING8, PR_EC_BASE+0x48));
|
||||||
|
|
||||||
|
/* user features */
|
||||||
|
define('PR_EC_ENABLED_FEATURES' ,mapi_prop_tag(PT_MV_TSTRING, PR_EC_BASE+0xB3));
|
||||||
|
define('PR_EC_ENABLED_FEATURES_A' ,mapi_prop_tag(PT_MV_STRING8, PR_EC_BASE+0xB3));
|
||||||
|
define('PR_EC_ENABLED_FEATURES_W' ,mapi_prop_tag(PT_MV_UNICODE, PR_EC_BASE+0xB3));
|
||||||
|
|
||||||
|
define('PR_EC_DISABLED_FEATURES' ,mapi_prop_tag(PT_MV_TSTRING, PR_EC_BASE+0xB4));
|
||||||
|
define('PR_EC_DISABLED_FEATURES_A' ,mapi_prop_tag(PT_MV_STRING8, PR_EC_BASE+0xB4));
|
||||||
|
define('PR_EC_DISABLED_FEATURES_W' ,mapi_prop_tag(PT_MV_UNICODE, PR_EC_BASE+0xB4));
|
||||||
|
|
||||||
/* WA properties */
|
/* WA properties */
|
||||||
define('PR_EC_WA_ATTACHMENT_HIDDEN_OVERRIDE' ,mapi_prop_tag(PT_BOOLEAN, PR_EC_BASE+0xE0));
|
define('PR_EC_WA_ATTACHMENT_HIDDEN_OVERRIDE' ,mapi_prop_tag(PT_BOOLEAN, PR_EC_BASE+0xE0));
|
||||||
|
|
||||||
|
|
|
@ -224,7 +224,7 @@ class MAPIMapping {
|
||||||
"responserequested" => PR_RESPONSE_REQUESTED,
|
"responserequested" => PR_RESPONSE_REQUESTED,
|
||||||
// timezone
|
// timezone
|
||||||
"alldayevent" => "PT_BOOLEAN:PSETID_Appointment:0x8215",
|
"alldayevent" => "PT_BOOLEAN:PSETID_Appointment:0x8215",
|
||||||
"busystatus" => "PT_LONG:PSETID_Appointment:0x8205",
|
"busystatus" => "PT_LONG:PSETID_Appointment:0x8224",
|
||||||
"rtf" => PR_RTF_COMPRESSED,
|
"rtf" => PR_RTF_COMPRESSED,
|
||||||
"dtstamp" => PR_LAST_MODIFICATION_TIME,
|
"dtstamp" => PR_LAST_MODIFICATION_TIME,
|
||||||
"endtime" => "PT_SYSTIME:PSETID_Appointment:0x820e",
|
"endtime" => "PT_SYSTIME:PSETID_Appointment:0x820e",
|
||||||
|
|
|
@ -231,7 +231,7 @@ class MAPIProvider {
|
||||||
// set server default timezone (correct timezone should be configured!)
|
// set server default timezone (correct timezone should be configured!)
|
||||||
$tz = TimezoneUtil::GetFullTZ();
|
$tz = TimezoneUtil::GetFullTZ();
|
||||||
}
|
}
|
||||||
$message->timezone = base64_encode($this->getSyncBlobFromTZ($tz));
|
$message->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz));
|
||||||
|
|
||||||
if(isset($messageprops[$appointmentprops["isrecurring"]]) && $messageprops[$appointmentprops["isrecurring"]]) {
|
if(isset($messageprops[$appointmentprops["isrecurring"]]) && $messageprops[$appointmentprops["isrecurring"]]) {
|
||||||
// Process recurrence
|
// Process recurrence
|
||||||
|
@ -314,6 +314,11 @@ class MAPIProvider {
|
||||||
|
|
||||||
if (!isset($message->nativebodytype)) $message->nativebodytype = $this->getNativeBodyType($messageprops);
|
if (!isset($message->nativebodytype)) $message->nativebodytype = $this->getNativeBodyType($messageprops);
|
||||||
|
|
||||||
|
// If the user is working from a location other than the office the busystatus should be interpreted as free.
|
||||||
|
if (isset($message->busystatus) && $message->busystatus == fbWorkingElsewhere) {
|
||||||
|
$message->busystatus = fbFree;
|
||||||
|
}
|
||||||
|
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,6 +464,10 @@ class MAPIProvider {
|
||||||
if(!isset($syncMessage->exceptions))
|
if(!isset($syncMessage->exceptions))
|
||||||
$syncMessage->exceptions = array();
|
$syncMessage->exceptions = array();
|
||||||
|
|
||||||
|
// If the user is working from a location other than the office the busystatus should be interpreted as free.
|
||||||
|
if (isset($exception->busystatus) && $exception->busystatus == fbWorkingElsewhere) {
|
||||||
|
$exception->busystatus = fbFree;
|
||||||
|
}
|
||||||
array_push($syncMessage->exceptions, $exception);
|
array_push($syncMessage->exceptions, $exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,7 +553,7 @@ class MAPIProvider {
|
||||||
else
|
else
|
||||||
$tz = $this->getGMTTZ();
|
$tz = $this->getGMTTZ();
|
||||||
|
|
||||||
$message->meetingrequest->timezone = base64_encode($this->getSyncBlobFromTZ($tz));
|
$message->meetingrequest->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz));
|
||||||
|
|
||||||
// send basedate if exception
|
// send basedate if exception
|
||||||
if(isset($props[$meetingrequestproperties["recReplTime"]]) ||
|
if(isset($props[$meetingrequestproperties["recReplTime"]]) ||
|
||||||
|
@ -624,6 +633,7 @@ class MAPIProvider {
|
||||||
$req->processMeetingCancellation();
|
$req->processMeetingCancellation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$message->contentclass = DEFAULT_CALENDAR_CONTENTCLASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add attachments
|
// Add attachments
|
||||||
|
@ -746,7 +756,7 @@ class MAPIProvider {
|
||||||
//TODO contentclass and nativebodytype and internetcpid
|
//TODO contentclass and nativebodytype and internetcpid
|
||||||
if (!isset($message->internetcpid)) $message->internetcpid = (defined('STORE_INTERNET_CPID')) ? constant('STORE_INTERNET_CPID') : INTERNET_CPID_WINDOWS1252;
|
if (!isset($message->internetcpid)) $message->internetcpid = (defined('STORE_INTERNET_CPID')) ? constant('STORE_INTERNET_CPID') : INTERNET_CPID_WINDOWS1252;
|
||||||
$this->setFlag($mapimessage, $message);
|
$this->setFlag($mapimessage, $message);
|
||||||
$message->contentclass = DEFAULT_EMAIL_CONTENTCLASS;
|
if (!isset($message->contentclass)) $message->contentclass = DEFAULT_EMAIL_CONTENTCLASS;
|
||||||
if (!isset($message->nativebodytype)) $message->nativebodytype = $this->getNativeBodyType($messageprops);
|
if (!isset($message->nativebodytype)) $message->nativebodytype = $this->getNativeBodyType($messageprops);
|
||||||
|
|
||||||
// reply, reply to all, forward flags
|
// reply, reply to all, forward flags
|
||||||
|
@ -1842,27 +1852,6 @@ class MAPIProvider {
|
||||||
return $tz;
|
return $tz;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pack timezone info for Sync
|
|
||||||
*
|
|
||||||
* @param array $tz
|
|
||||||
*
|
|
||||||
* @access private
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function getSyncBlobFromTZ($tz) {
|
|
||||||
// set the correct TZ name (done using the Bias)
|
|
||||||
if (!isset($tz["tzname"]) || !$tz["tzname"] || !isset($tz["tznamedst"]) || !$tz["tznamedst"])
|
|
||||||
$tz = TimezoneUtil::FillTZNames($tz);
|
|
||||||
|
|
||||||
$packed = pack("la64vvvvvvvv" . "la64vvvvvvvv" . "l",
|
|
||||||
$tz["bias"], $tz["tzname"], 0, $tz["dstendmonth"], $tz["dstendday"], $tz["dstendweek"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"], $tz["dstendmillis"],
|
|
||||||
$tz["stdbias"], $tz["tznamedst"], 0, $tz["dststartmonth"], $tz["dststartday"], $tz["dststartweek"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"], $tz["dststartmillis"],
|
|
||||||
$tz["dstbias"]);
|
|
||||||
|
|
||||||
return $packed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pack timezone info for MAPI
|
* Pack timezone info for MAPI
|
||||||
*
|
*
|
||||||
|
|
|
@ -91,6 +91,9 @@ class BackendZarafa implements IBackend, ISearchProvider {
|
||||||
private $wastebasket;
|
private $wastebasket;
|
||||||
private $addressbook;
|
private $addressbook;
|
||||||
|
|
||||||
|
// ZCP config parameter for PR_EC_ENABLED_FEATURES / PR_EC_DISABLED_FEATURES
|
||||||
|
const ZPUSH_ENABLED = 'mobile';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor of the Zarafa Backend
|
* Constructor of the Zarafa Backend
|
||||||
*
|
*
|
||||||
|
@ -200,6 +203,8 @@ class BackendZarafa implements IBackend, ISearchProvider {
|
||||||
|
|
||||||
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->Logon(): User '%s' is authenticated",$user));
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->Logon(): User '%s' is authenticated",$user));
|
||||||
|
|
||||||
|
$this->isZPushEnabled();
|
||||||
|
|
||||||
// check if this is a Zarafa 7 store with unicode support
|
// check if this is a Zarafa 7 store with unicode support
|
||||||
MAPIUtils::IsUnicodeStore($this->store);
|
MAPIUtils::IsUnicodeStore($this->store);
|
||||||
return true;
|
return true;
|
||||||
|
@ -1180,6 +1185,22 @@ class BackendZarafa implements IBackend, ISearchProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the email address and the display name of the user. Used by autodiscover.
|
||||||
|
*
|
||||||
|
* @param string $username The username
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return Array
|
||||||
|
*/
|
||||||
|
public function GetUserDetails($username) {
|
||||||
|
ZLog::Write(LOGLEVEL_WBXML, sprintf("ZarafaBackend->GetUserDetails for '%s'.", $username));
|
||||||
|
$zarafauserinfo = @mapi_zarafa_getuser_by_name($this->defaultstore, $username);
|
||||||
|
$userDetails['emailaddress'] = (isset($zarafauserinfo['emailaddress']) && $zarafauserinfo['emailaddress']) ? $zarafauserinfo['emailaddress'] : false;
|
||||||
|
$userDetails['fullname'] = (isset($zarafauserinfo['fullname']) && $zarafauserinfo['fullname']) ? $zarafauserinfo['fullname'] : false;
|
||||||
|
return $userDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------------------------------------
|
/**----------------------------------------------------------------------------------------------------------
|
||||||
* Private methods
|
* Private methods
|
||||||
|
@ -1549,8 +1570,11 @@ class BackendZarafa implements IBackend, ISearchProvider {
|
||||||
$searchGreater = strtotime($cpo->GetSearchValueGreater());
|
$searchGreater = strtotime($cpo->GetSearchValueGreater());
|
||||||
$searchLess = strtotime($cpo->GetSearchValueLess());
|
$searchLess = strtotime($cpo->GetSearchValueLess());
|
||||||
|
|
||||||
|
if (version_compare(phpversion(),'5.3.4') < 0) {
|
||||||
|
ZLog::Write(LOGLEVEL_WARN, sprintf("Your system's PHP version (%s) might not correctly process unicode strings. Search containing such characters might not return correct results. It is recommended to update to at least PHP 5.3.4. See ZP-541 for more information.", phpversion()));
|
||||||
|
}
|
||||||
// split the search on whitespache and look for every word
|
// split the search on whitespache and look for every word
|
||||||
$searchText = preg_split("/\W+/", $searchText);
|
$searchText = preg_split("/\W+/u", $searchText);
|
||||||
$searchProps = array(PR_BODY, PR_SUBJECT, PR_DISPLAY_TO, PR_DISPLAY_CC, PR_SENDER_NAME, PR_SENDER_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS);
|
$searchProps = array(PR_BODY, PR_SUBJECT, PR_DISPLAY_TO, PR_DISPLAY_CC, PR_SENDER_NAME, PR_SENDER_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS);
|
||||||
$resAnd = array();
|
$resAnd = array();
|
||||||
foreach($searchText as $term) {
|
foreach($searchText as $term) {
|
||||||
|
@ -1831,6 +1855,25 @@ class BackendZarafa implements IBackend, ISearchProvider {
|
||||||
}
|
}
|
||||||
return $this->addressbook;
|
return $this->addressbook;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the user is not disabled for Z-Push.
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @throws FatalException if user is disabled for Z-Push
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
private function isZPushEnabled() {
|
||||||
|
$addressbook = $this->getAddressbook();
|
||||||
|
$userEntryid = mapi_getprops($this->store, array(PR_MAILBOX_OWNER_ENTRYID));
|
||||||
|
$mailuser = mapi_ab_openentry($addressbook, $userEntryid[PR_MAILBOX_OWNER_ENTRYID]);
|
||||||
|
$enabledFeatures = mapi_getprops($mailuser, array(PR_EC_DISABLED_FEATURES));
|
||||||
|
if (isset($enabledFeatures[PR_EC_DISABLED_FEATURES]) && is_array($enabledFeatures[PR_EC_DISABLED_FEATURES]) && in_array(self::ZPUSH_ENABLED, $enabledFeatures[PR_EC_DISABLED_FEATURES])) {
|
||||||
|
throw new FatalException("User is disabled for Z-Push.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -60,12 +60,68 @@
|
||||||
// This setting specifies the owner parameter in the certificate to look at.
|
// This setting specifies the owner parameter in the certificate to look at.
|
||||||
define("CERTIFICATE_OWNER_PARAMETER", "SSL_CLIENT_S_DN_CN");
|
define("CERTIFICATE_OWNER_PARAMETER", "SSL_CLIENT_S_DN_CN");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whether to use the complete email address as a login name
|
||||||
|
* (e.g. user@company.com) or the username only (user).
|
||||||
|
* This is required for Z-Push to work properly after autodiscover.
|
||||||
|
* Possible values:
|
||||||
|
* false - use the username only (default).
|
||||||
|
* true - use the complete email address.
|
||||||
|
*/
|
||||||
|
define('USE_FULLEMAIL_FOR_LOGIN', false);
|
||||||
|
|
||||||
|
/**********************************************************************************
|
||||||
|
* Device pre-authorization. Useful when using Z-Push as a standalone product.
|
||||||
|
*
|
||||||
|
* It will use the STATE_MACHINE specified below, to store the users/devices
|
||||||
|
* FILE => STATE_DIR/PreAuthUserDevices
|
||||||
|
* SQL => auth_users
|
||||||
|
*
|
||||||
|
* FALSE => default
|
||||||
|
* TRUE
|
||||||
|
*/
|
||||||
|
define('PRE_AUTHORIZE_USERS', false);
|
||||||
|
|
||||||
|
// New users are pre-authorized automatically
|
||||||
|
define('PRE_AUTHORIZE_NEW_USERS', false);
|
||||||
|
|
||||||
|
// New devices are pre-authorized automatically for pre-authorized users
|
||||||
|
define('PRE_AUTHORIZE_NEW_DEVICES', false);
|
||||||
|
|
||||||
|
// Max number of devices pre-authorized for user, you can pre-authorize more manually
|
||||||
|
define('PRE_AUTHORIZE_MAX_DEVICES', 5);
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************************
|
||||||
|
* Select StateMachine mechanism
|
||||||
|
*
|
||||||
|
* FILE => FileStateMachine, default
|
||||||
|
* SQL => SqlStateMachine
|
||||||
|
*/
|
||||||
|
define('STATE_MACHINE', 'FILE');
|
||||||
|
|
||||||
/**********************************************************************************
|
/**********************************************************************************
|
||||||
* Default FileStateMachine settings
|
* Default FileStateMachine settings
|
||||||
*/
|
*/
|
||||||
define('STATE_DIR', '/var/lib/z-push/');
|
define('STATE_DIR', '/var/lib/z-push/');
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************************
|
||||||
|
* Optional SqlStateMachine settings
|
||||||
|
*
|
||||||
|
* DSN: formatted PDO connection string
|
||||||
|
* mysql:host=xxx;port=xxx;dbname=xxx
|
||||||
|
* DON'T FORGET TO INSTALL THE PHP-DRIVER PACKAGE!!!
|
||||||
|
* USER: username to DB
|
||||||
|
* PASSWORD: password to DB
|
||||||
|
* OPTIONS: array with options needed
|
||||||
|
*/
|
||||||
|
define('STATE_SQL_DSN', '');
|
||||||
|
define('STATE_SQL_USER', '');
|
||||||
|
define('STATE_SQL_PASSWORD', '');
|
||||||
|
define('STATE_SQL_OPTIONS', serialize(array(PDO::ATTR_PERSISTENT => true)));
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************************
|
/**********************************************************************************
|
||||||
* Logging settings
|
* Logging settings
|
||||||
* Possible LOGLEVEL and LOGUSERLEVEL values are:
|
* Possible LOGLEVEL and LOGUSERLEVEL values are:
|
||||||
|
@ -192,6 +248,13 @@
|
||||||
// this full list, so this feature is disabled by default. Enable with care.
|
// this full list, so this feature is disabled by default. Enable with care.
|
||||||
define('ALLOW_WEBSERVICE_USERS_ACCESS', false);
|
define('ALLOW_WEBSERVICE_USERS_ACCESS', false);
|
||||||
|
|
||||||
|
// Users with many folders can use the 'partial foldersync' feature, where the server
|
||||||
|
// actively stops processing the folder list if it takes too long. Other requests are
|
||||||
|
// then redirected to the FolderSync to synchronize the remaining items.
|
||||||
|
// Device compatibility for this procedure is not fully understood.
|
||||||
|
// NOTE: THIS IS AN EXPERIMENTAL FEATURE WHICH COULD PREVENT YOUR MOBILES FROM SYNCHRONIZING.
|
||||||
|
define('USE_PARTIAL_FOLDERSYNC', false);
|
||||||
|
|
||||||
/**********************************************************************************
|
/**********************************************************************************
|
||||||
* Backend settings
|
* Backend settings
|
||||||
*/
|
*/
|
||||||
|
|
150
sources/include/Auth/SASL.php
Executable file
150
sources/include/Auth/SASL.php
Executable file
|
@ -0,0 +1,150 @@
|
||||||
|
<?php
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Copyright (c) 2002-2003 Richard Heyes |
|
||||||
|
// | All rights reserved. |
|
||||||
|
// | |
|
||||||
|
// | Redistribution and use in source and binary forms, with or without |
|
||||||
|
// | modification, are permitted provided that the following conditions |
|
||||||
|
// | are met: |
|
||||||
|
// | |
|
||||||
|
// | o Redistributions of source code must retain the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer. |
|
||||||
|
// | o Redistributions in binary form must reproduce the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer in the |
|
||||||
|
// | documentation and/or other materials provided with the distribution.|
|
||||||
|
// | o The names of the authors may not be used to endorse or promote |
|
||||||
|
// | products derived from this software without specific prior written |
|
||||||
|
// | permission. |
|
||||||
|
// | |
|
||||||
|
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
|
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
|
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
|
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
|
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
|
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
|
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
|
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
|
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
|
// | |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Author: Richard Heyes <richard@php.net> |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client implementation of various SASL mechanisms
|
||||||
|
*
|
||||||
|
* @author Richard Heyes <richard@php.net>
|
||||||
|
* @access public
|
||||||
|
* @version 1.0
|
||||||
|
* @package Auth_SASL
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push changes
|
||||||
|
*
|
||||||
|
* removed PEAR dependency by implementing own raiseError()
|
||||||
|
*
|
||||||
|
* Reference implementation used:
|
||||||
|
* http://download.pear.php.net/package/Auth_SASL-1.0.6.tgz
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
//require_once('PEAR.php');
|
||||||
|
|
||||||
|
class Auth_SASL
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Factory class. Returns an object of the request
|
||||||
|
* type.
|
||||||
|
*
|
||||||
|
* @param string $type One of: Anonymous
|
||||||
|
* Plain
|
||||||
|
* CramMD5
|
||||||
|
* DigestMD5
|
||||||
|
* SCRAM-* (any mechanism of the SCRAM family)
|
||||||
|
* Types are not case sensitive
|
||||||
|
*/
|
||||||
|
function &factory($type)
|
||||||
|
{
|
||||||
|
switch (strtolower($type)) {
|
||||||
|
case 'anonymous':
|
||||||
|
$filename = 'include/Auth/SASL/Anonymous.php';
|
||||||
|
$classname = 'Auth_SASL_Anonymous';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'login':
|
||||||
|
$filename = 'include/Auth/SASL/Login.php';
|
||||||
|
$classname = 'Auth_SASL_Login';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'plain':
|
||||||
|
$filename = 'include/Auth/SASL/Plain.php';
|
||||||
|
$classname = 'Auth_SASL_Plain';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'external':
|
||||||
|
$filename = 'include/Auth/SASL/External.php';
|
||||||
|
$classname = 'Auth_SASL_External';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'crammd5':
|
||||||
|
// $msg = 'Deprecated mechanism name. Use IANA-registered name: CRAM-MD5.';
|
||||||
|
// trigger_error($msg, E_USER_DEPRECATED);
|
||||||
|
case 'cram-md5':
|
||||||
|
$filename = 'include/Auth/SASL/CramMD5.php';
|
||||||
|
$classname = 'Auth_SASL_CramMD5';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'digestmd5':
|
||||||
|
// $msg = 'Deprecated mechanism name. Use IANA-registered name: DIGEST-MD5.';
|
||||||
|
// trigger_error($msg, E_USER_DEPRECATED);
|
||||||
|
case 'digest-md5':
|
||||||
|
// $msg = 'DIGEST-MD5 is a deprecated SASL mechanism as per RFC-6331. Using it could be a security risk.';
|
||||||
|
// trigger_error($msg, E_USER_NOTICE);
|
||||||
|
$filename = 'include/Auth/SASL/DigestMD5.php';
|
||||||
|
$classname = 'Auth_SASL_DigestMD5';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$scram = '/^SCRAM-(.{1,9})$/i';
|
||||||
|
if (preg_match($scram, $type, $matches))
|
||||||
|
{
|
||||||
|
$hash = $matches[1];
|
||||||
|
$filename = 'include/Auth/SASL/SCRAM.php';
|
||||||
|
$classname = 'Auth_SASL_SCRAM';
|
||||||
|
$parameter = $hash;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return Auth_SASL::raiseError('Invalid SASL mechanism type');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once($filename);
|
||||||
|
if (isset($parameter))
|
||||||
|
$obj = new $classname($parameter);
|
||||||
|
else
|
||||||
|
$obj = new $classname();
|
||||||
|
return $obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push helper for error logging
|
||||||
|
* removing PEAR dependency
|
||||||
|
*
|
||||||
|
* @param string debug message
|
||||||
|
* @return boolean always false as there was an error
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
static function raiseError($message) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, "Auth_SASL error: ". $message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
71
sources/include/Auth/SASL/Anonymous.php
Executable file
71
sources/include/Auth/SASL/Anonymous.php
Executable file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Copyright (c) 2002-2003 Richard Heyes |
|
||||||
|
// | All rights reserved. |
|
||||||
|
// | |
|
||||||
|
// | Redistribution and use in source and binary forms, with or without |
|
||||||
|
// | modification, are permitted provided that the following conditions |
|
||||||
|
// | are met: |
|
||||||
|
// | |
|
||||||
|
// | o Redistributions of source code must retain the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer. |
|
||||||
|
// | o Redistributions in binary form must reproduce the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer in the |
|
||||||
|
// | documentation and/or other materials provided with the distribution.|
|
||||||
|
// | o The names of the authors may not be used to endorse or promote |
|
||||||
|
// | products derived from this software without specific prior written |
|
||||||
|
// | permission. |
|
||||||
|
// | |
|
||||||
|
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
|
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
|
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
|
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
|
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
|
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
|
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
|
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
|
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
|
// | |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Author: Richard Heyes <richard@php.net> |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implmentation of ANONYMOUS SASL mechanism
|
||||||
|
*
|
||||||
|
* @author Richard Heyes <richard@php.net>
|
||||||
|
* @access public
|
||||||
|
* @version 1.0
|
||||||
|
* @package Auth_SASL
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('include/Auth/SASL/Common.php');
|
||||||
|
|
||||||
|
class Auth_SASL_Anonymous extends Auth_SASL_Common
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Not much to do here except return the token supplied.
|
||||||
|
* No encoding, hashing or encryption takes place for this
|
||||||
|
* mechanism, simply one of:
|
||||||
|
* o An email address
|
||||||
|
* o An opaque string not containing "@" that can be interpreted
|
||||||
|
* by the sysadmin
|
||||||
|
* o Nothing
|
||||||
|
*
|
||||||
|
* We could have some logic here for the second option, but this
|
||||||
|
* would by no means create something interpretable.
|
||||||
|
*
|
||||||
|
* @param string $token Optional email address or string to provide
|
||||||
|
* as trace information.
|
||||||
|
* @return string The unaltered input token
|
||||||
|
*/
|
||||||
|
function getResponse($token = '')
|
||||||
|
{
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
129
sources/include/Auth/SASL/Common.php
Executable file
129
sources/include/Auth/SASL/Common.php
Executable file
|
@ -0,0 +1,129 @@
|
||||||
|
<?php
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Copyright (c) 2002-2003 Richard Heyes |
|
||||||
|
// | All rights reserved. |
|
||||||
|
// | |
|
||||||
|
// | Redistribution and use in source and binary forms, with or without |
|
||||||
|
// | modification, are permitted provided that the following conditions |
|
||||||
|
// | are met: |
|
||||||
|
// | |
|
||||||
|
// | o Redistributions of source code must retain the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer. |
|
||||||
|
// | o Redistributions in binary form must reproduce the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer in the |
|
||||||
|
// | documentation and/or other materials provided with the distribution.|
|
||||||
|
// | o The names of the authors may not be used to endorse or promote |
|
||||||
|
// | products derived from this software without specific prior written |
|
||||||
|
// | permission. |
|
||||||
|
// | |
|
||||||
|
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
|
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
|
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
|
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
|
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
|
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
|
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
|
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
|
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
|
// | |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Author: Richard Heyes <richard@php.net> |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common functionality to SASL mechanisms
|
||||||
|
*
|
||||||
|
* @author Richard Heyes <richard@php.net>
|
||||||
|
* @access public
|
||||||
|
* @version 1.0
|
||||||
|
* @package Auth_SASL
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push changes
|
||||||
|
*
|
||||||
|
* removed PEAR dependency by implementing own raiseError()
|
||||||
|
*
|
||||||
|
* Reference implementation used:
|
||||||
|
* http://download.pear.php.net/package/Auth_SASL-1.0.6.tgz
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Auth_SASL_Common
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Function which implements HMAC MD5 digest
|
||||||
|
*
|
||||||
|
* @param string $key The secret key
|
||||||
|
* @param string $data The data to hash
|
||||||
|
* @param bool $raw_output Whether the digest is returned in binary or hexadecimal format.
|
||||||
|
*
|
||||||
|
* @return string The HMAC-MD5 digest
|
||||||
|
*/
|
||||||
|
function _HMAC_MD5($key, $data, $raw_output = FALSE)
|
||||||
|
{
|
||||||
|
if (strlen($key) > 64) {
|
||||||
|
$key = pack('H32', md5($key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($key) < 64) {
|
||||||
|
$key = str_pad($key, 64, chr(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
$k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
|
||||||
|
$k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
|
||||||
|
|
||||||
|
$inner = pack('H32', md5($k_ipad . $data));
|
||||||
|
$digest = md5($k_opad . $inner, $raw_output);
|
||||||
|
|
||||||
|
return $digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function which implements HMAC-SHA-1 digest
|
||||||
|
*
|
||||||
|
* @param string $key The secret key
|
||||||
|
* @param string $data The data to hash
|
||||||
|
* @param bool $raw_output Whether the digest is returned in binary or hexadecimal format.
|
||||||
|
* @return string The HMAC-SHA-1 digest
|
||||||
|
* @author Jehan <jehan.marmottard@gmail.com>
|
||||||
|
* @access protected
|
||||||
|
*/
|
||||||
|
protected function _HMAC_SHA1($key, $data, $raw_output = FALSE)
|
||||||
|
{
|
||||||
|
if (strlen($key) > 64) {
|
||||||
|
$key = sha1($key, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($key) < 64) {
|
||||||
|
$key = str_pad($key, 64, chr(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
$k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
|
||||||
|
$k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
|
||||||
|
|
||||||
|
$inner = pack('H40', sha1($k_ipad . $data));
|
||||||
|
$digest = sha1($k_opad . $inner, $raw_output);
|
||||||
|
|
||||||
|
return $digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push helper for error logging
|
||||||
|
* removing PEAR dependency
|
||||||
|
*
|
||||||
|
* @param string debug message
|
||||||
|
* @return boolean always false as there was an error
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function raiseError($message) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, "SCRAM error: ". $message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
68
sources/include/Auth/SASL/CramMD5.php
Executable file
68
sources/include/Auth/SASL/CramMD5.php
Executable file
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Copyright (c) 2002-2003 Richard Heyes |
|
||||||
|
// | All rights reserved. |
|
||||||
|
// | |
|
||||||
|
// | Redistribution and use in source and binary forms, with or without |
|
||||||
|
// | modification, are permitted provided that the following conditions |
|
||||||
|
// | are met: |
|
||||||
|
// | |
|
||||||
|
// | o Redistributions of source code must retain the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer. |
|
||||||
|
// | o Redistributions in binary form must reproduce the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer in the |
|
||||||
|
// | documentation and/or other materials provided with the distribution.|
|
||||||
|
// | o The names of the authors may not be used to endorse or promote |
|
||||||
|
// | products derived from this software without specific prior written |
|
||||||
|
// | permission. |
|
||||||
|
// | |
|
||||||
|
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
|
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
|
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
|
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
|
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
|
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
|
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
|
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
|
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
|
// | |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Author: Richard Heyes <richard@php.net> |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implmentation of CRAM-MD5 SASL mechanism
|
||||||
|
*
|
||||||
|
* @author Richard Heyes <richard@php.net>
|
||||||
|
* @access public
|
||||||
|
* @version 1.0
|
||||||
|
* @package Auth_SASL
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('include/Auth/SASL/Common.php');
|
||||||
|
|
||||||
|
class Auth_SASL_CramMD5 extends Auth_SASL_Common
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Implements the CRAM-MD5 SASL mechanism
|
||||||
|
* This DOES NOT base64 encode the return value,
|
||||||
|
* you will need to do that yourself.
|
||||||
|
*
|
||||||
|
* @param string $user Username
|
||||||
|
* @param string $pass Password
|
||||||
|
* @param string $challenge The challenge supplied by the server.
|
||||||
|
* this should be already base64_decoded.
|
||||||
|
*
|
||||||
|
* @return string The string to pass back to the server, of the form
|
||||||
|
* "<user> <digest>". This is NOT base64_encoded.
|
||||||
|
*/
|
||||||
|
function getResponse($user, $pass, $challenge)
|
||||||
|
{
|
||||||
|
return $user . ' ' . $this->_HMAC_MD5($pass, $challenge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
197
sources/include/Auth/SASL/DigestMD5.php
Executable file
197
sources/include/Auth/SASL/DigestMD5.php
Executable file
|
@ -0,0 +1,197 @@
|
||||||
|
<?php
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Copyright (c) 2002-2003 Richard Heyes |
|
||||||
|
// | All rights reserved. |
|
||||||
|
// | |
|
||||||
|
// | Redistribution and use in source and binary forms, with or without |
|
||||||
|
// | modification, are permitted provided that the following conditions |
|
||||||
|
// | are met: |
|
||||||
|
// | |
|
||||||
|
// | o Redistributions of source code must retain the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer. |
|
||||||
|
// | o Redistributions in binary form must reproduce the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer in the |
|
||||||
|
// | documentation and/or other materials provided with the distribution.|
|
||||||
|
// | o The names of the authors may not be used to endorse or promote |
|
||||||
|
// | products derived from this software without specific prior written |
|
||||||
|
// | permission. |
|
||||||
|
// | |
|
||||||
|
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
|
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
|
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
|
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
|
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
|
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
|
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
|
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
|
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
|
// | |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Author: Richard Heyes <richard@php.net> |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implmentation of DIGEST-MD5 SASL mechanism
|
||||||
|
*
|
||||||
|
* @author Richard Heyes <richard@php.net>
|
||||||
|
* @access public
|
||||||
|
* @version 1.0
|
||||||
|
* @package Auth_SASL
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('include/Auth/SASL/Common.php');
|
||||||
|
|
||||||
|
class Auth_SASL_DigestMD5 extends Auth_SASL_Common
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Provides the (main) client response for DIGEST-MD5
|
||||||
|
* requires a few extra parameters than the other
|
||||||
|
* mechanisms, which are unavoidable.
|
||||||
|
*
|
||||||
|
* @param string $authcid Authentication id (username)
|
||||||
|
* @param string $pass Password
|
||||||
|
* @param string $challenge The digest challenge sent by the server
|
||||||
|
* @param string $hostname The hostname of the machine you're connecting to
|
||||||
|
* @param string $service The servicename (eg. imap, pop, acap etc)
|
||||||
|
* @param string $authzid Authorization id (username to proxy as)
|
||||||
|
* @return string The digest response (NOT base64 encoded)
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function getResponse($authcid, $pass, $challenge, $hostname, $service, $authzid = '')
|
||||||
|
{
|
||||||
|
$challenge = $this->_parseChallenge($challenge);
|
||||||
|
$authzid_string = '';
|
||||||
|
if ($authzid != '') {
|
||||||
|
$authzid_string = ',authzid="' . $authzid . '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($challenge)) {
|
||||||
|
$cnonce = $this->_getCnonce();
|
||||||
|
$digest_uri = sprintf('%s/%s', $service, $hostname);
|
||||||
|
$response_value = $this->_getResponseValue($authcid, $pass, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $authzid);
|
||||||
|
|
||||||
|
if ($challenge['realm']) {
|
||||||
|
return sprintf('username="%s",realm="%s"' . $authzid_string .
|
||||||
|
',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
|
||||||
|
} else {
|
||||||
|
return sprintf('username="%s"' . $authzid_string . ',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $this->raiseError('Invalid digest challenge');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and verifies the digest challenge*
|
||||||
|
*
|
||||||
|
* @param string $challenge The digest challenge
|
||||||
|
* @return array The parsed challenge as an assoc
|
||||||
|
* array in the form "directive => value".
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _parseChallenge($challenge)
|
||||||
|
{
|
||||||
|
$tokens = array();
|
||||||
|
while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches)) {
|
||||||
|
|
||||||
|
// Ignore these as per rfc2831
|
||||||
|
if ($matches[1] == 'opaque' OR $matches[1] == 'domain') {
|
||||||
|
$challenge = substr($challenge, strlen($matches[0]) + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed multiple "realm" and "auth-param"
|
||||||
|
if (!empty($tokens[$matches[1]]) AND ($matches[1] == 'realm' OR $matches[1] == 'auth-param')) {
|
||||||
|
if (is_array($tokens[$matches[1]])) {
|
||||||
|
$tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
|
||||||
|
} else {
|
||||||
|
$tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other multiple instance = failure
|
||||||
|
} elseif (!empty($tokens[$matches[1]])) {
|
||||||
|
$tokens = array();
|
||||||
|
break;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the just parsed directive from the challenge
|
||||||
|
$challenge = substr($challenge, strlen($matches[0]) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defaults and required directives
|
||||||
|
*/
|
||||||
|
// Realm
|
||||||
|
if (empty($tokens['realm'])) {
|
||||||
|
$tokens['realm'] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maxbuf
|
||||||
|
if (empty($tokens['maxbuf'])) {
|
||||||
|
$tokens['maxbuf'] = 65536;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required: nonce, algorithm
|
||||||
|
if (empty($tokens['nonce']) OR empty($tokens['algorithm'])) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the response= part of the digest response
|
||||||
|
*
|
||||||
|
* @param string $authcid Authentication id (username)
|
||||||
|
* @param string $pass Password
|
||||||
|
* @param string $realm Realm as provided by the server
|
||||||
|
* @param string $nonce Nonce as provided by the server
|
||||||
|
* @param string $cnonce Client nonce
|
||||||
|
* @param string $digest_uri The digest-uri= value part of the response
|
||||||
|
* @param string $authzid Authorization id
|
||||||
|
* @return string The response= part of the digest response
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _getResponseValue($authcid, $pass, $realm, $nonce, $cnonce, $digest_uri, $authzid = '')
|
||||||
|
{
|
||||||
|
if ($authzid == '') {
|
||||||
|
$A1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce);
|
||||||
|
} else {
|
||||||
|
$A1 = sprintf('%s:%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce, $authzid);
|
||||||
|
}
|
||||||
|
$A2 = 'AUTHENTICATE:' . $digest_uri;
|
||||||
|
return md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($A1), $nonce, $cnonce, md5($A2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the client nonce for the response
|
||||||
|
*
|
||||||
|
* @return string The cnonce value
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _getCnonce()
|
||||||
|
{
|
||||||
|
if (@file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) {
|
||||||
|
return base64_encode(fread($fd, 32));
|
||||||
|
|
||||||
|
} elseif (@file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) {
|
||||||
|
return base64_encode(fread($fd, 32));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$str = '';
|
||||||
|
for ($i=0; $i<32; $i++) {
|
||||||
|
$str .= chr(mt_rand(0, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64_encode($str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
63
sources/include/Auth/SASL/External.php
Normal file
63
sources/include/Auth/SASL/External.php
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Copyright (c) 2008 Christoph Schulz |
|
||||||
|
// | All rights reserved. |
|
||||||
|
// | |
|
||||||
|
// | Redistribution and use in source and binary forms, with or without |
|
||||||
|
// | modification, are permitted provided that the following conditions |
|
||||||
|
// | are met: |
|
||||||
|
// | |
|
||||||
|
// | o Redistributions of source code must retain the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer. |
|
||||||
|
// | o Redistributions in binary form must reproduce the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer in the |
|
||||||
|
// | documentation and/or other materials provided with the distribution.|
|
||||||
|
// | o The names of the authors may not be used to endorse or promote |
|
||||||
|
// | products derived from this software without specific prior written |
|
||||||
|
// | permission. |
|
||||||
|
// | |
|
||||||
|
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
|
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
|
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
|
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
|
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
|
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
|
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
|
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
|
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
|
// | |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Author: Christoph Schulz <develop@kristov.de> |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implmentation of EXTERNAL SASL mechanism
|
||||||
|
*
|
||||||
|
* @author Christoph Schulz <develop@kristov.de>
|
||||||
|
* @access public
|
||||||
|
* @version 1.0.3
|
||||||
|
* @package Auth_SASL
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('include/Auth/SASL/Common.php');
|
||||||
|
|
||||||
|
class Auth_SASL_External extends Auth_SASL_Common
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns EXTERNAL response
|
||||||
|
*
|
||||||
|
* @param string $authcid Authentication id (username)
|
||||||
|
* @param string $pass Password
|
||||||
|
* @param string $authzid Autorization id
|
||||||
|
* @return string EXTERNAL Response
|
||||||
|
*/
|
||||||
|
function getResponse($authcid, $pass, $authzid = '')
|
||||||
|
{
|
||||||
|
return $authzid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
65
sources/include/Auth/SASL/Login.php
Executable file
65
sources/include/Auth/SASL/Login.php
Executable file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Copyright (c) 2002-2003 Richard Heyes |
|
||||||
|
// | All rights reserved. |
|
||||||
|
// | |
|
||||||
|
// | Redistribution and use in source and binary forms, with or without |
|
||||||
|
// | modification, are permitted provided that the following conditions |
|
||||||
|
// | are met: |
|
||||||
|
// | |
|
||||||
|
// | o Redistributions of source code must retain the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer. |
|
||||||
|
// | o Redistributions in binary form must reproduce the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer in the |
|
||||||
|
// | documentation and/or other materials provided with the distribution.|
|
||||||
|
// | o The names of the authors may not be used to endorse or promote |
|
||||||
|
// | products derived from this software without specific prior written |
|
||||||
|
// | permission. |
|
||||||
|
// | |
|
||||||
|
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
|
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
|
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
|
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
|
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
|
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
|
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
|
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
|
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
|
// | |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Author: Richard Heyes <richard@php.net> |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is technically not a SASL mechanism, however
|
||||||
|
* it's used by Net_Sieve, Net_Cyrus and potentially
|
||||||
|
* other protocols , so here is a good place to abstract
|
||||||
|
* it.
|
||||||
|
*
|
||||||
|
* @author Richard Heyes <richard@php.net>
|
||||||
|
* @access public
|
||||||
|
* @version 1.0
|
||||||
|
* @package Auth_SASL
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('include/Auth/SASL/Common.php');
|
||||||
|
|
||||||
|
class Auth_SASL_Login extends Auth_SASL_Common
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Pseudo SASL LOGIN mechanism
|
||||||
|
*
|
||||||
|
* @param string $user Username
|
||||||
|
* @param string $pass Password
|
||||||
|
* @return string LOGIN string
|
||||||
|
*/
|
||||||
|
function getResponse($user, $pass)
|
||||||
|
{
|
||||||
|
return sprintf('LOGIN %s %s', $user, $pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
63
sources/include/Auth/SASL/Plain.php
Executable file
63
sources/include/Auth/SASL/Plain.php
Executable file
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Copyright (c) 2002-2003 Richard Heyes |
|
||||||
|
// | All rights reserved. |
|
||||||
|
// | |
|
||||||
|
// | Redistribution and use in source and binary forms, with or without |
|
||||||
|
// | modification, are permitted provided that the following conditions |
|
||||||
|
// | are met: |
|
||||||
|
// | |
|
||||||
|
// | o Redistributions of source code must retain the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer. |
|
||||||
|
// | o Redistributions in binary form must reproduce the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer in the |
|
||||||
|
// | documentation and/or other materials provided with the distribution.|
|
||||||
|
// | o The names of the authors may not be used to endorse or promote |
|
||||||
|
// | products derived from this software without specific prior written |
|
||||||
|
// | permission. |
|
||||||
|
// | |
|
||||||
|
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
|
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
|
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
|
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
|
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
|
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
|
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
|
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
|
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
|
// | |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Author: Richard Heyes <richard@php.net> |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implmentation of PLAIN SASL mechanism
|
||||||
|
*
|
||||||
|
* @author Richard Heyes <richard@php.net>
|
||||||
|
* @access public
|
||||||
|
* @version 1.0
|
||||||
|
* @package Auth_SASL
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('include/Auth/SASL/Common.php');
|
||||||
|
|
||||||
|
class Auth_SASL_Plain extends Auth_SASL_Common
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns PLAIN response
|
||||||
|
*
|
||||||
|
* @param string $authcid Authentication id (username)
|
||||||
|
* @param string $pass Password
|
||||||
|
* @param string $authzid Autorization id
|
||||||
|
* @return string PLAIN Response
|
||||||
|
*/
|
||||||
|
function getResponse($authcid, $pass, $authzid = '')
|
||||||
|
{
|
||||||
|
return $authzid . chr(0) . $authcid . chr(0) . $pass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
305
sources/include/Auth/SASL/SCRAM.php
Normal file
305
sources/include/Auth/SASL/SCRAM.php
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
<?php
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Copyright (c) 2011 Jehan |
|
||||||
|
// | All rights reserved. |
|
||||||
|
// | |
|
||||||
|
// | Redistribution and use in source and binary forms, with or without |
|
||||||
|
// | modification, are permitted provided that the following conditions |
|
||||||
|
// | are met: |
|
||||||
|
// | |
|
||||||
|
// | o Redistributions of source code must retain the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer. |
|
||||||
|
// | o Redistributions in binary form must reproduce the above copyright |
|
||||||
|
// | notice, this list of conditions and the following disclaimer in the |
|
||||||
|
// | documentation and/or other materials provided with the distribution.|
|
||||||
|
// | o The names of the authors may not be used to endorse or promote |
|
||||||
|
// | products derived from this software without specific prior written |
|
||||||
|
// | permission. |
|
||||||
|
// | |
|
||||||
|
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
|
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||||
|
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||||
|
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
|
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||||
|
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||||
|
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||||
|
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||||
|
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||||
|
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
|
// | |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
// | Author: Jehan <jehan.marmottard@gmail.com |
|
||||||
|
// +-----------------------------------------------------------------------+
|
||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of SCRAM-* SASL mechanisms.
|
||||||
|
* SCRAM mechanisms have 3 main steps (initial response, response to the server challenge, then server signature
|
||||||
|
* verification) which keep state-awareness. Therefore a single class instanciation must be done and reused for the whole
|
||||||
|
* authentication process.
|
||||||
|
*
|
||||||
|
* @author Jehan <jehan.marmottard@gmail.com>
|
||||||
|
* @access public
|
||||||
|
* @version 1.0
|
||||||
|
* @package Auth_SASL
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('include/Auth/SASL/Common.php');
|
||||||
|
|
||||||
|
class Auth_SASL_SCRAM extends Auth_SASL_Common
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Construct a SCRAM-H client where 'H' is a cryptographic hash function.
|
||||||
|
*
|
||||||
|
* @param string $hash The name cryptographic hash function 'H' as registered by IANA in the "Hash Function Textual
|
||||||
|
* Names" registry.
|
||||||
|
* @link http://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xml "Hash Function Textual
|
||||||
|
* Names"
|
||||||
|
* format of core PHP hash function.
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function __construct($hash)
|
||||||
|
{
|
||||||
|
// Though I could be strict, I will actually also accept the naming used in the PHP core hash framework.
|
||||||
|
// For instance "sha1" is accepted, while the registered hash name should be "SHA-1".
|
||||||
|
$hash = strtolower($hash);
|
||||||
|
$hashes = array('md2' => 'md2',
|
||||||
|
'md5' => 'md5',
|
||||||
|
'sha-1' => 'sha1',
|
||||||
|
'sha1' => 'sha1',
|
||||||
|
'sha-224' > 'sha224',
|
||||||
|
'sha224' > 'sha224',
|
||||||
|
'sha-256' => 'sha256',
|
||||||
|
'sha256' => 'sha256',
|
||||||
|
'sha-384' => 'sha384',
|
||||||
|
'sha384' => 'sha384',
|
||||||
|
'sha-512' => 'sha512',
|
||||||
|
'sha512' => 'sha512');
|
||||||
|
if (function_exists('hash_hmac') && isset($hashes[$hash]))
|
||||||
|
{
|
||||||
|
$this->hash = create_function('$data', 'return hash("' . $hashes[$hash] . '", $data, TRUE);');
|
||||||
|
$this->hmac = create_function('$key,$str,$raw', 'return hash_hmac("' . $hashes[$hash] . '", $str, $key, $raw);');
|
||||||
|
}
|
||||||
|
elseif ($hash == 'md5')
|
||||||
|
{
|
||||||
|
$this->hash = create_function('$data', 'return md5($data, true);');
|
||||||
|
$this->hmac = array($this, '_HMAC_MD5');
|
||||||
|
}
|
||||||
|
elseif (in_array($hash, array('sha1', 'sha-1')))
|
||||||
|
{
|
||||||
|
$this->hash = create_function('$data', 'return sha1($data, true);');
|
||||||
|
$this->hmac = array($this, '_HMAC_SHA1');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return $this->raiseError('Invalid SASL mechanism type');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the (main) client response for SCRAM-H.
|
||||||
|
*
|
||||||
|
* @param string $authcid Authentication id (username)
|
||||||
|
* @param string $pass Password
|
||||||
|
* @param string $challenge The challenge sent by the server.
|
||||||
|
* If the challenge is NULL or an empty string, the result will be the "initial response".
|
||||||
|
* @param string $authzid Authorization id (username to proxy as)
|
||||||
|
* @return string|false The response (binary, NOT base64 encoded)
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
public function getResponse($authcid, $pass, $challenge = NULL, $authzid = NULL)
|
||||||
|
{
|
||||||
|
$authcid = $this->_formatName($authcid);
|
||||||
|
if (empty($authcid))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!empty($authzid))
|
||||||
|
{
|
||||||
|
$authzid = $this->_formatName($authzid);
|
||||||
|
if (empty($authzid))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($challenge))
|
||||||
|
{
|
||||||
|
return $this->_generateInitialResponse($authcid, $authzid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->_generateResponse($challenge, $pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a name for inclusion in a SCRAM response.
|
||||||
|
*
|
||||||
|
* @param string $username a name to be prepared.
|
||||||
|
* @return string the reformated name.
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private function _formatName($username)
|
||||||
|
{
|
||||||
|
// TODO: prepare through the SASLprep profile of the stringprep algorithm.
|
||||||
|
// See RFC-4013.
|
||||||
|
|
||||||
|
$username = str_replace('=', '=3D', $username);
|
||||||
|
$username = str_replace(',', '=2C', $username);
|
||||||
|
return $username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the initial response which can be either sent directly in the first message or as a response to an empty
|
||||||
|
* server challenge.
|
||||||
|
*
|
||||||
|
* @param string $authcid Prepared authentication identity.
|
||||||
|
* @param string $authzid Prepared authorization identity.
|
||||||
|
* @return string The SCRAM response to send.
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private function _generateInitialResponse($authcid, $authzid)
|
||||||
|
{
|
||||||
|
$init_rep = '';
|
||||||
|
$gs2_cbind_flag = 'n,'; // TODO: support channel binding.
|
||||||
|
$this->gs2_header = $gs2_cbind_flag . (!empty($authzid)? 'a=' . $authzid : '') . ',';
|
||||||
|
|
||||||
|
// I must generate a client nonce and "save" it for later comparison on second response.
|
||||||
|
$this->cnonce = $this->_getCnonce();
|
||||||
|
// XXX: in the future, when mandatory and/or optional extensions are defined in any updated RFC,
|
||||||
|
// this message can be updated.
|
||||||
|
$this->first_message_bare = 'n=' . $authcid . ',r=' . $this->cnonce;
|
||||||
|
return $this->gs2_header . $this->first_message_bare;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and verifies a non-empty SCRAM challenge.
|
||||||
|
*
|
||||||
|
* @param string $challenge The SCRAM challenge
|
||||||
|
* @return string|false The response to send; false in case of wrong challenge or if an initial response has not
|
||||||
|
* been generated first.
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private function _generateResponse($challenge, $password)
|
||||||
|
{
|
||||||
|
// XXX: as I don't support mandatory extension, I would fail on them.
|
||||||
|
// And I simply ignore any optional extension.
|
||||||
|
$server_message_regexp = "#^r=([\x21-\x2B\x2D-\x7E]+),s=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9]{3}=|[A-Xa-z0-9]{2}==)?),i=([0-9]*)(,[A-Za-z]=[^,])*$#";
|
||||||
|
if (!isset($this->cnonce, $this->gs2_header)
|
||||||
|
|| !preg_match($server_message_regexp, $challenge, $matches))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$nonce = $matches[1];
|
||||||
|
$salt = base64_decode($matches[2]);
|
||||||
|
if (!$salt)
|
||||||
|
{
|
||||||
|
// Invalid Base64.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$i = intval($matches[3]);
|
||||||
|
|
||||||
|
$cnonce = substr($nonce, 0, strlen($this->cnonce));
|
||||||
|
if ($cnonce <> $this->cnonce)
|
||||||
|
{
|
||||||
|
// Invalid challenge! Are we under attack?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$channel_binding = 'c=' . base64_encode($this->gs2_header); // TODO: support channel binding.
|
||||||
|
$final_message = $channel_binding . ',r=' . $nonce; // XXX: no extension.
|
||||||
|
|
||||||
|
// TODO: $password = $this->normalize($password); // SASLprep profile of stringprep.
|
||||||
|
$saltedPassword = $this->hi($password, $salt, $i);
|
||||||
|
$this->saltedPassword = $saltedPassword;
|
||||||
|
$clientKey = call_user_func($this->hmac, $saltedPassword, "Client Key", TRUE);
|
||||||
|
$storedKey = call_user_func($this->hash, $clientKey, TRUE);
|
||||||
|
$authMessage = $this->first_message_bare . ',' . $challenge . ',' . $final_message;
|
||||||
|
$this->authMessage = $authMessage;
|
||||||
|
$clientSignature = call_user_func($this->hmac, $storedKey, $authMessage, TRUE);
|
||||||
|
$clientProof = $clientKey ^ $clientSignature;
|
||||||
|
$proof = ',p=' . base64_encode($clientProof);
|
||||||
|
|
||||||
|
return $final_message . $proof;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SCRAM has also a server verification step. On a successful outcome, it will send additional data which must
|
||||||
|
* absolutely be checked against this function. If this fails, the entity which we are communicating with is probably
|
||||||
|
* not the server as it has not access to your ServerKey.
|
||||||
|
*
|
||||||
|
* @param string $data The additional data sent along a successful outcome.
|
||||||
|
* @return bool Whether the server has been authenticated.
|
||||||
|
* If false, the client must close the connection and consider to be under a MITM attack.
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
public function processOutcome($data)
|
||||||
|
{
|
||||||
|
$verifier_regexp = '#^v=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9]{3}=|[A-Xa-z0-9]{2}==)?)$#';
|
||||||
|
if (!isset($this->saltedPassword, $this->authMessage)
|
||||||
|
|| !preg_match($verifier_regexp, $data, $matches))
|
||||||
|
{
|
||||||
|
// This cannot be an outcome, you never sent the challenge's response.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$verifier = $matches[1];
|
||||||
|
$proposed_serverSignature = base64_decode($verifier);
|
||||||
|
$serverKey = call_user_func($this->hmac, $this->saltedPassword, "Server Key", true);
|
||||||
|
$serverSignature = call_user_func($this->hmac, $serverKey, $this->authMessage, TRUE);
|
||||||
|
return ($proposed_serverSignature === $serverSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hi() call, which is essentially PBKDF2 (RFC-2898) with HMAC-H() as the pseudorandom function.
|
||||||
|
*
|
||||||
|
* @param string $str The string to hash.
|
||||||
|
* @param string $hash The hash value.
|
||||||
|
* @param int $i The iteration count.
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private function hi($str, $salt, $i)
|
||||||
|
{
|
||||||
|
$int1 = "\0\0\0\1";
|
||||||
|
$ui = call_user_func($this->hmac, $str, $salt . $int1, true);
|
||||||
|
$result = $ui;
|
||||||
|
for ($k = 1; $k < $i; $k++)
|
||||||
|
{
|
||||||
|
$ui = call_user_func($this->hmac, $str, $ui, true);
|
||||||
|
$result = $result ^ $ui;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the client nonce for the response
|
||||||
|
*
|
||||||
|
* @return string The cnonce value
|
||||||
|
* @access private
|
||||||
|
* @author Richard Heyes <richard@php.net>
|
||||||
|
*/
|
||||||
|
private function _getCnonce()
|
||||||
|
{
|
||||||
|
// TODO: I reused the nonce function from the DigestMD5 class.
|
||||||
|
// I should probably make this a protected function in Common.
|
||||||
|
if (@file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) {
|
||||||
|
return base64_encode(fread($fd, 32));
|
||||||
|
|
||||||
|
} elseif (@file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) {
|
||||||
|
return base64_encode(fread($fd, 32));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$str = '';
|
||||||
|
for ($i=0; $i<32; $i++) {
|
||||||
|
$str .= chr(mt_rand(0, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64_encode($str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
300
sources/include/Mail.php
Normal file
300
sources/include/Mail.php
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* PEAR's Mail:: interface.
|
||||||
|
*
|
||||||
|
* PHP versions 4 and 5
|
||||||
|
*
|
||||||
|
* LICENSE:
|
||||||
|
*
|
||||||
|
* Copyright (c) 2002-2007, Richard Heyes
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* o Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* o Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* o The names of the authors may not be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior written
|
||||||
|
* permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* @category Mail
|
||||||
|
* @package Mail
|
||||||
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
||||||
|
* @copyright 1997-2010 Chuck Hagenbuch
|
||||||
|
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||||
|
* @version CVS: $Id: Mail.php 307489 2011-01-14 19:06:57Z alec $
|
||||||
|
* @link http://pear.php.net/package/Mail/
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push changes
|
||||||
|
*
|
||||||
|
* removed PEAR dependency by implementing own raiseError()
|
||||||
|
*
|
||||||
|
* Reference implementation used:
|
||||||
|
* http://download.pear.php.net/package/Mail-1.2.0.tgz
|
||||||
|
* SVN trunk version r333509
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PEAR's Mail:: interface. Defines the interface for implementing
|
||||||
|
* mailers under the PEAR hierarchy, and provides supporting functions
|
||||||
|
* useful in multiple mailer backends.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @version $Revision: 307489 $
|
||||||
|
* @package Mail
|
||||||
|
*/
|
||||||
|
class Mail
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Line terminator used for separating header lines.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $sep = "\r\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an interface for generating Mail:: objects of various
|
||||||
|
* types
|
||||||
|
*
|
||||||
|
* @param string $driver The kind of Mail:: object to instantiate.
|
||||||
|
* @param array $params The parameters to pass to the Mail:: object.
|
||||||
|
* @return object Mail a instance of the driver class or if fails a PEAR Error
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
return $mailer;
|
||||||
|
} else {
|
||||||
|
return Mail::raiseError('Unable to find class for driver ' . $driver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements Mail::send() function using php's built-in mail()
|
||||||
|
* command.
|
||||||
|
*
|
||||||
|
* @param mixed $recipients Either a comma-seperated list of recipients
|
||||||
|
* (RFC822 compliant), or an array of recipients,
|
||||||
|
* each RFC822 valid. This may contain recipients not
|
||||||
|
* specified in the headers, for Bcc:, resending
|
||||||
|
* messages, etc.
|
||||||
|
*
|
||||||
|
* @param array $headers The array of headers to send with the mail, in an
|
||||||
|
* associative array, where the array key is the
|
||||||
|
* header name (ie, 'Subject'), and the array value
|
||||||
|
* is the header value (ie, 'test'). The header
|
||||||
|
* produced from those values would be 'Subject:
|
||||||
|
* test'.
|
||||||
|
*
|
||||||
|
* @param string $body The full text of the message body, including any
|
||||||
|
* Mime parts, etc.
|
||||||
|
*
|
||||||
|
* @return mixed Returns true on success, or a PEAR_Error
|
||||||
|
* containing a descriptive error message on
|
||||||
|
* failure.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @deprecated use Mail_mail::send instead
|
||||||
|
*/
|
||||||
|
function send($recipients, $headers, $body)
|
||||||
|
{
|
||||||
|
if (!is_array($headers)) {
|
||||||
|
return Mail::raiseError('$headers must be an array');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->_sanitizeHeaders($headers);
|
||||||
|
//if (is_a($result, 'PEAR_Error')) {
|
||||||
|
if ($result === false) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're passed an array of recipients, implode it.
|
||||||
|
if (is_array($recipients)) {
|
||||||
|
$recipients = implode(', ', $recipients);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the Subject out of the headers array so that we can
|
||||||
|
// pass it as a seperate argument to mail().
|
||||||
|
$subject = '';
|
||||||
|
if (isset($headers['Subject'])) {
|
||||||
|
$subject = $headers['Subject'];
|
||||||
|
unset($headers['Subject']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatten the headers out.
|
||||||
|
list(, $text_headers) = Mail::prepareHeaders($headers);
|
||||||
|
|
||||||
|
return mail($recipients, $subject, $body, $text_headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize an array of mail headers by removing any additional header
|
||||||
|
* strings present in a legitimate header's value. The goal of this
|
||||||
|
* filter is to prevent mail injection attacks.
|
||||||
|
*
|
||||||
|
* @param array $headers The associative array of headers to sanitize.
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _sanitizeHeaders(&$headers)
|
||||||
|
{
|
||||||
|
foreach ($headers as $key => $value) {
|
||||||
|
$headers[$key] =
|
||||||
|
preg_replace('=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i',
|
||||||
|
null, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take an array of mail headers and return a string containing
|
||||||
|
* text usable in sending a message.
|
||||||
|
*
|
||||||
|
* @param array $headers The array of headers to prepare, in an associative
|
||||||
|
* array, where the array key is the header name (ie,
|
||||||
|
* 'Subject'), and the array value is the header
|
||||||
|
* value (ie, 'test'). The header produced from those
|
||||||
|
* values would be 'Subject: test'.
|
||||||
|
*
|
||||||
|
* @return mixed Returns false if it encounters a bad address,
|
||||||
|
* otherwise returns an array containing two
|
||||||
|
* elements: Any From: address found in the headers,
|
||||||
|
* and the plain text version of the headers.
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function prepareHeaders($headers)
|
||||||
|
{
|
||||||
|
$lines = array();
|
||||||
|
$from = null;
|
||||||
|
|
||||||
|
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')) {
|
||||||
|
if ($addresses === false) {
|
||||||
|
return $addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
$from = $addresses[0]->mailbox . '@' . $addresses[0]->host;
|
||||||
|
|
||||||
|
// Reject envelope From: addresses with spaces.
|
||||||
|
if (strstr($from, ' ')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines[] = $key . ': ' . $value;
|
||||||
|
} elseif (strcasecmp($key, 'Received') === 0) {
|
||||||
|
$received = array();
|
||||||
|
if (is_array($value)) {
|
||||||
|
foreach ($value as $line) {
|
||||||
|
$received[] = $key . ': ' . $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$received[] = $key . ': ' . $value;
|
||||||
|
}
|
||||||
|
// Put Received: headers at the top. Spam detectors often
|
||||||
|
// flag messages with Received: headers after the Subject:
|
||||||
|
// as spam.
|
||||||
|
$lines = array_merge($received, $lines);
|
||||||
|
} else {
|
||||||
|
// If $value is an array (i.e., a list of addresses), convert
|
||||||
|
// it to a comma-delimited string of its elements (addresses).
|
||||||
|
if (is_array($value)) {
|
||||||
|
$value = implode(', ', $value);
|
||||||
|
}
|
||||||
|
$lines[] = $key . ': ' . $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array($from, join($this->sep, $lines));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a set of recipients and parse them, returning an array of
|
||||||
|
* bare addresses (forward paths) that can be passed to sendmail
|
||||||
|
* or an smtp server with the rcpt to: command.
|
||||||
|
*
|
||||||
|
* @param mixed Either a comma-seperated list of recipients
|
||||||
|
* (RFC822 compliant), or an array of recipients,
|
||||||
|
* each RFC822 valid.
|
||||||
|
*
|
||||||
|
* @return mixed An array of forward paths (bare addresses) or a PEAR_Error
|
||||||
|
* object if the address list could not be parsed.
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
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)) {
|
||||||
|
$recipients = implode(', ', $recipients);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse recipients, leaving out all personal info. This is
|
||||||
|
// for smtp recipients, etc. All relevant personal information
|
||||||
|
// should already be in the headers.
|
||||||
|
$parser = new Mail_RFC822();
|
||||||
|
$addresses = $parser->parseAddressList($recipients, 'localhost', false);
|
||||||
|
|
||||||
|
// If parseAddressList() returned a PEAR_Error object, just return it.
|
||||||
|
//if (is_a($addresses, 'PEAR_Error')) {
|
||||||
|
if ($addresses === false) {
|
||||||
|
return $addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
$recipients = array();
|
||||||
|
if (is_array($addresses)) {
|
||||||
|
foreach ($addresses as $ob) {
|
||||||
|
$recipients[] = $ob->mailbox . '@' . $ob->host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $recipients;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push helper for error logging
|
||||||
|
* removing PEAR dependency
|
||||||
|
*
|
||||||
|
* @param string debug message
|
||||||
|
* @return boolean always false as there was an error
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
static function raiseError($message) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, "Mail error: ". $message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
193
sources/include/Mail/mail.php
Normal file
193
sources/include/Mail/mail.php
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* internal PHP-mail() implementation of the PEAR Mail:: interface.
|
||||||
|
*
|
||||||
|
* PHP versions 4 and 5
|
||||||
|
*
|
||||||
|
* LICENSE:
|
||||||
|
*
|
||||||
|
* Copyright (c) 2010 Chuck Hagenbuch
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* o Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* o Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* o The names of the authors may not be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior written
|
||||||
|
* permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* @category Mail
|
||||||
|
* @package Mail
|
||||||
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
||||||
|
* @copyright 2010 Chuck Hagenbuch
|
||||||
|
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||||
|
* @version CVS: $Id: mail.php 294747 2010-02-08 08:18:33Z clockwerx $
|
||||||
|
* @link http://pear.php.net/package/Mail/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push changes
|
||||||
|
*
|
||||||
|
* removed PEAR dependency by implementing own raiseError()
|
||||||
|
*
|
||||||
|
* Reference implementation used:
|
||||||
|
* http://download.pear.php.net/package/Mail-1.2.0.tgz
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* internal PHP-mail() implementation of the PEAR Mail:: interface.
|
||||||
|
* @package Mail
|
||||||
|
* @version $Revision: 294747 $
|
||||||
|
*/
|
||||||
|
class Mail_mail extends Mail {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any arguments to pass to the mail() function.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $_params = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* Instantiates a new Mail_mail:: object based on the parameters
|
||||||
|
* passed in.
|
||||||
|
*
|
||||||
|
* @param array $params Extra arguments for the mail() function.
|
||||||
|
*/
|
||||||
|
function Mail_mail($params = null)
|
||||||
|
{
|
||||||
|
// The other mail implementations accept parameters as arrays.
|
||||||
|
// In the interest of being consistent, explode an array into
|
||||||
|
// a string of parameter arguments.
|
||||||
|
if (is_array($params)) {
|
||||||
|
$this->_params = join(' ', $params);
|
||||||
|
} else {
|
||||||
|
$this->_params = $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Because the mail() function may pass headers as command
|
||||||
|
* line arguments, we can't guarantee the use of the standard
|
||||||
|
* "\r\n" separator. Instead, we use the system's native line
|
||||||
|
* separator. */
|
||||||
|
if (defined('PHP_EOL')) {
|
||||||
|
$this->sep = PHP_EOL;
|
||||||
|
} else {
|
||||||
|
$this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements Mail_mail::send() function using php's built-in mail()
|
||||||
|
* command.
|
||||||
|
*
|
||||||
|
* @param mixed $recipients Either a comma-seperated list of recipients
|
||||||
|
* (RFC822 compliant), or an array of recipients,
|
||||||
|
* each RFC822 valid. This may contain recipients not
|
||||||
|
* specified in the headers, for Bcc:, resending
|
||||||
|
* messages, etc.
|
||||||
|
*
|
||||||
|
* @param array $headers The array of headers to send with the mail, in an
|
||||||
|
* associative array, where the array key is the
|
||||||
|
* header name (ie, 'Subject'), and the array value
|
||||||
|
* is the header value (ie, 'test'). The header
|
||||||
|
* produced from those values would be 'Subject:
|
||||||
|
* test'.
|
||||||
|
*
|
||||||
|
* @param string $body The full text of the message body, including any
|
||||||
|
* Mime parts, etc.
|
||||||
|
*
|
||||||
|
* @return mixed Returns true on success, or a PEAR_Error
|
||||||
|
* containing a descriptive error message on
|
||||||
|
* failure.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function send($recipients, $headers, $body)
|
||||||
|
{
|
||||||
|
if (!is_array($headers)) {
|
||||||
|
return Mail_mail::raiseError('$headers must be an array');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->_sanitizeHeaders($headers);
|
||||||
|
//if (is_a($result, 'PEAR_Error')) {
|
||||||
|
if ($result === false) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're passed an array of recipients, implode it.
|
||||||
|
if (is_array($recipients)) {
|
||||||
|
$recipients = implode(', ', $recipients);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the Subject out of the headers array so that we can
|
||||||
|
// pass it as a seperate argument to mail().
|
||||||
|
$subject = '';
|
||||||
|
if (isset($headers['Subject'])) {
|
||||||
|
$subject = $headers['Subject'];
|
||||||
|
unset($headers['Subject']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also remove the To: header. The mail() function will add its own
|
||||||
|
// To: header based on the contents of $recipients.
|
||||||
|
unset($headers['To']);
|
||||||
|
|
||||||
|
// Flatten the headers out.
|
||||||
|
$headerElements = $this->prepareHeaders($headers);
|
||||||
|
//if (is_a($headerElements, 'PEAR_Error')) {
|
||||||
|
if ($headerElements === false) {
|
||||||
|
return $headerElements;
|
||||||
|
}
|
||||||
|
list(, $text_headers) = $headerElements;
|
||||||
|
|
||||||
|
// We only use mail()'s optional fifth parameter if the additional
|
||||||
|
// parameters have been provided and we're not running in safe mode.
|
||||||
|
if (empty($this->_params) || ini_get('safe_mode')) {
|
||||||
|
$result = mail($recipients, $subject, $body, $text_headers);
|
||||||
|
} else {
|
||||||
|
$result = mail($recipients, $subject, $body, $text_headers,
|
||||||
|
$this->_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the mail() function returned failure, we need to create a
|
||||||
|
// PEAR_Error object and return it instead of the boolean result.
|
||||||
|
if ($result === false) {
|
||||||
|
$result = Mail_mail::raiseError('mail() returned failure');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push helper for error logging
|
||||||
|
* removing PEAR dependency
|
||||||
|
*
|
||||||
|
* @param string debug message
|
||||||
|
* @return boolean always false as there was an error
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
static function raiseError($message) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, "Mail<mail> error: ". $message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
197
sources/include/Mail/sendmail.php
Normal file
197
sources/include/Mail/sendmail.php
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
<?php
|
||||||
|
//
|
||||||
|
// +----------------------------------------------------------------------+
|
||||||
|
// | PHP Version 4 |
|
||||||
|
// +----------------------------------------------------------------------+
|
||||||
|
// | Copyright (c) 1997-2003 The PHP Group |
|
||||||
|
// +----------------------------------------------------------------------+
|
||||||
|
// | This source file is subject to version 2.02 of the PHP license, |
|
||||||
|
// | that is bundled with this package in the file LICENSE, and is |
|
||||||
|
// | available at through the world-wide-web at |
|
||||||
|
// | http://www.php.net/license/2_02.txt. |
|
||||||
|
// | If you did not receive a copy of the PHP license and are unable to |
|
||||||
|
// | obtain it through the world-wide-web, please send a note to |
|
||||||
|
// | license@php.net so we can mail you a copy immediately. |
|
||||||
|
// +----------------------------------------------------------------------+
|
||||||
|
// | Author: Chuck Hagenbuch <chuck@horde.org> |
|
||||||
|
// +----------------------------------------------------------------------+
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push changes
|
||||||
|
*
|
||||||
|
* removed PEAR dependency by implementing own raiseError()
|
||||||
|
*
|
||||||
|
* Reference implementation used:
|
||||||
|
* http://download.pear.php.net/package/Mail-1.2.0.tgz
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sendmail implementation of the PEAR Mail:: interface.
|
||||||
|
* @access public
|
||||||
|
* @package Mail
|
||||||
|
* @version $Revision: 294744 $
|
||||||
|
*/
|
||||||
|
class Mail_sendmail extends Mail {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location of the sendmail or sendmail wrapper binary on the
|
||||||
|
* filesystem.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $sendmail_path = '/usr/sbin/sendmail';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any extra command-line parameters to pass to the sendmail or
|
||||||
|
* sendmail wrapper binary.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $sendmail_args = '-i';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* Instantiates a new Mail_sendmail:: object based on the parameters
|
||||||
|
* passed in. It looks for the following parameters:
|
||||||
|
* sendmail_path The location of the sendmail binary on the
|
||||||
|
* filesystem. Defaults to '/usr/sbin/sendmail'.
|
||||||
|
*
|
||||||
|
* sendmail_args Any extra parameters to pass to the sendmail
|
||||||
|
* or sendmail wrapper binary.
|
||||||
|
*
|
||||||
|
* If a parameter is present in the $params array, it replaces the
|
||||||
|
* default.
|
||||||
|
*
|
||||||
|
* @param array $params Hash containing any parameters different from the
|
||||||
|
* defaults.
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function Mail_sendmail($params)
|
||||||
|
{
|
||||||
|
if (isset($params['sendmail_path'])) {
|
||||||
|
$this->sendmail_path = $params['sendmail_path'];
|
||||||
|
}
|
||||||
|
if (isset($params['sendmail_args'])) {
|
||||||
|
$this->sendmail_args = $params['sendmail_args'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Because we need to pass message headers to the sendmail program on
|
||||||
|
* the commandline, we can't guarantee the use of the standard "\r\n"
|
||||||
|
* separator. Instead, we use the system's native line separator.
|
||||||
|
*/
|
||||||
|
if (defined('PHP_EOL')) {
|
||||||
|
$this->sep = PHP_EOL;
|
||||||
|
} else {
|
||||||
|
$this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements Mail::send() function using the sendmail
|
||||||
|
* command-line binary.
|
||||||
|
*
|
||||||
|
* @param mixed $recipients Either a comma-seperated list of recipients
|
||||||
|
* (RFC822 compliant), or an array of recipients,
|
||||||
|
* each RFC822 valid. This may contain recipients not
|
||||||
|
* specified in the headers, for Bcc:, resending
|
||||||
|
* messages, etc.
|
||||||
|
*
|
||||||
|
* @param array $headers The array of headers to send with the mail, in an
|
||||||
|
* associative array, where the array key is the
|
||||||
|
* header name (ie, 'Subject'), and the array value
|
||||||
|
* is the header value (ie, 'test'). The header
|
||||||
|
* produced from those values would be 'Subject:
|
||||||
|
* test'.
|
||||||
|
*
|
||||||
|
* @param string $body The full text of the message body, including any
|
||||||
|
* Mime parts, etc.
|
||||||
|
*
|
||||||
|
* @return mixed Returns true on success, or a PEAR_Error
|
||||||
|
* containing a descriptive error message on
|
||||||
|
* failure.
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function send($recipients, $headers, $body)
|
||||||
|
{
|
||||||
|
if (!is_array($headers)) {
|
||||||
|
return Mail_sendmail::raiseError('$headers must be an array');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->_sanitizeHeaders($headers);
|
||||||
|
//if (is_a($result, 'PEAR_Error')) {
|
||||||
|
if ($result === false) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$recipients = $this->parseRecipients($recipients);
|
||||||
|
//if (is_a($recipients, 'PEAR_Error')) {
|
||||||
|
if ($recipients === false) {
|
||||||
|
return $recipients;
|
||||||
|
}
|
||||||
|
$recipients = implode(' ', array_map('escapeshellarg', $recipients));
|
||||||
|
|
||||||
|
$headerElements = $this->prepareHeaders($headers);
|
||||||
|
//if (is_a($headerElements, 'PEAR_Error')) {
|
||||||
|
if ($headerElements === false) {
|
||||||
|
return $headerElements;
|
||||||
|
}
|
||||||
|
list($from, $text_headers) = $headerElements;
|
||||||
|
|
||||||
|
/* Since few MTAs are going to allow this header to be forged
|
||||||
|
* unless it's in the MAIL FROM: exchange, we'll use
|
||||||
|
* Return-Path instead of From: if it's set. */
|
||||||
|
if (!empty($headers['Return-Path'])) {
|
||||||
|
$from = $headers['Return-Path'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($from)) {
|
||||||
|
return Mail_sendmail::raiseError('No from address given.');
|
||||||
|
} elseif (strpos($from, ' ') !== false ||
|
||||||
|
strpos($from, ';') !== false ||
|
||||||
|
strpos($from, '&') !== false ||
|
||||||
|
strpos($from, '`') !== false) {
|
||||||
|
return Mail_sendmail::raiseError('From address specified with dangerous characters.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$from = escapeshellarg($from); // Security bug #16200
|
||||||
|
|
||||||
|
$mail = @popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w');
|
||||||
|
if (!$mail) {
|
||||||
|
return Mail_sendmail::raiseError('Failed to open sendmail [' . $this->sendmail_path . '] for execution.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the headers following by two newlines: one to end the headers
|
||||||
|
// section and a second to separate the headers block from the body.
|
||||||
|
fputs($mail, $text_headers . $this->sep . $this->sep);
|
||||||
|
|
||||||
|
fputs($mail, $body);
|
||||||
|
$result = pclose($mail);
|
||||||
|
if (version_compare(phpversion(), '4.2.3') == -1) {
|
||||||
|
// With older php versions, we need to shift the pclose
|
||||||
|
// result to get the exit code.
|
||||||
|
$result = $result >> 8 & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result != 0) {
|
||||||
|
return Mail_sendmail::raiseError('sendmail returned error code ' . $result,
|
||||||
|
$result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push helper for error logging
|
||||||
|
* removing PEAR dependency
|
||||||
|
*
|
||||||
|
* @param string debug message
|
||||||
|
* @return boolean always false as there was an error
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
static function raiseError($message) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, "Mail<sendmail> error: ". $message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
490
sources/include/Mail/smtp.php
Normal file
490
sources/include/Mail/smtp.php
Normal file
|
@ -0,0 +1,490 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
|
||||||
|
*
|
||||||
|
* PHP versions 4 and 5
|
||||||
|
*
|
||||||
|
* LICENSE:
|
||||||
|
*
|
||||||
|
* Copyright (c) 2010, Chuck Hagenbuch
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* o Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* o Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* o The names of the authors may not be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior written
|
||||||
|
* permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* @category HTTP
|
||||||
|
* @package HTTP_Request
|
||||||
|
* @author Jon Parise <jon@php.net>
|
||||||
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
||||||
|
* @copyright 2010 Chuck Hagenbuch
|
||||||
|
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||||
|
* @version CVS: $Id: smtp.php 307488 2011-01-14 19:00:54Z alec $
|
||||||
|
* @link http://pear.php.net/package/Mail/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push changes
|
||||||
|
*
|
||||||
|
* removed PEAR dependency by implementing own raiseError()
|
||||||
|
*
|
||||||
|
* Reference implementation used:
|
||||||
|
* http://download.pear.php.net/package/Mail-1.2.0.tgz
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Error: Failed to create a Net_SMTP object */
|
||||||
|
define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000);
|
||||||
|
|
||||||
|
/** Error: Failed to connect to SMTP server */
|
||||||
|
define('PEAR_MAIL_SMTP_ERROR_CONNECT', 10001);
|
||||||
|
|
||||||
|
/** Error: SMTP authentication failure */
|
||||||
|
define('PEAR_MAIL_SMTP_ERROR_AUTH', 10002);
|
||||||
|
|
||||||
|
/** Error: No From: address has been provided */
|
||||||
|
define('PEAR_MAIL_SMTP_ERROR_FROM', 10003);
|
||||||
|
|
||||||
|
/** Error: Failed to set sender */
|
||||||
|
define('PEAR_MAIL_SMTP_ERROR_SENDER', 10004);
|
||||||
|
|
||||||
|
/** Error: Failed to add recipient */
|
||||||
|
define('PEAR_MAIL_SMTP_ERROR_RECIPIENT', 10005);
|
||||||
|
|
||||||
|
/** Error: Failed to send data */
|
||||||
|
define('PEAR_MAIL_SMTP_ERROR_DATA', 10006);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
|
||||||
|
* @access public
|
||||||
|
* @package Mail
|
||||||
|
* @version $Revision: 307488 $
|
||||||
|
*/
|
||||||
|
class Mail_smtp extends Mail {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMTP connection object.
|
||||||
|
*
|
||||||
|
* @var object
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
var $_smtp = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of service extension parameters to pass to the Net_SMTP
|
||||||
|
* mailFrom() command.
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
var $_extparams = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SMTP host to connect to.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $host = 'localhost';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The port the SMTP server is on.
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
var $port = 25;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should SMTP authentication be used?
|
||||||
|
*
|
||||||
|
* This value may be set to true, false or the name of a specific
|
||||||
|
* authentication method.
|
||||||
|
*
|
||||||
|
* If the value is set to true, the Net_SMTP package will attempt to use
|
||||||
|
* the best authentication method advertised by the remote SMTP server.
|
||||||
|
*
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
|
var $auth = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The username to use if the SMTP server requires authentication.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $username = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The password to use if the SMTP server requires authentication.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $password = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hostname or domain that will be sent to the remote SMTP server in the
|
||||||
|
* HELO / EHLO message.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
var $localhost = 'localhost';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMTP connection timeout value. NULL indicates no timeout.
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
var $timeout = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on Net_SMTP debugging?
|
||||||
|
*
|
||||||
|
* @var boolean $debug
|
||||||
|
*/
|
||||||
|
var $debug = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the SMTP connection should persist over
|
||||||
|
* multiple calls to the send() method.
|
||||||
|
*
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
var $persist = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use SMTP command pipelining (specified in RFC 2920) if the SMTP server
|
||||||
|
* supports it. This speeds up delivery over high-latency connections. By
|
||||||
|
* default, use the default value supplied by Net_SMTP.
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
var $pipelining;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* Instantiates a new Mail_smtp:: object based on the parameters
|
||||||
|
* passed in. It looks for the following parameters:
|
||||||
|
* host The server to connect to. Defaults to localhost.
|
||||||
|
* port The port to connect to. Defaults to 25.
|
||||||
|
* auth SMTP authentication. Defaults to none.
|
||||||
|
* username The username to use for SMTP auth. No default.
|
||||||
|
* password The password to use for SMTP auth. No default.
|
||||||
|
* localhost The local hostname / domain. Defaults to localhost.
|
||||||
|
* timeout The SMTP connection timeout. Defaults to none.
|
||||||
|
* verp Whether to use VERP or not. Defaults to false.
|
||||||
|
* DEPRECATED as of 1.2.0 (use setMailParams()).
|
||||||
|
* debug Activate SMTP debug mode? Defaults to false.
|
||||||
|
* persist Should the SMTP connection persist?
|
||||||
|
* pipelining Use SMTP command pipelining
|
||||||
|
*
|
||||||
|
* If a parameter is present in the $params array, it replaces the
|
||||||
|
* default.
|
||||||
|
*
|
||||||
|
* @param array Hash containing any parameters different from the
|
||||||
|
* defaults.
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function Mail_smtp($params)
|
||||||
|
{
|
||||||
|
if (isset($params['host'])) $this->host = $params['host'];
|
||||||
|
if (isset($params['port'])) $this->port = $params['port'];
|
||||||
|
if (isset($params['auth'])) $this->auth = $params['auth'];
|
||||||
|
if (isset($params['username'])) $this->username = $params['username'];
|
||||||
|
if (isset($params['password'])) $this->password = $params['password'];
|
||||||
|
if (isset($params['localhost'])) $this->localhost = $params['localhost'];
|
||||||
|
if (isset($params['timeout'])) $this->timeout = $params['timeout'];
|
||||||
|
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'];
|
||||||
|
|
||||||
|
// Deprecated options
|
||||||
|
if (isset($params['verp'])) {
|
||||||
|
$this->addServiceExtensionParameter('XVERP', is_bool($params['verp']) ? null : $params['verp']);
|
||||||
|
}
|
||||||
|
|
||||||
|
register_shutdown_function(array(&$this, '_Mail_smtp'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor implementation to ensure that we disconnect from any
|
||||||
|
* potentially-alive persistent SMTP connections.
|
||||||
|
*/
|
||||||
|
function _Mail_smtp()
|
||||||
|
{
|
||||||
|
$this->disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements Mail::send() function using SMTP.
|
||||||
|
*
|
||||||
|
* @param mixed $recipients Either a comma-seperated list of recipients
|
||||||
|
* (RFC822 compliant), or an array of recipients,
|
||||||
|
* each RFC822 valid. This may contain recipients not
|
||||||
|
* specified in the headers, for Bcc:, resending
|
||||||
|
* messages, etc.
|
||||||
|
*
|
||||||
|
* @param array $headers The array of headers to send with the mail, in an
|
||||||
|
* associative array, where the array key is the
|
||||||
|
* header name (e.g., 'Subject'), and the array value
|
||||||
|
* is the header value (e.g., 'test'). The header
|
||||||
|
* produced from those values would be 'Subject:
|
||||||
|
* test'.
|
||||||
|
*
|
||||||
|
* @param string $body The full text of the message body, including any
|
||||||
|
* MIME parts, etc.
|
||||||
|
*
|
||||||
|
* @return mixed Returns true on success, or a PEAR_Error
|
||||||
|
* containing a descriptive error message on
|
||||||
|
* failure.
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function send($recipients, $headers, $body)
|
||||||
|
{
|
||||||
|
/* If we don't already have an SMTP object, create one. */
|
||||||
|
$result = &$this->getSMTPObject();
|
||||||
|
//if (PEAR::isError($result)) {
|
||||||
|
if ($result === false) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($headers)) {
|
||||||
|
return Mail_smtp::raiseError('$headers must be an array');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_sanitizeHeaders($headers);
|
||||||
|
|
||||||
|
$headerElements = $this->prepareHeaders($headers);
|
||||||
|
//if (is_a($headerElements, 'PEAR_Error')) {
|
||||||
|
if ($headerElements === false) {
|
||||||
|
$this->_smtp->rset();
|
||||||
|
return $headerElements;
|
||||||
|
}
|
||||||
|
list($from, $textHeaders) = $headerElements;
|
||||||
|
|
||||||
|
/* Since few MTAs are going to allow this header to be forged
|
||||||
|
* unless it's in the MAIL FROM: exchange, we'll use
|
||||||
|
* Return-Path instead of From: if it's set. */
|
||||||
|
if (!empty($headers['Return-Path'])) {
|
||||||
|
$from = $headers['Return-Path'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($from)) {
|
||||||
|
$this->_smtp->rset();
|
||||||
|
return Mail_smtp::raiseError('No From: address has been provided',
|
||||||
|
PEAR_MAIL_SMTP_ERROR_FROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = null;
|
||||||
|
if (!empty($this->_extparams)) {
|
||||||
|
foreach ($this->_extparams as $key => $val) {
|
||||||
|
$params .= ' ' . $key . (is_null($val) ? '' : '=' . $val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if (PEAR::isError($res = $this->_smtp->mailFrom($from, ltrim($params)))) {
|
||||||
|
if (($res = $this->_smtp->mailFrom($from, ltrim($params))) === false) {
|
||||||
|
$error = $this->_error("Failed to set sender: $from", $res);
|
||||||
|
$this->_smtp->rset();
|
||||||
|
return Mail_smtp::raiseError($error, PEAR_MAIL_SMTP_ERROR_SENDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
$recipients = $this->parseRecipients($recipients);
|
||||||
|
//if (is_a($recipients, 'PEAR_Error')) {
|
||||||
|
if ($recipients === false) {
|
||||||
|
$this->_smtp->rset();
|
||||||
|
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')) {
|
||||||
|
if ($res === false) {
|
||||||
|
$error = $this->_error("Failed to add recipient: $recipient", $res);
|
||||||
|
$this->_smtp->rset();
|
||||||
|
return Mail_smtp::raiseError($error, PEAR_MAIL_SMTP_ERROR_RECIPIENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send the message's headers and the body as SMTP data. */
|
||||||
|
$res = $this->_smtp->data($body, $textHeaders);
|
||||||
|
list(,$args) = $this->_smtp->getResponse();
|
||||||
|
|
||||||
|
if (preg_match("/Ok: queued as (.*)/", $args, $queued)) {
|
||||||
|
$this->queued_as = $queued[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we need the greeting; from it we can extract the authorative name of the mail server we've really connected to.
|
||||||
|
* ideal if we're connecting to a round-robin of relay servers and need to track which exact one took the email */
|
||||||
|
$this->greeting = $this->_smtp->getGreeting();
|
||||||
|
|
||||||
|
//if (is_a($res, 'PEAR_Error')) {
|
||||||
|
if ($res === false) {
|
||||||
|
$error = $this->_error('Failed to send data', $res);
|
||||||
|
$this->_smtp->rset();
|
||||||
|
return Mail_smtp::raiseError($error, PEAR_MAIL_SMTP_ERROR_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If persistent connections are disabled, destroy our SMTP object. */
|
||||||
|
if ($this->persist === false) {
|
||||||
|
$this->disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the SMTP server by instantiating a Net_SMTP object.
|
||||||
|
*
|
||||||
|
* @return mixed Returns a reference to the Net_SMTP object on success, or
|
||||||
|
* a PEAR_Error containing a descriptive error message on
|
||||||
|
* failure.
|
||||||
|
*
|
||||||
|
* @since 1.2.0
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function &getSMTPObject()
|
||||||
|
{
|
||||||
|
if (is_object($this->_smtp) !== false) {
|
||||||
|
return $this->_smtp;
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once 'include/Net/SMTP.php';
|
||||||
|
$this->_smtp = &new Net_SMTP($this->host,
|
||||||
|
$this->port,
|
||||||
|
$this->localhost,
|
||||||
|
$this->pipelining);
|
||||||
|
|
||||||
|
/* If we still don't have an SMTP object at this point, fail. */
|
||||||
|
if (is_object($this->_smtp) === false) {
|
||||||
|
return Mail_smtp::raiseError('Failed to create a Net_SMTP object',
|
||||||
|
PEAR_MAIL_SMTP_ERROR_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configure the SMTP connection. */
|
||||||
|
if ($this->debug) {
|
||||||
|
$this->_smtp->setDebug(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attempt to connect to the configured SMTP server. */
|
||||||
|
//if (PEAR::isError($res = $this->_smtp->connect($this->timeout))) {
|
||||||
|
if (($res = $this->_smtp->connect($this->timeout)) === false) {
|
||||||
|
$error = $this->_error('Failed to connect to ' .
|
||||||
|
$this->host . ':' . $this->port,
|
||||||
|
$res);
|
||||||
|
return Mail_smtp::raiseError($error, PEAR_MAIL_SMTP_ERROR_CONNECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attempt to authenticate if authentication has been enabled. */
|
||||||
|
if ($this->auth) {
|
||||||
|
$method = is_string($this->auth) ? $this->auth : '';
|
||||||
|
|
||||||
|
//if (PEAR::isError($res = $this->_smtp->auth($this->username, $this->password, $method))) {
|
||||||
|
if (($res = $this->_smtp->auth($this->username, $this->password, $method)) === false) {
|
||||||
|
$error = $this->_error("$method authentication failure",
|
||||||
|
$res);
|
||||||
|
$this->_smtp->rset();
|
||||||
|
return Mail_smtp::raiseError($error, PEAR_MAIL_SMTP_ERROR_AUTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_smtp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add parameter associated with a SMTP service extension.
|
||||||
|
*
|
||||||
|
* @param string Extension keyword.
|
||||||
|
* @param string Any value the keyword needs.
|
||||||
|
*
|
||||||
|
* @since 1.2.0
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function addServiceExtensionParameter($keyword, $value = null)
|
||||||
|
{
|
||||||
|
$this->_extparams[$keyword] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect and destroy the current SMTP connection.
|
||||||
|
*
|
||||||
|
* @return boolean True if the SMTP connection no longer exists.
|
||||||
|
*
|
||||||
|
* @since 1.1.9
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
function disconnect()
|
||||||
|
{
|
||||||
|
/* If we have an SMTP object, disconnect and destroy it. */
|
||||||
|
if (is_object($this->_smtp) && $this->_smtp->disconnect()) {
|
||||||
|
$this->_smtp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We are disconnected if we no longer have an SMTP object. */
|
||||||
|
return ($this->_smtp === null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a standardized string describing the current SMTP error.
|
||||||
|
*
|
||||||
|
* @param string $text Custom string describing the error context.
|
||||||
|
* @param object $error Reference to the current PEAR_Error object.
|
||||||
|
*
|
||||||
|
* @return string A string describing the current SMTP error.
|
||||||
|
*
|
||||||
|
* @since 1.1.7
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _error($text, &$error)
|
||||||
|
{
|
||||||
|
/* Split the SMTP response into a code and a response string. */
|
||||||
|
list($code, $response) = $this->_smtp->getResponse();
|
||||||
|
|
||||||
|
/* Build our standardized error string. */
|
||||||
|
return $text
|
||||||
|
. ' [SMTP: ' . $error->getMessage()
|
||||||
|
. " (code: $code, response: $response)]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push helper for error logging
|
||||||
|
* removing PEAR dependency
|
||||||
|
*
|
||||||
|
* @param string debug message
|
||||||
|
* @return boolean always false as there was an error
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
static function raiseError($message) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, "Mail<smtp> error: ". $message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
1254
sources/include/Net/SMTP.php
Normal file
1254
sources/include/Net/SMTP.php
Normal file
File diff suppressed because it is too large
Load diff
716
sources/include/Net/Socket.php
Normal file
716
sources/include/Net/Socket.php
Normal file
|
@ -0,0 +1,716 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Net_Socket
|
||||||
|
*
|
||||||
|
* PHP Version 4
|
||||||
|
*
|
||||||
|
* Copyright (c) 1997-2013 The PHP Group
|
||||||
|
*
|
||||||
|
* This source file is subject to version 2.0 of the PHP license,
|
||||||
|
* that is bundled with this package in the file LICENSE, and is
|
||||||
|
* available at through the world-wide-web at
|
||||||
|
* http://www.php.net/license/2_02.txt.
|
||||||
|
* If you did not receive a copy of the PHP license and are unable to
|
||||||
|
* obtain it through the world-wide-web, please send a note to
|
||||||
|
* license@php.net so we can mail you a copy immediately.
|
||||||
|
*
|
||||||
|
* Authors: Stig Bakken <ssb@php.net>
|
||||||
|
* Chuck Hagenbuch <chuck@horde.org>
|
||||||
|
*
|
||||||
|
* @category Net
|
||||||
|
* @package Net_Socket
|
||||||
|
* @author Stig Bakken <ssb@php.net>
|
||||||
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
||||||
|
* @copyright 1997-2003 The PHP Group
|
||||||
|
* @license http://www.php.net/license/2_02.txt PHP 2.02
|
||||||
|
* @link http://pear.php.net/packages/Net_Socket
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push changes
|
||||||
|
*
|
||||||
|
* removed PEAR dependency by implementing own raiseError(), and defining OS_WINDOWS
|
||||||
|
*
|
||||||
|
* Reference implementation used:
|
||||||
|
* http://download.pear.php.net/package/Net_Socket-1.0.14.tgz
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
//require_once 'PEAR.php';
|
||||||
|
|
||||||
|
if (substr(PHP_OS, 0, 3) == 'WIN') {
|
||||||
|
define('OS_WINDOWS', true);
|
||||||
|
} else {
|
||||||
|
define('OS_WINDOWS', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
define('NET_SOCKET_READ', 1);
|
||||||
|
define('NET_SOCKET_WRITE', 2);
|
||||||
|
define('NET_SOCKET_ERROR', 4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generalized Socket class.
|
||||||
|
*
|
||||||
|
* @category Net
|
||||||
|
* @package Net_Socket
|
||||||
|
* @author Stig Bakken <ssb@php.net>
|
||||||
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
||||||
|
* @copyright 1997-2003 The PHP Group
|
||||||
|
* @license http://www.php.net/license/2_02.txt PHP 2.02
|
||||||
|
* @link http://pear.php.net/packages/Net_Socket
|
||||||
|
*/
|
||||||
|
//class Net_Socket extends PEAR
|
||||||
|
class Net_Socket
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Socket file pointer.
|
||||||
|
* @var resource $fp
|
||||||
|
*/
|
||||||
|
var $fp = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the socket is blocking. Defaults to true.
|
||||||
|
* @var boolean $blocking
|
||||||
|
*/
|
||||||
|
var $blocking = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the socket is persistent. Defaults to false.
|
||||||
|
* @var boolean $persistent
|
||||||
|
*/
|
||||||
|
var $persistent = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The IP address to connect to.
|
||||||
|
* @var string $addr
|
||||||
|
*/
|
||||||
|
var $addr = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The port number to connect to.
|
||||||
|
* @var integer $port
|
||||||
|
*/
|
||||||
|
var $port = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of seconds to wait on socket operations before assuming
|
||||||
|
* there's no more data. Defaults to no timeout.
|
||||||
|
* @var integer|float $timeout
|
||||||
|
*/
|
||||||
|
var $timeout = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bytes to read at a time in readLine() and
|
||||||
|
* readAll(). Defaults to 2048.
|
||||||
|
* @var integer $lineLength
|
||||||
|
*/
|
||||||
|
var $lineLength = 2048;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The string to use as a newline terminator. Usually "\r\n" or "\n".
|
||||||
|
* @var string $newline
|
||||||
|
*/
|
||||||
|
var $newline = "\r\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the specified port. If called when the socket is
|
||||||
|
* already connected, it disconnects and connects again.
|
||||||
|
*
|
||||||
|
* @param string $addr IP address or host name (may be with protocol prefix).
|
||||||
|
* @param integer $port TCP port number.
|
||||||
|
* @param boolean $persistent (optional) Whether the connection is
|
||||||
|
* persistent (kept open between requests
|
||||||
|
* by the web server).
|
||||||
|
* @param integer $timeout (optional) Connection socket timeout.
|
||||||
|
* @param array $options See options for stream_context_create.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*
|
||||||
|
* @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
|
||||||
|
*/
|
||||||
|
function connect($addr, $port = 0, $persistent = null,
|
||||||
|
$timeout = null, $options = null)
|
||||||
|
{
|
||||||
|
if (is_resource($this->fp)) {
|
||||||
|
@fclose($this->fp);
|
||||||
|
$this->fp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$addr) {
|
||||||
|
return $this->raiseError('$addr cannot be empty');
|
||||||
|
} else if (strspn($addr, ':.0123456789') == strlen($addr)) {
|
||||||
|
$this->addr = strpos($addr, ':') !== false ? '['.$addr.']' : $addr;
|
||||||
|
} else {
|
||||||
|
$this->addr = $addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->port = $port % 65536;
|
||||||
|
|
||||||
|
if ($persistent !== null) {
|
||||||
|
$this->persistent = $persistent;
|
||||||
|
}
|
||||||
|
|
||||||
|
$openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
|
||||||
|
$errno = 0;
|
||||||
|
$errstr = '';
|
||||||
|
|
||||||
|
$old_track_errors = @ini_set('track_errors', 1);
|
||||||
|
|
||||||
|
if ($timeout <= 0) {
|
||||||
|
$timeout = @ini_get('default_socket_timeout');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($options && function_exists('stream_context_create')) {
|
||||||
|
$context = stream_context_create($options);
|
||||||
|
|
||||||
|
// Since PHP 5 fsockopen doesn't allow context specification
|
||||||
|
if (function_exists('stream_socket_client')) {
|
||||||
|
$flags = STREAM_CLIENT_CONNECT;
|
||||||
|
|
||||||
|
if ($this->persistent) {
|
||||||
|
$flags = STREAM_CLIENT_PERSISTENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
$addr = $this->addr . ':' . $this->port;
|
||||||
|
$fp = stream_socket_client($addr, $errno, $errstr,
|
||||||
|
$timeout, $flags, $context);
|
||||||
|
} else {
|
||||||
|
$fp = @$openfunc($this->addr, $this->port, $errno,
|
||||||
|
$errstr, $timeout, $context);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$fp) {
|
||||||
|
if ($errno == 0 && !strlen($errstr) && isset($php_errormsg)) {
|
||||||
|
$errstr = $php_errormsg;
|
||||||
|
}
|
||||||
|
@ini_set('track_errors', $old_track_errors);
|
||||||
|
return $this->raiseError($errstr, $errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ini_set('track_errors', $old_track_errors);
|
||||||
|
$this->fp = $fp;
|
||||||
|
$this->setTimeout();
|
||||||
|
return $this->setBlocking($this->blocking);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects from the peer, closes the socket.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed true on success or a PEAR_Error instance otherwise
|
||||||
|
*/
|
||||||
|
function disconnect()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
@fclose($this->fp);
|
||||||
|
$this->fp = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the newline character/sequence to use.
|
||||||
|
*
|
||||||
|
* @param string $newline Newline character(s)
|
||||||
|
* @return boolean True
|
||||||
|
*/
|
||||||
|
function setNewline($newline)
|
||||||
|
{
|
||||||
|
$this->newline = $newline;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find out if the socket is in blocking mode.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean The current blocking mode.
|
||||||
|
*/
|
||||||
|
function isBlocking()
|
||||||
|
{
|
||||||
|
return $this->blocking;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the socket connection should be blocking or
|
||||||
|
* not. A read call to a non-blocking socket will return immediately
|
||||||
|
* if there is no data available, whereas it will block until there
|
||||||
|
* is data for blocking sockets.
|
||||||
|
*
|
||||||
|
* @param boolean $mode True for blocking sockets, false for nonblocking.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed true on success or a PEAR_Error instance otherwise
|
||||||
|
*/
|
||||||
|
function setBlocking($mode)
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->blocking = $mode;
|
||||||
|
stream_set_blocking($this->fp, (int)$this->blocking);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the timeout value on socket descriptor,
|
||||||
|
* expressed in the sum of seconds and microseconds
|
||||||
|
*
|
||||||
|
* @param integer $seconds Seconds.
|
||||||
|
* @param integer $microseconds Microseconds, optional.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed True on success or false on failure or
|
||||||
|
* a PEAR_Error instance when not connected
|
||||||
|
*/
|
||||||
|
function setTimeout($seconds = null, $microseconds = null)
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($seconds === null && $microseconds === null) {
|
||||||
|
$seconds = (int) $this->timeout;
|
||||||
|
$microseconds = (int) (($this->timeout - $seconds) * 1000000);
|
||||||
|
} else {
|
||||||
|
$this->timeout = $seconds + $microseconds/1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->timeout > 0) {
|
||||||
|
return stream_set_timeout($this->fp, (int) $seconds, (int) $microseconds);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the file buffering size on the stream.
|
||||||
|
* See php's stream_set_write_buffer for more information.
|
||||||
|
*
|
||||||
|
* @param integer $size Write buffer size.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed on success or an PEAR_Error object otherwise
|
||||||
|
*/
|
||||||
|
function setWriteBuffer($size)
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
$returned = stream_set_write_buffer($this->fp, $size);
|
||||||
|
if ($returned == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return $this->raiseError('Cannot set write buffer.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns information about an existing socket resource.
|
||||||
|
* Currently returns four entries in the result array:
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* timed_out (bool) - The socket timed out waiting for data<br>
|
||||||
|
* blocked (bool) - The socket was blocked<br>
|
||||||
|
* eof (bool) - Indicates EOF event<br>
|
||||||
|
* unread_bytes (int) - Number of bytes left in the socket buffer<br>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed Array containing information about existing socket
|
||||||
|
* resource or a PEAR_Error instance otherwise
|
||||||
|
*/
|
||||||
|
function getStatus()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream_get_meta_data($this->fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specified line of data
|
||||||
|
*
|
||||||
|
* @param int $size Reading ends when size - 1 bytes have been read,
|
||||||
|
* or a newline or an EOF (whichever comes first).
|
||||||
|
* If no size is specified, it will keep reading from
|
||||||
|
* the stream until it reaches the end of the line.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed $size bytes of data from the socket, or a PEAR_Error if
|
||||||
|
* not connected. If an error occurs, FALSE is returned.
|
||||||
|
*/
|
||||||
|
function gets($size = null)
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($size)) {
|
||||||
|
return @fgets($this->fp);
|
||||||
|
} else {
|
||||||
|
return @fgets($this->fp, $size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a specified amount of data. This is guaranteed to return,
|
||||||
|
* and has the added benefit of getting everything in one fread()
|
||||||
|
* chunk; if you know the size of the data you're getting
|
||||||
|
* beforehand, this is definitely the way to go.
|
||||||
|
*
|
||||||
|
* @param integer $size The number of bytes to read from the socket.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return $size bytes of data from the socket, or a PEAR_Error if
|
||||||
|
* not connected.
|
||||||
|
*/
|
||||||
|
function read($size)
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
return @fread($this->fp, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a specified amount of data.
|
||||||
|
*
|
||||||
|
* @param string $data Data to write.
|
||||||
|
* @param integer $blocksize Amount of data to write at once.
|
||||||
|
* NULL means all at once.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed If the socket is not connected, returns an instance of
|
||||||
|
* PEAR_Error.
|
||||||
|
* If the write succeeds, returns the number of bytes written.
|
||||||
|
* If the write fails, returns false.
|
||||||
|
* If the socket times out, returns an instance of PEAR_Error.
|
||||||
|
*/
|
||||||
|
function write($data, $blocksize = null)
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($blocksize) && !OS_WINDOWS) {
|
||||||
|
$written = @fwrite($this->fp, $data);
|
||||||
|
|
||||||
|
// Check for timeout or lost connection
|
||||||
|
if (!$written) {
|
||||||
|
$meta_data = $this->getStatus();
|
||||||
|
|
||||||
|
if (!is_array($meta_data)) {
|
||||||
|
return $meta_data; // PEAR_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($meta_data['timed_out'])) {
|
||||||
|
return $this->raiseError('timed out');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $written;
|
||||||
|
} else {
|
||||||
|
if (is_null($blocksize)) {
|
||||||
|
$blocksize = 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pos = 0;
|
||||||
|
$size = strlen($data);
|
||||||
|
while ($pos < $size) {
|
||||||
|
$written = @fwrite($this->fp, substr($data, $pos, $blocksize));
|
||||||
|
|
||||||
|
// Check for timeout or lost connection
|
||||||
|
if (!$written) {
|
||||||
|
$meta_data = $this->getStatus();
|
||||||
|
|
||||||
|
if (!is_array($meta_data)) {
|
||||||
|
return $meta_data; // PEAR_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($meta_data['timed_out'])) {
|
||||||
|
return $this->raiseError('timed out');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $written;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pos += $written;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a line of data to the socket, followed by a trailing newline.
|
||||||
|
*
|
||||||
|
* @param string $data Data to write
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed fwrite() result, or PEAR_Error when not connected
|
||||||
|
*/
|
||||||
|
function writeLine($data)
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
return fwrite($this->fp, $data . $this->newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for end-of-file on a socket descriptor.
|
||||||
|
*
|
||||||
|
* Also returns true if the socket is disconnected.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function eof()
|
||||||
|
{
|
||||||
|
return (!is_resource($this->fp) || feof($this->fp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a byte of data
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return 1 byte of data from the socket, or a PEAR_Error if
|
||||||
|
* not connected.
|
||||||
|
*/
|
||||||
|
function readByte()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ord(@fread($this->fp, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a word of data
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return 1 word of data from the socket, or a PEAR_Error if
|
||||||
|
* not connected.
|
||||||
|
*/
|
||||||
|
function readWord()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
$buf = @fread($this->fp, 2);
|
||||||
|
return (ord($buf[0]) + (ord($buf[1]) << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an int of data
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return integer 1 int of data from the socket, or a PEAR_Error if
|
||||||
|
* not connected.
|
||||||
|
*/
|
||||||
|
function readInt()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
$buf = @fread($this->fp, 4);
|
||||||
|
return (ord($buf[0]) + (ord($buf[1]) << 8) +
|
||||||
|
(ord($buf[2]) << 16) + (ord($buf[3]) << 24));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a zero-terminated string of data
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string, or a PEAR_Error if
|
||||||
|
* not connected.
|
||||||
|
*/
|
||||||
|
function readString()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
$string = '';
|
||||||
|
while (($char = @fread($this->fp, 1)) != "\x00") {
|
||||||
|
$string .= $char;
|
||||||
|
}
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an IP Address and returns it in a dot formatted string
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return Dot formatted string, or a PEAR_Error if
|
||||||
|
* not connected.
|
||||||
|
*/
|
||||||
|
function readIPAddress()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
$buf = @fread($this->fp, 4);
|
||||||
|
return sprintf('%d.%d.%d.%d', ord($buf[0]), ord($buf[1]),
|
||||||
|
ord($buf[2]), ord($buf[3]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read until either the end of the socket or a newline, whichever
|
||||||
|
* comes first. Strips the trailing newline from the returned data.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return All available data up to a newline, without that
|
||||||
|
* newline, or until the end of the socket, or a PEAR_Error if
|
||||||
|
* not connected.
|
||||||
|
*/
|
||||||
|
function readLine()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = '';
|
||||||
|
|
||||||
|
$timeout = time() + $this->timeout;
|
||||||
|
|
||||||
|
while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) {
|
||||||
|
$line .= @fgets($this->fp, $this->lineLength);
|
||||||
|
if (substr($line, -1) == "\n") {
|
||||||
|
return rtrim($line, $this->newline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read until the socket closes, or until there is no more data in
|
||||||
|
* the inner PHP buffer. If the inner buffer is empty, in blocking
|
||||||
|
* mode we wait for at least 1 byte of data. Therefore, in
|
||||||
|
* blocking mode, if there is no data at all to be read, this
|
||||||
|
* function will never exit (unless the socket is closed on the
|
||||||
|
* remote end).
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*
|
||||||
|
* @return string All data until the socket closes, or a PEAR_Error if
|
||||||
|
* not connected.
|
||||||
|
*/
|
||||||
|
function readAll()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = '';
|
||||||
|
while (!feof($this->fp)) {
|
||||||
|
$data .= @fread($this->fp, $this->lineLength);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the equivalent of the select() system call on the socket
|
||||||
|
* with a timeout specified by tv_sec and tv_usec.
|
||||||
|
*
|
||||||
|
* @param integer $state Which of read/write/error to check for.
|
||||||
|
* @param integer $tv_sec Number of seconds for timeout.
|
||||||
|
* @param integer $tv_usec Number of microseconds for timeout.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return False if select fails, integer describing which of read/write/error
|
||||||
|
* are ready, or PEAR_Error if not connected.
|
||||||
|
*/
|
||||||
|
function select($state, $tv_sec, $tv_usec = 0)
|
||||||
|
{
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
|
||||||
|
$read = null;
|
||||||
|
$write = null;
|
||||||
|
$except = null;
|
||||||
|
if ($state & NET_SOCKET_READ) {
|
||||||
|
$read[] = $this->fp;
|
||||||
|
}
|
||||||
|
if ($state & NET_SOCKET_WRITE) {
|
||||||
|
$write[] = $this->fp;
|
||||||
|
}
|
||||||
|
if ($state & NET_SOCKET_ERROR) {
|
||||||
|
$except[] = $this->fp;
|
||||||
|
}
|
||||||
|
if (false === ($sr = stream_select($read, $write, $except,
|
||||||
|
$tv_sec, $tv_usec))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = 0;
|
||||||
|
if (count($read)) {
|
||||||
|
$result |= NET_SOCKET_READ;
|
||||||
|
}
|
||||||
|
if (count($write)) {
|
||||||
|
$result |= NET_SOCKET_WRITE;
|
||||||
|
}
|
||||||
|
if (count($except)) {
|
||||||
|
$result |= NET_SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns encryption on/off on a connected socket.
|
||||||
|
*
|
||||||
|
* @param bool $enabled Set this parameter to true to enable encryption
|
||||||
|
* and false to disable encryption.
|
||||||
|
* @param integer $type Type of encryption. See stream_socket_enable_crypto()
|
||||||
|
* for values.
|
||||||
|
*
|
||||||
|
* @see http://se.php.net/manual/en/function.stream-socket-enable-crypto.php
|
||||||
|
* @access public
|
||||||
|
* @return false on error, true on success and 0 if there isn't enough data
|
||||||
|
* and the user should try again (non-blocking sockets only).
|
||||||
|
* A PEAR_Error object is returned if the socket is not
|
||||||
|
* connected
|
||||||
|
*/
|
||||||
|
function enableCrypto($enabled, $type)
|
||||||
|
{
|
||||||
|
if (version_compare(phpversion(), "5.1.0", ">=")) {
|
||||||
|
if (!is_resource($this->fp)) {
|
||||||
|
return $this->raiseError('not connected');
|
||||||
|
}
|
||||||
|
return @stream_socket_enable_crypto($this->fp, $enabled, $type);
|
||||||
|
} else {
|
||||||
|
$msg = 'Net_Socket::enableCrypto() requires php version >= 5.1.0';
|
||||||
|
return $this->raiseError($msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Z-Push helper for error logging
|
||||||
|
* removing PEAR dependency
|
||||||
|
*
|
||||||
|
* @param string debug message
|
||||||
|
* @return boolean always false as there was an error
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function raiseError($message) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, "Net_Socket error: ". $message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
1113
sources/include/caldav-client-v2.php
Normal file
1113
sources/include/caldav-client-v2.php
Normal file
File diff suppressed because it is too large
Load diff
1770
sources/include/iCalendar.php
Normal file
1770
sources/include/iCalendar.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -356,7 +356,7 @@ class Mail_mimeDecode
|
||||||
if ($this->_rfc822_bodies) {
|
if ($this->_rfc822_bodies) {
|
||||||
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
|
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
|
||||||
$charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset;
|
$charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset;
|
||||||
$return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body);
|
$return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset, false) : $body);
|
||||||
}
|
}
|
||||||
|
|
||||||
$obj = new Mail_mimeDecode($body);
|
$obj = new Mail_mimeDecode($body);
|
||||||
|
@ -371,7 +371,7 @@ class Mail_mimeDecode
|
||||||
$content_transfer_encoding['value'] = '7bit';
|
$content_transfer_encoding['value'] = '7bit';
|
||||||
// if there is no explicit charset, then don't try to convert to default charset, and make sure that only text mimetypes are converted
|
// 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']: '';
|
$charset = (isset($return->ctype_parameters['charset']) && ((isset($return->ctype_primary) && $return->ctype_primary == 'text') || !isset($return->ctype_primary)) )? $return->ctype_parameters['charset']: '';
|
||||||
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value'], $charset) : $body) : null;
|
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value'], $charset, false) : $body) : null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -711,8 +711,11 @@ class Mail_mimeDecode
|
||||||
// Remove white space between encoded-words
|
// Remove white space between encoded-words
|
||||||
$input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
|
$input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
|
||||||
|
|
||||||
|
$encodedwords = false;
|
||||||
|
$charset = '';
|
||||||
// For each encoded-word...
|
// For each encoded-word...
|
||||||
while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
|
while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
|
||||||
|
$encodedwords = true;
|
||||||
|
|
||||||
$encoded = $matches[1];
|
$encoded = $matches[1];
|
||||||
$charset = $matches[2];
|
$charset = $matches[2];
|
||||||
|
@ -727,12 +730,17 @@ class Mail_mimeDecode
|
||||||
case 'q':
|
case 'q':
|
||||||
$text = str_replace('_', ' ', $text);
|
$text = str_replace('_', ' ', $text);
|
||||||
preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
|
preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
|
||||||
foreach($matches[1] as $value)
|
foreach($matches[1] as $value) {
|
||||||
$text = str_replace('='.$value, chr(hexdec($value)), $text);
|
$text = str_replace('='.$value, chr(hexdec($value)), $text);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$input = str_replace($encoded, $this->_fromCharset($charset, $text), $input);
|
$input = str_replace($encoded, $this->_autoconvert_encoding($text, $charset), $input);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$encodedwords) {
|
||||||
|
$input = $this->_autoconvert_encoding($input, $charset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $input;
|
return $input;
|
||||||
|
@ -744,33 +752,76 @@ class Mail_mimeDecode
|
||||||
*
|
*
|
||||||
* @param string Input body to decode
|
* @param string Input body to decode
|
||||||
* @param string Encoding type to use.
|
* @param string Encoding type to use.
|
||||||
|
* @param string Charset
|
||||||
|
* @param boolean Must try to autodetect the real charset used
|
||||||
* @return string Decoded body
|
* @return string Decoded body
|
||||||
* @access private
|
* @access private
|
||||||
*/
|
*/
|
||||||
function _decodeBody($input, $encoding = '7bit', $charset = '')
|
function _decodeBody($input, $encoding = '7bit', $charset = '', $detectCharset = true)
|
||||||
{
|
{
|
||||||
switch (strtolower($encoding)) {
|
switch (strtolower($encoding)) {
|
||||||
case '7bit':
|
|
||||||
return $this->_fromCharset($charset, $input);;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '8bit':
|
|
||||||
return $this->_fromCharset($charset, $input);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'quoted-printable':
|
case 'quoted-printable':
|
||||||
return $this->_fromCharset($charset, $this->_quotedPrintableDecode($input));
|
$input_decoded = $this->_quotedPrintableDecode($input);
|
||||||
|
return $detectCharset ? $this->_autoconvert_encoding($input_decoded, $charset) : $input_decoded;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'base64':
|
case 'base64':
|
||||||
return $this->_fromCharset($charset, base64_decode($input));
|
$input_decoded = base64_decode($input);
|
||||||
|
return $detectCharset ? $this->_autoconvert_encoding($input_decoded, $charset) : $input_decoded;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case '7bit':
|
||||||
|
case '8bit':
|
||||||
default:
|
default:
|
||||||
return $input;
|
return $detectCharset ? $this->_autoconvert_encoding($input, $charset) : $input;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error handler dummy for _autoconvert_encoding
|
||||||
|
*
|
||||||
|
* @param integer $errno
|
||||||
|
* @param string $errstr
|
||||||
|
* @return boolean true
|
||||||
|
* @access public static
|
||||||
|
*/
|
||||||
|
static function _iconv_notice_handler($errno, $errstr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autoconvert the text from any encoding. THIS WILL NEVER WORK 100%.
|
||||||
|
* Will ignore the E_NOTICE for iconv when detecting ilegal charsets
|
||||||
|
*
|
||||||
|
* @param string $input Input string to convert
|
||||||
|
* @param string $supposed_encoding Encoding that the text is possibly using
|
||||||
|
* @return string Converted string
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _autoconvert_encoding($input, $supposed_encoding = "UTF-8") {
|
||||||
|
$input_converted = $input;
|
||||||
|
|
||||||
|
if (function_exists("mb_detect_order")) {
|
||||||
|
$mb_order = array_merge(array($supposed_encoding), mb_detect_order());
|
||||||
|
set_error_handler('Mail_mimeDecode::_iconv_notice_handler');
|
||||||
|
try {
|
||||||
|
$input_converted = iconv(mb_detect_encoding($input, $mb_order, true), $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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $input_converted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a quoted-printable string, this
|
* Given a quoted-printable string, this
|
||||||
* function will decode and return it.
|
* function will decode and return it.
|
||||||
|
@ -865,6 +916,34 @@ class Mail_mimeDecode
|
||||||
return $files;
|
return $files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all parts in the message with specified type and concatenate them together, unless the
|
||||||
|
* Content-Disposition is 'attachment', in which case the text is apparently an attachment
|
||||||
|
*
|
||||||
|
* @param string $message mimedecode message(part)
|
||||||
|
* @param string $message message subtype
|
||||||
|
* @param string &$body body reference
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
static function getBodyRecursive($message, $subtype, &$body) {
|
||||||
|
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,"multipart")==0 && isset($message->parts) && is_array($message->parts)) {
|
||||||
|
foreach($message->parts as $part) {
|
||||||
|
// Check testing/samples/m1009.txt
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getSendArray() returns the arguments required for Mail::send()
|
* getSendArray() returns the arguments required for Mail::send()
|
||||||
* used to build the arguments for a mail::send() call
|
* used to build the arguments for a mail::send() call
|
||||||
|
@ -1026,25 +1105,6 @@ class Mail_mimeDecode
|
||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Z-Push helper to decode text
|
|
||||||
*
|
|
||||||
* @param string current charset of input
|
|
||||||
* @param string input
|
|
||||||
* @return string XML version of input
|
|
||||||
* @access private
|
|
||||||
*/
|
|
||||||
function _fromCharset($charset, $input) {
|
|
||||||
if($charset == '' || (strtolower($charset) == $this->_charset))
|
|
||||||
return $input;
|
|
||||||
|
|
||||||
// all ISO-8859-1 are converted as if they were Windows-1252 - see Mantis #456
|
|
||||||
if (strtolower($charset) == 'iso-8859-1')
|
|
||||||
$charset = 'Windows-1252';
|
|
||||||
|
|
||||||
return @iconv($charset, $this->_charset. "//TRANSLIT", $input);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Z-Push helper for error logging
|
* Z-Push helper for error logging
|
||||||
* removing PEAR dependency
|
* removing PEAR dependency
|
||||||
|
|
1270
sources/include/mimePart.php
Normal file
1270
sources/include/mimePart.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,37 +1,48 @@
|
||||||
<?php
|
<?php
|
||||||
// +-----------------------------------------------------------------------+
|
/**
|
||||||
// | Copyright (c) 2001-2002, Richard Heyes |
|
* RFC 822 Email address list validation Utility
|
||||||
// | All rights reserved. |
|
*
|
||||||
// | |
|
* PHP versions 4 and 5
|
||||||
// | Redistribution and use in source and binary forms, with or without |
|
*
|
||||||
// | modification, are permitted provided that the following conditions |
|
* LICENSE:
|
||||||
// | are met: |
|
*
|
||||||
// | |
|
* Copyright (c) 2001-2010, Richard Heyes
|
||||||
// | o Redistributions of source code must retain the above copyright |
|
* All rights reserved.
|
||||||
// | notice, this list of conditions and the following disclaimer. |
|
*
|
||||||
// | o Redistributions in binary form must reproduce the above copyright |
|
* Redistribution and use in source and binary forms, with or without
|
||||||
// | notice, this list of conditions and the following disclaimer in the |
|
* modification, are permitted provided that the following conditions
|
||||||
// | documentation and/or other materials provided with the distribution.|
|
* are met:
|
||||||
// | o The names of the authors may not be used to endorse or promote |
|
*
|
||||||
// | products derived from this software without specific prior written |
|
* o Redistributions of source code must retain the above copyright
|
||||||
// | permission. |
|
* notice, this list of conditions and the following disclaimer.
|
||||||
// | |
|
* o Redistributions in binary form must reproduce the above copyright
|
||||||
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
* documentation and/or other materials provided with the distribution.
|
||||||
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
* o The names of the authors may not be used to endorse or promote
|
||||||
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
* products derived from this software without specific prior written
|
||||||
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
* permission.
|
||||||
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
*
|
||||||
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
// | |
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
// +-----------------------------------------------------------------------+
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
// | Authors: Richard Heyes <richard@phpguru.org> |
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
// | Chuck Hagenbuch <chuck@horde.org> |
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
// +-----------------------------------------------------------------------+
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* @category Mail
|
||||||
|
* @package Mail
|
||||||
|
* @author Richard Heyes <richard@phpguru.org>
|
||||||
|
* @author Chuck Hagenbuch <chuck@horde.org
|
||||||
|
* @copyright 2001-2010 Richard Heyes
|
||||||
|
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||||
|
* @version CVS: $Id: RFC822.php 294749 2010-02-08 08:22:25Z clockwerx $
|
||||||
|
* @link http://pear.php.net/package/Mail/
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RFC 822 Email address list validation Utility
|
* RFC 822 Email address list validation Utility
|
||||||
|
@ -52,7 +63,7 @@
|
||||||
*
|
*
|
||||||
* @author Richard Heyes <richard@phpguru.org>
|
* @author Richard Heyes <richard@phpguru.org>
|
||||||
* @author Chuck Hagenbuch <chuck@horde.org>
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
||||||
* @version $Revision: 1.23 $
|
* @version $Revision: 294749 $
|
||||||
* @license BSD
|
* @license BSD
|
||||||
* @package Mail
|
* @package Mail
|
||||||
*/
|
*/
|
||||||
|
@ -183,6 +194,7 @@ class Mail_RFC822 {
|
||||||
$this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address);
|
$this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address);
|
||||||
|
|
||||||
while ($this->address = $this->_splitAddresses($this->address));
|
while ($this->address = $this->_splitAddresses($this->address));
|
||||||
|
|
||||||
if ($this->address === false || isset($this->error)) {
|
if ($this->address === false || isset($this->error)) {
|
||||||
//require_once 'PEAR.php';
|
//require_once 'PEAR.php';
|
||||||
return $this->raiseError($this->error);
|
return $this->raiseError($this->error);
|
||||||
|
@ -342,22 +354,39 @@ class Mail_RFC822 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a string has an unclosed quotes or not.
|
* Checks if a string has unclosed quotes or not.
|
||||||
*
|
*
|
||||||
* @access private
|
* @access private
|
||||||
* @param string $string The string to check.
|
* @param string $string The string to check.
|
||||||
* @return boolean True if there are unclosed quotes inside the string, false otherwise.
|
* @return boolean True if there are unclosed quotes inside the string,
|
||||||
|
* false otherwise.
|
||||||
*/
|
*/
|
||||||
function _hasUnclosedQuotes($string)
|
function _hasUnclosedQuotes($string)
|
||||||
{
|
{
|
||||||
$string = explode('"', $string);
|
$string = trim($string);
|
||||||
$string_cnt = count($string);
|
$iMax = strlen($string);
|
||||||
|
$in_quote = false;
|
||||||
|
$i = $slashes = 0;
|
||||||
|
|
||||||
for ($i = 0; $i < (count($string) - 1); $i++)
|
for (; $i < $iMax; ++$i) {
|
||||||
if (substr($string[$i], -1) == '\\')
|
switch ($string[$i]) {
|
||||||
$string_cnt--;
|
case '\\':
|
||||||
|
++$slashes;
|
||||||
|
break;
|
||||||
|
|
||||||
return ($string_cnt % 2 === 0);
|
case '"':
|
||||||
|
if ($slashes % 2 == 0) {
|
||||||
|
$in_quote = !$in_quote;
|
||||||
|
}
|
||||||
|
// Fall through to default action below.
|
||||||
|
|
||||||
|
default:
|
||||||
|
$slashes = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $in_quote;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -511,6 +540,7 @@ class Mail_RFC822 {
|
||||||
{
|
{
|
||||||
// Splits on one or more Tab or space.
|
// Splits on one or more Tab or space.
|
||||||
$parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
|
$parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
|
||||||
$phrase_parts = array();
|
$phrase_parts = array();
|
||||||
while (count($parts) > 0){
|
while (count($parts) > 0){
|
||||||
$phrase_parts[] = $this->_splitCheck($parts, ' ');
|
$phrase_parts[] = $this->_splitCheck($parts, ' ');
|
||||||
|
@ -553,6 +583,7 @@ class Mail_RFC822 {
|
||||||
// Validation has been turned off; assume the atom is okay.
|
// Validation has been turned off; assume the atom is okay.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for any char from ASCII 0 - ASCII 127
|
// Check for any char from ASCII 0 - ASCII 127
|
||||||
if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
|
if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -567,6 +598,7 @@ class Mail_RFC822 {
|
||||||
if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
|
if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,8 +647,8 @@ class Mail_RFC822 {
|
||||||
$comment = $this->_splitCheck($parts, ')');
|
$comment = $this->_splitCheck($parts, ')');
|
||||||
$comments[] = $comment;
|
$comments[] = $comment;
|
||||||
|
|
||||||
// +1 is for the trailing )
|
// +2 is for the brackets
|
||||||
$_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1);
|
$_mailbox = substr($_mailbox, strpos($_mailbox, '('.$comment)+strlen($comment)+2);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -642,7 +674,6 @@ class Mail_RFC822 {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) {
|
if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
707
sources/include/z_RTF.php
Normal file
707
sources/include/z_RTF.php
Normal file
|
@ -0,0 +1,707 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
This class contains code from rtfclass.php that was written by Markus Fischer and placed by him under
|
||||||
|
GPLv2 License.
|
||||||
|
|
||||||
|
=======================================NOTES FROM ORIGINAL AUTHOR====================================
|
||||||
|
Rich Text Format - Parsing Class
|
||||||
|
================================
|
||||||
|
|
||||||
|
(c) 2000 Markus Fischer
|
||||||
|
<mfischer@josefine.ben.tuwien.ac.at>
|
||||||
|
http://josefine.ben.tuwien.ac.at/~mfischer/
|
||||||
|
|
||||||
|
Latest versions of this class can always be found at
|
||||||
|
http://josefine.ben.tuwien.ac.at/~mfischer/developing/php/rtf/rtfclass.phps
|
||||||
|
Testing suite is available at
|
||||||
|
http://josefine.ben.tuwien.ac.at/~mfischer/developing/php/rtf/
|
||||||
|
|
||||||
|
License: GPLv2
|
||||||
|
|
||||||
|
Specification:
|
||||||
|
http://msdn.microsoft.com/library/default.asp?URL=/library/specs/rtfspec.htm
|
||||||
|
|
||||||
|
General Notes:
|
||||||
|
==============
|
||||||
|
Unknown or unspupported control symbols are silently gnored
|
||||||
|
|
||||||
|
Group stacking is still not supported :(
|
||||||
|
group stack logic implemented; however not really used yet
|
||||||
|
=====================================================================================================
|
||||||
|
|
||||||
|
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
|
||||||
|
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 {
|
||||||
|
var $LZRTF_HDR_DATA = "{\\rtf1\\ansi\\mac\\deff0\\deftab720{\\fonttbl;}{\\f0\\fnil \\froman \\fswiss \\fmodern \\fscript \\fdecor MS Sans SerifSymbolArialTimes New RomanCourier{\\colortbl\\red0\\green0\\blue0\n\r\\par \\pard\\plain\\f0\\fs20\\b\\i\\u\\tab\\tx";
|
||||||
|
var $LZRTF_HDR_LEN = 207;
|
||||||
|
var $CRC32_TABLE = array( 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
|
||||||
|
0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
|
||||||
|
0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
|
||||||
|
0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
|
||||||
|
0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
|
||||||
|
0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
|
||||||
|
0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,
|
||||||
|
0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
|
||||||
|
0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
|
||||||
|
0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
|
||||||
|
0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,
|
||||||
|
0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
|
||||||
|
0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,
|
||||||
|
0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
|
||||||
|
0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
|
||||||
|
0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
|
||||||
|
0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,
|
||||||
|
0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
|
||||||
|
0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,
|
||||||
|
0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
|
||||||
|
0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
|
||||||
|
0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
|
||||||
|
0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,
|
||||||
|
0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
|
||||||
|
0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,
|
||||||
|
0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
|
||||||
|
0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
|
||||||
|
0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
|
||||||
|
0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,
|
||||||
|
0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
|
||||||
|
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
|
||||||
|
|
||||||
|
var $wantXML = false; // convert to XML
|
||||||
|
var $wantHTML = false; // convert to HTML
|
||||||
|
var $wantASCII = false; // convert to HTML
|
||||||
|
|
||||||
|
// the only variable which should be accessed from the outside
|
||||||
|
var $out; // output data stream (depends on which $wantXXXXX is set to true
|
||||||
|
var $outstyles; // htmlified styles (generated after parsing if wantHTML
|
||||||
|
var $styles; // if wantHTML, stylesheet definitions are put in here
|
||||||
|
|
||||||
|
// internal parser variables --------------------------------
|
||||||
|
// control word variables
|
||||||
|
var $cword; // holds the current (or last) control word, depending on $cw
|
||||||
|
var $cw; // are we currently parsing a control word ?
|
||||||
|
var $cfirst; // could this be the first character ? so watch out for control symbols
|
||||||
|
|
||||||
|
var $flags = array(); // parser flags
|
||||||
|
|
||||||
|
var $queue; // every character which is no sepcial char, not belongs to a control word/symbol; is generally considered being 'plain'
|
||||||
|
|
||||||
|
var $stack = array(); // group stack
|
||||||
|
|
||||||
|
/* keywords which don't follw the specification (used by Word '97 - 2000) */
|
||||||
|
// not yet used
|
||||||
|
var $control_exception = array(
|
||||||
|
"clFitText",
|
||||||
|
"clftsWidth(-?[0-9]+)?",
|
||||||
|
"clNoWrap(-?[0-9]+)?",
|
||||||
|
"clwWidth(-?[0-9]+)?",
|
||||||
|
"tdfrmtxtBottom(-?[0-9]+)?",
|
||||||
|
"tdfrmtxtLeft(-?[0-9]+)?",
|
||||||
|
"tdfrmtxtRight(-?[0-9]+)?",
|
||||||
|
"tdfrmtxtTop(-?[0-9]+)?",
|
||||||
|
"trftsWidthA(-?[0-9]+)?",
|
||||||
|
"trftsWidthB(-?[0-9]+)?",
|
||||||
|
"trftsWidth(-?[0-9]+)?",
|
||||||
|
"trwWithA(-?[0-9]+)?",
|
||||||
|
"trwWithB(-?[0-9]+)?",
|
||||||
|
"trwWith(-?[0-9]+)?",
|
||||||
|
"spectspecifygen(-?[0-9]+)?",
|
||||||
|
);
|
||||||
|
|
||||||
|
var $charset_table = array(
|
||||||
|
"0" => "ANSI",
|
||||||
|
"1" => "Default",
|
||||||
|
"2" => "Symbol",
|
||||||
|
"77" => "Mac",
|
||||||
|
"128" => "Shift Jis",
|
||||||
|
"129" => "Hangul",
|
||||||
|
"130" => "Johab",
|
||||||
|
"134" => "GB2312",
|
||||||
|
"136" => "Big5",
|
||||||
|
"161" => "Greek",
|
||||||
|
"162" => "Turkish",
|
||||||
|
"163" => "Vietnamese",
|
||||||
|
"177" => "Hebrew",
|
||||||
|
"178" => "Arabic",
|
||||||
|
"179" => "Arabic Traditional",
|
||||||
|
"180" => "Arabic user",
|
||||||
|
"181" => "Hebrew user",
|
||||||
|
"186" => "Baltic",
|
||||||
|
"204" => "Russian",
|
||||||
|
"222" => "Thai",
|
||||||
|
"238" => "Eastern European",
|
||||||
|
"255" => "PC 437",
|
||||||
|
"255" => "OEM",
|
||||||
|
);
|
||||||
|
|
||||||
|
/* note: the only conversion table used */
|
||||||
|
var $fontmodifier_table = array(
|
||||||
|
"bold" => "b",
|
||||||
|
"italic" => "i",
|
||||||
|
"underlined" => "u",
|
||||||
|
"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) {
|
||||||
|
if (($this->rtf = $this->uncompress($data))) {
|
||||||
|
$this->rtf_len = strlen($this->rtf);
|
||||||
|
};
|
||||||
|
if($this->rtf_len == 0) {
|
||||||
|
debugLog("No data in stream found");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function output($typ) {
|
||||||
|
switch($typ) {
|
||||||
|
case "ascii": $this->wantASCII = true; break;
|
||||||
|
case "xml": $this->wantXML = true; break;
|
||||||
|
case "html": $this->wantHTML = true; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// uncompress - uncompress compressed rtf data
|
||||||
|
// src = the compressed raw rtf in LZRTF format
|
||||||
|
function uncompress($src) {
|
||||||
|
$header = unpack("LcSize/LuSize/Lmagic/Lcrc32",substr($src,0,16));
|
||||||
|
$in = 16;
|
||||||
|
if ($header['cSize'] != strlen($src)-4) {
|
||||||
|
debugLog("Stream too short");
|
||||||
|
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']);
|
||||||
|
} else if ($header['magic'] == 0x75465a4c) { // compressed RTF - uncompress.
|
||||||
|
$dst = $this->LZRTF_HDR_DATA;
|
||||||
|
$out = $this->LZRTF_HDR_LEN;
|
||||||
|
$oblen = $this->LZRTF_HDR_LEN + $header['uSize'];
|
||||||
|
$flagCount = 0;
|
||||||
|
$flags = 0;
|
||||||
|
while ($out<$oblen) {
|
||||||
|
$flags = ($flagCount++ % 8 == 0) ? ord($src{$in++}) : $flags >> 1;
|
||||||
|
if (($flags & 1) == 1) {
|
||||||
|
$offset = ord($src{$in++});
|
||||||
|
$length = ord($src{$in++});
|
||||||
|
$offset = ($offset << 4) | ($length >> 4);
|
||||||
|
$length = ($length & 0xF) + 2;
|
||||||
|
$offset = (int)($out / 4096) * 4096 + $offset;
|
||||||
|
if ($offset >= $out) $offset -= 4096;
|
||||||
|
$end = $offset + $length;
|
||||||
|
while ($offset < $end) {
|
||||||
|
$dst{$out++} = $dst{$offset++};
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
$dst{$out++} = $src{$in++};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$src = $dst;
|
||||||
|
$dest = substr($src,$this->LZRTF_HDR_LEN,$header['uSize']);
|
||||||
|
} else { // unknown magic - returfn false (please report if this ever happens)
|
||||||
|
debugLog("Unknown Magic");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LZRTFCalcCRC32 - calculates the CRC32 of the LZRTF data part
|
||||||
|
// 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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parserInit() { /* Default values according to the specs */
|
||||||
|
$this->flags = array(
|
||||||
|
"fontsize" => 24,
|
||||||
|
"beginparagraph" => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseControl($control, $parameter) {
|
||||||
|
switch ($control) {
|
||||||
|
case "fonttbl": // font table definition start
|
||||||
|
$this->flags["fonttbl"] = true; // signal fonttable control words they are allowed to behave as expected
|
||||||
|
break;
|
||||||
|
case "f": // define or set font
|
||||||
|
if($this->flags["fonttbl"]) { // if its set, the fonttable definition is written to; else its read from
|
||||||
|
$this->flags["fonttbl_current_write"] = $parameter;
|
||||||
|
} else {
|
||||||
|
$this->flags["fonttbl_current_read"] = $parameter;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "fcharset": // this is for preparing flushQueue; it then moves the Queue to $this->fonttable .. instead to formatted output
|
||||||
|
$this->flags["fonttbl_want_fcharset"] = $parameter;
|
||||||
|
break;
|
||||||
|
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;
|
||||||
|
case "par": // define new paragraph (for now, thats a simple break in html) begin new line
|
||||||
|
$this->flags["beginparagraph"] = true;
|
||||||
|
if($this->wantHTML) {
|
||||||
|
$this->out .= "</div>";
|
||||||
|
}
|
||||||
|
if($this->wantASCII) {
|
||||||
|
$this->out .= "\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "bnone": // bold
|
||||||
|
$parameter = "0";
|
||||||
|
case "b":
|
||||||
|
// haven'y yet figured out WHY I need a (string)-cast here ... hm
|
||||||
|
if((string)$parameter == "0")
|
||||||
|
$this->flags["bold"] = false;
|
||||||
|
else
|
||||||
|
$this->flags["bold"] = true;
|
||||||
|
break;
|
||||||
|
case "ulnone": // underlined
|
||||||
|
$parameter = "0";
|
||||||
|
case "ul":
|
||||||
|
if((string)$parameter == "0")
|
||||||
|
$this->flags["underlined"] = false;
|
||||||
|
else
|
||||||
|
$this->flags["underlined"] = true;
|
||||||
|
break;
|
||||||
|
case "inone": // italic
|
||||||
|
$parameter = "0";
|
||||||
|
case "i":
|
||||||
|
if((string)$parameter == "0")
|
||||||
|
$this->flags["italic"] = false;
|
||||||
|
else
|
||||||
|
$this->flags["italic"] = true;
|
||||||
|
break;
|
||||||
|
case "strikenone": // strikethru
|
||||||
|
$parameter = "0";
|
||||||
|
case "strike":
|
||||||
|
if((string)$parameter == "0")
|
||||||
|
$this->flags["strikethru"] = false;
|
||||||
|
else
|
||||||
|
$this->flags["strikethru"] = true;
|
||||||
|
break;
|
||||||
|
case "plain": // reset all font modifiers and fontsize to 12
|
||||||
|
$this->flags["bold"] = false;
|
||||||
|
$this->flags["italic"] = false;
|
||||||
|
$this->flags["underlined"] = false;
|
||||||
|
$this->flags["strikethru"] = false;
|
||||||
|
$this->flags["fontsize"] = 12;
|
||||||
|
|
||||||
|
$this->flags["subscription"] = false;
|
||||||
|
$this->flags["superscription"] = false;
|
||||||
|
break;
|
||||||
|
case "subnone": // subscription
|
||||||
|
$parameter = "0";
|
||||||
|
case "sub":
|
||||||
|
if((string)$parameter == "0")
|
||||||
|
$this->flags["subscription"] = false;
|
||||||
|
else
|
||||||
|
$this->flags["subscription"] = true;
|
||||||
|
break;
|
||||||
|
case "supernone": // superscription
|
||||||
|
$parameter = "0";
|
||||||
|
case "super":
|
||||||
|
if((string)$parameter == "0")
|
||||||
|
$this->flags["superscription"] = false;
|
||||||
|
else
|
||||||
|
$this->flags["superscription"] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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]);
|
||||||
|
if($this->wantXML) {
|
||||||
|
$this->out.="<control word=\"".$match[1]."\"";
|
||||||
|
if(strlen($match[2]) > 0)
|
||||||
|
$this->out.=" param=\"".$match[2]."\"";
|
||||||
|
$this->out.="/>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
If output stream supports comments, dispatch it
|
||||||
|
*/
|
||||||
|
|
||||||
|
function flushComment($comment) {
|
||||||
|
if($this->wantXML || $this->wantHTML) {
|
||||||
|
$this->out.="<!-- ".$comment." -->";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dispatch start/end of logical rtf groups (not every output type needs it; merely debugging purpose)
|
||||||
|
*/
|
||||||
|
|
||||||
|
function flushGroup($state) {
|
||||||
|
if($state == "open") { /* push onto the stack */
|
||||||
|
array_push($this->stack, $this->flags);
|
||||||
|
|
||||||
|
if($this->wantXML)
|
||||||
|
$this->out.="<group>";
|
||||||
|
}
|
||||||
|
if($state == "close") { /* pop from the stack */
|
||||||
|
$this->last_flags = $this->flags;
|
||||||
|
$this->flags = array_pop($this->stack);
|
||||||
|
|
||||||
|
$this->flags["fonttbl_current_write"] = ""; // on group close, no more fontdefinition will be written to this id
|
||||||
|
// this is not really the right way to do it !
|
||||||
|
// of course a '}' not necessarily donates a fonttable end; a fonttable
|
||||||
|
// group at least *can* contain sub-groups
|
||||||
|
// therefore an stacked approach is heavily needed
|
||||||
|
$this->flags["fonttbl"] = false; // no matter what you do, if a group closes, its fonttbl definition is closed too
|
||||||
|
|
||||||
|
if($this->wantXML)
|
||||||
|
$this->out.="</group>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushHead() {
|
||||||
|
if($this->wantXML)
|
||||||
|
$this->out.="<rtf>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushBottom() {
|
||||||
|
if($this->wantXML)
|
||||||
|
$this->out.="</rtf>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkHtmlSpanContent($command) {
|
||||||
|
reset($this->fontmodifier_table);
|
||||||
|
while(list($rtf, $html) = each($this->fontmodifier_table)) {
|
||||||
|
if($this->flags[$rtf] == true) {
|
||||||
|
if($command == "start")
|
||||||
|
$this->out .= "<".$html.">";
|
||||||
|
else
|
||||||
|
$this->out .= "</".$html.">";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
flush text in queue
|
||||||
|
*/
|
||||||
|
function flushQueue() {
|
||||||
|
if(strlen($this->queue)) {
|
||||||
|
// processing logic
|
||||||
|
if (isset($this->flags["fonttbl_want_fcharset"]) &&
|
||||||
|
preg_match("/^[0-9]+$/", $this->flags["fonttbl_want_fcharset"])) {
|
||||||
|
$this->fonttable[$this->flags["fonttbl_want_fcharset"]]["charset"] = $this->queue;
|
||||||
|
$this->flags["fonttbl_want_fcharset"] = "";
|
||||||
|
$this->queue = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// output logic
|
||||||
|
if (strlen($this->queue)) {
|
||||||
|
/*
|
||||||
|
Everything which passes this is (or, at leat, *should*) be only outputted plaintext
|
||||||
|
Thats why we can safely add the css-stylesheet when using wantHTML
|
||||||
|
*/
|
||||||
|
if($this->wantXML)
|
||||||
|
$this->out.= "<plain>".$this->queue."</plain>";
|
||||||
|
else if($this->wantHTML) {
|
||||||
|
// only output html if a valid (for now, just numeric;) fonttable is given
|
||||||
|
if (!isset($this->flags["fonttbl_current_read"])) $this->flags["fonttbl_current_read"] = "";
|
||||||
|
if(preg_match("/^[0-9]+$/", $this->flags["fonttbl_current_read"])) {
|
||||||
|
if($this->flags["beginparagraph"] == true) {
|
||||||
|
$this->flags["beginparagraph"] = false;
|
||||||
|
$this->out .= "<div align=\"";
|
||||||
|
switch($this->flags["alignment"]) {
|
||||||
|
case "right":
|
||||||
|
$this->out .= "right";
|
||||||
|
break;
|
||||||
|
case "center":
|
||||||
|
$this->out .= "center";
|
||||||
|
break;
|
||||||
|
case "left":
|
||||||
|
default:
|
||||||
|
$this->out .= "left";
|
||||||
|
}
|
||||||
|
$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 */
|
||||||
|
$this->out .= "<span class=\"f".$this->flags["fonttbl_current_read"]."s".$this->flags["fontsize"]."\">";
|
||||||
|
|
||||||
|
/* check if the span content has a modifier */
|
||||||
|
$this->checkHtmlSpanContent("start");
|
||||||
|
/* write span content */
|
||||||
|
$this->out .= $this->queue;
|
||||||
|
/* close modifiers */
|
||||||
|
$this->checkHtmlSpanContent("stop");
|
||||||
|
/* close span */
|
||||||
|
"</span>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->queue = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
handle special charactes like \'ef
|
||||||
|
*/
|
||||||
|
|
||||||
|
function flushSpecial($special) {
|
||||||
|
if(strlen($special) == 2) {
|
||||||
|
if($this->wantASCII)
|
||||||
|
$this->out .= chr(hexdec('0x'.$special));
|
||||||
|
else if($this->wantXML)
|
||||||
|
$this->out .= "<special value=\"".$special."\"/>";
|
||||||
|
else if($this->wantHTML){
|
||||||
|
$this->out .= "<special value=\"".$special."\"/>";
|
||||||
|
switch($special) {
|
||||||
|
case "c1": $this->out .= "Á"; break;
|
||||||
|
case "e1": $this->out .= "á"; break;
|
||||||
|
case "c0": $this->out .= "À"; break;
|
||||||
|
case "e0": $this->out .= "à"; break;
|
||||||
|
case "c9": $this->out .= "É"; break;
|
||||||
|
case "e9": $this->out .= "é"; break;
|
||||||
|
case "c8": $this->out .= "È"; break;
|
||||||
|
case "e8": $this->out .= "è"; break;
|
||||||
|
case "cd": $this->out .= "Í"; break;
|
||||||
|
case "ed": $this->out .= "í"; break;
|
||||||
|
case "cc": $this->out .= "Ì"; break;
|
||||||
|
case "ec": $this->out .= "ì"; break;
|
||||||
|
case "d3": $this->out .= "Ó"; break;
|
||||||
|
case "f3": $this->out .= "ó"; break;
|
||||||
|
case "d2": $this->out .= "Ò"; break;
|
||||||
|
case "f2": $this->out .= "ò"; break;
|
||||||
|
case "da": $this->out .= "Ú"; break;
|
||||||
|
case "fa": $this->out .= "ú"; break;
|
||||||
|
case "d9": $this->out .= "Ù"; break;
|
||||||
|
case "f9": $this->out .= "ù"; break;
|
||||||
|
case "80": $this->out .= "€"; break;
|
||||||
|
case "d1": $this->out .= "Ñ"; break;
|
||||||
|
case "f1": $this->out .= "ñ"; break;
|
||||||
|
case "c7": $this->out .= "Ç"; break;
|
||||||
|
case "e7": $this->out .= "ç"; break;
|
||||||
|
case "dc": $this->out .= "Ü"; break;
|
||||||
|
case "fc": $this->out .= "ü"; break;
|
||||||
|
case "bf": $this->out .= "¿"; break;
|
||||||
|
case "a1": $this->out .= "¡"; break;
|
||||||
|
case "b7": $this->out .= "·"; break;
|
||||||
|
case "a9": $this->out .= "©"; break;
|
||||||
|
case "ae": $this->out .= "®"; break;
|
||||||
|
case "ba": $this->out .= "º"; break;
|
||||||
|
case "aa": $this->out .= "ª"; break;
|
||||||
|
case "b2": $this->out .= "²"; break;
|
||||||
|
case "b3": $this->out .= "³"; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Output errors at end
|
||||||
|
*/
|
||||||
|
function flushErrors() {
|
||||||
|
if(count($this->err) > 0) {
|
||||||
|
if($this->wantXML) {
|
||||||
|
$this->out .= "<errors>";
|
||||||
|
while(list($num,$value) = each($this->err)) {
|
||||||
|
$this->out .= "<message>".$value."</message>";
|
||||||
|
}
|
||||||
|
$this->out .= "</errors>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeStyles() {
|
||||||
|
$this->outstyles = "<style type=\"text/css\"><!--\n";
|
||||||
|
reset($this->styles);
|
||||||
|
while(list($stylename, $styleattrib) = each($this->styles)) {
|
||||||
|
$this->outstyles .= ".".$stylename." { ".$styleattrib." }\n";
|
||||||
|
}
|
||||||
|
$this->outstyles .= "--></style>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse() {
|
||||||
|
|
||||||
|
$this->parserInit();
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$this->cw= false; // flag if control word is currently parsed
|
||||||
|
$this->cfirst = false; // first control character ?
|
||||||
|
$this->cword = ""; // last or current control word (depends on $this->cw
|
||||||
|
|
||||||
|
$this->queue = ""; // plain text data found during parsing
|
||||||
|
|
||||||
|
$this->flushHead();
|
||||||
|
|
||||||
|
while($i < $this->rtf_len) {
|
||||||
|
switch($this->rtf[$i]) {
|
||||||
|
case "{":
|
||||||
|
if($this->cw) {
|
||||||
|
$this->flushControl();
|
||||||
|
$this->cw = false;
|
||||||
|
$this->cfirst = false;
|
||||||
|
} else
|
||||||
|
$this->flushQueue();
|
||||||
|
|
||||||
|
$this->flushGroup("open");
|
||||||
|
break;
|
||||||
|
case "}":
|
||||||
|
if($this->cw) {
|
||||||
|
$this->flushControl();
|
||||||
|
$this->cw = false;
|
||||||
|
$this->cfirst = false;
|
||||||
|
} else
|
||||||
|
$this->flushQueue();
|
||||||
|
|
||||||
|
$this->flushGroup("close");
|
||||||
|
break;
|
||||||
|
case "\\":
|
||||||
|
if($this->cfirst) { // catches '\\'
|
||||||
|
$this->queue .= "\\"; // replaced single quotes
|
||||||
|
$this->cfirst = false;
|
||||||
|
$this->cw = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if($this->cw) {
|
||||||
|
$this->flushControl();
|
||||||
|
} else
|
||||||
|
$this->flushQueue();
|
||||||
|
$this->cw = true;
|
||||||
|
$this->cfirst = true;
|
||||||
|
$this->cword = "";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if((ord($this->rtf[$i]) == 10) || (ord($this->rtf[$i]) == 13)) break; // eat line breaks
|
||||||
|
if($this->cw) { // active control word ?
|
||||||
|
/*
|
||||||
|
Watch the RE: there's an optional space at the end which IS part of
|
||||||
|
the control word (but actually its ignored by flushControl)
|
||||||
|
*/
|
||||||
|
if(preg_match("/^[a-zA-Z0-9-]?$/", $this->rtf[$i])) { // continue parsing
|
||||||
|
$this->cword .= $this->rtf[$i];
|
||||||
|
$this->cfirst = false;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
Control word could be a 'control symbol', like \~ or \* etc.
|
||||||
|
*/
|
||||||
|
$specialmatch = false;
|
||||||
|
if($this->cfirst) {
|
||||||
|
if($this->rtf[$i] == '\'') { // expect to get some special chars
|
||||||
|
$this->flushQueue();
|
||||||
|
$this->flushSpecial($this->rtf[$i+1].$this->rtf[$i+2]);
|
||||||
|
$i+=2;
|
||||||
|
$specialmatch = true;
|
||||||
|
$this->cw = false;
|
||||||
|
$this->cfirst = false;
|
||||||
|
$this->cword = "";
|
||||||
|
} else
|
||||||
|
if(preg_match("/^[{}\*]$/", $this->rtf[$i])) {
|
||||||
|
$this->flushComment("control symbols not yet handled");
|
||||||
|
$specialmatch = true;
|
||||||
|
}
|
||||||
|
$this->cfirst = false;
|
||||||
|
} else {
|
||||||
|
if($this->rtf[$i] == ' ') { // space delimtes control words, so just discard it and flush the controlword
|
||||||
|
$this->cw = false;
|
||||||
|
$this->flushControl();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!$specialmatch) {
|
||||||
|
$this->flushControl();
|
||||||
|
$this->cw = false;
|
||||||
|
$this->cfirst = false;
|
||||||
|
/*
|
||||||
|
The current character is a delimeter, but is NOT
|
||||||
|
part of the control word so we hop one step back
|
||||||
|
in the stream and process it again
|
||||||
|
*/
|
||||||
|
$i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// < and > need translation before putting into queue when XML or HTML is wanted
|
||||||
|
if(($this->wantHTML) || ($this->wantXML)) {
|
||||||
|
switch($this->rtf[$i]) {
|
||||||
|
case "<":
|
||||||
|
$this->queue .= "<";
|
||||||
|
break;
|
||||||
|
case ">":
|
||||||
|
$this->queue .= ">";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->queue .= $this->rtf[$i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
$this->queue .= $this->rtf[$i];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
$this->flushQueue();
|
||||||
|
$this->flushErrors();
|
||||||
|
$this->flushBottom();
|
||||||
|
|
||||||
|
if($this->wantHTML) {
|
||||||
|
$this->makeStyles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
972
sources/include/z_caldav.php
Normal file
972
sources/include/z_caldav.php
Normal file
|
@ -0,0 +1,972 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* A Class for connecting to a caldav server
|
||||||
|
*
|
||||||
|
* Based on caldav-client-v2.php by Andrew McMillan <andrew@mcmillan.net.nz>
|
||||||
|
* but using cURL instead of home-brew request construction. cURL code re-used
|
||||||
|
* from carddav.php by Jean-Louis Dupond. Additional bugfixes to
|
||||||
|
* caldav-client-v2.php by xbgmsharp <xbgmsharp@gmail.com>.
|
||||||
|
*
|
||||||
|
* Copyright Andrew McMillan (original caldav-client-v2.php), Jean-Louis Dupond (cURL code), xbgmsharp (bugfixes)
|
||||||
|
* Copyright Thorsten Köster
|
||||||
|
* License GNU LGPL version 3 or later (http://www.gnu.org/licenses/lgpl-3.0.txt)
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('XMLDocument.php');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for holding basic calendar information
|
||||||
|
*/
|
||||||
|
class CalendarInfo {
|
||||||
|
public $url;
|
||||||
|
public $displayname;
|
||||||
|
public $getctag;
|
||||||
|
public $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 $server, $base_url, $user, $pass, $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
|
||||||
|
*/
|
||||||
|
const USERAGENT = 'ModifiedDAViCalClient';
|
||||||
|
|
||||||
|
protected $headers = array();
|
||||||
|
protected $xmlResponse = ""; // xml received
|
||||||
|
protected $httpResponseCode = 0; // http response code
|
||||||
|
protected $httpResponseHeaders = "";
|
||||||
|
protected $httpResponseBody = "";
|
||||||
|
|
||||||
|
protected $parser; // our XML parser object
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CardDAV server connection (curl handle)
|
||||||
|
*
|
||||||
|
* @var resource
|
||||||
|
*/
|
||||||
|
private $curl;
|
||||||
|
|
||||||
|
|
||||||
|
private $synctoken = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor, initialises the class
|
||||||
|
*
|
||||||
|
* @param string $caldav_url The URL for the calendar server
|
||||||
|
* @param string $user The name of the user logging in
|
||||||
|
* @param string $pass The password for that user
|
||||||
|
*/
|
||||||
|
function __construct( $caldav_url, $user, $pass ) {
|
||||||
|
$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 (substr($this->base_url, -1, 1) !== '/') {
|
||||||
|
$this->base_url = $this->base_url . '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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") );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, '<?xml');
|
||||||
|
if ($pos !== false) {
|
||||||
|
$this->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 ) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("XML parsing error: %s - %s", 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 );
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("XML Reponse:\n%s\n", $this->xmlResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_parser_free($parser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function curl_init() {
|
||||||
|
if (empty($this->curl)) {
|
||||||
|
$this->curl = curl_init();
|
||||||
|
curl_setopt($this->curl, CURLOPT_HEADER, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_USERAGENT, self::USERAGENT);
|
||||||
|
|
||||||
|
if ($this->auth !== null) {
|
||||||
|
curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
||||||
|
curl_setopt($this->curl, CURLOPT_USERPWD, $this->auth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, $method, $content = null, $content_type = "text/plain") {
|
||||||
|
$this->curl_init();
|
||||||
|
|
||||||
|
if ( !isset($url) ) $url = $this->base_url;
|
||||||
|
$url = preg_replace('{^https?://[^/]+}', '', $url);
|
||||||
|
$url = $this->server . $url;
|
||||||
|
|
||||||
|
curl_setopt($this->curl, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method);
|
||||||
|
|
||||||
|
if ($content !== null)
|
||||||
|
{
|
||||||
|
curl_setopt($this->curl, CURLOPT_POST, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
curl_setopt($this->curl, CURLOPT_POST, false);
|
||||||
|
curl_setopt($this->curl, CURLOPT_POSTFIELDS, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = array();
|
||||||
|
$headers['content-type'] = 'Content-type: ' . $content_type;
|
||||||
|
foreach( $this->headers as $ii => $head ) {
|
||||||
|
$headers[$ii] = $head;
|
||||||
|
}
|
||||||
|
curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
|
||||||
|
$this->xmlResponse = '';
|
||||||
|
|
||||||
|
//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));
|
||||||
|
$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));
|
||||||
|
$this->httpResponseBody = substr($response, $header_size);
|
||||||
|
|
||||||
|
$this->headers = array(); // reset the headers array for our next request
|
||||||
|
$this->ParseResponse($this->httpResponseBody);
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an OPTIONS request to the server
|
||||||
|
*
|
||||||
|
* @param string $url The URL to make the request to
|
||||||
|
*
|
||||||
|
* @return array The allowed options
|
||||||
|
*/
|
||||||
|
function DoOptionsRequest( $url = null ) {
|
||||||
|
$headers = $this->DoRequest($url, "OPTIONS");
|
||||||
|
$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 ) {
|
||||||
|
return $this->DoRequest($url, $request_method, $xml, "text/xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single item from the server.
|
||||||
|
*
|
||||||
|
* @param string $url The URL to GET
|
||||||
|
*/
|
||||||
|
function DoGETRequest( $url ) {
|
||||||
|
return $this->DoRequest($url, "GET");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the HEAD of a single item from the server.
|
||||||
|
*
|
||||||
|
* @param string $url The URL to HEAD
|
||||||
|
*/
|
||||||
|
function DoHEADRequest( $url ) {
|
||||||
|
return $this->DoRequest($url, "HEAD");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT a text/icalendar resource, returning the etag
|
||||||
|
*
|
||||||
|
* @param string $url The URL to make the request to
|
||||||
|
* @param string $icalendar The iCalendar resource to send to the server
|
||||||
|
* @param string $etag The etag of an existing resource to be overwritten, or '*' for a new resource.
|
||||||
|
*
|
||||||
|
* @return string The content of the response from the server
|
||||||
|
*/
|
||||||
|
function DoPUTRequest( $url, $icalendar, $etag = null ) {
|
||||||
|
if ( $etag != null ) {
|
||||||
|
$this->SetMatch( ($etag != '*'), $etag );
|
||||||
|
}
|
||||||
|
$this->DoRequest($url, "PUT", $icalendar, 'text/calendar; encoding="utf-8"');
|
||||||
|
|
||||||
|
$etag = null;
|
||||||
|
if ( preg_match( '{^ETag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders, $matches ) ) {
|
||||||
|
$etag = $matches[1];
|
||||||
|
}
|
||||||
|
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 ) ) {
|
||||||
|
$etag = $matches[1];
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE a text/icalendar resource
|
||||||
|
*
|
||||||
|
* @param string $url The URL to make the request to
|
||||||
|
* @param string $etag The etag of an existing resource to be deleted, or '*' for any resource at that URL.
|
||||||
|
*
|
||||||
|
* @return int The HTTP Result Code for the DELETE
|
||||||
|
*/
|
||||||
|
function DoDELETERequest( $url, $etag = null ) {
|
||||||
|
if ( $etag != null ) {
|
||||||
|
$this->SetMatch( true, $etag );
|
||||||
|
}
|
||||||
|
$this->DoRequest($url, "DELETE");
|
||||||
|
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->DoRequest($url, "PROPFIND", $xml->Render('propfind',$prop), "text/xml");
|
||||||
|
return $this->xmlResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("xmltags[$tagname] or xmltags[$tagname][$i] is not set."));
|
||||||
|
}
|
||||||
|
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 <prop> ... </prop> 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( $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>'.$href.'</href>';
|
||||||
|
}
|
||||||
|
$body = <<<EOXML
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:calendar-multiget xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<prop><getetag/><C:calendar-data/></prop>
|
||||||
|
$hrefs
|
||||||
|
</C:calendar-multiget>
|
||||||
|
EOXML;
|
||||||
|
|
||||||
|
$this->DoRequest($this->calendar_url, "REPORT", $body, "text/xml");
|
||||||
|
|
||||||
|
$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 <filter> 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 = null ) {
|
||||||
|
if ( !empty($url) ) {
|
||||||
|
$this->SetCalendar($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = <<<EOXML
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<D:prop>
|
||||||
|
<C:calendar-data/>
|
||||||
|
<D:getetag/>
|
||||||
|
</D:prop>
|
||||||
|
$filter
|
||||||
|
</C:calendar-query>
|
||||||
|
EOXML;
|
||||||
|
|
||||||
|
$this->SetDepth(1);
|
||||||
|
$this->DoRequest($this->calendar_url, "REPORT", $body, "text/xml");
|
||||||
|
|
||||||
|
$report = array();
|
||||||
|
foreach( $this->xmlnodes as $k => $v ) {
|
||||||
|
switch( $v['tag'] ) {
|
||||||
|
case 'DAV::response':
|
||||||
|
if ( $v['type'] == 'open' ) {
|
||||||
|
$response = array();
|
||||||
|
} elseif ( $v['type'] == 'close' ) {
|
||||||
|
$report[] = $response;
|
||||||
|
}
|
||||||
|
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 = "<C:time-range start=\"$start\" end=\"$finish\"/>";
|
||||||
|
} else {
|
||||||
|
$range = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$filter = <<<EOFILTER
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="VEVENT">
|
||||||
|
$range
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
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
|
||||||
|
<C:time-range start="$start" end="$finish"/>
|
||||||
|
EOTIME;
|
||||||
|
} else {
|
||||||
|
$time_range = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning! May contain traces of double negatives...
|
||||||
|
$neg_cancelled = ( $cancelled === true ? "no" : "yes" );
|
||||||
|
$neg_completed = ( $cancelled === true ? "no" : "yes" );
|
||||||
|
|
||||||
|
$filter = <<<EOFILTER
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="VTODO">
|
||||||
|
<C:prop-filter name="STATUS">
|
||||||
|
<C:text-match negate-condition="$neg_completed">COMPLETED</C:text-match>
|
||||||
|
</C:prop-filter>
|
||||||
|
<C:prop-filter name="STATUS">
|
||||||
|
<C:text-match negate-condition="$neg_cancelled">CANCELLED</C:text-match>
|
||||||
|
</C:prop-filter>$time_range
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
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 = <<<EOFILTER
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="$component_type">
|
||||||
|
<C:prop-filter name="UID">
|
||||||
|
<C:text-match icollation="i;octet">$uid</C:text-match>
|
||||||
|
</C:prop-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a Sync operation. This is the fastest way to detect changes.
|
||||||
|
*
|
||||||
|
* @param string $url URL for the calendar
|
||||||
|
* @param boolean $initial It's the first synchronization
|
||||||
|
* @param boolean $support_dav_sync The CalDAV server supports sync-collection
|
||||||
|
*
|
||||||
|
* @return array of responses
|
||||||
|
*/
|
||||||
|
public function GetSync($relative_url = null, $initial = true, $support_dav_sync = false) {
|
||||||
|
if (!empty($relative_url)) {
|
||||||
|
$this->SetCalendar($relative_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($support_dav_sync) {
|
||||||
|
$token = ($initial ? "" : $this->synctoken[$this->calendar_url]);
|
||||||
|
|
||||||
|
$body = <<<EOXML
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<D:sync-collection xmlns:D="DAV:">
|
||||||
|
<D:sync-token>$token</D:sync-token>
|
||||||
|
<D:sync-level>1</D:sync-level>
|
||||||
|
<D:prop>
|
||||||
|
<D:getetag/>
|
||||||
|
<D:getlastmodified/>
|
||||||
|
</D:prop>
|
||||||
|
</D:sync-collection>
|
||||||
|
EOXML;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$body = <<<EOXML
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<D:prop>
|
||||||
|
<D:getetag/>
|
||||||
|
<D:getlastmodified/>
|
||||||
|
</D:prop>
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR" />
|
||||||
|
</C:filter>
|
||||||
|
</C:calendar-query>
|
||||||
|
EOXML;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->SetDepth(1);
|
||||||
|
$this->DoRequest($this->calendar_url, "REPORT", $body, "text/xml");
|
||||||
|
|
||||||
|
$report = array();
|
||||||
|
foreach( $this->xmlnodes as $k => $v ) {
|
||||||
|
switch( $v['tag'] ) {
|
||||||
|
case 'DAV::response':
|
||||||
|
if ( $v['type'] == 'open' ) {
|
||||||
|
$response = array();
|
||||||
|
} elseif ( $v['type'] == 'close' ) {
|
||||||
|
$report[] = $response;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'DAV::href':
|
||||||
|
$response['href'] = basename( rawurldecode($v['value']) );
|
||||||
|
break;
|
||||||
|
case 'DAV::getlastmodified':
|
||||||
|
if (isset($v['value'])) {
|
||||||
|
$response['getlastmodified'] = $v['value'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$response['getlastmodified'] = '';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'DAV::getetag':
|
||||||
|
$response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
|
||||||
|
break;
|
||||||
|
case 'DAV::sync-token':
|
||||||
|
$this->synctoken[$this->calendar_url] = $v['value'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $report;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
892
sources/include/z_carddav.php
Normal file
892
sources/include/z_carddav.php
Normal file
|
@ -0,0 +1,892 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CardDAV PHP
|
||||||
|
*
|
||||||
|
* Simple CardDAV query
|
||||||
|
* --------------------
|
||||||
|
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
|
||||||
|
* $carddav->set_auth('username', 'password');
|
||||||
|
* echo $carddav->get();
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Simple vCard query
|
||||||
|
* ------------------
|
||||||
|
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
|
||||||
|
* $carddav->set_auth('username', 'password');
|
||||||
|
* echo $carddav->get_vcard('0126FFB4-2EB74D0A-302EA17F');
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* XML vCard query
|
||||||
|
* ------------------
|
||||||
|
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
|
||||||
|
* $carddav->set_auth('username', 'password');
|
||||||
|
* echo $carddav->get_xml_vcard('0126FFB4-2EB74D0A-302EA17F');
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Check CardDAV server connection
|
||||||
|
* -------------------------------
|
||||||
|
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
|
||||||
|
* $carddav->set_auth('username', 'password');
|
||||||
|
* var_dump($carddav->check_connection());
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* CardDAV delete query
|
||||||
|
* --------------------
|
||||||
|
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
|
||||||
|
* $carddav->set_auth('username', 'password');
|
||||||
|
* $carddav->delete('0126FFB4-2EB74D0A-302EA17F');
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* CardDAV add query
|
||||||
|
* --------------------
|
||||||
|
* $vcard = 'BEGIN:VCARD
|
||||||
|
* VERSION:3.0
|
||||||
|
* UID:1f5ea45f-b28a-4b96-25as-ed4f10edf57b
|
||||||
|
* FN:Christian Putzke
|
||||||
|
* N:Christian;Putzke;;;
|
||||||
|
* EMAIL;TYPE=OTHER:christian.putzke@graviox.de
|
||||||
|
* END:VCARD';
|
||||||
|
*
|
||||||
|
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
|
||||||
|
* $carddav->set_auth('username', 'password');
|
||||||
|
* $vcard_id = $carddav->add($vcard);
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* CardDAV update query
|
||||||
|
* --------------------
|
||||||
|
* $vcard = 'BEGIN:VCARD
|
||||||
|
* VERSION:3.0
|
||||||
|
* UID:1f5ea45f-b28a-4b96-25as-ed4f10edf57b
|
||||||
|
* FN:Christian Putzke
|
||||||
|
* N:Christian;Putzke;;;
|
||||||
|
* EMAIL;TYPE=OTHER:christian.putzke@graviox.de
|
||||||
|
* END:VCARD';
|
||||||
|
*
|
||||||
|
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
|
||||||
|
* $carddav->set_auth('username', 'password');
|
||||||
|
* $carddav->update($vcard, '0126FFB4-2EB74D0A-302EA17F');
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* CardDAV debug
|
||||||
|
* -------------
|
||||||
|
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
|
||||||
|
* $carddav->enable_debug();
|
||||||
|
* $carddav->set_auth('username', 'password');
|
||||||
|
* $carddav->get();
|
||||||
|
* var_dump($carddav->get_debug());
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* CardDAV server list
|
||||||
|
* -------------------
|
||||||
|
* DAViCal: https://example.com/{resource|principal|username}/{collection}/
|
||||||
|
* Apple Addressbook Server: https://example.com/addressbooks/users/{resource|principal|username}/{collection}/
|
||||||
|
* memotoo: https://sync.memotoo.com/cardDAV/
|
||||||
|
* SabreDAV: https://example.com/addressbooks/{resource|principal|username}/{collection}/
|
||||||
|
* ownCloud: https://example.com/apps/contacts/carddav.php/addressbooks/{resource|principal|username}/{collection}/
|
||||||
|
* SOGo: https://example.com/SOGo/dav/{resource|principal|username}/Contacts/{collection}/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Christian Putzke <christian.putzke@graviox.de>
|
||||||
|
* @copyright Christian Putzke
|
||||||
|
* @link http://www.graviox.de/
|
||||||
|
* @link https://twitter.com/cputzke/
|
||||||
|
* @since 20.07.2011
|
||||||
|
* @version 0.6
|
||||||
|
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class carddav_backend
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* CardDAV PHP Version
|
||||||
|
*
|
||||||
|
* @constant string
|
||||||
|
*/
|
||||||
|
const VERSION = '0.6.c';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User agent displayed in http requests
|
||||||
|
*
|
||||||
|
* @constant string
|
||||||
|
*/
|
||||||
|
const USERAGENT = 'Z-Push CardDAV/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CardDAV server url
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $url = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CardDAV server url_parts
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $url_parts = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication string
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $auth = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication: username
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $username = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication: password
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $password = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Characters used for vCard id generation
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $vcard_id_chars = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CardDAV server connection (curl handle)
|
||||||
|
*
|
||||||
|
* @var resource
|
||||||
|
*/
|
||||||
|
private $curl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug on or off
|
||||||
|
*
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
private $debug = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All available debug information
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $debug_information = array();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync-token for sync-collection operations.
|
||||||
|
*
|
||||||
|
* @var array[string]
|
||||||
|
*/
|
||||||
|
private $synctoken = array();
|
||||||
|
|
||||||
|
|
||||||
|
/* VCard File URL Extension
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $url_vcard_extension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception codes
|
||||||
|
*/
|
||||||
|
const EXCEPTION_WRONG_HTTP_STATUS_CODE_GET = 1000;
|
||||||
|
const EXCEPTION_WRONG_HTTP_STATUS_CODE_GET_VCARD = 1001;
|
||||||
|
const EXCEPTION_WRONG_HTTP_STATUS_CODE_GET_XML_VCARD = 1002;
|
||||||
|
const EXCEPTION_WRONG_HTTP_STATUS_CODE_DELETE = 1003;
|
||||||
|
const EXCEPTION_WRONG_HTTP_STATUS_CODE_ADD = 1004;
|
||||||
|
const EXCEPTION_WRONG_HTTP_STATUS_CODE_UPDATE = 1005;
|
||||||
|
const EXCEPTION_MALFORMED_XML_RESPONSE = 1006;
|
||||||
|
const EXCEPTION_COULD_NOT_GENERATE_NEW_VCARD_ID = 1007;
|
||||||
|
const EXCEPTION_COULD_NOT_FIND_VCARD_HREF = 1008;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* Sets the CardDAV server url
|
||||||
|
*
|
||||||
|
* @param string $url CardDAV server url
|
||||||
|
* @param string $url_vcard_extension extension needed to recover the vcard, it could be empty
|
||||||
|
*/
|
||||||
|
public function __construct($url = null, $url_vcard_extension = '.vcf') {
|
||||||
|
if ($url !== null) {
|
||||||
|
$this->set_url($url);
|
||||||
|
}
|
||||||
|
$this->url_vcard_extension = $url_vcard_extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets debug information
|
||||||
|
*
|
||||||
|
* @param array $debug_information Debug information
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set_debug(array $debug_information) {
|
||||||
|
$this->debug_information[] = $debug_information;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the CardDAV server url
|
||||||
|
*
|
||||||
|
* @param string $url CardDAV server url
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set_url($url) {
|
||||||
|
$this->url = $url;
|
||||||
|
|
||||||
|
// Url always end with trailing /
|
||||||
|
if (substr($this->url, -1, 1) !== '/') {
|
||||||
|
$this->url .= '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->url_parts = parse_url($this->url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets authentication information
|
||||||
|
*
|
||||||
|
* @param string $username CardDAV server username
|
||||||
|
* @param string $password CardDAV server password
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set_auth($username, $password) {
|
||||||
|
$this->username = $username;
|
||||||
|
$this->password = $password;
|
||||||
|
$this->auth = $username . ':' . $password;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all available debug information
|
||||||
|
*
|
||||||
|
* @return array $this->debug_information All available debug information
|
||||||
|
*/
|
||||||
|
public function get_debug() {
|
||||||
|
return $this->debug_information;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the CardDAV vcard url extension
|
||||||
|
*
|
||||||
|
* Most providers do requests handling Vcards with .vcf, however
|
||||||
|
* this isn't always the case and some providers (such as Google)
|
||||||
|
* returned a 404 if the .vcf extension is used - or the other
|
||||||
|
* way around, returning 404 unless .vcf is used.
|
||||||
|
*
|
||||||
|
* Both approaches are technically correct, see rfc635
|
||||||
|
* http://tools.ietf.org/html/rfc6352
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param string $extension File extension
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set_vcard_extension($extension) {
|
||||||
|
$this->url_vcard_extension = $extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all vCards including additional information from the CardDAV server.
|
||||||
|
* This operation could be slow if you have a lot of vcards.
|
||||||
|
*
|
||||||
|
* @param boolean $include_vcards Include vCards within the response (simplified only)
|
||||||
|
* @param boolean $raw Get response raw or simplified
|
||||||
|
* @params boolean $discover Only discover addressbooks
|
||||||
|
* @return string Raw or simplified XML response
|
||||||
|
*/
|
||||||
|
public function get($include_vcards = true, $raw = false, $discover = false) {
|
||||||
|
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->get"));
|
||||||
|
if ($discover) {
|
||||||
|
$result = $this->query($this->url, 'PROPFIND', null, null, '1');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$result = $this->query($this->url, 'PROPFIND');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($result['http_code']) {
|
||||||
|
case 200:
|
||||||
|
case 207:
|
||||||
|
if ($raw === true) {
|
||||||
|
return $result['response'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return $this->simplify($result['response'], $include_vcards);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception('Woops, something\'s gone wrong! The CardDAV server returned the http status code ' . $result['http_code'] . '.', self::EXCEPTION_WRONG_HTTP_STATUS_CODE_GET);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all vcards matching a full name or mail.
|
||||||
|
*
|
||||||
|
* @param string $pattern Pattern to search
|
||||||
|
* @param integer $limit Return only N vcards
|
||||||
|
* @param boolean $include_vcards Include vCards within the response (simplified only)
|
||||||
|
* @param boolean $raw Get response raw or simplified
|
||||||
|
* @param boolean $support_fn_search If the server supports searchs by fn
|
||||||
|
* @return string Raw or simplified XML response
|
||||||
|
*/
|
||||||
|
public function search_vcards($pattern, $limit, $include_vcards = true, $raw = false, $support_fn_search = false) {
|
||||||
|
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->search_vcards"));
|
||||||
|
if ($support_fn_search) {
|
||||||
|
$xml = <<<EOFCONTENTSEARCH
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:addressbook-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
|
||||||
|
<D:prop>
|
||||||
|
<D:getetag/>
|
||||||
|
<C:address-data>
|
||||||
|
<C:allprop/>
|
||||||
|
</C:address-data>
|
||||||
|
</D:prop>
|
||||||
|
<C:filter test="anyof">
|
||||||
|
<C:prop-filter name="FN">
|
||||||
|
<C:text-match collation="i;unicode-casemap" negate-condition="no" match-type="contains">$pattern</C:text-match>
|
||||||
|
</C:prop-filter>
|
||||||
|
</C:filter>
|
||||||
|
<C:limit>
|
||||||
|
<C:nresults>$limit</C:nresults>
|
||||||
|
</C:limit>
|
||||||
|
</C:addressbook-query>
|
||||||
|
EOFCONTENTSEARCH;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$xml = <<<EOFCONTENTSEARCH
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:addressbook-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
|
||||||
|
<D:prop>
|
||||||
|
<D:getetag/>
|
||||||
|
<C:address-data>
|
||||||
|
<C:allprop/>
|
||||||
|
</C:address-data>
|
||||||
|
</D:prop>
|
||||||
|
<C:filter test="anyof">
|
||||||
|
<C:prop-filter name="sn">
|
||||||
|
<C:text-match collation="i;unicode-casemap" negate-condition="no" match-type="contains">$pattern</C:text-match>
|
||||||
|
</C:prop-filter>
|
||||||
|
<C:prop-filter name="givenname">
|
||||||
|
<C:text-match collation="i;unicode-casemap" negate-condition="no" match-type="contains">$pattern</C:text-match>
|
||||||
|
</C:prop-filter>
|
||||||
|
<C:prop-filter name="email">
|
||||||
|
<C:text-match collation="i;unicode-casemap" negate-condition="no" match-type="contains">$pattern</C:text-match>
|
||||||
|
</C:prop-filter>
|
||||||
|
</C:filter>
|
||||||
|
<C:limit>
|
||||||
|
<C:nresults>$limit</C:nresults>
|
||||||
|
</C:limit>
|
||||||
|
</C:addressbook-query>
|
||||||
|
EOFCONTENTSEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->do_query_report($xml, $include_vcards, $raw, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all vcards or changes since the last sync.
|
||||||
|
*
|
||||||
|
* @param boolean $initial If the sync should be full
|
||||||
|
* @param boolean $include_vcards If the vCards should be included within the response
|
||||||
|
* @param boolean $support_carddav_sync If the cardDAV server supports sync-collection operations (DAViCal supports it)
|
||||||
|
* @return string Simplified XML response
|
||||||
|
*/
|
||||||
|
public function do_sync($initial = true, $include_vcards = false, $support_carddav_sync = false) {
|
||||||
|
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->do_sync"));
|
||||||
|
|
||||||
|
if ($support_carddav_sync) {
|
||||||
|
if ($initial) {
|
||||||
|
$token = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$token = $this->synctoken[$this->url];
|
||||||
|
}
|
||||||
|
|
||||||
|
$xml = <<<EOFXMLINITIALSYNC
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<D:sync-collection xmlns:D="DAV:">
|
||||||
|
<D:sync-token>$token</D:sync-token>
|
||||||
|
<D:sync-level>1</D:sync-level>
|
||||||
|
<D:prop>
|
||||||
|
<D:getetag/>
|
||||||
|
<D:getlastmodified/>
|
||||||
|
</D:prop>
|
||||||
|
</D:sync-collection>
|
||||||
|
EOFXMLINITIALSYNC;
|
||||||
|
|
||||||
|
return $this->do_query_report($xml, $include_vcards, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return $this->get($include_vcards, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a REPORT query against the server
|
||||||
|
*
|
||||||
|
* @param string $xml XML body request
|
||||||
|
* @param boolean $include_vcards If the vCards should be included within the response
|
||||||
|
* @param boolean $raw If the response should be raw or XML simplified
|
||||||
|
* @param boolean $remove_duplicates If we will apply uniqness to the response vcards
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function do_query_report($xml, $include_vcards = true, $raw = false, $remove_duplicates = false) {
|
||||||
|
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->do_query_report"));
|
||||||
|
$result = $this->query($this->url, 'REPORT', $xml, 'text/xml');
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($result['http_code']) {
|
||||||
|
case 200:
|
||||||
|
case 207:
|
||||||
|
if ($raw === true) {
|
||||||
|
return $result['response'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return $this->simplify($result['response'], $include_vcards, $remove_duplicates);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception('Woops, something\'s gone wrong! The CardDAV server returned the http status code ' . $result['http_code'] . '.', self::EXCEPTION_WRONG_HTTP_STATUS_CODE_GET);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception $ex) {
|
||||||
|
// vcard not found
|
||||||
|
if ($ex->getCode() == self::EXCEPTION_COULD_NOT_FIND_VCARD_HREF) {
|
||||||
|
if (strlen($this->url_vcard_extension) == 0 || stripos($xml, $this->url_vcard_extension) === FALSE) {
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// try to do the same without the $this->url_vcard_extension
|
||||||
|
return $this->do_query_report(str_ireplace($this->url_vcard_extension, "", $xml), $include_vcards, $raw, $remove_duplicates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a clean vCard from the CardDAV server
|
||||||
|
*
|
||||||
|
* @param string $vcard_href vCard href on the CardDAV server
|
||||||
|
* @return string vCard (text/vcard)
|
||||||
|
*/
|
||||||
|
private function get_vcard($vcard_href) {
|
||||||
|
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->get_vcard"));
|
||||||
|
$url = $this->url_parts['scheme'] . '://' . $this->url_parts['host'] . ':' . $this->url_parts['port'] . $vcard_href;
|
||||||
|
$result = $this->query($url, 'GET');
|
||||||
|
|
||||||
|
switch ($result['http_code']) {
|
||||||
|
case 200:
|
||||||
|
case 207:
|
||||||
|
return $result['response'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception('Woops, something\'s gone wrong! The CardDAV server returned the http status code ' . $result['http_code'] . '.', self::EXCEPTION_WRONG_HTTP_STATUS_CODE_GET_VCARD);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a vCard + XML from the CardDAV Server
|
||||||
|
*
|
||||||
|
* @param string $vcard_id vCard id on the CardDAV Server
|
||||||
|
* @return string Raw or simplified vCard (text/xml)
|
||||||
|
*/
|
||||||
|
public function get_xml_vcard($vcard_id) {
|
||||||
|
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->get_xml_vcard"));
|
||||||
|
$href = $this->url_parts['path'] . str_replace($this->url_vcard_extension, null, $vcard_id) . $this->url_vcard_extension;
|
||||||
|
|
||||||
|
// If we don't ask for allprop, SOGo doesn't return the content_type
|
||||||
|
$xml = <<<EOFXMLGETXMLVCARD
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<C:addressbook-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
|
||||||
|
<D:prop>
|
||||||
|
<D:getetag/>
|
||||||
|
<D:getlastmodified/>
|
||||||
|
<C:address-data>
|
||||||
|
<C:allprop/>
|
||||||
|
</C:address-data>
|
||||||
|
</D:prop>
|
||||||
|
<D:href>$href</D:href>
|
||||||
|
</C:addressbook-multiget>
|
||||||
|
EOFXMLGETXMLVCARD;
|
||||||
|
|
||||||
|
return $this->do_query_report($xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the debug mode
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enable_debug() {
|
||||||
|
$this->debug = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the CardDAV server is reachable
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function check_connection() {
|
||||||
|
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->check_connection"));
|
||||||
|
$result = $this->query($this->url, 'OPTIONS');
|
||||||
|
|
||||||
|
$status = false;
|
||||||
|
switch($result['http_code']) {
|
||||||
|
case 200:
|
||||||
|
case 207:
|
||||||
|
case 401:
|
||||||
|
$status = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an entry from the CardDAV server
|
||||||
|
*
|
||||||
|
* @param string $vcard_id vCard id on the CardDAV server
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function delete($vcard_id) {
|
||||||
|
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->delete"));
|
||||||
|
$result = $this->query($this->url . $vcard_id . $this->url_vcard_extension, 'DELETE');
|
||||||
|
|
||||||
|
|
||||||
|
switch ($result['http_code']) {
|
||||||
|
case 204:
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception('Woops, something\'s gone wrong! The CardDAV server returned the http status code ' . $result['http_code'] . '.', self::EXCEPTION_WRONG_HTTP_STATUS_CODE_DELETE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an entry to the CardDAV server
|
||||||
|
*
|
||||||
|
* @param string $vcard vCard
|
||||||
|
* @param string $vcard_id vCard id on the CardDAV server
|
||||||
|
* @return string The new vCard id
|
||||||
|
*/
|
||||||
|
public function add($vcard, $vcard_id = null) {
|
||||||
|
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->add"));
|
||||||
|
if ($vcard_id === null) {
|
||||||
|
$vcard_id = $this->generate_vcard_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
$vcard = str_replace("\nEND:VCARD","\nUID:" . $vcard_id . "\r\nEND:VCARD", $vcard);
|
||||||
|
$result = $this->query($this->url . $vcard_id . $this->url_vcard_extension, 'PUT', $vcard, 'text/vcard');
|
||||||
|
|
||||||
|
|
||||||
|
switch($result['http_code']) {
|
||||||
|
case 201:
|
||||||
|
case 204:
|
||||||
|
return $vcard_id;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception('Woops, something\'s gone wrong! The CardDAV server returned the http status code ' . $result['http_code'] . '.', self::EXCEPTION_WRONG_HTTP_STATUS_CODE_ADD);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an entry to the CardDAV server
|
||||||
|
*
|
||||||
|
* @param string $vcard vCard
|
||||||
|
* @param string $vcard_id vCard id on the CardDAV server
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function update($vcard, $vcard_id) {
|
||||||
|
try {
|
||||||
|
return $this->add($vcard, $vcard_id);
|
||||||
|
}
|
||||||
|
catch (Exception $e) {
|
||||||
|
throw new Exception($e->getMessage(), self::EXCEPTION_WRONG_HTTP_STATUS_CODE_UPDATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplify CardDAV XML response
|
||||||
|
*
|
||||||
|
* @param string $response CardDAV XML response
|
||||||
|
* @param boolean $include_vcards Include vCards or not
|
||||||
|
* @param boolean $remove_duplicates If we will apply uniqness to the response vcards
|
||||||
|
* @return string Simplified CardDAV XML response
|
||||||
|
*/
|
||||||
|
private function simplify($response, $include_vcards = true, $remove_duplicates = false) {
|
||||||
|
$response = $this->remove_namespaces($response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$xml = new SimpleXMLElement($response);
|
||||||
|
}
|
||||||
|
catch(Exception $e) {
|
||||||
|
throw new Exception('The XML response seems to be malformed and can\'t be simplified!', self::EXCEPTION_MALFORMED_XML_RESPONSE, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($xml->{'sync-token'})) {
|
||||||
|
$this->synctoken[$this->url] = $xml->{'sync-token'};
|
||||||
|
}
|
||||||
|
|
||||||
|
$simplified_xml = new XMLWriter();
|
||||||
|
$simplified_xml->openMemory();
|
||||||
|
$simplified_xml->setIndent(4);
|
||||||
|
|
||||||
|
$simplified_xml->startDocument('1.0', 'utf-8');
|
||||||
|
$simplified_xml->startElement('response');
|
||||||
|
|
||||||
|
if (!empty($xml->response)) {
|
||||||
|
$unique_etags = array();
|
||||||
|
|
||||||
|
foreach ($xml->response as $response) {
|
||||||
|
if (isset($response->propstat)) {
|
||||||
|
if ((strlen($this->url_vcard_extension) > 0 && preg_match('/'.$this->url_vcard_extension.'/', $response->href) &&
|
||||||
|
!(isset($response->propstat->prop->resourcetype) && isset($response->propstat->prop->resourcetype->addressbook)))
|
||||||
|
|| preg_match('/vcard/', $response->propstat->prop->getcontenttype) || isset($response->propstat->prop->{'address-data'}) || isset($response->propstat->prop->{'addressbook-data'})) {
|
||||||
|
// It's a vcard
|
||||||
|
$id = basename($response->href);
|
||||||
|
$id = str_replace($this->url_vcard_extension, null, $id);
|
||||||
|
|
||||||
|
if (!empty($id)) {
|
||||||
|
$simplified_xml->startElement('element');
|
||||||
|
$simplified_xml->writeElement('id', $id);
|
||||||
|
$simplified_xml->writeElement('etag', str_replace('"', null, $response->propstat->prop->getetag));
|
||||||
|
$simplified_xml->writeElement('last_modified', $response->propstat->prop->getlastmodified);
|
||||||
|
|
||||||
|
if ($include_vcards === true) {
|
||||||
|
if (isset($response->propstat->prop->{'address-data'})) {
|
||||||
|
// We already have the full vcard
|
||||||
|
$simplified_xml->writeElement('vcard', $response->propstat->prop->{'address-data'});
|
||||||
|
}
|
||||||
|
else if (isset($response->propstat->prop->{'addressbook-data'})) {
|
||||||
|
// We already have the full vcard, also
|
||||||
|
$simplified_xml->writeElement('vcard', $response->propstat->prop->{'addressbook-data'});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We don't have the vcard, we need to get it. We never should hit here, it would mean a buggy server
|
||||||
|
$simplified_xml->writeElement('vcard', $this->get_vcard($response->href));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$simplified_xml->endElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isset($response->propstat->prop->resourcetype->addressbook)) {
|
||||||
|
// It's an addressbook
|
||||||
|
if (isset($response->propstat->prop->href)) {
|
||||||
|
$href = $response->propstat->prop->href;
|
||||||
|
}
|
||||||
|
else if (isset($response->href)) {
|
||||||
|
$href = $response->href;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$href = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = str_replace($this->url_parts['path'], null, $this->url) . $href;
|
||||||
|
$simplified_xml->startElement('addressbook_element');
|
||||||
|
$simplified_xml->writeElement('display_name', $response->propstat->prop->displayname);
|
||||||
|
$simplified_xml->writeElement('url', $url);
|
||||||
|
$simplified_xml->writeElement('last_modified', $response->propstat->prop->getlastmodified);
|
||||||
|
$simplified_xml->endElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We don't have a propstat node, so it will be an error answer
|
||||||
|
if (isset($response->status) && preg_match('/404 Not Found/', $response->status)) {
|
||||||
|
throw new Exception('Not found!', self::EXCEPTION_COULD_NOT_FIND_VCARD_HREF);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Exception('The XML response is an error message and can\'t be simplified!', self::EXCEPTION_MALFORMED_XML_RESPONSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($unique_etags);
|
||||||
|
}
|
||||||
|
|
||||||
|
$simplified_xml->endElement();
|
||||||
|
$simplified_xml->endDocument();
|
||||||
|
|
||||||
|
return $simplified_xml->outputMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans CardDAV XML response
|
||||||
|
*
|
||||||
|
* @param string $response CardDAV XML response
|
||||||
|
* @return string $response Cleaned CardDAV XML response
|
||||||
|
*/
|
||||||
|
private function remove_namespaces($response) {
|
||||||
|
// $response = preg_replace('/<[a-z0-9]+:(.*)/i', '<$1', $response);
|
||||||
|
// $response = preg_replace('/<\/[a-z0-9]+:(.*)/i', '</$1', $response);
|
||||||
|
|
||||||
|
// Removing namespace it's pretty hard with regex.
|
||||||
|
// Also, each server uses different namespaces, so using namespaces it's impossible
|
||||||
|
// This uses DOMDocument, so it won't be the fastest or least memory-using way.
|
||||||
|
// Feel free to suggest or improve it
|
||||||
|
// http://stackoverflow.com/questions/15634291/remove-name-space-from-xml-file-and-save-as-new-xml
|
||||||
|
$xsl = <<<EOFXSL
|
||||||
|
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml" version="1.0" encoding="UTF-8" />
|
||||||
|
<xsl:template match="*">
|
||||||
|
<xsl:element name="{local-name()}">
|
||||||
|
<xsl:apply-templates select="@* | node()"/>
|
||||||
|
</xsl:element>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
||||||
|
EOFXSL;
|
||||||
|
|
||||||
|
$dom = new DOMDocument();
|
||||||
|
$dom->loadXML($response);
|
||||||
|
$stylesheet = new DOMDocument();
|
||||||
|
$stylesheet->loadXML($xsl);
|
||||||
|
$xsltprocessor = new XSLTProcessor();
|
||||||
|
$xsltprocessor->importStylesheet($stylesheet);
|
||||||
|
|
||||||
|
$response = $xsltprocessor->transformToXML($dom);
|
||||||
|
$dom = null;
|
||||||
|
$stylesheet = null;
|
||||||
|
$xsltprocessor = null;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Curl initialization
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function curl_init() {
|
||||||
|
if (empty($this->curl)) {
|
||||||
|
$this->curl = curl_init();
|
||||||
|
curl_setopt($this->curl, CURLOPT_HEADER, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_USERAGENT, self::USERAGENT.self::VERSION);
|
||||||
|
|
||||||
|
if ($this->auth !== null) {
|
||||||
|
curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
||||||
|
curl_setopt($this->curl, CURLOPT_USERPWD, $this->auth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the CardDAV server via curl and returns the response
|
||||||
|
*
|
||||||
|
* @param string $url CardDAV server URL
|
||||||
|
* @param string $method HTTP method like (OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE)
|
||||||
|
* @param string $content Content for CardDAV queries
|
||||||
|
* @param string $content_type Set content type
|
||||||
|
* @param string $depth Set Depth
|
||||||
|
* @return array Raw CardDAV Response and http status code
|
||||||
|
*/
|
||||||
|
private function query($url, $method, $content = null, $content_type = null, $depth = "infinity") {
|
||||||
|
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->query - '%s' '%s' '%s' '%s'", $url, $method, $content, $content_type));
|
||||||
|
|
||||||
|
$this->curl_init();
|
||||||
|
|
||||||
|
curl_setopt($this->curl, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method);
|
||||||
|
|
||||||
|
if ($content !== null) {
|
||||||
|
curl_setopt($this->curl, CURLOPT_POST, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $content);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
curl_setopt($this->curl, CURLOPT_POST, false);
|
||||||
|
curl_setopt($this->curl, CURLOPT_POSTFIELDS, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($content_type !== null) {
|
||||||
|
curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-type: '.$content_type. '; charset=utf-8', 'Depth: '.$depth));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Depth: '.$depth));
|
||||||
|
}
|
||||||
|
|
||||||
|
$complete_response = curl_exec($this->curl);
|
||||||
|
$header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE);
|
||||||
|
$http_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
|
||||||
|
$header = trim(substr($complete_response, 0, $header_size));
|
||||||
|
$response = substr($complete_response, $header_size);
|
||||||
|
|
||||||
|
$return = array(
|
||||||
|
'response' => $response,
|
||||||
|
'http_code' => $http_code
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->debug === true) {
|
||||||
|
$debug = $return;
|
||||||
|
$debug['url'] = $url;
|
||||||
|
$debug['method'] = $method;
|
||||||
|
$debug['content'] = $content;
|
||||||
|
$debug['content_type'] = $content_type;
|
||||||
|
$debug['header'] = $header;
|
||||||
|
$this->set_debug($debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a valid and unused vCard id
|
||||||
|
*
|
||||||
|
* @return string $vcard_id Valid vCard id
|
||||||
|
*/
|
||||||
|
private function generate_vcard_id() {
|
||||||
|
$vcard_id = null;
|
||||||
|
|
||||||
|
for ($number = 0; $number <= 25; $number ++) {
|
||||||
|
if ($number == 8 || $number == 17) {
|
||||||
|
$vcard_id .= '-';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$vcard_id .= $this->vcard_id_chars[mt_rand(0, (count($this->vcard_id_chars) - 1))];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$carddav = new carddav_backend($this->url);
|
||||||
|
$carddav->set_auth($this->username, $this->password);
|
||||||
|
|
||||||
|
$result = $carddav->query($this->url . $vcard_id . $this->url_vcard_extension, 'GET');
|
||||||
|
|
||||||
|
if ($result['http_code'] !== 404) {
|
||||||
|
$vcard_id = $this->generate_vcard_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $vcard_id;
|
||||||
|
}
|
||||||
|
catch (Exception $e) {
|
||||||
|
throw new Exception($e->getMessage(), self::EXCEPTION_COULD_NOT_GENERATE_NEW_VCARD_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor
|
||||||
|
* Close curl connection if it's open
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __destruct() {
|
||||||
|
if (!empty($this->curl)) {
|
||||||
|
curl_close($this->curl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -133,15 +133,24 @@ include_once('version.php');
|
||||||
Request::Initialize();
|
Request::Initialize();
|
||||||
ZLog::Initialize();
|
ZLog::Initialize();
|
||||||
|
|
||||||
|
$autenticationInfo = Request::AuthenticationInfo();
|
||||||
|
$GETUser = Request::GetGETUser();
|
||||||
|
|
||||||
ZLog::Write(LOGLEVEL_DEBUG,"-------- Start");
|
ZLog::Write(LOGLEVEL_DEBUG,"-------- Start");
|
||||||
ZLog::Write(LOGLEVEL_INFO,
|
ZLog::Write(LOGLEVEL_INFO,
|
||||||
sprintf("Version='%s' method='%s' from='%s' cmd='%s' getUser='%s' devId='%s' devType='%s'",
|
sprintf("Version='%s' method='%s' from='%s' cmd='%s' getUser='%s' devId='%s' devType='%s'",
|
||||||
@constant('ZPUSH_VERSION'), Request::GetMethod(), Request::GetRemoteAddr(),
|
@constant('ZPUSH_VERSION'), Request::GetMethod(), Request::GetRemoteAddr(),
|
||||||
Request::GetCommand(), Request::GetGETUser(), Request::GetDeviceID(), Request::GetDeviceType()));
|
Request::GetCommand(), $GETUser, Request::GetDeviceID(), Request::GetDeviceType()));
|
||||||
|
|
||||||
// Stop here if this is an OPTIONS request
|
// Stop here if this is an OPTIONS request
|
||||||
if (Request::IsMethodOPTIONS())
|
if (Request::IsMethodOPTIONS()) {
|
||||||
throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST);
|
if (!$autenticationInfo || !$GETUser) {
|
||||||
|
throw new AuthenticationRequiredException("Access denied. Please send authorisation information");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ZPush::CheckAdvancedConfig();
|
ZPush::CheckAdvancedConfig();
|
||||||
|
|
||||||
|
@ -152,11 +161,22 @@ include_once('version.php');
|
||||||
if(Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetDeviceID() || !Request::GetDeviceType()))
|
if(Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetDeviceID() || !Request::GetDeviceType()))
|
||||||
throw new FatalException("Requested the Z-Push URL without the required GET parameters");
|
throw new FatalException("Requested the Z-Push URL without the required GET parameters");
|
||||||
|
|
||||||
|
|
||||||
|
// This won't be useful with Zarafa, but it will be with standalone Z-Push
|
||||||
|
if (defined('PRE_AUTHORIZE_USERS') && PRE_AUTHORIZE_USERS === true) {
|
||||||
|
if (!Request::IsMethodGET()) {
|
||||||
|
// Check if User/Device are authorized
|
||||||
|
if (ZPush::GetDeviceManager()->GetUserDevicePermission($GETUser, Request::GetDeviceID()) != SYNC_COMMONSTATUS_SUCCESS) {
|
||||||
|
throw new AuthenticationRequiredException("Access denied. Username and Device not authorized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load the backend
|
// Load the backend
|
||||||
$backend = ZPush::GetBackend();
|
$backend = ZPush::GetBackend();
|
||||||
|
|
||||||
// always request the authorization header
|
// always request the authorization header
|
||||||
if (! Request::AuthenticationInfo() || !Request::GetGETUser())
|
if (!$autenticationInfo || !$GETUser)
|
||||||
throw new AuthenticationRequiredException("Access denied. Please send authorisation information");
|
throw new AuthenticationRequiredException("Access denied. Please send authorisation information");
|
||||||
|
|
||||||
// check the provisioning information
|
// check the provisioning information
|
||||||
|
|
|
@ -67,6 +67,7 @@ class ASDevice extends StateObject {
|
||||||
'asversion' => false,
|
'asversion' => false,
|
||||||
'ignoredmessages' => array(),
|
'ignoredmessages' => array(),
|
||||||
'announcedASversion' => false,
|
'announcedASversion' => false,
|
||||||
|
'foldersynccomplete' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
static private $loadedData;
|
static private $loadedData;
|
||||||
|
|
|
@ -115,6 +115,7 @@ class ChangesMemoryWrapper extends HierarchyCache implements IImportChanges, IEx
|
||||||
public function LoadConflicts($contentparameters, $state) { return true; }
|
public function LoadConflicts($contentparameters, $state) { return true; }
|
||||||
public function ConfigContentParameters($contentparameters) { return true; }
|
public function ConfigContentParameters($contentparameters) { return true; }
|
||||||
public function ImportMessageReadFlag($id, $flags) { return true; }
|
public function ImportMessageReadFlag($id, $flags) { return true; }
|
||||||
|
public function ImportMessageStarFlag($id, $flags) { return true; }
|
||||||
public function ImportMessageMove($id, $newfolder) { return true; }
|
public function ImportMessageMove($id, $newfolder) { return true; }
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------------------------------------
|
/**----------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
@ -595,6 +595,11 @@ class DeviceManager {
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function IsHierarchyFullResyncRequired() {
|
public function IsHierarchyFullResyncRequired() {
|
||||||
|
// do not check for loop detection, if the foldersync is not yet complete
|
||||||
|
if ($this->GetFolderSyncComplete() === false) {
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, "DeviceManager->IsHierarchyFullResyncRequired(): aborted, as exporting of folders has not yet completed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// check for potential process loops like described in ZP-5
|
// check for potential process loops like described in ZP-5
|
||||||
return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired();
|
return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired();
|
||||||
}
|
}
|
||||||
|
@ -695,6 +700,45 @@ class DeviceManager {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the indicator if the FolderSync was completed successfully (all folders synchronized)
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function GetFolderSyncComplete() {
|
||||||
|
return $this->device->GetFolderSyncComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets if the FolderSync was completed successfully (all folders synchronized)
|
||||||
|
*
|
||||||
|
* @param boolean $complete indicating if all folders were sent
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function SetFolderSyncComplete($complete, $user = false, $devid = false) {
|
||||||
|
$this->device->SetFolderSyncComplete($complete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the Loop detection data for a user & device
|
||||||
|
*
|
||||||
|
* @param string $user
|
||||||
|
* @param string $devid
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function ClearLoopDetectionData($user, $devid) {
|
||||||
|
if ($user == false || $devid == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ClearLoopDetectionData(): clearing data for user '%s' and device '%s'", $user, $devid));
|
||||||
|
return $this->loopdetection->ClearData($user, $devid);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the device needs an AS version update
|
* Indicates if the device needs an AS version update
|
||||||
*
|
*
|
||||||
|
@ -709,6 +753,24 @@ class DeviceManager {
|
||||||
return ($announced != $latest);
|
return ($announced != $latest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**----------------------------------------------------------------------------------------------------------
|
||||||
|
* DeviceManager User-Device pre-authorization
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the User-Device has permission to sync against this Z-Push.
|
||||||
|
*
|
||||||
|
* @param string $user Username
|
||||||
|
* @param string $devid DeviceId
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function GetUserDevicePermission($user, $devid) {
|
||||||
|
return $this->statemachine->GetUserDevicePermission($user, $devid);
|
||||||
|
}
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------------------------------------
|
/**----------------------------------------------------------------------------------------------------------
|
||||||
* private DeviceManager methods
|
* private DeviceManager methods
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -294,4 +294,4 @@ abstract class InterProcessData {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -189,6 +189,41 @@ class ImportChangesStream implements IImportChanges {
|
||||||
return true;
|
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
|
* ImportMessageMove is not implemented, as this operation can not be streamed to a WBXMLEncoder
|
||||||
*
|
*
|
||||||
|
|
|
@ -449,6 +449,13 @@ class SyncCollections implements Iterator {
|
||||||
// wait for changes
|
// wait for changes
|
||||||
$started = time();
|
$started = time();
|
||||||
$endat = time() + $lifetime;
|
$endat = time() + $lifetime;
|
||||||
|
|
||||||
|
// always use policy key from the request if it was sent
|
||||||
|
$policyKey = $this->GetReferencePolicyKey();
|
||||||
|
if (Request::WasPolicyKeySent() && Request::GetPolicyKey() != 0) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("refpolkey:'%s', sent polkey:'%s'", $policyKey, Request::GetPolicyKey()));
|
||||||
|
$policyKey = Request::GetPolicyKey();
|
||||||
|
}
|
||||||
while(($now = time()) < $endat) {
|
while(($now = time()) < $endat) {
|
||||||
// how long are we waiting for changes
|
// how long are we waiting for changes
|
||||||
$this->waitingTime = $now-$started;
|
$this->waitingTime = $now-$started;
|
||||||
|
@ -460,7 +467,7 @@ class SyncCollections implements Iterator {
|
||||||
|
|
||||||
// Check if provisioning is necessary
|
// Check if provisioning is necessary
|
||||||
// if a PolicyKey was sent use it. If not, compare with the ReferencePolicyKey
|
// if a PolicyKey was sent use it. If not, compare with the ReferencePolicyKey
|
||||||
if (PROVISIONING === true && $this->GetReferencePolicyKey() !== false && ZPush::GetDeviceManager()->ProvisioningRequired($this->GetReferencePolicyKey(), true))
|
if (PROVISIONING === true && $policyKey !== false && ZPush::GetDeviceManager()->ProvisioningRequired($policyKey, true))
|
||||||
// the hierarchysync forces provisioning
|
// the hierarchysync forces provisioning
|
||||||
throw new StatusException("SyncCollections->CheckForChanges(): PolicyKey changed. Provisioning required.", self::ERROR_WRONG_HIERARCHY);
|
throw new StatusException("SyncCollections->CheckForChanges(): PolicyKey changed. Provisioning required.", self::ERROR_WRONG_HIERARCHY);
|
||||||
|
|
||||||
|
|
|
@ -147,7 +147,7 @@ class ZPush {
|
||||||
self::COMMAND_SETTINGS => array(self::ASV_12, self::REQUESTHANDLER => "Settings"),
|
self::COMMAND_SETTINGS => array(self::ASV_12, self::REQUESTHANDLER => "Settings"),
|
||||||
|
|
||||||
self::COMMAND_WEBSERVICE_DEVICE => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND),
|
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_USERS => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -276,12 +276,16 @@ class ZPush {
|
||||||
else if (SINK_FORCERECHECK !== false && (!is_int(SINK_FORCERECHECK) || SINK_FORCERECHECK < 1))
|
else if (SINK_FORCERECHECK !== false && (!is_int(SINK_FORCERECHECK) || SINK_FORCERECHECK < 1))
|
||||||
throw new FatalMisconfigurationException("The SINK_FORCERECHECK value must be 'false' or a number higher than 0.");
|
throw new FatalMisconfigurationException("The SINK_FORCERECHECK value must be 'false' or a number higher than 0.");
|
||||||
|
|
||||||
if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) {
|
if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) {
|
||||||
define('SYNC_CONTACTS_MAXPICTURESIZE', 49152);
|
define('SYNC_CONTACTS_MAXPICTURESIZE', 49152);
|
||||||
}
|
}
|
||||||
else if ((!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1))
|
else if ((!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1))
|
||||||
throw new FatalMisconfigurationException("The SYNC_CONTACTS_MAXPICTURESIZE value must be a number higher than 0.");
|
throw new FatalMisconfigurationException("The SYNC_CONTACTS_MAXPICTURESIZE value must be a number higher than 0.");
|
||||||
|
|
||||||
|
if (!defined('USE_PARTIAL_FOLDERSYNC')) {
|
||||||
|
define('USE_PARTIAL_FOLDERSYNC', false);
|
||||||
|
}
|
||||||
|
|
||||||
// the check on additional folders will not throw hard errors, as this is probably changed on live systems
|
// the check on additional folders will not throw hard errors, as this is probably changed on live systems
|
||||||
if (isset($additionalFolders) && !is_array($additionalFolders))
|
if (isset($additionalFolders) && !is_array($additionalFolders))
|
||||||
ZLog::Write(LOGLEVEL_ERROR, "ZPush::CheckConfig() : The additional folders synchronization not available as array.");
|
ZLog::Write(LOGLEVEL_ERROR, "ZPush::CheckConfig() : The additional folders synchronization not available as array.");
|
||||||
|
@ -349,8 +353,14 @@ class ZPush {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Initialize the default StateMachine
|
// Initialize the default StateMachine
|
||||||
include_once('lib/default/filestatemachine.php');
|
if (defined('STATE_MACHINE') && STATE_MACHINE == 'SQL') {
|
||||||
ZPush::$stateMachine = new FileStateMachine();
|
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 (ZPush::$stateMachine->GetStateVersion() !== ZPush::GetLatestStateVersion()) {
|
||||||
|
@ -617,9 +627,9 @@ class ZPush {
|
||||||
$message $additionalMessage
|
$message $additionalMessage
|
||||||
<br><br>
|
<br><br>
|
||||||
More information about Z-Push can be found at:<br>
|
More information about Z-Push can be found at:<br>
|
||||||
<a href="http://z-push.sf.net/">Z-Push homepage</a><br>
|
<a href="http://z-push.org/">Z-Push homepage</a><br>
|
||||||
<a href="http://z-push.sf.net/download">Z-Push download page at BerliOS</a><br>
|
<a href="http://z-push.org/download">Z-Push download page</a><br>
|
||||||
<a href="http://z-push.sf.net/tracker">Z-Push Bugtracker and Roadmap</a><br>
|
<a href="http://jira.zarafa.com/browse/ZP">Z-Push Bugtracker and Roadmap</a><br>
|
||||||
<br>
|
<br>
|
||||||
All modifications to this sourcecode must be published and returned to the community.<br>
|
All modifications to this sourcecode must be published and returned to the community.<br>
|
||||||
Please see <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3 License</a> for details.<br>
|
Please see <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3 License</a> for details.<br>
|
||||||
|
@ -676,7 +686,9 @@ END;
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
static public function GetSupportedProtocolVersions($valueOnly = false) {
|
static public function GetSupportedProtocolVersions($valueOnly = false) {
|
||||||
$versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions)+1)));
|
//$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)));
|
||||||
ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetSupportedProtocolVersions(): " . $versions);
|
ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetSupportedProtocolVersions(): " . $versions);
|
||||||
|
|
||||||
if ($valueOnly === true)
|
if ($valueOnly === true)
|
||||||
|
|
|
@ -1025,6 +1025,7 @@ define("SYNC_FLAGSTATUS_COMPLETE", 1);
|
||||||
define("SYNC_FLAGSTATUS_ACTIVE", 2);
|
define("SYNC_FLAGSTATUS_ACTIVE", 2);
|
||||||
|
|
||||||
define("DEFAULT_EMAIL_CONTENTCLASS", "urn:content-classes:message");
|
define("DEFAULT_EMAIL_CONTENTCLASS", "urn:content-classes:message");
|
||||||
|
define("DEFAULT_CALENDAR_CONTENTCLASS", "urn:content-classes:calendarmessage");
|
||||||
|
|
||||||
define("SYNC_MAIL_LASTVERB_UNKNOWN", 0);
|
define("SYNC_MAIL_LASTVERB_UNKNOWN", 0);
|
||||||
define("SYNC_MAIL_LASTVERB_REPLYSENDER", 1);
|
define("SYNC_MAIL_LASTVERB_REPLYSENDER", 1);
|
||||||
|
|
|
@ -203,6 +203,17 @@ abstract class Backend implements IBackend {
|
||||||
return $r;
|
return $r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the email address and the display name of the user. Used by autodiscover.
|
||||||
|
*
|
||||||
|
* @param string $username The username
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return Array
|
||||||
|
*/
|
||||||
|
public function GetUserDetails($username) {
|
||||||
|
return array('emailaddress' => $username, 'fullname' => $username);
|
||||||
|
}
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------------------------------------
|
/**----------------------------------------------------------------------------------------------------------
|
||||||
* Protected methods for BackendStorage
|
* Protected methods for BackendStorage
|
||||||
|
|
|
@ -340,6 +340,25 @@ abstract class BackendDiff extends Backend {
|
||||||
*/
|
*/
|
||||||
public abstract function SetReadFlag($folderid, $id, $flags, $contentParameters);
|
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
|
* 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
|
* this means just unlinking the file its in or somesuch. After this call has succeeded, a call to
|
||||||
|
|
|
@ -88,7 +88,19 @@ class DiffState implements IChanges {
|
||||||
*/
|
*/
|
||||||
public function ConfigContentParameters($contentparameters) {
|
public function ConfigContentParameters($contentparameters) {
|
||||||
$this->contentparameters = $contentparameters;
|
$this->contentparameters = $contentparameters;
|
||||||
$this->cutoffdate = Utils::GetCutOffDate($contentparameters->GetFilterType());
|
|
||||||
|
$filtertype = $contentparameters->GetFilterType();
|
||||||
|
switch($contentparameters->GetContentClass()) {
|
||||||
|
case "Email":
|
||||||
|
case "Calendar":
|
||||||
|
$this->cutoffdate = ($filtertype === false) ? 0 : Utils::GetCutOffDate($filtertype);
|
||||||
|
break;
|
||||||
|
case "Contacts":
|
||||||
|
case "Tasks":
|
||||||
|
default:
|
||||||
|
$this->cutoffdate = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,8 +132,12 @@ class DiffState implements IChanges {
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
static public function RowCmp($a, $b) {
|
static public function RowCmp($a, $b) {
|
||||||
// TODO implement different comparing functions
|
if (is_numeric($a["id"]) && is_numeric($b["id"])) {
|
||||||
return $a["id"] < $b["id"] ? 1 : -1;
|
return $a["id"] < $b["id"] ? 1 : -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return strcmp($a["id"], $b["id"]) < 0 ? 1 : -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,7 +168,7 @@ class DiffState implements IChanges {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if($this->syncstate[$iold]["id"] == $new[$inew]["id"]) {
|
if($this->syncstate[$iold]["id"] == $new[$inew]["id"]) {
|
||||||
// Both messages are still available, compare flags and mod
|
// Both messages are still available, compare flags, star and mod
|
||||||
if(isset($this->syncstate[$iold]["flags"]) && isset($new[$inew]["flags"]) && $this->syncstate[$iold]["flags"] != $new[$inew]["flags"]) {
|
if(isset($this->syncstate[$iold]["flags"]) && isset($new[$inew]["flags"]) && $this->syncstate[$iold]["flags"] != $new[$inew]["flags"]) {
|
||||||
// Flags changed
|
// Flags changed
|
||||||
$change["type"] = "flags";
|
$change["type"] = "flags";
|
||||||
|
@ -161,6 +177,14 @@ class DiffState implements IChanges {
|
||||||
$changes[] = $change;
|
$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($this->syncstate[$iold]["mod"] != $new[$inew]["mod"]) {
|
||||||
$change["type"] = "change";
|
$change["type"] = "change";
|
||||||
$change["id"] = $new[$inew]["id"];
|
$change["id"] = $new[$inew]["id"];
|
||||||
|
@ -180,6 +204,7 @@ class DiffState implements IChanges {
|
||||||
// Message in new seems to be new (add)
|
// Message in new seems to be new (add)
|
||||||
$change["type"] = "change";
|
$change["type"] = "change";
|
||||||
$change["flags"] = SYNC_NEWMESSAGE;
|
$change["flags"] = SYNC_NEWMESSAGE;
|
||||||
|
$change["star"] = SYNC_NEWMESSAGE;
|
||||||
$change["id"] = $new[$inew]["id"];
|
$change["id"] = $new[$inew]["id"];
|
||||||
$changes[] = $change;
|
$changes[] = $change;
|
||||||
$inew++;
|
$inew++;
|
||||||
|
@ -199,6 +224,7 @@ class DiffState implements IChanges {
|
||||||
// All data left in new have been added
|
// All data left in new have been added
|
||||||
$change["type"] = "change";
|
$change["type"] = "change";
|
||||||
$change["flags"] = SYNC_NEWMESSAGE;
|
$change["flags"] = SYNC_NEWMESSAGE;
|
||||||
|
$change["star"] = SYNC_NEWMESSAGE;
|
||||||
$change["id"] = $new[$inew]["id"];
|
$change["id"] = $new[$inew]["id"];
|
||||||
$changes[] = $change;
|
$changes[] = $change;
|
||||||
$inew++;
|
$inew++;
|
||||||
|
@ -235,6 +261,9 @@ class DiffState implements IChanges {
|
||||||
if($type == "flags") {
|
if($type == "flags") {
|
||||||
// Update flags
|
// Update flags
|
||||||
$this->syncstate[$i]["flags"] = $change["flags"];
|
$this->syncstate[$i]["flags"] = $change["flags"];
|
||||||
|
} else if($type == "star") {
|
||||||
|
// Update star
|
||||||
|
$this->syncstate[$i]["star"] = $change["star"];
|
||||||
} else if($type == "delete") {
|
} else if($type == "delete") {
|
||||||
// Delete item
|
// Delete item
|
||||||
array_splice($this->syncstate, $i, 1);
|
array_splice($this->syncstate, $i, 1);
|
||||||
|
|
|
@ -195,6 +195,10 @@ class ExportChangesDiff extends DiffState implements IExportChanges{
|
||||||
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageReadFlag($change["id"], $change["flags"]) == true)
|
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageReadFlag($change["id"], $change["flags"]) == true)
|
||||||
$this->updateState("flags", $change);
|
$this->updateState("flags", $change);
|
||||||
break;
|
break;
|
||||||
|
case "star":
|
||||||
|
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageStarFlag($change["id"], $change["star"]) == true)
|
||||||
|
$this->updateState("star", $change);
|
||||||
|
break;
|
||||||
case "move":
|
case "move":
|
||||||
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageMove($change["id"], $change["parent"]) == true)
|
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageMove($change["id"], $change["parent"]) == true)
|
||||||
$this->updateState("move", $change);
|
$this->updateState("move", $change);
|
||||||
|
|
|
@ -100,6 +100,7 @@ class ImportChangesDiff extends DiffState implements IImportChanges {
|
||||||
$change["mod"] = 0; // dummy, will be updated later if the change succeeds
|
$change["mod"] = 0; // dummy, will be updated later if the change succeeds
|
||||||
$change["parent"] = $this->folderid;
|
$change["parent"] = $this->folderid;
|
||||||
$change["flags"] = (isset($message->read)) ? $message->read : 0;
|
$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);
|
$this->updateState("change", $change);
|
||||||
|
|
||||||
if($conflict && $this->flags == SYNC_CONFLICT_OVERWRITE_PIM)
|
if($conflict && $this->flags == SYNC_CONFLICT_OVERWRITE_PIM)
|
||||||
|
@ -184,6 +185,35 @@ class ImportChangesDiff extends DiffState implements IImportChanges {
|
||||||
return true;
|
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
|
* Imports a move of a message. This occurs when a user moves an item to another folder
|
||||||
*
|
*
|
||||||
|
|
|
@ -423,6 +423,117 @@ class FileStateMachine implements IStateMachine {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the User-Device has permission to sync against this Z-Push.
|
||||||
|
*
|
||||||
|
* @param string $user Username
|
||||||
|
* @param string $devid DeviceId
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function GetUserDevicePermission($user, $devid) {
|
||||||
|
include_once("simplemutex.php");
|
||||||
|
$mutex = new SimpleMutex();
|
||||||
|
|
||||||
|
$status = SYNC_COMMONSTATUS_SUCCESS;
|
||||||
|
|
||||||
|
$userFile = STATE_DIR . 'PreAuthUserDevices';
|
||||||
|
|
||||||
|
if ($mutex->Block()) {
|
||||||
|
if (@file_exists($userFile)) {
|
||||||
|
$userList = json_decode(@file_get_contents($userFile), true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$userList = Array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Android PROVISIONING initial step
|
||||||
|
if ($devid != "validate") {
|
||||||
|
$changed = false;
|
||||||
|
|
||||||
|
if (array_key_exists($user, $userList)) {
|
||||||
|
// User already pre-authorized
|
||||||
|
|
||||||
|
// User could be blocked if a "authorized" device exist and it's false
|
||||||
|
if (!$userList[$user]["authorized"]) {
|
||||||
|
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Blocked user '%s', tried '%s'", $user, $devid));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (array_key_exists($devid, $userList[$user])) {
|
||||||
|
// Device pre-authorized found
|
||||||
|
|
||||||
|
if ($userList[$user][$devid] === false) {
|
||||||
|
$status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Blocked device '%s' for user '%s'", $devid, $user));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Pre-authorized device '%s' for user '%s'", $devid, $user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 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])) {
|
||||||
|
$userList[$user][$devid] = true;
|
||||||
|
$changed = true;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Pre-authorized new device '%s' for user '%s'", $devid, $user));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$status = SYNC_COMMONSTATUS_MAXDEVICESREACHED;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Max number of devices reached for user '%s', tried '%s'", $user, $devid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER;
|
||||||
|
$userList[$user][$devid] = false;
|
||||||
|
$changed = true;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Blocked new device '%s' for user '%s'", $devid, $user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// User not pre-authorized
|
||||||
|
|
||||||
|
if (defined('PRE_AUTHORIZE_NEW_USERS') && PRE_AUTHORIZE_NEW_USERS === true) {
|
||||||
|
$userList[$user] = array("authorized" => 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])) {
|
||||||
|
$userList[$user][$devid] = true;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Pre-authorized new device '%s' for new user '%s'", $devid, $user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER;
|
||||||
|
$userList[$user][$devid] = false;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Blocked new device '%s' for new user '%s'", $devid, $user));
|
||||||
|
}
|
||||||
|
|
||||||
|
$changed = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
|
||||||
|
$userList[$user] = array("authorized" => false, $devid => false);
|
||||||
|
$changed = true;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Blocked new user '%s' and device '%s'", $user, $devid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($changed) {
|
||||||
|
file_put_contents($userFile, json_encode($userList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mutex->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------------------------------------
|
/**----------------------------------------------------------------------------------------------------------
|
||||||
* Private FileStateMachine stuff
|
* Private FileStateMachine stuff
|
||||||
*/
|
*/
|
||||||
|
|
781
sources/lib/default/sqlstatemachine.php
Normal file
781
sources/lib/default/sqlstatemachine.php
Normal file
|
@ -0,0 +1,781 @@
|
||||||
|
<?php
|
||||||
|
/***********************************************
|
||||||
|
* File : sqlstatemachine.php
|
||||||
|
* Project : Z-Push
|
||||||
|
* Descr : This class handles state requests;
|
||||||
|
* Each Import/Export mechanism can
|
||||||
|
* store its own state information,
|
||||||
|
* which is stored through the
|
||||||
|
* state machine.
|
||||||
|
*
|
||||||
|
* Created : 25.08.2013
|
||||||
|
*
|
||||||
|
* Copyright 2007 - 2013 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,
|
||||||
|
* as published by the Free Software Foundation with the following additional
|
||||||
|
* term according to sec. 7:
|
||||||
|
*
|
||||||
|
* According to sec. 7 of the GNU Affero General Public License, version 3,
|
||||||
|
* the terms of the AGPL are supplemented with the following terms:
|
||||||
|
*
|
||||||
|
* "Zarafa" is a registered trademark of Zarafa B.V.
|
||||||
|
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
|
||||||
|
* The licensing of the Program under the AGPL does not imply a trademark license.
|
||||||
|
* Therefore any rights, title and interest in our trademarks remain entirely with us.
|
||||||
|
*
|
||||||
|
* However, if you propagate an unmodified version of the Program you are
|
||||||
|
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
|
||||||
|
* Furthermore you may use our trademarks where it is necessary to indicate
|
||||||
|
* the intended purpose of a product or service provided you use it in accordance
|
||||||
|
* with honest practices in industrial or commercial matters.
|
||||||
|
* If you want to propagate modified versions of the Program under the name "Z-Push",
|
||||||
|
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
|
||||||
|
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Consult LICENSE file for details
|
||||||
|
************************************************/
|
||||||
|
|
||||||
|
class SqlStateMachine implements IStateMachine {
|
||||||
|
const SUPPORTED_STATE_VERSION = IStateMachine::STATEVERSION_02;
|
||||||
|
const VERSION = "version";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private $dbh;
|
||||||
|
private $options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* Performs some basic checks and initilizes the state directory
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @throws FatalMisconfigurationException
|
||||||
|
*/
|
||||||
|
public function SqlStateMachine() {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, "SqlStateMachine(): init");
|
||||||
|
|
||||||
|
if (!defined('STATE_SQL_DSN') || !defined('STATE_SQL_USER') || !defined('STATE_SQL_PASSWORD')) {
|
||||||
|
throw new FatalMisconfigurationException("No configuration for the state sql database available.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->options = array();
|
||||||
|
if (defined('STATE_SQL_OPTIONS')) {
|
||||||
|
$this->options = unserialize(STATE_SQL_OPTIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
throw new FatalMisconfigurationException(sprintf("Not possible to connect to the state database: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a hash value indicating the latest dataset of the named
|
||||||
|
* state with a specified key and counter.
|
||||||
|
* If the state is changed between two calls of this method
|
||||||
|
* the returned hash should be different
|
||||||
|
*
|
||||||
|
* @param string $devid the device id
|
||||||
|
* @param string $type the state type
|
||||||
|
* @param string $key (opt)
|
||||||
|
* @param string $counter (opt)
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string
|
||||||
|
* @throws StateNotFoundException, StateInvalidException
|
||||||
|
*/
|
||||||
|
public function GetStateHash($devid, $type, $key = false, $counter = false) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetStateHash(): '%s', '%s', '%s', '%s'", $devid, $type, $key, $counter));
|
||||||
|
|
||||||
|
$sql = "select updated_at from zpush_states where device_id = :devid and state_type = :type and uuid = :key and counter = :counter";
|
||||||
|
$params = $this->getParams($devid, $type, $key, $counter);
|
||||||
|
|
||||||
|
$hash = null;
|
||||||
|
$sth = null;
|
||||||
|
$record = null;
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
$sth->execute($params);
|
||||||
|
|
||||||
|
$record = $sth->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$record) {
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
throw new StateNotFoundException(sprintf("SqlStateMachine->GetStateHash(): Could not locate state"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// datetime->format("U") returns EPOCH
|
||||||
|
$datetime = new DateTime($record["updated_at"]);
|
||||||
|
$hash = $datetime->format("U");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
throw new StateNotFoundException(sprintf("SqlStateMachine->GetStateHash(): Could not locate state: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetStateHash(): return '%s'", $hash));
|
||||||
|
|
||||||
|
return $hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a state for a specified key and counter.
|
||||||
|
* This method sould call IStateMachine->CleanStates()
|
||||||
|
* to remove older states (same key, previous counters)
|
||||||
|
*
|
||||||
|
* @param string $devid the device id
|
||||||
|
* @param string $type the state type
|
||||||
|
* @param string $key (opt)
|
||||||
|
* @param string $counter (opt)
|
||||||
|
* @param string $cleanstates (opt)
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return mixed
|
||||||
|
* @throws StateNotFoundException, StateInvalidException
|
||||||
|
*/
|
||||||
|
public function GetState($devid, $type, $key = false, $counter = false, $cleanstates = true) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetState(): '%s', '%s', '%s', '%s', '%s'", $devid, $type, $key, $counter, $cleanstates));
|
||||||
|
if ($counter && $cleanstates)
|
||||||
|
$this->CleanStates($devid, $type, $key, $counter);
|
||||||
|
|
||||||
|
$sql = "select state_data from zpush_states where device_id = :devid and state_type = :type and uuid = :key and counter = :counter";
|
||||||
|
$params = $this->getParams($devid, $type, $key, $counter);
|
||||||
|
|
||||||
|
$data = null;
|
||||||
|
$sth = null;
|
||||||
|
$record = null;
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
$sth->execute($params);
|
||||||
|
|
||||||
|
$record = $sth->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$record) {
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
// throw an exception on all other states, but not FAILSAVE as it's most of the times not there by default
|
||||||
|
if ($type !== IStateMachine::FAILSAVE) {
|
||||||
|
throw new StateNotFoundException(sprintf("SqlStateMachine->GetState(): Could not locate state"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$data = unserialize($record["state_data"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
throw new StateNotFoundException(sprintf("SqlStateMachine->GetState(): Could not locate state: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes ta state to for a key and counter
|
||||||
|
*
|
||||||
|
* @param mixed $state
|
||||||
|
* @param string $devid the device id
|
||||||
|
* @param string $type the state type
|
||||||
|
* @param string $key (opt)
|
||||||
|
* @param int $counter (opt)
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean
|
||||||
|
* @throws StateInvalidException
|
||||||
|
*/
|
||||||
|
public function SetState($state, $devid, $type, $key = false, $counter = false) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->SetState(): '%s', '%s', '%s', '%s'", $devid, $type, $key, $counter));
|
||||||
|
|
||||||
|
$sql = "select device_id from zpush_states where device_id = :devid and state_type = :type and uuid = :key and counter = :counter";
|
||||||
|
$params = $this->getParams($devid, $type, $key, $counter);
|
||||||
|
|
||||||
|
$sth = null;
|
||||||
|
$record = null;
|
||||||
|
$bytes = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Existing record, we update it
|
||||||
|
$sql = "update zpush_states set state_data = :data, updated_at = :updated_at where device_id = :devid and state_type = :type and uuid = :key and counter = :counter";
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$sth->execute($params) ) {
|
||||||
|
$this->clearConnection($this->dbh, $sth);
|
||||||
|
throw new FatalMisconfigurationException(sprintf("SqlStateMachine->SetState(): Could not write state"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$bytes = strlen($params[":data"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
$this->clearConnection($this->dbh, $sth);
|
||||||
|
throw new FatalMisconfigurationException(sprintf("SqlStateMachine->SetState(): Could not write state: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
|
||||||
|
return $bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up all older states
|
||||||
|
* If called with a $counter, all states previous state counter can be removed
|
||||||
|
* If called without $counter, all keys (independently from the counter) can be removed
|
||||||
|
*
|
||||||
|
* @param string $devid the device id
|
||||||
|
* @param string $type the state type
|
||||||
|
* @param string $key
|
||||||
|
* @param string $counter (opt)
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return
|
||||||
|
* @throws StateInvalidException
|
||||||
|
*/
|
||||||
|
public function CleanStates($devid, $type, $key, $counter = false) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->CleanStates(): '%s', '%s', '%s', '%s'", $devid, $type, $key, $counter));
|
||||||
|
|
||||||
|
|
||||||
|
if ($counter === false) {
|
||||||
|
// Remove all the states. Counter are -1 or > 0, then deleting >= -1 deletes all
|
||||||
|
$sql = "delete from zpush_states where device_id = :devid and state_type = :type and uuid = :key and counter >= :counter";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$sql = "delete from zpush_states where device_id = :devid and state_type = :type and uuid = :key and counter < :counter";
|
||||||
|
}
|
||||||
|
$params = $this->getParams($devid, $type, $key, $counter);
|
||||||
|
|
||||||
|
$sth = null;
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
$sth->execute($params);
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->CleanStates(): Error deleting states: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links a user to a device
|
||||||
|
*
|
||||||
|
* @param string $username
|
||||||
|
* @param string $devid
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean indicating if the user was added or not (existed already)
|
||||||
|
*/
|
||||||
|
public function LinkUserDevice($username, $devid) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->LinkUserDevice(): '%s', '%s'", $username, $devid));
|
||||||
|
|
||||||
|
$sth = null;
|
||||||
|
$record = null;
|
||||||
|
$changed = false;
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
$sql = "select username from zpush_users where username = :username and device_id = :devid";
|
||||||
|
$params = array(":username" => $username, ":devid" => $devid);
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
$sth->execute($params);
|
||||||
|
|
||||||
|
$record = $sth->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($record) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, "SqlStateMachine->LinkUserDevice(): nothing changed");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$sth = null;
|
||||||
|
$sql = "insert into zpush_users (username, device_id, created_at, updated_at) values (:username, :devid, :created_at, :updated_at)";
|
||||||
|
$params[":created_at"] = $params[":updated_at"] = $this->getNow();
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
if ($sth->execute($params)) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->LinkUserDevice(): Linked user-device: '%s' '%s'", $username, $devid));
|
||||||
|
$changed = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->LinkUserDevice(): Unable to link user-device"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->LinkUserDevice(): Error linking user-device: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
|
||||||
|
return $changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlinks a device from a user
|
||||||
|
*
|
||||||
|
* @param string $username
|
||||||
|
* @param string $devid
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function UnLinkUserDevice($username, $devid) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->UnLinkUserDevice(): '%s', '%s'", $username, $devid));
|
||||||
|
|
||||||
|
$sth = null;
|
||||||
|
$changed = false;
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
$sql = "delete from zpush_users where username = :username and device_id = :devid";
|
||||||
|
$params = array(":username" => $username, ":devid" => $devid);
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
if ($sth->execute($params)) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->UnLinkUserDevice(): Unlinked user-device: '%s' '%s'", $username, $devid));
|
||||||
|
$changed = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, "SqlStateMachine->UnLinkUserDevice(): nothing changed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->UnLinkUserDevice(): Error unlinking user-device: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth);
|
||||||
|
|
||||||
|
return $changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with all device ids for a user.
|
||||||
|
* If no user is set, all device ids should be returned
|
||||||
|
*
|
||||||
|
* @param string $username (opt)
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function GetAllDevices($username = false) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetAllDevices(): '%s'", $username));
|
||||||
|
|
||||||
|
$sth = null;
|
||||||
|
$record = null;
|
||||||
|
$out = array();
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
if ($username === false) {
|
||||||
|
$sql = "select distinct(device_id) from zpush_users order by device_id";
|
||||||
|
$params = array();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$sql = "select device_id from zpush_users where username = :username order by device_id";
|
||||||
|
$params = array(":username" => $username);
|
||||||
|
}
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
$sth->execute($params);
|
||||||
|
|
||||||
|
while ($record = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$out[] = $record["device_id"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetAllDevices(): Error listing devices: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current version of the state files
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function GetStateVersion() {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetStateVersion()"));
|
||||||
|
|
||||||
|
$sth = null;
|
||||||
|
$record = null;
|
||||||
|
$version = IStateMachine::STATEVERSION_01;
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
$sql = "select key_value from zpush_settings where key_name = :key_name";
|
||||||
|
$params = array(":key_name" => self::VERSION);
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
$sth->execute($params);
|
||||||
|
|
||||||
|
$record = $sth->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($record) {
|
||||||
|
$version = $record["key_value"];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->SetStateVersion(self::SUPPORTED_STATE_VERSION);
|
||||||
|
$version = self::SUPPORTED_STATE_VERSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetStateVersion(): Error getting state version: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
|
||||||
|
return $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current version of the state files
|
||||||
|
*
|
||||||
|
* @param int $version the new supported version
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function SetStateVersion($version) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->SetStateVersion(): '%s'", $version));
|
||||||
|
|
||||||
|
$sth = null;
|
||||||
|
$record = null;
|
||||||
|
$status = false;
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
$sql = "select key_value from zpush_settings where key_name = :key_name";
|
||||||
|
$params = array(":key_name" => self::VERSION);
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
$sth->execute($params);
|
||||||
|
|
||||||
|
$record = $sth->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($record) {
|
||||||
|
$sth = null;
|
||||||
|
$sql = "update zpush_settings set key_value = :value, updated_at = :updated_at where key_name = :key_name";
|
||||||
|
$params[":value"] = $version;
|
||||||
|
$params[":updated_at"] = $this->getNow();
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
if ($sth->execute($params)) {
|
||||||
|
$status = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$sth = null;
|
||||||
|
$sql = "insert into zpush_settings (key_name, key_value, created_at, updated_at) values (:key_name, :value, :created_at, :updated_at)";
|
||||||
|
$params[":value"] = $version;
|
||||||
|
$params[":updated_at"] = $params[":created_at"] = $this->getNow();
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
if ($sth->execute($params)) {
|
||||||
|
$status = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->SetStateVersion(): Error saving state version: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all available states for a device id
|
||||||
|
*
|
||||||
|
* @param string $devid the device id
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return array(mixed)
|
||||||
|
*/
|
||||||
|
public function GetAllStatesForDevice($devid) {
|
||||||
|
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetAllStatesForDevice(): '%s'", $devid));
|
||||||
|
|
||||||
|
$sth = null;
|
||||||
|
$record = null;
|
||||||
|
$out = array();
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
$sql = "select state_type, uuid, counter from zpush_states where device_id = :devid order by id_state";
|
||||||
|
$params = array(":devid" => $devid);
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
$sth->execute($params);
|
||||||
|
|
||||||
|
while ($record = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$state = array('type' => false, 'counter' => false, 'uuid' => false);
|
||||||
|
if ($record["state_type"] !== null && strlen($record["state_type"]) > 0) {
|
||||||
|
$state["type"] = $record["state_type"];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($record["counter"] !== null && is_numeric($record["counter"])) {
|
||||||
|
$state["type"] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($record["counter"] !== null && strlen($record["counter"]) > 0) {
|
||||||
|
$state["counter"] = $record["counter"];
|
||||||
|
}
|
||||||
|
if ($record["uuid"] !== null && strlen($record["uuid"]) > 0) {
|
||||||
|
$state["uuid"] = $record["uuid"];
|
||||||
|
}
|
||||||
|
$out[] = $state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetAllStatesForDevice(): Error listing states: %s", $ex->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the User-Device has permission to sync against this Z-Push.
|
||||||
|
*
|
||||||
|
* @param string $user Username
|
||||||
|
* @param string $devid DeviceId
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function GetUserDevicePermission($user, $devid) {
|
||||||
|
$status = SYNC_COMMONSTATUS_SUCCESS;
|
||||||
|
|
||||||
|
$userExist = false;
|
||||||
|
$userBlocked = false;
|
||||||
|
$deviceExist = false;
|
||||||
|
$deviceBlocked = false;
|
||||||
|
|
||||||
|
// Android PROVISIONING initial step
|
||||||
|
if ($devid != "validate") {
|
||||||
|
|
||||||
|
$sth = null;
|
||||||
|
$record = null;
|
||||||
|
try {
|
||||||
|
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
|
||||||
|
|
||||||
|
$sql = "select authorized from zpush_preauth_users where username = :user and device_id = :devid";
|
||||||
|
$params = array(":user" => $user, ":devid" => "authorized");
|
||||||
|
$paramsNewDevid = array();
|
||||||
|
$paramsNewUser = array();
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
$sth->execute($params);
|
||||||
|
if ($record = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$userExist = true;
|
||||||
|
$userBlocked = !$record["authorized"];
|
||||||
|
}
|
||||||
|
$record = null;
|
||||||
|
$sth = null;
|
||||||
|
|
||||||
|
if ($userExist) {
|
||||||
|
// User already pre-authorized
|
||||||
|
|
||||||
|
// User could be blocked if a "authorized" device exist and it's false
|
||||||
|
if ($userBlocked) {
|
||||||
|
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Blocked user '%s', tried '%s'", $user, $devid));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$params[":devid"] = $devid;
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
$sth->execute($params);
|
||||||
|
if ($record = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$deviceExist = true;
|
||||||
|
$deviceBlocked = !$record["authorized"];
|
||||||
|
}
|
||||||
|
$record = null;
|
||||||
|
$sth = null;
|
||||||
|
|
||||||
|
if ($deviceExist) {
|
||||||
|
// Device pre-authorized found
|
||||||
|
|
||||||
|
if ($deviceBlocked) {
|
||||||
|
$status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Blocked device '%s' for user '%s'", $devid, $user));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Pre-authorized device '%s' for user '%s'", $devid, $user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 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])) {
|
||||||
|
$paramsNewDevid[":auth"] = true;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Pre-authorized new device '%s' for 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;
|
||||||
|
$paramsNewDevid[":auth"] = false;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Blocked new device '%s' for user '%s'", $devid, $user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// User not pre-authorized
|
||||||
|
|
||||||
|
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])) {
|
||||||
|
$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_DEVICEBLOCKEDFORUSER;
|
||||||
|
$paramsNewDevid[":auth"] = false;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Blocked new device '%s' for new user '%s'", $devid, $user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
|
||||||
|
$paramsNewUser[":auth"] = false;
|
||||||
|
$paramsNewDevid[":auth"] = false;
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Blocked new user '%s' and device '%s'", $user, $devid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($paramsNewUser) > 0) {
|
||||||
|
$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";
|
||||||
|
$paramsNewUser[":created_at"] = $paramsNewUser[":updated_at"] = $this->getNow();
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
if (!$sth->execute($paramsNewUser)) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetUserDevicePermission(): Error creating new user"));
|
||||||
|
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($paramsNewDevid) > 0) {
|
||||||
|
$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;
|
||||||
|
$paramsNewDevid[":created_at"] = $paramsNewDevid[":updated_at"] = $this->getNow();
|
||||||
|
|
||||||
|
$sth = $this->dbh->prepare($sql);
|
||||||
|
if (!$sth->execute($paramsNewDevid)) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetUserDevicePermission(): Error creating user new device"));
|
||||||
|
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(PDOException $ex) {
|
||||||
|
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetUserDevicePermission(): Error checking permission for username '%s' device '%s': %s", $user, $devid, $ex->getMessage()));
|
||||||
|
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clearConnection($this->dbh, $sth, $record);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**----------------------------------------------------------------------------------------------------------
|
||||||
|
* Private SqlStateMachine stuff
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a string with the datetime NOW
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private function getNow() {
|
||||||
|
$now = new DateTime("NOW");
|
||||||
|
return $now->format("Y-m-d H:i:s");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array with the params for the PDO query
|
||||||
|
*
|
||||||
|
* @params string $devid
|
||||||
|
* @params string $type
|
||||||
|
* @params string $key
|
||||||
|
* @params string $counter
|
||||||
|
* @return array
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private function getParams($devid, $type, $key, $counter) {
|
||||||
|
return array(":devid" => $devid, ":type" => $type, ":key" => $key, ":counter" => ($counter === false ? -1 : $counter) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free PDO resources.
|
||||||
|
*
|
||||||
|
* @params PDOConnection $dbh
|
||||||
|
* @params PDOStatement $sth
|
||||||
|
* @params PDORecord $record
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
private function clearConnection(&$dbh, &$sth = null, &$record = null) {
|
||||||
|
if ($record != null) {
|
||||||
|
$record = null;
|
||||||
|
}
|
||||||
|
if ($sth != null) {
|
||||||
|
$sth = null;
|
||||||
|
}
|
||||||
|
if ($dbh != null) {
|
||||||
|
$dbh = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
?>
|
|
@ -289,6 +289,16 @@ interface IBackend {
|
||||||
* @return SyncObject $resolveRecipients
|
* @return SyncObject $resolveRecipients
|
||||||
*/
|
*/
|
||||||
public function ResolveRecipients($resolveRecipients);
|
public function ResolveRecipients($resolveRecipients);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the email address and the display name of the user. Used by autodiscover.
|
||||||
|
*
|
||||||
|
* @param string $username The username
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return Array
|
||||||
|
*/
|
||||||
|
public function GetUserDetails($username);
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
|
@ -98,6 +98,19 @@ interface IImportChanges extends IChanges {
|
||||||
*/
|
*/
|
||||||
public function ImportMessageReadFlag($id, $flags);
|
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
|
* Imports a move of a message. This occurs when a user moves an item to another folder
|
||||||
*
|
*
|
||||||
|
|
|
@ -188,7 +188,36 @@ class FolderSync extends RequestProcessor {
|
||||||
$exporter->InitializeExporter($changesMem);
|
$exporter->InitializeExporter($changesMem);
|
||||||
|
|
||||||
// Stream all changes to the ImportExportChangesMem
|
// Stream all changes to the ImportExportChangesMem
|
||||||
while(is_array($exporter->Synchronize()));
|
$maxExporttime = Request::GetExpectedConnectionTimeout();
|
||||||
|
$totalChanges = $exporter->GetChangeCount();
|
||||||
|
$started = time();
|
||||||
|
$exported = 0;
|
||||||
|
$partial = false;
|
||||||
|
while(is_array($exporter->Synchronize())) {
|
||||||
|
$exported++;
|
||||||
|
|
||||||
|
if (time() % 4 ) {
|
||||||
|
self::$topCollector->AnnounceInformation(sprintf("Exported %d from %d folders", $exported, $totalChanges));
|
||||||
|
}
|
||||||
|
|
||||||
|
// if partial sync is allowed, stop if this takes too long
|
||||||
|
if (USE_PARTIAL_FOLDERSYNC && (time() - $started) > $maxExporttime) {
|
||||||
|
ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): Exporting folders is too slow. In %d seconds only %d from %d changes were processed.",(time() - $started), $exported, $totalChanges));
|
||||||
|
self::$topCollector->AnnounceInformation(sprintf("Partial export of %d out of %d folders", $exported, $totalChanges), true);
|
||||||
|
self::$deviceManager->SetFolderSyncComplete(false);
|
||||||
|
$partial = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the foldersync complete flag
|
||||||
|
if (USE_PARTIAL_FOLDERSYNC && $partial == false && self::$deviceManager->GetFolderSyncComplete() === false) {
|
||||||
|
// 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());
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, "Request->HandleFolderSync(): Chunked exporting of folders completed successfully");
|
||||||
|
}
|
||||||
|
|
||||||
// get the new state from the backend
|
// get the new state from the backend
|
||||||
$newsyncstate = (isset($exporter))?$exporter->GetState():"";
|
$newsyncstate = (isset($exporter))?$exporter->GetState():"";
|
||||||
|
|
|
@ -90,144 +90,145 @@ class ItemOperations extends RequestProcessor {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($fetch) {
|
// process operation
|
||||||
if(!self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_STORE))
|
while(1) {
|
||||||
return false;
|
if ($fetch) {
|
||||||
$operation['store'] = self::$decoder->getElementContent();
|
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_STORE)) {
|
||||||
if(!self::$decoder->getElementEndTag())
|
$operation['store'] = self::$decoder->getElementContent();
|
||||||
return false;//SYNC_ITEMOPERATIONS_STORE
|
if(!self::$decoder->getElementEndTag())
|
||||||
|
return false;//SYNC_ITEMOPERATIONS_STORE
|
||||||
|
}
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_SEARCH_LONGID)) {
|
if(self::$decoder->getElementStartTag(SYNC_SEARCH_LONGID)) {
|
||||||
$operation['longid'] = self::$decoder->getElementContent();
|
$operation['longid'] = self::$decoder->getElementContent();
|
||||||
if(!self::$decoder->getElementEndTag())
|
if(!self::$decoder->getElementEndTag())
|
||||||
return false;//SYNC_SEARCH_LONGID
|
return false;//SYNC_SEARCH_LONGID
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
|
if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
|
||||||
$operation['folderid'] = self::$decoder->getElementContent();
|
$operation['folderid'] = self::$decoder->getElementContent();
|
||||||
if(!self::$decoder->getElementEndTag())
|
if(!self::$decoder->getElementEndTag())
|
||||||
return false;//SYNC_FOLDERID
|
return false;//SYNC_FOLDERID
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) {
|
if(self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) {
|
||||||
$operation['serverid'] = self::$decoder->getElementContent();
|
$operation['serverid'] = self::$decoder->getElementContent();
|
||||||
if(!self::$decoder->getElementEndTag())
|
if(!self::$decoder->getElementEndTag())
|
||||||
return false;//SYNC_SERVERENTRYID
|
return false;//SYNC_SERVERENTRYID
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_FILEREFERENCE)) {
|
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_FILEREFERENCE)) {
|
||||||
$operation['filereference'] = self::$decoder->getElementContent();
|
$operation['filereference'] = self::$decoder->getElementContent();
|
||||||
if(!self::$decoder->getElementEndTag())
|
if(!self::$decoder->getElementEndTag())
|
||||||
return false;//SYNC_AIRSYNCBASE_FILEREFERENCE
|
return false;//SYNC_AIRSYNCBASE_FILEREFERENCE
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_OPTIONS)) {
|
if(($el = self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_OPTIONS)) && ($el[EN_FLAGS] & EN_FLAGS_CONTENT)) {
|
||||||
//TODO other options
|
//TODO other options
|
||||||
//schema
|
//schema
|
||||||
//range
|
//range
|
||||||
//username
|
//username
|
||||||
//password
|
//password
|
||||||
//bodypartpreference
|
//bodypartpreference
|
||||||
//rm:RightsManagementSupport
|
//rm:RightsManagementSupport
|
||||||
|
|
||||||
// Save all OPTIONS into a ContentParameters object
|
// Save all OPTIONS into a ContentParameters object
|
||||||
$operation["cpo"] = new ContentParameters();
|
$operation["cpo"] = new ContentParameters();
|
||||||
while(1) {
|
while(1) {
|
||||||
// Android 4.3 sends empty options tag, so we don't have to look further
|
while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) {
|
||||||
$e = self::$decoder->peek();
|
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
|
||||||
if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
|
$bptype = self::$decoder->getElementContent();
|
||||||
break;
|
$operation["cpo"]->BodyPreference($bptype);
|
||||||
}
|
if(!self::$decoder->getElementEndTag()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) {
|
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
|
||||||
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
|
$operation["cpo"]->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent());
|
||||||
$bptype = self::$decoder->getElementContent();
|
if(!self::$decoder->getElementEndTag())
|
||||||
$operation["cpo"]->BodyPreference($bptype);
|
return false;
|
||||||
if(!self::$decoder->getElementEndTag()) {
|
}
|
||||||
|
|
||||||
|
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
|
||||||
|
$operation["cpo"]->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
|
||||||
|
if(!self::$decoder->getElementEndTag())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
|
||||||
|
$operation["cpo"]->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
|
||||||
|
if(!self::$decoder->getElementEndTag())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!self::$decoder->getElementEndTag())
|
||||||
|
return false;//SYNC_AIRSYNCBASE_BODYPREFERENCE
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
|
||||||
|
$operation["cpo"]->SetMimeSupport(self::$decoder->getElementContent());
|
||||||
|
if(!self::$decoder->getElementEndTag())
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_RANGE)) {
|
||||||
|
$operation["range"] = self::$decoder->getElementContent();
|
||||||
|
if(!self::$decoder->getElementEndTag())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_SCHEMA)) {
|
||||||
|
// read schema tags
|
||||||
|
while (1) {
|
||||||
|
// TODO save elements
|
||||||
|
$el = self::$decoder->getElement();
|
||||||
|
$e = self::$decoder->peek();
|
||||||
|
if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
|
||||||
|
self::$decoder->getElementEndTag();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
|
//break if it reached the endtag
|
||||||
$operation["cpo"]->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent());
|
$e = self::$decoder->peek();
|
||||||
if(!self::$decoder->getElementEndTag())
|
if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
|
||||||
return false;
|
self::$decoder->getElementEndTag();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
|
|
||||||
$operation["cpo"]->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
|
|
||||||
if(!self::$decoder->getElementEndTag())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
|
|
||||||
$operation["cpo"]->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
|
|
||||||
if(!self::$decoder->getElementEndTag())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!self::$decoder->getElementEndTag())
|
|
||||||
return false;//SYNC_AIRSYNCBASE_BODYPREFERENCE
|
|
||||||
}
|
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
|
|
||||||
$operation["cpo"]->SetMimeSupport(self::$decoder->getElementContent());
|
|
||||||
if(!self::$decoder->getElementEndTag())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_RANGE)) {
|
|
||||||
$operation["range"] = self::$decoder->getElementContent();
|
|
||||||
if(!self::$decoder->getElementEndTag())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_SCHEMA)) {
|
|
||||||
// read schema tags
|
|
||||||
while (1) {
|
|
||||||
// TODO save elements
|
|
||||||
$el = self::$decoder->getElement();
|
|
||||||
$e = self::$decoder->peek();
|
|
||||||
if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
|
|
||||||
self::$decoder->getElementEndTag();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//break if it reached the endtag
|
|
||||||
$e = self::$decoder->peek();
|
|
||||||
if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
|
|
||||||
self::$decoder->getElementEndTag();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} // end if fetch
|
||||||
}
|
|
||||||
|
|
||||||
if ($efc) {
|
if ($efc) {
|
||||||
if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
|
if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
|
||||||
$operation['folderid'] = self::$decoder->getElementContent();
|
$operation['folderid'] = self::$decoder->getElementContent();
|
||||||
if(!self::$decoder->getElementEndTag())
|
if(!self::$decoder->getElementEndTag())
|
||||||
return false;//SYNC_FOLDERID
|
return false;//SYNC_FOLDERID
|
||||||
}
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_OPTIONS)) {
|
|
||||||
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_DELETESUBFOLDERS)) {
|
|
||||||
$operation['deletesubfolders'] = true;
|
|
||||||
if (($dsf = self::$decoder->getElementContent()) !== false) {
|
|
||||||
$operation['deletesubfolders'] = (boolean)$dsf;
|
|
||||||
if(!self::$decoder->getElementEndTag())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_OPTIONS)) {
|
||||||
|
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_DELETESUBFOLDERS)) {
|
||||||
|
$operation['deletesubfolders'] = true;
|
||||||
|
if (($dsf = self::$decoder->getElementContent()) !== false) {
|
||||||
|
$operation['deletesubfolders'] = (boolean)$dsf;
|
||||||
|
if(!self::$decoder->getElementEndTag())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self::$decoder->getElementEndTag();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO move
|
||||||
|
|
||||||
|
//break if it reached the endtag SYNC_ITEMOPERATIONS_FETCH or SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS or SYNC_ITEMOPERATIONS_MOVE
|
||||||
|
$e = self::$decoder->peek();
|
||||||
|
if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
|
||||||
self::$decoder->getElementEndTag();
|
self::$decoder->getElementEndTag();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
} // end while operation
|
||||||
|
|
||||||
//TODO move
|
|
||||||
|
|
||||||
if(!self::$decoder->getElementEndTag())
|
|
||||||
return false; //SYNC_ITEMOPERATIONS_FETCH or SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS or SYNC_ITEMOPERATIONS_MOVE
|
|
||||||
|
|
||||||
$itemoperations[] = $operation;
|
$itemoperations[] = $operation;
|
||||||
//break if it reached the endtag
|
//break if it reached the endtag
|
||||||
|
@ -236,11 +237,7 @@ class ItemOperations extends RequestProcessor {
|
||||||
self::$decoder->getElementEndTag(); //SYNC_ITEMOPERATIONS_ITEMOPERATIONS
|
self::$decoder->getElementEndTag(); //SYNC_ITEMOPERATIONS_ITEMOPERATIONS
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} // end operations loop
|
||||||
}
|
|
||||||
|
|
||||||
// if(!self::$decoder->getElementEndTag())
|
|
||||||
// return false;//SYNC_ITEMOPERATIONS_ITEMOPERATIONS
|
|
||||||
|
|
||||||
$status = SYNC_ITEMOPERATIONSSTATUS_SUCCESS;
|
$status = SYNC_ITEMOPERATIONSSTATUS_SUCCESS;
|
||||||
|
|
||||||
|
|
|
@ -113,8 +113,12 @@ class Request {
|
||||||
self::$command = self::filterEvilInput($_GET["Cmd"], self::LETTERS_ONLY);
|
self::$command = self::filterEvilInput($_GET["Cmd"], self::LETTERS_ONLY);
|
||||||
|
|
||||||
// getUser is unfiltered, as everything is allowed.. even "/", "\" or ".."
|
// getUser is unfiltered, as everything is allowed.. even "/", "\" or ".."
|
||||||
if(isset($_GET["User"]))
|
if(isset($_GET["User"])) {
|
||||||
self::$getUser = strtolower($_GET["User"]);
|
self::$getUser = strtolower($_GET["User"]);
|
||||||
|
if(defined('USE_FULLEMAIL_FOR_LOGIN') && ! USE_FULLEMAIL_FOR_LOGIN) {
|
||||||
|
self::$getUser = Utils::GetLocalPartFromEmail(self::$getUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
if(isset($_GET["DeviceId"]))
|
if(isset($_GET["DeviceId"]))
|
||||||
self::$devid = strtolower(self::filterEvilInput($_GET["DeviceId"], self::WORDCHAR_ONLY));
|
self::$devid = strtolower(self::filterEvilInput($_GET["DeviceId"], self::WORDCHAR_ONLY));
|
||||||
if(isset($_GET["DeviceType"]))
|
if(isset($_GET["DeviceType"]))
|
||||||
|
@ -140,8 +144,12 @@ class Request {
|
||||||
if (!isset(self::$command) && isset($query['Command']))
|
if (!isset(self::$command) && isset($query['Command']))
|
||||||
self::$command = Utils::GetCommandFromCode($query['Command']);
|
self::$command = Utils::GetCommandFromCode($query['Command']);
|
||||||
|
|
||||||
if (!isset(self::$getUser) && isset($query[self::COMMANDPARAM_USER]))
|
if (!isset(self::$getUser) && isset($query[self::COMMANDPARAM_USER])) {
|
||||||
self::$getUser = strtolower($query[self::COMMANDPARAM_USER]);
|
self::$getUser = strtolower($query[self::COMMANDPARAM_USER]);
|
||||||
|
if(defined('USE_FULLEMAIL_FOR_LOGIN') && ! USE_FULLEMAIL_FOR_LOGIN) {
|
||||||
|
self::$getUser = Utils::GetLocalPartFromEmail(self::$getUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isset(self::$devid) && isset($query['DevID']))
|
if (!isset(self::$devid) && isset($query['DevID']))
|
||||||
self::$devid = strtolower(self::filterEvilInput($query['DevID'], self::WORDCHAR_ONLY));
|
self::$devid = strtolower(self::filterEvilInput($query['DevID'], self::WORDCHAR_ONLY));
|
||||||
|
@ -172,8 +180,12 @@ class Request {
|
||||||
}
|
}
|
||||||
|
|
||||||
// in base64 encoded query string user is not necessarily set
|
// in base64 encoded query string user is not necessarily set
|
||||||
if (!isset(self::$getUser) && isset($_SERVER['PHP_AUTH_USER']))
|
if (!isset(self::$getUser) && isset($_SERVER['PHP_AUTH_USER'])) {
|
||||||
list(self::$getUser,) = Utils::SplitDomainUser(strtolower($_SERVER['PHP_AUTH_USER']));
|
list(self::$getUser,) = Utils::SplitDomainUser(strtolower($_SERVER['PHP_AUTH_USER']));
|
||||||
|
if(defined('USE_FULLEMAIL_FOR_LOGIN') && ! USE_FULLEMAIL_FOR_LOGIN) {
|
||||||
|
self::$getUser = Utils::GetLocalPartFromEmail(self::$getUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -233,6 +245,9 @@ class Request {
|
||||||
list(self::$authUser, self::$authDomain) = Utils::SplitDomainUser($_SERVER['PHP_AUTH_USER']);
|
list(self::$authUser, self::$authDomain) = Utils::SplitDomainUser($_SERVER['PHP_AUTH_USER']);
|
||||||
self::$authPassword = (isset($_SERVER['PHP_AUTH_PW']))?$_SERVER['PHP_AUTH_PW'] : "";
|
self::$authPassword = (isset($_SERVER['PHP_AUTH_PW']))?$_SERVER['PHP_AUTH_PW'] : "";
|
||||||
}
|
}
|
||||||
|
if(defined('USE_FULLEMAIL_FOR_LOGIN') && ! USE_FULLEMAIL_FOR_LOGIN) {
|
||||||
|
self::$authUser = Utils::GetLocalPartFromEmail(self::$authUser);
|
||||||
|
}
|
||||||
// authUser & authPassword are unfiltered!
|
// authUser & authPassword are unfiltered!
|
||||||
return (self::$authUser != "" && self::$authPassword != "");
|
return (self::$authUser != "" && self::$authPassword != "");
|
||||||
}
|
}
|
||||||
|
@ -566,6 +581,32 @@ class Request {
|
||||||
return (isset(self::$headers["content-length"]))? (int) self::$headers["content-length"] : 0;
|
return (isset(self::$headers["content-length"]))? (int) self::$headers["content-length"] : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the amount of seconds this request is able to be kept open without the client
|
||||||
|
* closing it. This depends on the vendor.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
static public function GetExpectedConnectionTimeout() {
|
||||||
|
// Different vendors implement different connection timeouts.
|
||||||
|
// In order to optimize processing, we return a specific time for the major
|
||||||
|
// classes currently known (feedback welcome).
|
||||||
|
// The amount of time returned is somehow lower than the max timeout so we have
|
||||||
|
// time for processing.
|
||||||
|
|
||||||
|
// Apple and Windows Phone have higher timeouts (4min = 240sec)
|
||||||
|
if (in_array(self::GetDeviceType(), array("iPod", "iPad", "iPhone", "WP"))) {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
// Samsung devices have a intermediate timeout (90sec)
|
||||||
|
if (in_array(self::GetDeviceType(), array("SAMSUNGGTI"))) {
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for all other devices, a timeout of 30 seconds is expected
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
|
||||||
/**----------------------------------------------------------------------------------------------------------
|
/**----------------------------------------------------------------------------------------------------------
|
||||||
* Private stuff
|
* Private stuff
|
||||||
|
|
|
@ -401,25 +401,27 @@ class Search extends RequestProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elseif ($searchname == ISearchProvider::SEARCH_MAILBOX) {
|
elseif ($searchname == ISearchProvider::SEARCH_MAILBOX) {
|
||||||
foreach ($rows as $u) {
|
if (is_array($rows) && !empty($rows)) {
|
||||||
self::$encoder->startTag(SYNC_SEARCH_RESULT);
|
foreach ($rows as $u) {
|
||||||
self::$encoder->startTag(SYNC_FOLDERTYPE);
|
self::$encoder->startTag(SYNC_SEARCH_RESULT);
|
||||||
self::$encoder->content($u['class']);
|
self::$encoder->startTag(SYNC_FOLDERTYPE);
|
||||||
self::$encoder->endTag();
|
self::$encoder->content($u['class']);
|
||||||
self::$encoder->startTag(SYNC_SEARCH_LONGID);
|
self::$encoder->endTag();
|
||||||
self::$encoder->content($u['longid']);
|
self::$encoder->startTag(SYNC_SEARCH_LONGID);
|
||||||
self::$encoder->endTag();
|
self::$encoder->content($u['longid']);
|
||||||
self::$encoder->startTag(SYNC_FOLDERID);
|
self::$encoder->endTag();
|
||||||
self::$encoder->content($u['folderid']);
|
self::$encoder->startTag(SYNC_FOLDERID);
|
||||||
self::$encoder->endTag();
|
self::$encoder->content($u['folderid']);
|
||||||
|
self::$encoder->endTag();
|
||||||
|
|
||||||
self::$encoder->startTag(SYNC_SEARCH_PROPERTIES);
|
self::$encoder->startTag(SYNC_SEARCH_PROPERTIES);
|
||||||
$tmp = explode(":", $u['longid']);
|
$tmp = explode(":", $u['longid']);
|
||||||
$message = self::$backend->Fetch($u['folderid'], $tmp[1], $cpo);
|
$message = self::$backend->Fetch($u['folderid'], $tmp[1], $cpo);
|
||||||
$message->Encode(self::$encoder);
|
$message->Encode(self::$encoder);
|
||||||
|
|
||||||
self::$encoder->endTag();//result
|
self::$encoder->endTag();//result
|
||||||
self::$encoder->endTag();//properties
|
self::$encoder->endTag();//properties
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// it seems that android 4 requires range and searchtotal
|
// it seems that android 4 requires range and searchtotal
|
||||||
|
|
|
@ -62,6 +62,18 @@ class Sync extends RequestProcessor {
|
||||||
$wbxmlproblem = false;
|
$wbxmlproblem = false;
|
||||||
$emptysync = false;
|
$emptysync = false;
|
||||||
|
|
||||||
|
|
||||||
|
// check if the hierarchySync was fully completed
|
||||||
|
if (USE_PARTIAL_FOLDERSYNC) {
|
||||||
|
if (self::$deviceManager->GetFolderSyncComplete() === false) {
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): Sync request aborted, as exporting of folders has not yet completed");
|
||||||
|
self::$topCollector->AnnounceInformation("Aborted due incomplete folder sync", true);
|
||||||
|
$status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): FolderSync marked as complete");
|
||||||
|
}
|
||||||
|
|
||||||
// Start Synchronize
|
// Start Synchronize
|
||||||
if(self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) {
|
if(self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) {
|
||||||
|
|
||||||
|
@ -189,7 +201,7 @@ class Sync extends RequestProcessor {
|
||||||
else
|
else
|
||||||
$supfields[] = $el[EN_TAG];
|
$supfields[] = $el[EN_TAG];
|
||||||
}
|
}
|
||||||
self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields);
|
self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@ class SyncAppointment extends SyncObject {
|
||||||
// 3 = Out of office
|
// 3 = Out of office
|
||||||
SYNC_POOMCAL_BUSYSTATUS => array ( self::STREAMER_VAR => "busystatus",
|
SYNC_POOMCAL_BUSYSTATUS => array ( self::STREAMER_VAR => "busystatus",
|
||||||
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETTWO,
|
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_POOMCAL_ALLDAYEVENT => array ( self::STREAMER_VAR => "alldayevent",
|
SYNC_POOMCAL_ALLDAYEVENT => array ( self::STREAMER_VAR => "alldayevent",
|
||||||
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO)),
|
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO)),
|
||||||
|
|
|
@ -164,7 +164,7 @@ class SyncMail extends SyncObject {
|
||||||
self::STREAMER_ARRAY => SYNC_AIRSYNCBASE_ATTACHMENT);
|
self::STREAMER_ARRAY => SYNC_AIRSYNCBASE_ATTACHMENT);
|
||||||
|
|
||||||
$mapping[SYNC_POOMMAIL_CONTENTCLASS] = array ( self::STREAMER_VAR => "contentclass",
|
$mapping[SYNC_POOMMAIL_CONTENTCLASS] = array ( self::STREAMER_VAR => "contentclass",
|
||||||
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(DEFAULT_EMAIL_CONTENTCLASS) ));
|
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(DEFAULT_EMAIL_CONTENTCLASS, DEFAULT_CALENDAR_CONTENTCLASS) ));
|
||||||
|
|
||||||
$mapping[SYNC_POOMMAIL_FLAG] = array ( self::STREAMER_VAR => "flag",
|
$mapping[SYNC_POOMMAIL_FLAG] = array ( self::STREAMER_VAR => "flag",
|
||||||
self::STREAMER_TYPE => "SyncMailFlags",
|
self::STREAMER_TYPE => "SyncMailFlags",
|
||||||
|
|
|
@ -62,6 +62,7 @@ class SyncMeetingRequest extends SyncObject {
|
||||||
public $busystatus;
|
public $busystatus;
|
||||||
public $timezone;
|
public $timezone;
|
||||||
public $globalobjid;
|
public $globalobjid;
|
||||||
|
public $disallownewtimeproposal;
|
||||||
|
|
||||||
function SyncMeetingRequest() {
|
function SyncMeetingRequest() {
|
||||||
$mapping = array (
|
$mapping = array (
|
||||||
|
@ -126,6 +127,10 @@ class SyncMeetingRequest extends SyncObject {
|
||||||
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)) )),
|
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)) )),
|
||||||
|
|
||||||
SYNC_POOMMAIL_GLOBALOBJID => array ( self::STREAMER_VAR => "globalobjid"),
|
SYNC_POOMMAIL_GLOBALOBJID => array ( self::STREAMER_VAR => "globalobjid"),
|
||||||
|
|
||||||
|
SYNC_POOMMAIL_DISALLOWNEWTIMEPROPOSAL => array ( self::STREAMER_VAR => "disallownewtimeproposal",
|
||||||
|
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
|
||||||
|
self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
|
||||||
);
|
);
|
||||||
|
|
||||||
parent::SyncObject($mapping);
|
parent::SyncObject($mapping);
|
||||||
|
|
|
@ -49,10 +49,54 @@ if (!function_exists("quoted_printable_encode")) {
|
||||||
*
|
*
|
||||||
* @param string $string string to be encoded
|
* @param string $string string to be encoded
|
||||||
*
|
*
|
||||||
* @see http://www.php.net/manual/en/function.quoted-printable-decode.php#89417
|
* @see http://www.php.net/manual/en/function.quoted-printable-encode.php#106078
|
||||||
*/
|
*/
|
||||||
function quoted_printable_encode($string) {
|
function quoted_printable_encode($string) {
|
||||||
return preg_replace('/[^\r\n]{73}[^=\r\n]{2}/', "$0=\n", str_replace(array('%20', '%0D%0A', '%'), array(' ', "\r\n", '='), rawurlencode($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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1100,11 +1100,23 @@ class TimezoneUtil {
|
||||||
ZLog::Write(LOGLEVEL_DEBUG, "TimezoneUtil::GetFullTZ() for ". $phptimezone);
|
ZLog::Write(LOGLEVEL_DEBUG, "TimezoneUtil::GetFullTZ() for ". $phptimezone);
|
||||||
|
|
||||||
$servertzname = self::guessTZNameFromPHPName($phptimezone);
|
$servertzname = self::guessTZNameFromPHPName($phptimezone);
|
||||||
$offset = self::$tzonesoffsets[$servertzname];
|
return self::GetFullTZFromTZName($servertzname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a full timezone array
|
||||||
|
*
|
||||||
|
* @param string $tzname a TZID value
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
static public function GetFullTZFromTZName($tzname) {
|
||||||
|
$offset = self::$tzonesoffsets[$tzname];
|
||||||
|
|
||||||
$tz = array(
|
$tz = array(
|
||||||
"bias" => $offset[0],
|
"bias" => $offset[0],
|
||||||
"tzname" => self::encodeTZName(self::getMSTZnameFromTZName($servertzname)),
|
"tzname" => self::encodeTZName(self::getMSTZnameFromTZName($tzname)),
|
||||||
"dstendyear" => $offset[3],
|
"dstendyear" => $offset[3],
|
||||||
"dstendmonth" => $offset[4],
|
"dstendmonth" => $offset[4],
|
||||||
"dstendday" => $offset[6],
|
"dstendday" => $offset[6],
|
||||||
|
@ -1114,7 +1126,7 @@ class TimezoneUtil {
|
||||||
"dstendsecond" => $offset[9],
|
"dstendsecond" => $offset[9],
|
||||||
"dstendmillis" => $offset[10],
|
"dstendmillis" => $offset[10],
|
||||||
"stdbias" => $offset[1],
|
"stdbias" => $offset[1],
|
||||||
"tznamedst" => self::encodeTZName(self::getMSTZnameFromTZName($servertzname)),
|
"tznamedst" => self::encodeTZName(self::getMSTZnameFromTZName($tzname)),
|
||||||
"dststartyear" => $offset[11],
|
"dststartyear" => $offset[11],
|
||||||
"dststartmonth" => $offset[12],
|
"dststartmonth" => $offset[12],
|
||||||
"dststartday" => $offset[14],
|
"dststartday" => $offset[14],
|
||||||
|
@ -1258,6 +1270,26 @@ class TimezoneUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pack timezone info for Sync
|
||||||
|
*
|
||||||
|
* @param array $tz
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
static public function GetSyncBlobFromTZ($tz) {
|
||||||
|
// set the correct TZ name (done using the Bias)
|
||||||
|
if (!isset($tz["tzname"]) || !$tz["tzname"] || !isset($tz["tznamedst"]) || !$tz["tznamedst"])
|
||||||
|
$tz = TimezoneUtil::FillTZNames($tz);
|
||||||
|
|
||||||
|
$packed = pack("la64vvvvvvvv" . "la64vvvvvvvv" . "l",
|
||||||
|
$tz["bias"], $tz["tzname"], 0, $tz["dstendmonth"], $tz["dstendday"], $tz["dstendweek"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"], $tz["dstendmillis"],
|
||||||
|
$tz["stdbias"], $tz["tznamedst"], 0, $tz["dststartmonth"], $tz["dststartday"], $tz["dststartweek"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"], $tz["dststartmillis"],
|
||||||
|
$tz["dstbias"]);
|
||||||
|
|
||||||
|
return $packed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
|
@ -917,6 +917,78 @@ class Utils {
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the local part from email address.
|
||||||
|
*
|
||||||
|
* @param string $email
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function GetLocalPartFromEmail($email) {
|
||||||
|
$pos = strpos($email, '@');
|
||||||
|
if ($pos === false) {
|
||||||
|
return $email;
|
||||||
|
}
|
||||||
|
return substr($email, 0, $pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate date object from string and timezone.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
* @param string $timezone
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return int epoch
|
||||||
|
*/
|
||||||
|
public static function MakeUTCDate($value, $timezone = null) {
|
||||||
|
$tz = null;
|
||||||
|
if ($timezone) {
|
||||||
|
$tz = timezone_open($timezone);
|
||||||
|
}
|
||||||
|
if (!$tz) {
|
||||||
|
//If there is no timezone set, we use the default timezone
|
||||||
|
$tz = timezone_open(date_default_timezone_get());
|
||||||
|
}
|
||||||
|
//20110930T090000Z
|
||||||
|
$date = date_create_from_format('Ymd\THis\Z', $value, timezone_open("UTC"));
|
||||||
|
if (!$date) {
|
||||||
|
//20110930T090000
|
||||||
|
$date = date_create_from_format('Ymd\THis', $value, $tz);
|
||||||
|
}
|
||||||
|
if (!$date) {
|
||||||
|
//20110930 (Append T000000Z to the date, so it starts at midnight)
|
||||||
|
$date = date_create_from_format('Ymd\THis\Z', $value . "T000000Z", $tz);
|
||||||
|
}
|
||||||
|
return date_timestamp_get($date);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a tzid from various formats
|
||||||
|
*
|
||||||
|
* @param str $timezone
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return timezone id
|
||||||
|
*/
|
||||||
|
public static function ParseTimezone($timezone) {
|
||||||
|
//(GMT+01.00) Amsterdam / Berlin / Bern / Rome / Stockholm / Vienna
|
||||||
|
if (preg_match('/GMT(\\+|\\-)0(\d)/', $timezone, $matches)) {
|
||||||
|
return "Etc/GMT" . $matches[1] . $matches[2];
|
||||||
|
}
|
||||||
|
//(GMT+10.00) XXX / XXX / XXX / XXX
|
||||||
|
if (preg_match('/GMT(\\+|\\-)1(\d)/', $timezone, $matches)) {
|
||||||
|
return "Etc/GMT" . $matches[1] . "1" . $matches[2];
|
||||||
|
}
|
||||||
|
///inverse.ca/20101018_1/Europe/Amsterdam or /inverse.ca/20101018_1/America/Argentina/Buenos_Aires
|
||||||
|
if (preg_match('/\/[.[:word:]]+\/\w+\/(\w+)\/([\w\/]+)/', $timezone, $matches)) {
|
||||||
|
return $matches[1] . "/" . $matches[2];
|
||||||
|
}
|
||||||
|
return trim($timezone, '"');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
19
sources/sql/mysql.sql
Normal file
19
sources/sql/mysql.sql
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
create table zpush_settings (key_name varchar(50) not null, key_value varchar(50) not null, created_at datetime not null, updated_at datetime not null, primary key (key_name));
|
||||||
|
|
||||||
|
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,
|
||||||
|
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);
|
||||||
|
|
||||||
|
-- This is optional, and will require extra configuration in your mysql
|
||||||
|
-- http://www.mysqlperformanceblog.com/2012/05/30/data-compression-in-innodb-for-text-and-blob-fields/
|
||||||
|
alter table zpush_states engine=InnoDB row_format=compressed key_block_size=16;
|
||||||
|
|
||||||
|
|
||||||
|
-- This table has a primary key id integer, because I will be linking a Rails model against it (admin wui)
|
||||||
|
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);
|
51
sources/testing/samples/meeting_request.txt
Normal file
51
sources/testing/samples/meeting_request.txt
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
METHOD:REQUEST
|
||||||
|
PRODID:Microsoft Exchange Server 2010
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Romance Standard Time
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:16010101T030000
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:16010101T020000
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
|
||||||
|
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
|
||||||
|
DESCRIPTION;LANGUAGE=es-ES:Texto de la segunda cita\n\n
|
||||||
|
SUMMARY;LANGUAGE=es-ES:Segunda cita
|
||||||
|
DTSTART;TZID=Romance Standard Time:20140519T090000
|
||||||
|
DTEND;TZID=Romance Standard Time:20140519T093000
|
||||||
|
UID:040000008200E00074C5B7101A82E0080000000070BC3EB80871CF01000000000000000
|
||||||
|
010000000B3492E4691795F4E810CCD60A178B53C
|
||||||
|
CLASS:PUBLIC
|
||||||
|
PRIORITY:5
|
||||||
|
DTSTAMP:20140516T111408Z
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
SEQUENCE:0
|
||||||
|
LOCATION;LANGUAGE=es-ES:Oficina
|
||||||
|
X-MICROSOFT-CDO-APPT-SEQUENCE:0
|
||||||
|
X-MICROSOFT-CDO-OWNERAPPTID:860903390
|
||||||
|
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
|
||||||
|
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
|
||||||
|
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
|
||||||
|
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||||
|
X-MICROSOFT-CDO-INSTTYPE:0
|
||||||
|
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
DESCRIPTION:REMINDER
|
||||||
|
TRIGGER;RELATED=START:-PT15M
|
||||||
|
END:VALARM
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
28
sources/testing/samples/messages/emoticon.txt
Normal file
28
sources/testing/samples/messages/emoticon.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
Return-Path: <xxxx.xxxx@xxxx.xxx>
|
||||||
|
X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on xxxxx
|
||||||
|
X-Spam-Level:
|
||||||
|
X-Spam-Status: No, score=-1.0 required=5.0 tests=ALL_TRUSTED autolearn=ham
|
||||||
|
autolearn_force=no version=3.4.0
|
||||||
|
Delivered-To: xxxx@xxxxxxxxxxxx
|
||||||
|
Received: from localhost (xxxx.xxxxxxxxx.xx [127.0.0.1])
|
||||||
|
by xxxx.xxxxxxxxxxx.xx (Postfix) with ESMTP id D8AB65001D6
|
||||||
|
for <xxxxx@xxxxxxxx.xx>; Sun, 20 Jul 2014 15:30:58 +0200 (CEST)
|
||||||
|
X-Virus-Scanned: Debian amavisd-new at xxxx.xxxxxxxxx.xx
|
||||||
|
Received: from xxxx.xxxxxxxxx.xxx ([127.0.0.1])
|
||||||
|
by localhost (xxxx.xxxxxxxxxx.xx [127.0.0.1]) (amavisd-new, port 10024)
|
||||||
|
with ESMTP id vI3uJWoUh-Pz for <xxxx@xxxxxxxxx.xx>;
|
||||||
|
Sun, 20 Jul 2014 15:30:58 +0200 (CEST)
|
||||||
|
Received-SPF: none (xxxx.xxx: No applicable sender policy available) receiver=xxxx.xxxxxxxxxx.xx; identity=mailfrom; envelope-from="xxxx.xxxxx@xxxx.xxxx"; helo=$
|
||||||
|
From: xxxxxxxxxx <xxxx.xxxx@xxxx.xxx>
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset=utf-8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Mime-Version: 1.0 (1.0)
|
||||||
|
Subject: Testing
|
||||||
|
Message-Id: <BB54ADE1-199F-4920-863C-357EE09AE735@xxxx.xxx>
|
||||||
|
Date: Sun, 20 Jul 2014 15:31:09 +0200
|
||||||
|
To: xxxx xxxxx<xxxx@xxxxxxxx.xx>
|
||||||
|
|
||||||
|
Testing emojis =F0=9F=98=84
|
||||||
|
|
||||||
|
Sendt fra min iPad=
|
39
sources/testing/samples/messages/emoticon_base64.txt
Normal file
39
sources/testing/samples/messages/emoticon_base64.txt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
Return-Path: xxxxxx@xxxxxxxx.xxx
|
||||||
|
X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on mail.xxxxxxxxx.xx
|
||||||
|
X-Spam-Level:
|
||||||
|
X-Spam-Status: No, score=-1.0 required=5.0 tests=ALL_TRUSTED, HEADER_FROM_DIFFERENT_DOMAINS autolearn=unavailable autolearn_force=no version=3.4.0
|
||||||
|
Delivered-To: xxxxxxx@xxxxxxxxxxx.xx
|
||||||
|
Received: from localhost (xxxxx.xxxxxx.xx [127.0.0.1]) by xxxxx.xxxxxxxxxx.xx (Postfix) with ESMTP id 1982C500AD4; Fri, 11 Jul 2014 21:20:53 +0200 (CEST)
|
||||||
|
X-Virus-Scanned: Debian amavisd-new at xxxxx.xxxxxxx.xx
|
||||||
|
Received: from xxxxx.xxxxxxxxxx.xx ([127.0.0.1]) by localhost (xxxx.xxxxxxxx.xx [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 2nP27OZ1CUzN; Fri, 11 Jul 2014 21:20:52 +0200 (CEST)
|
||||||
|
Resent-From: xxxxx@xxxxxxx.xx
|
||||||
|
DMARC-Filter: OpenDMARC Filter v1.2.0 xxxx.xxxxxx.xxx 9Dxxx7500A9D
|
||||||
|
X-Virus-Scanned: Debian amavisd-new at xxxxx.xxxxxxxxxx.xx
|
||||||
|
MIME-Version: 1.0
|
||||||
|
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=xxxxxxxx.xx; s=mail; t=1405106446; bh=ufc8aM3vMmQISb8aKXpA3Rk8t/+ce6kFhZUr1x6DwtU=; h=From:Subject:Date:To:From; b=uQb7eWfRZdkUF+didgfmgJTWW78GdyHJlEqhz+WFl2Ouqkt7iXZPwljR4OIoFvumK1IJqsXWRW74Py+Mq2CWkxeuML/9kTrWBXJDMichOqll666EgG8/DyyYH6RPIhje9ygILX85E4VO0zO1N4uh0Tc/D0Ow93hUWS51D1cX7gA=
|
||||||
|
From: xxxxx@xxxxxxxx.xx
|
||||||
|
Subject: Kontroll
|
||||||
|
Message-ID: 28EF0B82-61D9-4BA3-B534-CE4FB6101D1A@xxxxxx.xxx
|
||||||
|
Date: Fri, 11 Jul 2014 21:21:03 +0200
|
||||||
|
To: stxxx xxxxx@xxxxxxxxx.xx
|
||||||
|
Content-Type: multipart/mixed; boundary="=_83c3cde24966264fd2b3537156aaaba6"
|
||||||
|
Received-SPF: Pass (xxxx.xxxxxxxx.loc: domain of xxxxxx@xxxxxx.xx designates xxx.xxx.x.xx as permitted sender) receiver=xxxx.xxxxxxxx.xxx; client-ip=xxx.xxx.x.xx; helo=xxxxx.xxxxxxx.xx;
|
||||||
|
X-Auto-Response-Suppress: DR, OOF, AutoReply
|
||||||
|
Resent-Message-Id: 20140711192053.1982C500AD4@xxxxxx.xxxxxx.xx
|
||||||
|
Resent-Date: Fri, 11 Jul 2014 21:20:53 +0200 (CEST)
|
||||||
|
|
||||||
|
--=_83c3cde24966264fd2b3537156aaaba6
|
||||||
|
Content-Type: multipart/alternative; boundary="=_3eaa2d9769aa280737bc3e5268efb2ad"
|
||||||
|
|
||||||
|
--=_3eaa2d9769aa280737bc3e5268efb2ad
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: text/plain; charset="utf-8"
|
||||||
|
|
||||||
|
RGV0IGVyIHDDpSB0aWRlIGF0dCB2aSBwbGFubGVnZ2VyIHVuaWZvcm1zIGtvbnRyb2xsIA0KS3Zh
|
||||||
|
ciBoZWxnIG5vZW4gc29tIGlra2plIGhhcg0KQWxsZSBtw6Uga3VubmUgZ2rDuHIgZW4gaW5uc2F0
|
||||||
|
cyBldHRlciBmZXJpZW4gMiAgcGVycyB4IDQgaGVsZ2VyIHPDpSB0cm9yIGVnIG15ZSBhdiBwcm9i
|
||||||
|
bGVtbWV0IGVyIHZla2tlDQpHb2Qgc29tbWVyIG9nIGZlcmllIPCfmI4NClZpYmVrZQ0KDQpTZW5k
|
||||||
|
dCBmcmEgbWluIGlQaG9uZQ==
|
||||||
|
--=_3eaa2d9769aa280737bc3e5268efb2ad--
|
||||||
|
|
||||||
|
--=_83c3cde24966264fd2b3537156aaaba6--
|
20
sources/testing/samples/messages/emoticon_subject.txt
Normal file
20
sources/testing/samples/messages/emoticon_subject.txt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Mime-Version: 1.0
|
||||||
|
From: xxxx@xxxxxxx.xxx
|
||||||
|
Subject: =?utf-8?B?8J+YhA==?=
|
||||||
|
Message-id: EC08EA2B-1439-423D-AA69-843776D5C39C@xxxxxxxx.xx
|
||||||
|
Date: Wed, 23 Jul 2014 19:50:36 +0200
|
||||||
|
To: test2014@xxxxxxxxxxx.xx
|
||||||
|
Content-Type: multipart/mixed; boundary="=_72ced2dca575896ff9586958e1bb6592"
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
--=_72ced2dca575896ff9586958e1bb6592
|
||||||
|
Content-Type: multipart/alternative; boundary="=_9818f474f7be6252ec285590ed281857"
|
||||||
|
|
||||||
|
--=_9818f474f7be6252ec285590ed281857
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
|
||||||
|
8J+Y4oCeDQoNClNlbmR0IGZyYSBtaW4gaVBob25l
|
||||||
|
--=_9818f474f7be6252ec285590ed281857--
|
||||||
|
|
||||||
|
--=_72ced2dca575896ff9586958e1bb6592--
|
48
sources/testing/samples/messages/french.txt
Normal file
48
sources/testing/samples/messages/french.txt
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
Return-Path: <edt@domain.com>
|
||||||
|
Delivered-To: informatique@domain.com
|
||||||
|
Received: from localhost (localhost.localdomain [127.0.0.1])
|
||||||
|
by domain.com (Postfix) with ESMTP id AB32E1000EA
|
||||||
|
for <informatique@domain.com>; Sun, 7 Sep 2014 09:50:21 +0200 (CEST)
|
||||||
|
Authentication-Results: domain.com (amavisd-new);
|
||||||
|
dkim=pass (1024-bit key) reason="pass (just generated, assumed good)"
|
||||||
|
header.d=domain.com
|
||||||
|
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=
|
||||||
|
domain.com; h=content-transfer-encoding:content-type
|
||||||
|
:content-type:mime-version:from:from:subject:subject:date:date
|
||||||
|
:received; s=mail; t=1410076220; x=1411890621; bh=paEf4SYhXmrsFW
|
||||||
|
vtqTtrw7K9W9eprm8TMRs5JiO2p6o=; b=Dszu0yEi/ktiqQoFdLPKxkvdDB+sU+
|
||||||
|
3b+B2O0Jf2PzVahHvMj7bGJSoW0eVb1u6tot6q5NOjFH85Ab1MZdhoga+jMW3h8E
|
||||||
|
5GrVvA4NumMV/HMJ8M/1LpAc5nTaGxN+8KfGHxCM0DsjZEFwuLwL3xU6PgSrggcC
|
||||||
|
6DT6GcpnPTRxM=
|
||||||
|
X-Virus-Scanned: Debian amavisd-new at domain.com
|
||||||
|
X-Spam-Flag: NO
|
||||||
|
X-Spam-Score: 0.138
|
||||||
|
X-Spam-Level:
|
||||||
|
X-Spam-Status: No, score=0.138 tagged_above=-999 required=4
|
||||||
|
tests=[MISSING_MID=0.14, NO_RECEIVED=-0.001, NO_RELAYS=-0.001]
|
||||||
|
autolearn=no
|
||||||
|
Received: from domain.com ([127.0.0.1])
|
||||||
|
by localhost (domain.com [127.0.0.1]) (amavisd-new, port 10024)
|
||||||
|
with ESMTP id YDofG_pOTs47 for <informatique@domain.com>;
|
||||||
|
Sun, 7 Sep 2014 09:50:20 +0200 (CEST)
|
||||||
|
Date: Sun, 07 Sep 2014 09:50:03 +0200
|
||||||
|
Subject: =?ISO-8859-1?Q?Re:_[TC5_BAT]_Construction_g=E9n=E9rale_/_08:30?=
|
||||||
|
From: =?ISO-8859-1?Q?Edt_Secr=E9tariat_ESITC_Cachan?=
|
||||||
|
<edt@domain.com>
|
||||||
|
To: Informatique ESITC <informatique@domain.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Message-Id: <20140907075021.AB32E1000EA@domain.com>
|
||||||
|
|
||||||
|
Qydlc3QgcmVjdGlmacOpLgpCb25uZSBqb3VybsOpZS4KTGUgc2VjcsOpdGFyaWF0IGVkdMKgIGRl
|
||||||
|
IGwnRVNJVEMKCkxlIDYgc2VwdC4gMjAxNCAyMzo0NCwgSW5mb3JtYXRpcXVlIEVTSVRDIDxpbmZv
|
||||||
|
cm1hdGlxdWVAYWRtLmVzaXRjLWNhY2hhbi5mcj4gYSDDqWNyaXQgOgo+Cj4gQm9uam91ciwgCj4K
|
||||||
|
PiBJbCBzZW1ibGVyYWl0IHF1J2lsIHkgYWl0IHVuZSBwZXRpdGUgZXJyZXVyIHN1ciBsJ2VtcGxv
|
||||||
|
aSBkdSB0ZW1wcyBkZXMgCj4gVEM1IEJBVC4gCj4KPiBMZSBMdW5kaSAwNSBKYW52aWVyIDIwMTUg
|
||||||
|
w6AgMDhIMzAuIExlIGNvdXJzIGRlIENHIGR1cmUgMWgzMCBhbG9ycyBxdSdpbCB5IAo+IGEgMiBz
|
||||||
|
w6lhbmNlcy4gCj4gRG9uYyBzb2l0IGMnZXN0IAo+IFNvaXQgdW5lIHPDqWFuY2UgZGUgMUgzMCAK
|
||||||
|
PiBTb2l0IDIgc8OpYW5jZXMgZGUgM0ggKGF1IHRvdGFsKSAKPiBNYWlzIHBhcyAyIHPDqWFuY2Vz
|
||||||
|
IGRlIDFIMzAgKGF1IHRvdGFsKSAKPgo+IENvcmRpYWxlbWVudCwgCj4gSW5mb3JtYXRpcXVlIEVT
|
||||||
|
SVRDIAo=
|
||||||
|
|
31
sources/testing/samples/messages/m0001.txt
Normal file
31
sources/testing/samples/messages/m0001.txt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
From: "Doug Sauder" <doug@example.com>
|
||||||
|
To: "Jürgen Schmürgen" <schmuergen@example.com>
|
||||||
|
Subject: Die Hasen und die Frösche (Microsoft Outlook 00)
|
||||||
|
Date: Wed, 17 May 2000 19:08:29 -0400
|
||||||
|
Message-ID: <NDBBIAKOPKHFGPLCODIGIEKBCHAA.doug@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
X-Priority: 3 (Normal)
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
|
||||||
|
Importance: Normal
|
||||||
|
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
|
||||||
|
|
||||||
|
Die Hasen und die Frösche
|
||||||
|
|
||||||
|
Die Hasen klagten einst über ihre mißliche Lage; "wir leben", sprach ein
|
||||||
|
Redner, "in steter Furcht vor Menschen und Tieren, eine Beute der Hunde, der
|
||||||
|
Adler, ja fast aller Raubtiere! Unsere stete Angst ist ärger als der Tod
|
||||||
|
selbst. Auf, laßt uns ein für allemal sterben."
|
||||||
|
|
||||||
|
In einem nahen Teich wollten sie sich nun ersäufen; sie eilten ihm zu;
|
||||||
|
allein das außerordentliche Getöse und ihre wunderbare Gestalt erschreckte
|
||||||
|
eine Menge Frösche, die am Ufer saßen, so sehr, daß sie aufs schnellste
|
||||||
|
untertauchten.
|
||||||
|
|
||||||
|
"Halt", rief nun eben dieser Sprecher, "wir wollen das Ersäufen noch ein
|
||||||
|
wenig aufschieben, denn auch uns fürchten, wie ihr seht, einige Tiere,
|
||||||
|
welche also wohl noch unglücklicher sein müssen als wir."
|
||||||
|
|
31
sources/testing/samples/messages/m0002.txt
Normal file
31
sources/testing/samples/messages/m0002.txt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
From: "Doug Sauder" <doug@example.com>
|
||||||
|
To: "Jürgen Schmürgen" <schmuergen@example.com>
|
||||||
|
Subject: Die Hasen und die Frösche (Microsoft Outlook 00)
|
||||||
|
Date: Wed, 17 May 2000 19:10:31 -0400
|
||||||
|
Message-ID: <NDBBIAKOPKHFGPLCODIGMEKBCHAA.doug@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
X-Priority: 3 (Normal)
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
|
||||||
|
Importance: Normal
|
||||||
|
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
|
||||||
|
|
||||||
|
Die Hasen und die Fr=F6sche
|
||||||
|
|
||||||
|
Die Hasen klagten einst =FCber ihre mi=DFliche Lage; "wir leben", sprach =
|
||||||
|
ein Redner, "in steter Furcht vor Menschen und Tieren, eine Beute der =
|
||||||
|
Hunde, der Adler, ja fast aller Raubtiere! Unsere stete Angst ist =
|
||||||
|
=E4rger als der Tod selbst. Auf, la=DFt uns ein f=FCr allemal sterben."=20
|
||||||
|
|
||||||
|
In einem nahen Teich wollten sie sich nun ers=E4ufen; sie eilten ihm zu; =
|
||||||
|
allein das au=DFerordentliche Get=F6se und ihre wunderbare Gestalt =
|
||||||
|
erschreckte eine Menge Fr=F6sche, die am Ufer sa=DFen, so sehr, da=DF =
|
||||||
|
sie aufs schnellste untertauchten.=20
|
||||||
|
|
||||||
|
"Halt", rief nun eben dieser Sprecher, "wir wollen das Ers=E4ufen noch =
|
||||||
|
ein wenig aufschieben, denn auch uns f=FCrchten, wie ihr seht, einige =
|
||||||
|
Tiere, welche also wohl noch ungl=FCcklicher sein m=FCssen als wir."=20
|
||||||
|
|
30
sources/testing/samples/messages/m0003.txt
Normal file
30
sources/testing/samples/messages/m0003.txt
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
From: "Doug Sauder" <doug@example.com>
|
||||||
|
To: "Jürgen Schmürgen" <schmuergen@example.com>
|
||||||
|
Subject: Die Hasen und die Frösche (Microsoft Outlook 00)
|
||||||
|
Date: Wed, 17 May 2000 19:11:50 -0400
|
||||||
|
Message-ID: <NDBBIAKOPKHFGPLCODIGAEKCCHAA.doug@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
X-Priority: 3 (Normal)
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
|
||||||
|
Importance: Normal
|
||||||
|
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
|
||||||
|
|
||||||
|
RGllIEhhc2VuIHVuZCBkaWUgRnL2c2NoZQ0KDQpEaWUgSGFzZW4ga2xhZ3RlbiBlaW5zdCD8YmVy
|
||||||
|
IGlocmUgbWnfbGljaGUgTGFnZTsgIndpciBsZWJlbiIsIHNwcmFjaCBlaW4gUmVkbmVyLCAiaW4g
|
||||||
|
c3RldGVyIEZ1cmNodCB2b3IgTWVuc2NoZW4gdW5kIFRpZXJlbiwgZWluZSBCZXV0ZSBkZXIgSHVu
|
||||||
|
ZGUsIGRlciBBZGxlciwgamEgZmFzdCBhbGxlciBSYXVidGllcmUhIFVuc2VyZSBzdGV0ZSBBbmdz
|
||||||
|
dCBpc3Qg5HJnZXIgYWxzIGRlciBUb2Qgc2VsYnN0LiBBdWYsIGxh33QgdW5zIGVpbiBm/HIgYWxs
|
||||||
|
ZW1hbCBzdGVyYmVuLiIgDQoNCkluIGVpbmVtIG5haGVuIFRlaWNoIHdvbGx0ZW4gc2llIHNpY2gg
|
||||||
|
bnVuIGVyc+R1ZmVuOyBzaWUgZWlsdGVuIGlobSB6dTsgYWxsZWluIGRhcyBhdd9lcm9yZGVudGxp
|
||||||
|
Y2hlIEdldPZzZSB1bmQgaWhyZSB3dW5kZXJiYXJlIEdlc3RhbHQgZXJzY2hyZWNrdGUgZWluZSBN
|
||||||
|
ZW5nZSBGcvZzY2hlLCBkaWUgYW0gVWZlciBzYd9lbiwgc28gc2VociwgZGHfIHNpZSBhdWZzIHNj
|
||||||
|
aG5lbGxzdGUgdW50ZXJ0YXVjaHRlbi4gDQoNCiJIYWx0IiwgcmllZiBudW4gZWJlbiBkaWVzZXIg
|
||||||
|
U3ByZWNoZXIsICJ3aXIgd29sbGVuIGRhcyBFcnPkdWZlbiBub2NoIGVpbiB3ZW5pZyBhdWZzY2hp
|
||||||
|
ZWJlbiwgZGVubiBhdWNoIHVucyBm/HJjaHRlbiwgd2llIGlociBzZWh0LCBlaW5pZ2UgVGllcmUs
|
||||||
|
IHdlbGNoZSBhbHNvIHdvaGwgbm9jaCB1bmds/GNrbGljaGVyIHNlaW4gbfxzc2VuIGFscyB3aXIu
|
||||||
|
IiANCg==
|
||||||
|
|
31
sources/testing/samples/messages/m0004.txt
Normal file
31
sources/testing/samples/messages/m0004.txt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
From: "Doug Sauder" <doug@example.com>
|
||||||
|
To: =?iso-8859-1?B?SvxyZ2VuIFNjaG38cmdlbg==?= <schmuergen@example.com>
|
||||||
|
Subject: =?iso-8859-1?Q?Die_Hasen_und_die_Fr=F6sche_=28Microsoft_Outlook_00=29?=
|
||||||
|
Date: Wed, 17 May 2000 19:13:51 -0400
|
||||||
|
Message-ID: <NDBBIAKOPKHFGPLCODIGEEKCCHAA.doug@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
X-Priority: 3 (Normal)
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
|
||||||
|
Importance: Normal
|
||||||
|
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
|
||||||
|
|
||||||
|
Die Hasen und die Frösche
|
||||||
|
|
||||||
|
Die Hasen klagten einst über ihre mißliche Lage; "wir leben", sprach ein
|
||||||
|
Redner, "in steter Furcht vor Menschen und Tieren, eine Beute der Hunde, der
|
||||||
|
Adler, ja fast aller Raubtiere! Unsere stete Angst ist ärger als der Tod
|
||||||
|
selbst. Auf, laßt uns ein für allemal sterben."
|
||||||
|
|
||||||
|
In einem nahen Teich wollten sie sich nun ersäufen; sie eilten ihm zu;
|
||||||
|
allein das außerordentliche Getöse und ihre wunderbare Gestalt erschreckte
|
||||||
|
eine Menge Frösche, die am Ufer saßen, so sehr, daß sie aufs schnellste
|
||||||
|
untertauchten.
|
||||||
|
|
||||||
|
"Halt", rief nun eben dieser Sprecher, "wir wollen das Ersäufen noch ein
|
||||||
|
wenig aufschieben, denn auch uns fürchten, wie ihr seht, einige Tiere,
|
||||||
|
welche also wohl noch unglücklicher sein müssen als wir."
|
||||||
|
|
31
sources/testing/samples/messages/m0005.txt
Normal file
31
sources/testing/samples/messages/m0005.txt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
From: "Doug Sauder" <doug@example.com>
|
||||||
|
To: =?iso-8859-1?B?SvxyZ2VuIFNjaG38cmdlbg==?= <schmuergen@example.com>
|
||||||
|
Subject: =?iso-8859-1?Q?Die_Hasen_und_die_Fr=F6sche_=28Microsoft_Outlook_00=29?=
|
||||||
|
Date: Wed, 17 May 2000 19:15:35 -0400
|
||||||
|
Message-ID: <NDBBIAKOPKHFGPLCODIGIEKCCHAA.doug@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
X-Priority: 3 (Normal)
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
|
||||||
|
Importance: Normal
|
||||||
|
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
|
||||||
|
|
||||||
|
Die Hasen und die Fr=F6sche
|
||||||
|
|
||||||
|
Die Hasen klagten einst =FCber ihre mi=DFliche Lage; "wir leben", sprach =
|
||||||
|
ein Redner, "in steter Furcht vor Menschen und Tieren, eine Beute der =
|
||||||
|
Hunde, der Adler, ja fast aller Raubtiere! Unsere stete Angst ist =
|
||||||
|
=E4rger als der Tod selbst. Auf, la=DFt uns ein f=FCr allemal sterben."=20
|
||||||
|
|
||||||
|
In einem nahen Teich wollten sie sich nun ers=E4ufen; sie eilten ihm zu; =
|
||||||
|
allein das au=DFerordentliche Get=F6se und ihre wunderbare Gestalt =
|
||||||
|
erschreckte eine Menge Fr=F6sche, die am Ufer sa=DFen, so sehr, da=DF =
|
||||||
|
sie aufs schnellste untertauchten.=20
|
||||||
|
|
||||||
|
"Halt", rief nun eben dieser Sprecher, "wir wollen das Ers=E4ufen noch =
|
||||||
|
ein wenig aufschieben, denn auch uns f=FCrchten, wie ihr seht, einige =
|
||||||
|
Tiere, welche also wohl noch ungl=FCcklicher sein m=FCssen als wir."=20
|
||||||
|
|
34
sources/testing/samples/messages/m0006.txt
Normal file
34
sources/testing/samples/messages/m0006.txt
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
From: "Doug Sauder" <doug@example.com>
|
||||||
|
To: "Joe Blow" <jblow@example.com>,
|
||||||
|
=?utf-7?B?SitBUHctcmdlbiBTY2htK0FQdy1yZ2Vu?= <schmuergen@example.com>
|
||||||
|
Subject: =?utf-7?Q?Die_Hasen_und_die_Fr+APY-sche_=28Microsoft_Outlook_00=29?=
|
||||||
|
Date: Wed, 17 May 2000 19:18:39 -0400
|
||||||
|
Message-ID: <NDBBIAKOPKHFGPLCODIGMEKCCHAA.doug@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="utf-7"
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
X-Priority: 3 (Normal)
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
|
||||||
|
Importance: Normal
|
||||||
|
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
|
||||||
|
|
||||||
|
Die Hasen und die Fr+APY-sche
|
||||||
|
|
||||||
|
Die Hasen klagten einst +APw-ber ihre mi+AN8-liche Lage+ADs- +ACI-wir =
|
||||||
|
leben+ACI-, sprach ein Redner, +ACI-in steter Furcht vor Menschen und =
|
||||||
|
Tieren, eine Beute der Hunde, der Adler, ja fast aller Raubtiere+ACE- =
|
||||||
|
Unsere stete Angst ist +AOQ-rger als der Tod selbst. Auf, la+AN8-t uns =
|
||||||
|
ein f+APw-r allemal sterben.+ACI-=20
|
||||||
|
|
||||||
|
In einem nahen Teich wollten sie sich nun ers+AOQ-ufen+ADs- sie eilten =
|
||||||
|
ihm zu+ADs- allein das au+AN8-erordentliche Get+APY-se und ihre =
|
||||||
|
wunderbare Gestalt erschreckte eine Menge Fr+APY-sche, die am Ufer =
|
||||||
|
sa+AN8-en, so sehr, da+AN8- sie aufs schnellste untertauchten.=20
|
||||||
|
|
||||||
|
+ACI-Halt+ACI-, rief nun eben dieser Sprecher, +ACI-wir wollen das =
|
||||||
|
Ers+AOQ-ufen noch ein wenig aufschieben, denn auch uns f+APw-rchten, wie =
|
||||||
|
ihr seht, einige Tiere, welche also wohl noch ungl+APw-cklicher sein =
|
||||||
|
m+APw-ssen als wir.+ACI-=20
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue