diff options
Diffstat (limited to 'webmail/plugins/carddav/carddav_backend.php')
| -rw-r--r-- | webmail/plugins/carddav/carddav_backend.php | 556 |
1 files changed, 556 insertions, 0 deletions
diff --git a/webmail/plugins/carddav/carddav_backend.php b/webmail/plugins/carddav/carddav_backend.php new file mode 100644 index 0000000..75e122d --- /dev/null +++ b/webmail/plugins/carddav/carddav_backend.php @@ -0,0 +1,556 @@ +<?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 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: http://sogo-demo.inverse.ca/SOGo/dav/{resource|principal|username}/Contacts/{collection}/ + * + * + * @author Christian Putzke <christian.putzke@graviox.de> + * @copyright Christian Putzke @ Graviox Studios + * @link http://www.graviox.de/ + * @link https://twitter.com/graviox/ + * @since 20.07.2011 + * @version 0.5.1 + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + * + */ + +class carddav_backend +{ + /** + * CardDAV PHP Version + * + * @constant string + */ + const VERSION = '0.5.1'; + + /** + * User agent displayed in http requests + * + * @constant string + */ + const USERAGENT = 'CardDAV PHP/'; + + /** + * 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; + + /** + * Constructor + * Sets the CardDAV server url + * + * @param string $url CardDAV server url + * @return void + */ + public function __construct($url = null) + { + if ($url !== null) + { + $this->set_url($url); + } + } + + /** + * Sets the CardDAV server url + * + * @param string $url CardDAV server url + * @return void + */ + public function set_url($url) + { + $this->url = $url; + + if (substr($this->url, -1, 1) !== '/') + { + $this->url = $this->url . '/'; + } + + $this->url_parts = parse_url($this->url); + } + + /** + * Sets authentication string + * + * @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 propfind XML response from the CardDAV server + * + * @param boolean $include_vcards vCards include vCards in the response (simplified only) + * @param boolean $raw Get response raw or simplified + * @return string Raw or simplified XML response + */ + public function get($include_vcards = true, $raw = false) + { + $response = $this->query($this->url, 'PROPFIND'); + + if ($response === false || $raw === true) + { + return $response; + } + else + { + return $this->simplify($response, $include_vcards); + } + } + + /** + * Gets a clean vCard from the CardDAV server + * + * @param string $vcard_id vCard id on the CardDAV server + * @return string vCard (text/vcard) + */ + public function get_vcard($vcard_id) + { + $vcard_id = str_replace('.vcf', null, $vcard_id); + return $this->query($this->url . $vcard_id . '.vcf', 'GET'); + } + + /** + * Gets a vCard + XML from the CardDAV Server + * + * @param string $vcard_id vCard id on the CardDAV Server + * @param boolean $raw Get response raw or simplified + * @return string Raw or simplified vCard (text/xml) + */ + public function get_xml_vcard($vcard_id, $raw = false) + { + $vcard_id = str_replace('.vcf', null, $vcard_id); + + $xml = new XMLWriter(); + $xml->openMemory(); + $xml->setIndent(4); + $xml->startDocument('1.0', 'utf-8'); + $xml->startElement('C:addressbook-multiget'); + $xml->writeAttribute('xmlns:D', 'DAV:'); + $xml->writeAttribute('xmlns:C', 'urn:ietf:params:xml:ns:carddav'); + $xml->startElement('D:prop'); + $xml->writeElement('D:getetag'); + $xml->writeElement('D:getlastmodified'); + $xml->endElement(); + $xml->writeElement('D:href', $this->url_parts['path'] . $vcard_id . '.vcf'); + $xml->endElement(); + $xml->endDocument(); + + $response = $this->query($this->url, 'REPORT', $xml->outputMemory(), 'text/xml'); + + if ($raw === true || $response === false) + { + return $response; + } + else + { + return $this->simplify($response, true); + } + } + + /** + * Checks if the CardDAV server is reachable + * + * @return boolean + */ + public function check_connection() + { + return $this->query($this->url, 'OPTIONS', null, null, true); + } + + /** + * Cleans the vCard + * + * @param string $vcard vCard + * @return string $vcard vCard + */ + private function clean_vcard($vcard) + { + $vcard = str_replace("\t", null, $vcard); + + return $vcard; + } + + /** + * Deletes an entry from the CardDAV server + * + * @param string $vcard_id vCard id on the CardDAV server + * @return boolean + */ + public function delete($vcard_id) + { + return $this->query($this->url . $vcard_id . '.vcf', 'DELETE', null, null, true); + } + + /** + * Adds an entry to the CardDAV server + * + * @param string $vcard vCard + * @return string The new vCard id + */ + public function add($vcard) + { + $vcard_id = $this->generate_vcard_id(); + $vcard = $this->clean_vcard($vcard); + + if ($this->query($this->url . $vcard_id . '.vcf', 'PUT', $vcard, 'text/vcard', true) === true) + { + return $vcard_id; + } + else + { + return false; + } + } + + /** + * 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) + { + $vcard_id = str_replace('.vcf', null, $vcard_id); + $vcard = $this->clean_vcard($vcard); + + return $this->query($this->url . $vcard_id . '.vcf', 'PUT', $vcard, 'text/vcard', true); + } + + /** + * Simplify CardDAV XML response + * + * @param string $response CardDAV XML response + * @param boolean $include_vcards Include vCards or not + * @return string Simplified CardDAV XML response + */ + private function simplify($response, $include_vcards = true) + { + $response = $this->clean_response($response); + $xml = new SimpleXMLElement($response); + + $simplified_xml = new XMLWriter(); + $simplified_xml->openMemory(); + $simplified_xml->setIndent(4); + + $simplified_xml->startDocument('1.0', 'utf-8'); + $simplified_xml->startElement('response'); + + foreach ($xml->response as $response) + { + if (preg_match('/vcard/', $response->propstat->prop->getcontenttype) || preg_match('/vcf/', $response->href)) + { + $id = basename($response->href); + $id = str_replace('.vcf', 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) + { + $simplified_xml->writeElement('vcard', $this->get_vcard($id)); + } + $simplified_xml->endElement(); + } + } + else if (preg_match('/unix-directory/', $response->propstat->prop->getcontenttype)) + { + 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(); + } + } + + $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 clean_response($response) + { + $response = utf8_encode($response); + $response = str_replace('D:', null, $response); + $response = str_replace('d:', null, $response); + $response = str_replace('C:', null, $response); + $response = str_replace('c:', null, $response); + + return $response; + } + + /** + * Curl initialization + * + * @return void + */ + public function curl_init() + { + if (empty($this->curl)) + { + $this->curl = curl_init(); + curl_setopt($this->curl, CURLOPT_HEADER, false); + 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); + } + } + } + + /** + * Quries 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 boolean $return_boolean Return just a boolean + * @return string CardDAV XML response + */ + private function query($url, $method, $content = null, $content_type = null, $return_boolean = false) + { + $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)); + } + else + { + curl_setopt($this->curl, CURLOPT_HTTPHEADER, array()); + } + + $response = curl_exec($this->curl); + $http_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); + + if (in_array($http_code, array(200, 207))) + { + return ($return_boolean === true ? true : $response); + } + else if ($return_boolean === true && in_array($http_code, array(201, 204))) + { + return true; + } + else + { + return false; + } + } + + /** + * Returns a valid and unused vCard id + * + * @return string Valid vCard id + */ + private function generate_vcard_id() + { + $id = null; + + for ($number = 0; $number <= 25; $number ++) + { + if ($number == 8 || $number == 17) + { + $id .= '-'; + } + else + { + $id .= $this->vcard_id_chars[mt_rand(0, (count($this->vcard_id_chars) - 1))]; + } + } + + $carddav = new carddav_backend($this->url); + $carddav->set_auth($this->username, $this->password); + + if ($carddav->query($this->url . $id . '.vcf', 'GET', null, null, true)) + { + return $this->generate_vcard_id(); + } + else + { + return $id; + } + } + + /** + * Destructor + * Close curl connection if it's open + * + * @return void + */ + public function __destruct() + { + if (!empty($this->curl)) + { + curl_close($this->curl); + } + } +} |
