Page MenuHomePhorge

No OneTemporary

diff --git a/lib/Kolab/CalDAV/Plugin.php b/lib/Kolab/CalDAV/Plugin.php
index f52a815..a6584da 100644
--- a/lib/Kolab/CalDAV/Plugin.php
+++ b/lib/Kolab/CalDAV/Plugin.php
@@ -1,250 +1,248 @@
<?php
/**
* Extended CalDAV plugin for the Kolab DAV server
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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/>.
*/
namespace Kolab\CalDAV;
use Sabre\DAV;
use Sabre\CalDAV;
use Sabre\VObject;
use Sabre\HTTP;
use Sabre\HTTP\URLUtil;
use Kolab\DAV\Auth\HTTPBasic;
/**
* Extended CalDAV plugin to tweak data validation
*/
class Plugin extends CalDAV\Plugin
{
// make already parsed text/calednar blocks available for later use
public static $parsed_vcalendar;
public static $parsed_vevent;
// allow the backend to force a redirect Location
public static $redirect_basename;
/**
* Initializes the plugin
*
* @param DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server)
{
parent::initialize($server);
$server->on('afterCreateFile', array($this, 'afterWriteContent'));
$server->on('afterWriteContent', array($this, 'afterWriteContent'));
}
/**
* Inject some additional HTTP response headers
*/
public function afterWriteContent($uri, $node)
{
// send Location: header to corrected URI
if (self::$redirect_basename) {
$path = explode('/', $uri);
array_pop($path);
array_push($path, self::$redirect_basename);
$this->server->httpResponse->setHeader('Location', $this->server->getBaseUri() . join('/', array_map('urlencode', $path)));
self::$redirect_basename = null;
}
}
/**
* Checks if the submitted iCalendar data is in fact, valid.
*
* An exception is thrown if it's not.
*
* @param resource|string $data
* @param string $path
* @param bool $modified Should be set to true, if this event handler
* changed &$data.
* @param RequestInterface $request The http request.
* @param ResponseInterface $response The http response.
* @param bool $isNew Is the item a new one, or an update.
* @return void
*/
protected function validateICalendar(&$data, $path, &$modified, HTTP\RequestInterface $request, HTTP\ResponseInterface $response, $isNew)
{
// If it's a stream, we convert it to a string first.
if (is_resource($data)) {
$data = stream_get_contents($data);
}
$before = md5($data);
// Converting the data to unicode, if needed.
$data = DAV\StringUtil::ensureUTF8($data);
if ($before !== md5($data))
$modified = true;
try {
// If the data starts with a [, we can reasonably assume we're dealing
// with a jCal object.
if (substr($data,0,1) === '[') {
$vobj = VObject\Reader::readJson($data);
// Converting $data back to iCalendar, as that's what we
// technically support everywhere.
$data = $vobj->serialize();
$modified = true;
} else {
// modification: Set options to be more tolerant when parsing extended or invalid properties
$vobj = VObject\Reader::read($data, VObject\Reader::OPTION_FORGIVING | VObject\Reader::OPTION_IGNORE_INVALID_LINES);
// keep the parsed object in memory for later processing
if ($vobj->name == 'VCALENDAR') {
self::$parsed_vcalendar = $vobj;
foreach ($vobj->getBaseComponents() ?: $vobj->getComponents() as $vevent) {
if ($vevent->name == 'VEVENT' || $vevent->name == 'VTODO') {
self::$parsed_vevent = $vevent;
break;
}
}
}
}
}
catch (VObject\ParseException $e) {
throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
}
if ($vobj->name !== 'VCALENDAR') {
throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
}
$sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
// Get the Supported Components for the target calendar
list($parentPath) = URLUtil::splitPath($path);
$calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
if (isset($calendarProperties[$sCCS])) {
$supportedComponents = $calendarProperties[$sCCS]->getValue();
}
else {
$supportedComponents = ['VTODO', 'VEVENT'];
}
$foundType = null;
$foundUID = null;
foreach($vobj->getComponents() as $component) {
switch($component->name) {
case 'VTIMEZONE':
continue 2;
case 'VEVENT':
case 'VTODO':
case 'VJOURNAL':
if (is_null($foundType)) {
$foundType = $component->name;
if (!in_array($foundType, $supportedComponents)) {
throw new CalDAV\Exception\InvalidComponentType('This resource only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType);
}
if (!isset($component->UID)) {
throw new DAV\Exception\BadRequest('Every ' . $component->name . ' component must have an UID');
}
$foundUID = (string)$component->UID;
} else {
if ($foundType !== $component->name) {
throw new DAV\Exception\BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType);
}
if ($foundUID !== (string)$component->UID) {
throw new DAV\Exception\BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
}
}
break;
default:
throw new DAV\Exception\BadRequest('You are not allowed to create components of type: ' . $component->name . ' here');
}
}
if (!$foundType)
throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
// We use an extra variable to allow event handles to tell us wether
// the object was modified or not.
//
// This helps us determine if we need to re-serialize the object.
$subModified = false;
$this->server->emit(
'calendarObjectChange',
[
$request,
$response,
$vobj,
$parentPath,
&$subModified,
$isNew
]
);
if ($subModified) {
// An event handler told us that it modified the object.
$data = $vobj->serialize();
// Using md5 to figure out if there was an *actual* change.
if (!$modified && $before !== md5($data)) {
$modified = true;
}
}
}
/**
* Returns a list of features for the DAV: HTTP header.
* Including 'calendar-schedule' to enable scheduling support in Thunderbird Lightning.
*
* @return array
*/
public function getFeatures()
{
$features = parent::getFeatures();
$features[] = 'calendar-schedule';
return $features;
}
/**
* PropFind
*
* This method handler is invoked before any after properties for a
* resource are fetched. This allows us to add in any CalDAV specific
* properties.
*
* @param DAV\PropFind $propFind
* @param DAV\INode $node
* @return void
*/
function propFind(DAV\PropFind $propFind, DAV\INode $node)
{
- if ($node instanceof DAV\SimpleCollection) {
- $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() {
- return new DAV\Property\Href($this->getCalendarHomeForPrincipal(HTTPBasic::$current_user) . '/');
- });
- }
+ $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() {
+ return new DAV\Property\Href($this->getCalendarHomeForPrincipal(HTTPBasic::$current_user) . '/');
+ });
parent::propFind($propFind, $node);
}
}
diff --git a/lib/Kolab/CardDAV/Plugin.php b/lib/Kolab/CardDAV/Plugin.php
index 4a5f117..7253e9e 100644
--- a/lib/Kolab/CardDAV/Plugin.php
+++ b/lib/Kolab/CardDAV/Plugin.php
@@ -1,257 +1,255 @@
<?php
/**
* Extended CardDAV plugin for the Kolab DAV server
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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/>.
*/
namespace Kolab\CardDAV;
use Sabre\DAV;
use Sabre\DAVACL;
use Sabre\CardDAV;
use Sabre\VObject;
use Kolab\DAV\Auth\HTTPBasic;
/**
* Extended CardDAV plugin to tweak data validation
*/
class Plugin extends CardDAV\Plugin
{
// make already parsed vcard blocks available for later use
public static $parsed_vcard;
// allow the backend to force a redirect Location
public static $redirect_basename;
// vcard version requested by the connecting client
public static $vcard_version = 'vcard3';
/**
* Initializes the plugin
*
* @param DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server)
{
parent::initialize($server);
$server->on('beforeMethod', array($this, 'beforeMethod'), 0);
$server->on('afterCreateFile', array($this, 'afterWriteContent'));
$server->on('afterWriteContent', array($this, 'afterWriteContent'));
}
/**
* Adds all CardDAV-specific properties
*
* @param DAV\PropFind $propFind
* @param DAV\INode $node
* @return void
*/
public function propFindEarly(DAV\PropFind $propFind, DAV\INode $node)
{
// publish global ldap address book for this principal
if ($node instanceof DAVACL\IPrincipal && empty($this->directories) && \rcube::get_instance()->config->get('kolabdav_ldap_directory')) {
$this->directories[] = self::ADDRESSBOOK_ROOT . '/' . $node->getName() . '/' . LDAPDirectory::DIRECTORY_NAME;
}
- if ($node instanceof DAV\SimpleCollection) {
- $propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() {
- return new DAV\Property\Href($this->getAddressBookHomeForPrincipal(HTTPBasic::$current_user) . '/');
- });
- }
+ $propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() {
+ return new DAV\Property\Href($this->getAddressBookHomeForPrincipal(HTTPBasic::$current_user) . '/');
+ });
parent::propFindEarly($propFind, $node);
}
/**
* Handler for beforeMethod events
*/
public function beforeMethod($request, $response)
{
$method = $request->getMethod();
if ($method == 'PUT' && $request->getHeader('If-None-Match') == '*') {
// In-None-Match: * is only valid with PUT requests creating a new resource.
// SOGo Conenctor for Thunderbird also sends it with update requests which then fail
// in the Server::checkPreconditions().
// See https://issues.kolab.org/show_bug.cgi?id=2589 and http://www.sogo.nu/bugs/view.php?id=1624
// This is a work-around for the buggy SOGo connector and should be removed once fixed.
if (strpos($request->getHeader('User-Agent'), 'Thunderbird/') > 0) {
unset($_SERVER['HTTP_IF_NONE_MATCH']);
}
}
else if ($method == 'GET' && ($accept = $request->getHeader('Accept'))) {
// determine requested vcard version from Accept: header
self::$vcard_version = parent::negotiateVCard($accept);
}
}
/**
* Inject some additional HTTP response headers
*/
public function afterWriteContent($uri, $node)
{
// send Location: header to corrected URI
if (self::$redirect_basename) {
$path = explode('/', $uri);
array_pop($path);
array_push($path, self::$redirect_basename);
$this->server->httpResponse->setHeader('Location', $this->server->getBaseUri() . join('/', array_map('urlencode', $path)));
self::$redirect_basename = null;
}
}
/**
* Checks if the submitted iCalendar data is in fact, valid.
*
* An exception is thrown if it's not.
*
* @param resource|string $data
* @param bool $modified Should be set to true, if this event handler
* changed &$data.
* @return void
*/
protected function validateVCard(&$data, &$modified)
{
// If it's a stream, we convert it to a string first.
if (is_resource($data)) {
$data = stream_get_contents($data);
}
$before = md5($data);
// Converting the data to unicode, if needed.
$data = DAV\StringUtil::ensureUTF8($data);
if (md5($data) !== $before) $modified = true;
try {
$vobj = VObject\Reader::read($data, VObject\Reader::OPTION_FORGIVING | VObject\Reader::OPTION_IGNORE_INVALID_LINES);
if ($vobj->name == 'VCARD') {
$this->parsed_vcard = $vobj;
}
}
catch (VObject\ParseException $e) {
throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage());
}
if ($vobj->name !== 'VCARD') {
throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
}
if (!isset($vobj->UID)) {
throw new DAV\Exception\BadRequest('Every vcard must have a UID.');
}
}
/**
* Wrapper for Plugin::negotiateVCard() to store the requested vcard version for the backend
*/
protected function negotiateVCard($input, &$mimeType = null)
{
self::$vcard_version = parent::negotiateVCard($input, $mimeType);
return self::$vcard_version;
}
/**
* Converts a vcard blob to a different version, or jcard.
*
* (optimized version that skips parsing and re-serialization if possible)
*
* @param string $data
* @param string $target
* @return string
*/
protected function convertVCard($data, $target)
{
$version = 'vcard3';
if (is_string($data) && preg_match('/VERSION:(\d)/', $data, $m)) {
$version = 'vcard' . $m[1];
}
// no conversion needed
if ($target == $version) {
return $data;
}
return parent::convertVCard($data, $target);
}
/**
* This function handles the addressbook-query REPORT
*
* This report is used by the client to filter an addressbook based on a
* complex query.
*
* @param \DOMNode $dom
* @return void
*/
protected function addressbookQueryReport($dom)
{
$node = $this->server->tree->getNodeForPath(($uri = $this->server->getRequestUri()));
console(__METHOD__, $uri);
// fix some bogus parameters in queries sent by the SOGo connector.
// issue submitted in http://www.sogo.nu/bugs/view.php?id=2655
$xpath = new \DOMXPath($dom);
$xpath->registerNameSpace('card', Plugin::NS_CARDDAV);
$filters = $xpath->query('/card:addressbook-query/card:filter');
if ($filters->length === 1) {
$filter = $filters->item(0);
$propFilters = $xpath->query('card:prop-filter', $filter);
for ($ii=0; $ii < $propFilters->length; $ii++) {
$propFilter = $propFilters->item($ii);
$name = $propFilter->getAttribute('name');
// attribute 'mail' => EMAIL
if ($name == 'mail') {
$propFilter->setAttribute('name', 'EMAIL');
}
$textMatches = $xpath->query('card:text-match', $propFilter);
for ($jj=0; $jj < $textMatches->length; $jj++) {
$textMatch = $textMatches->item($jj);
$collation = $textMatch->getAttribute('collation');
// 'i;unicasemap' is a non-standard collation
if ($collation == 'i;unicasemap') {
$textMatch->setAttribute('collation', 'i;unicode-casemap');
}
}
}
}
// query on LDAP node: pass along filter query
if ($node instanceof LDAPDirectory) {
$query = new CardDAV\AddressBookQueryParser($dom);
$query->parse();
// set query and ...
$node->setAddressbookQuery($query);
}
// ... proceed with default action
parent::addressbookQueryReport($dom);
}
}
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 22, 7:26 AM (20 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
80073
Default Alt Text
(18 KB)

Event Timeline