Merge pull request #57 from CatoTH/master
New Addon: Calendar with CalDAV support
This commit is contained in:
commit
915145fc27
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
class Sabre_DAVACL_Plugin_Friendica extends Sabre_DAVACL_Plugin {
|
||||
|
||||
/*
|
||||
* A dirty hack to make iOS CalDAV work with subdirectorys.
|
||||
* When using a Root URL like /dav/ (as it is necessary for friendica), iOS does not evaluate the current-user-principal property,
|
||||
* but only principal-URL. Actually principal-URL is not allowed in /dav/, only for Principal collections, but this seems
|
||||
* to be the only way to force iOS to look at the right location.
|
||||
*/
|
||||
|
||||
public function beforeGetProperties($uri, Sabre_DAV_INode $node, &$requestedProperties, &$returnedProperties) {
|
||||
|
||||
parent::beforeGetProperties($uri, $node, $requestedProperties, $returnedProperties);
|
||||
|
||||
if (false !== ($index = array_search('{DAV:}principal-URL', $requestedProperties))) {
|
||||
|
||||
unset($requestedProperties[$index]);
|
||||
$returnedProperties[200]['{DAV:}principal-URL'] = new Sabre_DAV_Property_Href('principals/users/' . strtolower($_SERVER["PHP_AUTH_USER"]) . '/');
|
||||
|
||||
}
|
||||
if (false !== ($index = array_search('{urn:ietf:params:xml:ns:caldav}calendar-home-set', $requestedProperties))) {
|
||||
|
||||
unset($requestedProperties[$index]);
|
||||
$returnedProperties[200]['{urn:ietf:params:xml:ns:caldav}calendar-home-set'] = new Sabre_DAV_Property_Href('calendars/' . strtolower($_SERVER["PHP_AUTH_USER"]) . '/');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
Calendar with CalDAV Support
|
||||
|
||||
This is a rewrite of the calendar system used by the german social network [Animexx](http://www.animexx.de/).
|
||||
It's still in a very early stage, so expect major bugs. Please feel free to report any of them, by mail (cato@animexx.de) or Friendica: http://friendica.hoessl.eu/profile/cato
|
||||
|
||||
At the moment, the calendar system supports the following features:
|
||||
- A web-based drag&drop interface for managing events
|
||||
- All-Day-Events, Multi-Day-Events, and time-based events
|
||||
- Access to the events using CalDAV (using iPhone, Thunderbird Lightning etc., see below)
|
||||
- read-only access to the friendica-native events (also using CalDAV)
|
||||
- The friendica-contacts are made available using CardDAV (confirmed to work with iOS)
|
||||
- Giving the subject, a description, a location and a color for the event (the color is not available through CalDAV, though)
|
||||
|
||||
Internationalization:
|
||||
- At the moment, settings for the US and the german systems are selectable (regarding the date format and the first day of the week). More will be added on request.
|
||||
- The basic design of the system is aware of timezones; however this is not reflected in the UI yet. It currently assumes that the timezone set in the friendica-installation matches the user's local time and matches the local time set in the user's operating system.
|
||||
|
||||
CalDAV device compatibility:
|
||||
- iOS (iPhone/iPodTouch) works
|
||||
- Thunderbird Lightning should work, not tested yet
|
||||
- Android: http://dmfs.org/caldav/ seems to work, not much tested yet, though
|
||||
|
||||
Installation
|
||||
After activating, serveral tables in the database have to be created. The admin-interface of the plugin will try to do this automatically.
|
||||
In case of errors, the SQL-statement to create the tables manually are shown in the admin-interface.
|
||||
|
||||
|
||||
Functuality missing: (a.k.a. "Roadmap")
|
||||
- Recurrence of events (this is only supported using the CalDAV-interface; recurring events saved using CalDAV will appear correctly multiple times in the web-based frontend; hovever those events will be read-only at the web-based frondend)
|
||||
- Sharing events; all events are private at the moment, therefore this system is not yet a complete replacement for the friendica-native events
|
||||
- Attendees / Collaboration
|
||||
|
||||
|
||||
|
||||
Used libraries
|
||||
|
||||
SabreDAV
|
||||
http://code.google.com/p/sabredav/
|
||||
New BSD License
|
||||
|
||||
wdCalendar
|
||||
http://www.web-delicious.com/jquery-plugins/
|
||||
GNU Lesser General Public License
|
||||
|
||||
jQueryUI
|
||||
http://jqueryui.com/
|
||||
Dual-licenced: MIT and GPL licenses
|
||||
|
||||
iCalCreator
|
||||
http://kigkonsult.se/iCalcreator/
|
||||
GNU Lesser General Public License
|
||||
|
||||
TimePicker
|
||||
http://www.texotela.co.uk/code/jquery/timepicker/
|
||||
Dual-licenced: MIT and GPL licenses
|
||||
|
||||
ColorPicker
|
||||
http://laktek.com/2008/10/27/really-simple-color-picker-in-jquery/
|
||||
MIT License
|
||||
|
||||
|
||||
|
||||
Author of this plugin (the parts that are not part of the libraries above):
|
||||
Tobias Hößl
|
||||
http://friendica.hoessl.eu/profile/cato
|
||||
http://www.hoessl.eu/
|
||||
tobias@hoessl.eu
|
||||
@TobiasHoessl
|
||||
|
||||
Originally developed for:
|
||||
Animexx e.V. / http://www.animexx.de/
|
|
@ -0,0 +1,879 @@
|
|||
1.7.0-alpha (2012-??-??)
|
||||
* BC Break: The calendarobjects database table has a bunch of new fields,
|
||||
and a migration script is required to ensure everything will keep
|
||||
working. Read the wiki for more details.
|
||||
* BC Break: The iCalendar interface now has a new method: calendarQuery.
|
||||
* BC Break: In this version a number of classes have been deleted, that
|
||||
have been previously deprecated. Namely:
|
||||
- Sabre_DAV_Directory (now: Sabre_DAV_Collection)
|
||||
- Sabre_DAV_SimpleDirectory (now: Sabre_DAV_SimpleCollection)
|
||||
- Sabre_VObject_Element_DateTime (now: Sabre_VObject_Property_DateTime)
|
||||
- Sabre_VObject_Element_MultiDateTime (now .._Property_MultiDateTime)
|
||||
* BC Break: Sabre_CalDAV_Schedule_IMip::sendMessage now has an extra
|
||||
argument. If you extended this class, you should fix this method. It's
|
||||
only used for informational purposes.
|
||||
* Changed: Responsibility for dealing with the calendar-query is now moved
|
||||
from the CalDAV plugin to the CalDAV backends. This allows for heavy
|
||||
optimizations.
|
||||
* Changed: The CalDAV PDO backend is now a lot faster for common calendar
|
||||
queries.
|
||||
* Fixed: Marking both the text/calendar and text/x-vcard as UTF-8 encoded.
|
||||
* Fixed: Workaround for the SOGO connector, as it doesn't understand
|
||||
receiving "text/x-vcard; charset=utf-8" for a contenttype.
|
||||
* Added: Sabre_DAV_Client now throws more specific exceptions in cases
|
||||
where we already has an exception class.
|
||||
* Added: Sabre_DAV_PartialUpdate. This plugin allows you to use the PATCH
|
||||
method to update parts of a file.
|
||||
|
||||
1.6.3-stable (2012-??-??)
|
||||
* Added: It's now possible to specify in Sabre_DAV_Client which type of
|
||||
authentication is to be used.
|
||||
* Fixed: Issue 206: Sabre_DAV_Client PUT requests are fixed.
|
||||
* Fixed: Issue 205: Parsing an iCalendar 0-second date interval.
|
||||
* Fixed: Issue 112: Stronger validation of iCalendar objects. Now making
|
||||
sure every iCalendar object only contains 1 component, and disallowing
|
||||
vcards, forcing every component to have a UID.
|
||||
* Fixed: Basic validation for vcards in the CardDAV plugin.
|
||||
* Fixed: Issue 213: Workaround for an Evolution bug, that prevented it
|
||||
from updating events.
|
||||
* Fixed: Issue 211: A time-limit query on a non-relative alarm trigger in
|
||||
a recurring event could result in an endless loop.
|
||||
* Fixed: All uri fields are now a maximum of 200 characters. The Bynari
|
||||
outlook plugin used much longer strings so this should improve
|
||||
compatibility.
|
||||
* Fixed: Added a workaround for a bug in KDE 4.8.2 contact syncing. See
|
||||
https://bugs.kde.org/show_bug.cgi?id=300047
|
||||
|
||||
1.6.2-stable (2012-04-16)
|
||||
* Fixed: Sabre_VObject_Node::$parent should have been public.
|
||||
* Fixed: Recurrence rules of events are now taken into consideration when
|
||||
doing time-range queries on alarms.
|
||||
* Fixed: Added a workaround for the fact that php's DateInterval cannot
|
||||
parse weeks and days at the same time.
|
||||
* Added: Sabre_DAV_Server::$exposeVersion, allowing you to hide SabreDAV's
|
||||
version number from various outputs.
|
||||
* Fixed: DTSTART values would be incorrect when expanding events.
|
||||
* Fixed: DTSTART and DTEND would be incorrect for expansion of WEEKLY
|
||||
BYDAY recurrences.
|
||||
* Fixed: Issue 203: A problem with overridden events hitting the exact
|
||||
date and time of a subsequent event in the recurrence set.
|
||||
* Fixed: There was a problem with recurrence rules, for example the 5th
|
||||
tuesday of the month, if this day did not exist.
|
||||
* Added: New HTTP status codes from draft-nottingham-http-new-status-04.
|
||||
|
||||
1.6.1-stable (2012-03-05)
|
||||
* Added: createFile and put() can now return an ETag.
|
||||
* Added: Sending back an ETag on for operations on CardDAV backends. This
|
||||
should help with OS X 10.6 Addressbook compatibility.
|
||||
* Fixed: Fixed a bug where an infinite loop could occur in the recurrence
|
||||
iterator if the recurrence was YEARLY, with a BYMONTH rule, and either
|
||||
BYDAY or BYMONTHDAY match the first day of the month.
|
||||
* Fixed: Events that are excluded using EXDATE are still counted in the
|
||||
COUNT= parameter in the RRULE property.
|
||||
* Added: Support for time-range filters on VALARM components.
|
||||
* Fixed: Correctly filtering all-day events.
|
||||
* Fixed: Sending back correct mimetypes from the browser plugin (thanks
|
||||
Jürgen).
|
||||
* Fixed: Issue 195: Sabre_CardDAV pear package had an incorrect dependency.
|
||||
* Fixed: Calendardata would be destroyed when performing a MOVE request.
|
||||
|
||||
1.6.0-stable (2012-02-22)
|
||||
* BC Break: Now requires PHP 5.3
|
||||
* BC Break: Any node that implemented Sabre_DAVACL_IACL must now also
|
||||
implement the getSupportedPrivilegeSet method. See website for details.
|
||||
* BC Break: Moved functions from Sabre_CalDAV_XMLUtil to
|
||||
Sabre_VObject_DateTimeParser.
|
||||
* BC Break: The Sabre_DAVACL_IPrincipalCollection now has two new methods:
|
||||
'searchPrincipals' and 'updatePrincipal'.
|
||||
* BC Break: Sabre_DAV_ILockable is removed and all related per-node
|
||||
locking functionality.
|
||||
* BC Break: Sabre_DAV_Exception_FileNotFound is now deprecated in favor of
|
||||
Sabre_DAV_Exception_NotFound. The former will be removed in a later
|
||||
version.
|
||||
* BC Break: Removed Sabre_CalDAV_ICalendarUtil, use Sabre_VObject instead.
|
||||
* BC Break: Sabre_CalDAV_Server is now deprecated, check out the
|
||||
documentation on how to setup a caldav server with just
|
||||
Sabre_DAV_Server.
|
||||
* BC Break: Default Principals PDO backend now needs a new field in the
|
||||
'principals' table. See the website for details.
|
||||
* Added: Ability to create new calendars and addressbooks from within the
|
||||
browser plugin.
|
||||
* Added: Browser plugin: icons for various nodes.
|
||||
* Added: Support for FREEBUSY reports!
|
||||
* Added: Support for creating principals with admin-level privileges.
|
||||
* Added: Possibility to let server send out invitation emails on behalf of
|
||||
CalDAV client, using Sabre_CalDAV_Schedule_IMip.
|
||||
* Changed: beforeCreateFile event now passes data argument by reference.
|
||||
* Changed: The 'propertyMap' property from Sabre_VObject_Reader, must now
|
||||
be specified in Sabre_VObject_Property::$classMap.
|
||||
* Added: Ability for plugins to tell the ACL plugin which principal
|
||||
plugins are searchable.
|
||||
* Added: [DAVACL] Per-node overriding of supported privileges. This allows
|
||||
for custom privileges where needed.
|
||||
* Added: [DAVACL] Public 'principalSearch' method on the DAVACL plugin,
|
||||
which allows for easy searching for principals, based on their
|
||||
properties.
|
||||
* Added: Sabre_VObject_Component::getComponents() to return a list of only
|
||||
components and not properties.
|
||||
* Added: An includes.php file in every sub-package (CalDAV, CardDAV, DAV,
|
||||
DAVACL, HTTP, VObject) as an alternative to the autoloader. This often
|
||||
works much faster.
|
||||
* Added: Support for the 'Me card', which allows Addressbook.app users
|
||||
specify which vcard is their own.
|
||||
* Added: Support for updating principal properties in the DAVACL principal
|
||||
backends.
|
||||
* Changed: Major refactoring in the calendar-query REPORT code. Should
|
||||
make things more flexible and correct.
|
||||
* Changed: The calendar-proxy-[read|write] principals will now only appear
|
||||
in the tree, if they actually exist in the Principal backend. This should
|
||||
reduce some problems people have been having with this.
|
||||
* Changed: Sabre_VObject_Element_* classes are now renamed to
|
||||
Sabre_VObject_Property. Old classes are retained for backwards
|
||||
compatibility, but this will be removed in the future.
|
||||
* Added: Sabre_VObject_FreeBusyGenerator to generate free-busy reports
|
||||
based on lists of events.
|
||||
* Added: Sabre_VObject_RecurrenceIterator to find all the dates and times
|
||||
for recurring events.
|
||||
* Fixed: Issue 97: Correctly handling RRULE for the calendar-query REPORT.
|
||||
* Fixed: Issue 154: Encoding of VObject parameters with no value was
|
||||
incorrect.
|
||||
* Added: Support for {DAV:}acl-restrictions property from RFC3744.
|
||||
* Added: The contentlength for calendar objects can now be supplied by a
|
||||
CalDAV backend, allowing for more optimizations.
|
||||
* Fixed: Much faster implementation of Sabre_DAV_URLUtil::encodePath.
|
||||
* Fixed: {DAV:}getcontentlength may now be not specified.
|
||||
* Fixed: Issue 66: Using rawurldecode instead of urldecode to decode paths
|
||||
from clients. This means that + will now be treated as a literal rather
|
||||
than a space, and this should improve compatibility with the Windows
|
||||
built-in client.
|
||||
* Added: Sabre_DAV_Exception_PaymentRequired exception, to emit HTTP 402
|
||||
status codes.
|
||||
* Added: Some mysql unique constraints to example files.
|
||||
* Fixed: Correctly formatting HTTP dates.
|
||||
* Fixed: Issue 94: Sending back Last-Modified header for 304 responses.
|
||||
* Added: Sabre_VObject_Component_VEvent, Sabre_VObject_Component_VJournal,
|
||||
Sabre_VObject_Component_VTodo and Sabre_VObject_Component_VCalendar.
|
||||
* Changed: Properties are now also automatically mapped to their
|
||||
appropriate classes, if they are created using the add() or __set()
|
||||
methods.
|
||||
* Changed: Cloning VObject objects now clones the entire tree, rather than
|
||||
just the default shallow copy.
|
||||
* Added: Support for recurrence expansion in the CALDAV:calendar-multiget
|
||||
and CALDAV:calendar-query REPORTS.
|
||||
* Changed: CalDAV PDO backend now sorts calendars based on the internal
|
||||
'calendarorder' field.
|
||||
* Added: Issue 181: Carddav backends may no optionally not supply the carddata in
|
||||
getCards, if etag and size are specified. This may speed up certain
|
||||
requests.
|
||||
* Added: More arguments to beforeWriteContent and beforeCreateFile (see
|
||||
WritingPlugins wiki document).
|
||||
* Added: Hook for iCalendar validation. This allows us to validate
|
||||
iCalendar objects when they're uploaded. At the moment we're just
|
||||
validating syntax.
|
||||
* Added: VObject now support Windows Timezone names correctly (thanks
|
||||
mrpace2).
|
||||
* Added: If a timezonename could not be detected, we fall back on the
|
||||
default PHP timezone.
|
||||
* Added: Now a Composer package (thanks willdurand).
|
||||
* Fixed: Support for \N as a newline character in the VObject reader.
|
||||
* Added: afterWriteContent, afterCreateFile and afterUnbind events.
|
||||
* Added: Postgresql example files. Not part of the unittests though, so
|
||||
use at your own risk.
|
||||
* Fixed: Issue 182: Removed backticks from sql queries, so it will work
|
||||
with Postgres.
|
||||
|
||||
1.5.9-stable (2012-04-16)
|
||||
* Fixed: Issue with parsing timezone identifiers that were surrounded by
|
||||
quotes. (Fixes emClient compatibility).
|
||||
|
||||
1.5.8-stable (2012-02-22)
|
||||
* Fixed: Issue 95: Another timezone parsing issue, this time in
|
||||
calendar-query.
|
||||
|
||||
1.5.7-stable (2012-02-19)
|
||||
* Fixed: VObject properties are now always encoded before components.
|
||||
* Fixed: Sabre_DAVACL had issues with multiple levels of privilege
|
||||
aggregration.
|
||||
* Changed: Added 'GuessContentType' plugin to fileserver.php example.
|
||||
* Fixed: The Browser plugin will now trigger the correct events when
|
||||
creating files.
|
||||
* Fixed: The ICSExportPlugin now considers ACL's.
|
||||
* Added: Made it optional to supply carddata from an Addressbook backend
|
||||
when requesting getCards. This can make some operations much faster, and
|
||||
could result in much lower memory use.
|
||||
* Fixed: Issue 187: Sabre_DAV_UUIDUtil was missing from includes file.
|
||||
* Fixed: Issue 191: beforeUnlock was triggered twice.
|
||||
|
||||
1.5.6-stable (2012-01-07)
|
||||
* Fixed: Issue 174: VObject could break UTF-8 characters.
|
||||
* Fixed: pear package installation issues.
|
||||
|
||||
1.5.5-stable (2011-12-16)
|
||||
* Fixed: CalDAV time-range filter workaround for recurring events.
|
||||
* Fixed: Bug in Sabre_DAV_Locks_Backend_File that didn't allow multiple
|
||||
files to be locked at the same time.
|
||||
|
||||
1.5.4-stable (2011-10-28)
|
||||
* Fixed: GuessContentType plugin now supports mixed case file extensions.
|
||||
* Fixed: DATE-TIME encoding was wrong in VObject. (we used 'DATETIME').
|
||||
* Changed: Sending back HTTP 204 after a PUT request on an existing resource
|
||||
instead of HTTP 200. This should fix Evolution CardDAV client
|
||||
compatibility.
|
||||
* Fixed: Issue 95: Parsing X-LIC-LOCATION if it's available.
|
||||
* Added: All VObject elements now have a reference to their parent node.
|
||||
|
||||
1.5.3-stable (2011-09-28)
|
||||
* Fixed: Sabre_DAV_Collection was missing from the includes file.
|
||||
* Fixed: Issue 152. iOS 1.4.2 apparantly requires HTTP/1.1 200 OK to be in
|
||||
uppercase.
|
||||
* Fixed: Issue 153: Support for files with mixed newline styles in
|
||||
Sabre_VObject.
|
||||
* Fixed: Issue 159: Automatically converting any vcard and icalendardata
|
||||
to UTF-8.
|
||||
* Added: Sabre_DAV_SimpleFile class for easy static file creation.
|
||||
* Added: Issue 158: Support for the CARDDAV:supported-address-data
|
||||
property.
|
||||
|
||||
1.5.2-stable (2011-09-21)
|
||||
* Fixed: carddata and calendardata MySQL fields are now of type
|
||||
'mediumblob'. 'TEXT' was too small sometimes to hold all the data.
|
||||
* Fixed: {DAV:}supported-report-set is now correctly reporting the reports
|
||||
for IAddressBook.
|
||||
* Added: Sabre_VObject_Property::add() to add duplicate parameters to
|
||||
properties.
|
||||
* Added: Issue 151: Sabre_CalDAV_ICalendar and Sabre_CalDAV_ICalendarObject
|
||||
interfaces.
|
||||
* Fixed: Issue 140: Not returning 201 Created if an event cancelled the
|
||||
creation of a file.
|
||||
* Fixed: Issue 150: Faster URLUtil::encodePath() implementation.
|
||||
* Fixed: Issue 144: Browser plugin could interfere with
|
||||
TemporaryFileFilterPlugin if it was loaded first.
|
||||
* Added: It's not possible to specify more 'alternate uris' in principal
|
||||
backends.
|
||||
|
||||
1.5.1-stable (2011-08-24)
|
||||
* Fixed: Issue 137. Hiding action interface in HTML browser for
|
||||
non-collections.
|
||||
* Fixed: addressbook-query is now correctly returned from the
|
||||
{DAV:}supported-report-set property.
|
||||
* Fixed: Issue 142: Bugs in groupwareserver.php example.
|
||||
* Fixed: Issue 139: Rejecting PUT requests with Content-Range.
|
||||
|
||||
1.5.0-stable (2011-08-12)
|
||||
* Added: CardDAV support.
|
||||
* Added: An experimental WebDAV client.
|
||||
* Added: MIME-Directory grouping support in the VObject library. This is
|
||||
very useful for people attempting to parse vcards.
|
||||
* BC Break: Adding parameters with the VObject libraries now overwrites
|
||||
the previous parameter, rather than just add it. This makes more sense
|
||||
for 99% of the cases.
|
||||
* BC Break: lib/Sabre.autoload.php is now removed in favor of
|
||||
lib/Sabre/autoload.php.
|
||||
* Deprecated: Sabre_DAV_Directory is now deprecated and will be removed in
|
||||
a future version. Use Sabre_DAV_Collection instead.
|
||||
* Deprecated: Sabre_DAV_SimpleDirectory is now deprecated and will be
|
||||
removed in a future version. Use Sabre_DAV_SimpleCollection instead.
|
||||
* Fixed: Problem with overriding tablenames for the CalDAV backend.
|
||||
* Added: Clark-notation parser to XML utility.
|
||||
* Added: unset() support to VObject components.
|
||||
* Fixed: Refactored CalDAV property fetching to be faster and simpler.
|
||||
* Added: Central string-matcher for CalDAV and CardDAV plugins.
|
||||
* Added: i;unicode-casemap support
|
||||
* Fixed: VObject bug: wouldn't parse parameters if they weren't specified
|
||||
in uppercase.
|
||||
* Fixed: VObject bug: Parameters now behave more like Properties.
|
||||
* Fixed: VObject bug: Parameters with no value are now correctly parsed.
|
||||
* Changed: If calendars don't specify which components they allow, 'all'
|
||||
components are assumed (e.g.: VEVENT, VTODO, VJOURNAL).
|
||||
* Changed: Browser plugin now uses POST variable 'sabreAction' instead of
|
||||
'action' to reduce the chance of collisions.
|
||||
|
||||
1.4.4-stable (2011-07-07)
|
||||
* Fixed: Issue 131: Custom CalDAV backends could break in certain cases.
|
||||
* Added: The option to override the default tablename all PDO backends
|
||||
use. (Issue 60).
|
||||
* Fixed: Issue 124: 'File' authentication backend now takes realm into
|
||||
consideration.
|
||||
* Fixed: Sabre_DAV_Property_HrefList now properly deserializes. This
|
||||
allows users to update the {DAV:}group-member-set property.
|
||||
* Added: Helper functions for DateTime-values in Sabre_VObject package.
|
||||
* Added: VObject library can now automatically map iCalendar properties to
|
||||
custom classes.
|
||||
|
||||
1.4.3-stable (2011-04-25)
|
||||
* Fixed: Issue 123: Added workaround for Windows 7 UNLOCK bug.
|
||||
* Fixed: datatype of lastmodified field in mysql.calendars.sql. Please
|
||||
change the DATETIME field to an INT to ensure this field will work
|
||||
correctly.
|
||||
* Change: Sabre_DAV_Property_Principal is now renamed to
|
||||
Sabre_DAVACL_Property_Principal.
|
||||
* Added: API level support for ACL HTTP method.
|
||||
* Fixed: Bug in serializing {DAV:}acl property.
|
||||
* Added: deserializer for {DAV:}resourcetype property.
|
||||
* Added: deserializer for {DAV:}acl property.
|
||||
* Added: deserializer for {DAV:}principal property.
|
||||
|
||||
1.4.2-beta (2011-04-01)
|
||||
* Added: It's not possible to disable listing of nodes that are denied
|
||||
read access by ACL.
|
||||
* Fixed: Changed a few properties in CalDAV classes from private to
|
||||
protected.
|
||||
* Fixed: Issue 119: Terrible things could happen when relying on
|
||||
guessBaseUri, the server was running on the root of the domain and a user
|
||||
tried to access a file ending in .php. This is a slight BC break.
|
||||
* Fixed: Issue 118: Lock tokens in If headers without a uri should be
|
||||
treated as the request uri, not 'all relevant uri's.
|
||||
* Fixed: Issue 120: PDO backend was incorrectly fetching too much locks in
|
||||
cases where there were similar named locked files in a directory.
|
||||
|
||||
1.4.1-beta (2011-02-26)
|
||||
* Fixed: Sabre_DAV_Locks_Backend_PDO returned too many locks.
|
||||
* Fixed: Sabre_HTTP_Request::getHeader didn't return Content-Type when
|
||||
running on apache, so a few workarounds were added.
|
||||
* Change: Slightly changed CalDAV Backend API's, to allow for heavy
|
||||
optimizations. This is non-bc breaking.
|
||||
|
||||
1.4.0-beta (2011-02-12)
|
||||
* Added: Partly RFC3744 ACL support.
|
||||
* Added: Calendar-delegation (caldav-proxy) support.
|
||||
* BC break: In order to fix Issue 99, a new argument had to be added to
|
||||
Sabre_DAV_Locks_Backend_*::getLocks classes. Consult the classes for
|
||||
details.
|
||||
* Deprecated: Sabre_DAV_Locks_Backend_FS is now deprecated and will be
|
||||
removed in a later version. Use PDO or the new File class instead.
|
||||
* Deprecated: The Sabre_CalDAV_ICalendarUtil class is now marked
|
||||
deprecated, and will be removed in a future version. Please use
|
||||
Sabre_VObject instead.
|
||||
* Removed: All principal-related functionality has been removed from the
|
||||
Sabre_DAV_Auth_Plugin, and moved to the Sabre_DAVACL_Plugin.
|
||||
* Added: VObject library, for easy vcard/icalendar parsing using a natural
|
||||
interface.
|
||||
* Added: Ability to automatically generate full .ics feeds off calendars.
|
||||
To use: Add the Sabre_CalDAV_ICSExportPlugin, and add ?export to your
|
||||
calendar url.
|
||||
* Added: Plugins can now specify a pluginname, for easy access using
|
||||
Sabre_DAV_Server::getPlugin().
|
||||
* Added: beforeGetProperties event.
|
||||
* Added: updateProperties event.
|
||||
* Added: Principal listings and calendar-access can now be done privately,
|
||||
disallowing users from accessing or modifying other users' data.
|
||||
* Added: You can now pass arrays to the Sabre_DAV_Server constructor. If
|
||||
it's an array with node-objects, a Root collection will automatically be
|
||||
created, and the nodes are used as top-level children.
|
||||
* Added: The principal base uri is now customizable. It used to be
|
||||
hardcoded to 'principals/[user]'.
|
||||
* Added: getSupportedReportSet method in ServerPlugin class. This allows
|
||||
you to easily specify which reports you're implementing.
|
||||
* Added: A '..' link to the HTML browser.
|
||||
* Fixed: Issue 99: Locks on child elements were ignored when their parent
|
||||
nodes were deleted.
|
||||
* Fixed: Issue 90: lockdiscovery property and LOCK response now include a
|
||||
{DAV}lockroot element.
|
||||
* Fixed: Issue 96: support for 'default' collation in CalDAV text-match
|
||||
filters.
|
||||
* Fixed: Issue 102: Ensuring that copy and move with identical source and
|
||||
destination uri's fails.
|
||||
* Fixed: Issue 105: Supporting MKCALENDAR with no body.
|
||||
* Fixed: Issue 109: Small fixes in Sabre_HTTP_Util.
|
||||
* Fixed: Issue 111: Properly catching the ownername in a lock (if it's a
|
||||
string)
|
||||
* Fixed: Sabre_DAV_ObjectTree::nodeExist always returned false for the
|
||||
root node.
|
||||
* Added: Global way to easily supply new resourcetypes for certain node
|
||||
classes.
|
||||
* Fixed: Issue 59: Allowing the user to override the authentication realm
|
||||
in Sabre_CalDAV_Server.
|
||||
* Update: Issue 97: Looser time-range checking if there's a recurrence
|
||||
rule in an event. This fixes 'missing recurring events'.
|
||||
|
||||
1.3.0 (2010-10-14)
|
||||
* Added: childExists method to Sabre_DAV_ICollection. This is an api
|
||||
break, so if you implement Sabre_DAV_ICollection directly, add the method.
|
||||
* Changed: Almost all HTTP method implementations now take a uri argument,
|
||||
including events. This allows for internal rerouting of certain calls.
|
||||
If you have custom plugins, make sure they use this argument. If they
|
||||
don't, they will likely still work, but it might get in the way of
|
||||
future changes.
|
||||
* Changed: All getETag methods MUST now surround the etag with
|
||||
double-quotes. This was a mistake made in all previous SabreDAV
|
||||
versions. If you don't do this, any If-Match, If-None-Match and If:
|
||||
headers using Etags will work incorrectly. (Issue 85).
|
||||
* Added: Sabre_DAV_Auth_Backend_AbstractBasic class, which can be used to
|
||||
easily implement basic authentication.
|
||||
* Removed: Sabre_DAV_PermissionDenied class. Use Sabre_DAV_Forbidden
|
||||
instead.
|
||||
* Removed: Sabre_DAV_IDirectory interface, use Sabre_DAV_ICollection
|
||||
instead.
|
||||
* Added: Browser plugin now uses {DAV:}displayname if this property is
|
||||
available.
|
||||
* Added: Cache layer in the ObjectTree.
|
||||
* Added: Tree classes now have a delete and getChildren method.
|
||||
* Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if
|
||||
the date is an exact match.
|
||||
* Fixed: Support for multiple ETags in If-Match and If-None-Match headers.
|
||||
* Fixed: Improved baseUrl handling.
|
||||
* Fixed: Issue 67: Non-seekable stream support in ::put()/::get().
|
||||
* Fixed: Issue 65: Invalid dates are now ignored.
|
||||
* Updated: Refactoring in Sabre_CalDAV to make everything a bit more
|
||||
ledgable.
|
||||
* Fixed: Issue 88, Issue 89: Fixed compatibility for running SabreDAV on
|
||||
Windows.
|
||||
* Fixed: Issue 86: Fixed Content-Range top-boundary from 'file size' to
|
||||
'file size'-1.
|
||||
|
||||
1.2.4 (2010-07-13)
|
||||
* Fixed: Issue 62: Guessing baseUrl fails when url contains a
|
||||
query-string.
|
||||
* Added: Apache configuration sample for CGI/FastCGI setups.
|
||||
* Fixed: Issue 64: Only returning calendar-data when it was actually
|
||||
requested.
|
||||
|
||||
1.2.3 (2010-06-26)
|
||||
* Fixed: Issue 57: Supporting quotes around etags in If-Match and
|
||||
If-None-Match
|
||||
|
||||
1.2.2 (2010-06-21)
|
||||
* Updated: SabreDAV now attempts to guess the BaseURI if it's not set.
|
||||
* Updated: Better compatibility with BitKinex
|
||||
* Fixed: Issue 56: Incorrect behaviour for If-None-Match headers and GET
|
||||
requests.
|
||||
* Fixed: Issue with certain encoded paths in Browser Plugin.
|
||||
|
||||
1.2.1 (2010-06-07)
|
||||
* Fixed: Issue 50, patch by Mattijs Hoitink.
|
||||
* Fixed: Issue 51, Adding windows 7 lockfiles to TemporaryFileFilter.
|
||||
* Fixed: Issue 38, Allowing custom filters to be added to
|
||||
TemporaryFileFilter.
|
||||
* Fixed: Issue 53, ETags in the If: header were always failing. This
|
||||
behaviour is now corrected.
|
||||
* Added: Apache Authentication backend, in case authentication through
|
||||
.htaccess is desired.
|
||||
* Updated: Small improvements to example files.
|
||||
|
||||
1.2.0 (2010-05-24)
|
||||
* Fixed: Browser plugin now displays international characters.
|
||||
* Changed: More properties in CalDAV classes are now protected instead of
|
||||
private.
|
||||
|
||||
1.2.0beta3 (2010-05-14)
|
||||
* Fixed: Custom properties were not properly sent back for allprops
|
||||
requests.
|
||||
* Fixed: Issue 49, incorrect parsing of PROPPATCH, affecting Office 2007.
|
||||
* Changed: Removed CalDAV items from includes.php, and added a few missing
|
||||
ones.
|
||||
|
||||
1.2.0beta2 (2010-05-04)
|
||||
* Fixed: Issue 46: Fatal error for some non-existent nodes.
|
||||
* Updated: some example sql to include email address.
|
||||
* Added: 208 and 508 statuscodes from RFC5842.
|
||||
* Added: Apache2 configuration examples
|
||||
|
||||
1.2.0beta1 (2010-04-28)
|
||||
* Fixed: redundant namespace declaration in resourcetypes.
|
||||
* Fixed: 2 locking bugs triggered by litmus when no Sabre_DAV_ILockable
|
||||
interface is used.
|
||||
* Changed: using http://sabredav.org/ns for all custom xml properties.
|
||||
* Added: email address property to principals.
|
||||
* Updated: CalendarObject validation.
|
||||
|
||||
1.2.0alpha4 (2010-04-24)
|
||||
* Added: Support for If-Range, If-Match, If-None-Match, If-Modified-Since,
|
||||
If-Unmodified-Since.
|
||||
* Changed: Brand new build system. Functionality is split up between
|
||||
Sabre, Sabre_HTTP, Sabre_DAV and Sabre_CalDAV packages. In addition to
|
||||
that a new non-pear package will be created with all this functionality
|
||||
combined.
|
||||
* Changed: Autoloader moved to Sabre/autoload.php.
|
||||
* Changed: The Allow: header is now more accurate, with appropriate HTTP
|
||||
methods per uri.
|
||||
* Changed: Now throwing back Sabre_DAV_Exception_MethodNotAllowed on a few
|
||||
places where Sabre_DAV_Exception_NotImplemented was used.
|
||||
|
||||
1.2.0alpha3 (2010-04-20)
|
||||
* Update: Complete rewrite of property updating. Now easier to use and
|
||||
atomic.
|
||||
* Fixed: Issue 16, automatically adding trailing / to baseUri.
|
||||
* Added: text/plain is used for .txt files in GuessContentType plugin.
|
||||
* Added: support for principal-property-search and
|
||||
principal-search-property-set reports.
|
||||
* Added: Issue 31: Hiding exception information by default. Can be turned
|
||||
on with the Sabre_DAV_Server::$debugExceptions property.
|
||||
|
||||
1.2.0alpha2 (2010-04-08)
|
||||
* Added: Calendars are now private and can only be read by the owner.
|
||||
* Fixed: double namespace declaration in multistatus responses.
|
||||
* Added: MySQL database dumps. MySQL is now also supported next to SQLite.
|
||||
* Added: expand-properties REPORT from RFC 3253.
|
||||
* Added: Sabre_DAV_Property_IHref interface for properties exposing urls.
|
||||
* Added: Issue 25: Throwing error on broken Finder behaviour.
|
||||
* Changed: Authentication backend is now aware of current user.
|
||||
|
||||
1.2.0alpha1 (2010-03-31)
|
||||
* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded
|
||||
special characters.
|
||||
* Fixed: Issue 34: Incorrect Lock-Token response header for LOCK. Fixes
|
||||
Office 2010 compatibility.
|
||||
* Added: Issue 35: SabreDAV version to header to OPTIONS response to ease
|
||||
debugging.
|
||||
* Fixed: Issue 36: Incorrect variable name, throwing error in some
|
||||
requests.
|
||||
* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter.
|
||||
* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8.
|
||||
* Fixed: Issue 39 & Issue 40: Basename fails on non-utf-8 locales.
|
||||
* Added: More unittests.
|
||||
* Added: SabreDAV version to all error responses.
|
||||
* Added: URLUtil class for decoding urls.
|
||||
* Changed: Now using pear.sabredav.org pear channel.
|
||||
* Changed: Sabre_DAV_Server::getCopyAndMoveInfo is now a public method.
|
||||
|
||||
1.1.2-alpha (2010-03-18)
|
||||
* Added: RFC5397 - current-user-principal support.
|
||||
* Fixed: Issue 27: encoding entities in property responses.
|
||||
* Added: naturalselection script now allows the user to specify a 'minimum
|
||||
number of bytes' for deletion. This should reduce load due to less
|
||||
crawling
|
||||
* Added: Full support for the calendar-query report.
|
||||
* Added: More unittests.
|
||||
* Added: Support for complex property deserialization through the static
|
||||
::unserialize() method.
|
||||
* Added: Support for modifying calendar-component-set
|
||||
* Fixed: Issue 29: Added TIMEOUT_INFINITE constant
|
||||
|
||||
1.1.1-alpha (2010-03-11)
|
||||
* Added: RFC5689 - Extended MKCOL support.
|
||||
* Fixed: Evolution support for CalDAV.
|
||||
* Fixed: PDO-locks backend was pretty much completely broken. This is
|
||||
100% unittested now.
|
||||
* Added: support for ctags.
|
||||
* Fixed: Comma's between HTTP methods in 'Allow' method.
|
||||
* Changed: default argument for Sabre_DAV_Locks_Backend_FS. This means a
|
||||
datadirectory must always be specified from now on.
|
||||
* Changed: Moved Sabre_DAV_Server::parseProps to
|
||||
Sabre_DAV_XMLUtil::parseProperties.
|
||||
* Changed: Sabre_DAV_IDirectory is now Sabre_DAV_ICollection.
|
||||
* Changed: Sabre_DAV_Exception_PermissionDenied is now
|
||||
Sabre_DAV_Exception_Forbidden.
|
||||
* Changed: Sabre_CalDAV_ICalendarCollection is removed.
|
||||
* Added: Sabre_DAV_IExtendedCollection.
|
||||
* Added: Many more unittests.
|
||||
* Added: support for calendar-timezone property.
|
||||
|
||||
1.1.0-alpha (2010-03-01)
|
||||
* Note: This version is forked from version 1.0.5, so release dates may be
|
||||
out of order.
|
||||
* Added: CalDAV - RFC 4791
|
||||
* Removed: Sabre_PHP_Exception. PHP has a built-in ErrorException for
|
||||
this.
|
||||
* Added: PDO authentication backend.
|
||||
* Added: Example sql for auth, caldav, locks for sqlite.
|
||||
* Added: Sabre_DAV_Browser_GuessContentType plugin
|
||||
* Changed: Authentication plugin refactored, making it possible to
|
||||
implement non-digest authentication.
|
||||
* Fixed: Better error display in browser plugin.
|
||||
* Added: Support for {DAV:}supported-report-set
|
||||
* Added: XML utility class with helper functions for the WebDAV protocol.
|
||||
* Added: Tons of unittests
|
||||
* Added: PrincipalCollection and Principal classes
|
||||
* Added: Sabre_DAV_Server::getProperties for easy property retrieval
|
||||
* Changed: {DAV:}resourceType defaults to 0
|
||||
* Changed: Any non-null resourceType now gets a / appended to the href
|
||||
value. Before this was just for {DAV:}collection's, but this is now also
|
||||
the case for for example {DAV:}principal.
|
||||
* Changed: The Href property class can now optionally create non-relative
|
||||
uri's.
|
||||
* Changed: Sabre_HTTP_Response now returns false if headers are already
|
||||
sent and header-methods are called.
|
||||
* Fixed: Issue 19: HEAD requests on Collections
|
||||
* Fixed: Issue 21: Typo in Sabre_DAV_Property_Response
|
||||
* Fixed: Issue 18: Doesn't work with Evolution Contacts
|
||||
|
||||
1.0.15-stable (2010-05-28)
|
||||
* Added: Issue 31: Hiding exception information by default. Can be turned
|
||||
on with the Sabre_DAV_Server::$debugExceptions property.
|
||||
* Added: Moved autoload from lib/ to lib/Sabre/autoload.php. This is also
|
||||
the case in the upcoming 1.2.0, so it will improve future compatibility.
|
||||
|
||||
1.0.14-stable (2010-04-15)
|
||||
* Fixed: double namespace declaration in multistatus responses.
|
||||
|
||||
1.0.13-stable (2010-03-30)
|
||||
* Fixed: Issue 40: Last references to basename/dirname
|
||||
|
||||
1.0.12-stable (2010-03-30)
|
||||
* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter.
|
||||
* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded
|
||||
special characters.
|
||||
* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8.
|
||||
* Fixed: Issue 39: Basename fails on non-utf-8 locales.
|
||||
* Added: More unittests.
|
||||
* Added: SabreDAV version to all error responses.
|
||||
* Added: URLUtil class for decoding urls.
|
||||
* Updated: Now using pear.sabredav.org pear channel.
|
||||
|
||||
1.0.11-stable (2010-03-23)
|
||||
* Non-public release. This release is identical to 1.0.10, but it is used
|
||||
to test releasing packages to pear.sabredav.org.
|
||||
|
||||
1.0.10-stable (2010-03-22)
|
||||
* Fixed: Issue 34: Invalid Lock-Token header response.
|
||||
* Added: Issue 35: Addign SabreDAV version to HTTP OPTIONS responses.
|
||||
|
||||
1.0.9-stable (2010-03-19)
|
||||
* Fixed: Issue 27: Entities not being encoded in PROPFIND responses.
|
||||
* Fixed: Issue 29: Added missing TIMEOUT_INFINITE constant.
|
||||
|
||||
1.0.8-stable (2010-03-03)
|
||||
* Fixed: Issue 21: typos causing errors
|
||||
* Fixed: Issue 23: Comma's between methods in Allow header.
|
||||
* Added: Sabre_DAV_ICollection interface, to aid in future compatibility.
|
||||
* Added: Sabre_DAV_Exception_Forbidden exception. This will replace
|
||||
Sabre_DAV_Exception_PermissionDenied in the future, and can already be
|
||||
used to ensure future compatibility.
|
||||
|
||||
1.0.7-stable (2010-02-24)
|
||||
* Fixed: Issue 19 regression for MS Office
|
||||
|
||||
1.0.6-stable (2010-02-23)
|
||||
* Fixed: Issue 19: HEAD requests on Collections
|
||||
|
||||
1.0.5-stable (2010-01-22)
|
||||
* Fixed: Fatal error when a malformed url was used for unlocking, in
|
||||
conjuction with Sabre.autoload.php due to a incorrect filename.
|
||||
* Fixed: Improved unittests and build system
|
||||
|
||||
1.0.4-stable (2010-01-11)
|
||||
* Fixed: needed 2 different releases. One for googlecode and one for
|
||||
pearfarm. This is to retain the old method to install SabreDAV until
|
||||
pearfarm becomes the standard installation method.
|
||||
|
||||
1.0.3-stable (2010-01-11)
|
||||
* Added: RFC4709 support (davmount)
|
||||
* Added: 6 unittests
|
||||
* Added: naturalselection. A tool to keep cache directories below a
|
||||
specified theshold.
|
||||
* Changed: Now using pearfarm.org channel server.
|
||||
|
||||
1.0.1-stable (2009-12-22)
|
||||
* Fixed: Issue 15: typos in examples
|
||||
* Fixed: Minor pear installation issues
|
||||
|
||||
1.0.0-stable (2009-11-02)
|
||||
* Added: SimpleDirectory class. This class allows creating static
|
||||
directory structures with ease.
|
||||
* Changed: Custom complex properties and exceptions now get an instance of
|
||||
Sabre_DAV_Server as their first argument in serialize()
|
||||
* Changed: Href complex property now prepends server's baseUri
|
||||
* Changed: delete before an overwriting copy/move is now handles by server
|
||||
class instead of tree classes
|
||||
* Changed: events must now explicitly return false to stop execution.
|
||||
Before, execution would be stopped by anything loosely evaluating to
|
||||
false.
|
||||
* Changed: the getPropertiesForPath method now takes a different set of
|
||||
arguments, and returns a different response. This allows plugin
|
||||
developers to return statuses for properties other than 200 and 404. The
|
||||
hrefs are now also always calculated relative to the baseUri, and not
|
||||
the uri of the request.
|
||||
* Changed: generatePropFindResponse is renamed to generateMultiStatus, and
|
||||
now takes a list of properties similar to the response of
|
||||
getPropertiesForPath. This was also needed to improve flexibility for
|
||||
plugin development.
|
||||
* Changed: Auth plugins are no longer included. They were not yet stable
|
||||
quality, so they will probably be reintroduced in a later version.
|
||||
* Changed: PROPPATCH also used generateMultiStatus now.
|
||||
* Removed: unknownProperties event. This is replaced by the
|
||||
afterGetProperties event, which should provide more flexibility.
|
||||
* Fixed: Only calling getSize() on IFile instances in httpHead()
|
||||
* Added: beforeBind event. This is invoked upon file or directory creation
|
||||
* Added: beforeWriteContent event, this is invoked by PUT and LOCK on an
|
||||
existing resource.
|
||||
* Added: beforeUnbind event. This is invoked right before deletion of any
|
||||
resource.
|
||||
* Added: afterGetProperties event. This event can be used to make
|
||||
modifications to property responses.
|
||||
* Added: beforeLock and beforeUnlock events.
|
||||
* Added: afterBind event.
|
||||
* Fixed: Copy and Move could fail in the root directory. This is now
|
||||
fixed.
|
||||
* Added: Plugins can now be retrieved by their classname. This is useful
|
||||
for inter-plugin communication.
|
||||
* Added: The Auth backend can now return usernames and user-id's.
|
||||
* Added: The Auth backend got a getUsers method
|
||||
* Added: Sabre_DAV_FSExt_Directory now returns quota info
|
||||
|
||||
0.12.1-beta (2009-09-11)
|
||||
* Fixed: UNLOCK bug. Unlock didn't work at all
|
||||
|
||||
0.12-beta (2009-09-10)
|
||||
* Updated: Browser plugin now shows multiple {DAV:}resourcetype values
|
||||
if available.
|
||||
* Added: Experimental PDO backend for Locks Manager
|
||||
* Fixed: Sending Content-Length: 0 for every empty response. This
|
||||
improves NGinx compatibility.
|
||||
* Fixed: Last modification time is reported in UTC timezone. This improves
|
||||
Finder compatibility.
|
||||
|
||||
0.11-beta (2009-08-11)
|
||||
* Updated: Now in Beta
|
||||
* Updated: Pear package no longer includes docs/ directory. These just
|
||||
contained rfc's, which are publically available. This reduces the
|
||||
package from ~800k to ~60k
|
||||
* Added: generatePropfindResponse now takes a baseUri argument
|
||||
* Added: ResourceType property can now contain multiple resourcetypes.
|
||||
* Fixed: Issue 13.
|
||||
|
||||
0.10-alpha (2009-08-03)
|
||||
* Added: Plugin to automatically map GET requests to non-files to
|
||||
PROPFIND (Sabre_DAV_Browser_MapGetToPropFind). This should allow
|
||||
easier debugging of complicated WebDAV setups.
|
||||
* Added: Sabre_DAV_Property_Href class. For future use.
|
||||
* Added: Ability to choose to use auth-int, auth or both for HTTP Digest
|
||||
authentication. (Issue 11)
|
||||
* Changed: Made more methods in Sabre_DAV_Server public.
|
||||
* Fixed: TemporaryFileFilter plugin now intercepts HTTP LOCK requests
|
||||
to non-existent files. (Issue 12)
|
||||
* Added: Central list of defined xml namespace prefixes. This can reduce
|
||||
Bandwidth and legibility for xml bodies with user-defined namespaces.
|
||||
* Added: now a PEAR-compatible package again, thanks to Michael Gauthier
|
||||
* Changed: moved default copy and move logic from ObjectTree to Tree class
|
||||
|
||||
0.9-alpha (2009-07-21)
|
||||
* Changed: Major refactoring, removed most of the logic from the Tree
|
||||
objects. The Server class now directly works with the INode, IFile
|
||||
and IDirectory objects. If you created your own Tree objects,
|
||||
this will most likely break in this release.
|
||||
* Changed: Moved all the Locking logic from the Tree and Server classes
|
||||
into a separate plugin.
|
||||
* Changed: TemporaryFileFilter is now a plugin.
|
||||
* Added: Comes with an autoloader script. This can be used instead of
|
||||
the includer script, and is preferred by some people.
|
||||
* Added: AWS Authentication class.
|
||||
* Added: simpleserversetup.py script. This will quickly get a fileserver
|
||||
up and running.
|
||||
* Added: When subscribing to events, it is now possible to supply a
|
||||
priority. This is for example needed to ensure that the Authentication
|
||||
Plugin is used before any other Plugin.
|
||||
* Added: 22 new tests.
|
||||
* Added: Users-manager plugin for .htdigest files. Experimental and
|
||||
subject to change.
|
||||
* Added: RFC 2324 HTTP 418 status code
|
||||
* Fixed: Exclusive locks could in some cases be picked up as shared locks
|
||||
* Fixed: Digest auth for non-apache servers had a bug (still not actually
|
||||
tested this well).
|
||||
|
||||
0.8-alpha (2009-05-30)
|
||||
* Changed: Renamed all exceptions! This is a compatibility break. Every
|
||||
Exception now follows Sabre_DAV_Exception_FileNotFound convention
|
||||
instead of Sabre_DAV_FileNotFoundException.
|
||||
* Added: Browser plugin now allows uploading and creating directories
|
||||
straight from the browser.
|
||||
* Added: 12 more unittests
|
||||
* Fixed: Locking bug, which became prevalent on Windows Vista.
|
||||
* Fixed: Netdrive support
|
||||
* Fixed: TemporaryFileFilter filtered out too many files. Fixed some
|
||||
of the regexes.
|
||||
* Fixed: Added README and ChangeLog to package
|
||||
|
||||
0.7-alpha (2009-03-29)
|
||||
* Added: System to return complex properties from PROPFIND.
|
||||
* Added: support for {DAV:}supportedlock.
|
||||
* Added: support for {DAV:}lockdiscovery.
|
||||
* Added: 6 new tests.
|
||||
* Added: New plugin system.
|
||||
* Added: Simple HTML directory plugin, for browser access.
|
||||
* Added: Server class now sends back standard pre-condition error xml
|
||||
bodies. This was new since RFC4918.
|
||||
* Added: Sabre_DAV_Tree_Aggregrate, which can 'host' multiple Tree objects
|
||||
into one.
|
||||
* Added: simple basis for HTTP REPORT method. This method is not used yet,
|
||||
but can be used by plugins to add reports.
|
||||
* Changed: ->getSize is only called for files, no longer for collections.
|
||||
r303
|
||||
* Changed: Sabre_DAV_FilterTree is now Sabre_DAV_Tree_Filter
|
||||
* Changed: Sabre_DAV_TemporaryFileFilter is now called
|
||||
Sabre_DAV_Tree_TemporaryFileFilter.
|
||||
* Changed: removed functions (get(/set)HTTPRequest(/Response)) from Server
|
||||
class, and using a public property instead.
|
||||
* Fixed: bug related to parsing proppatch and propfind requests. Didn't
|
||||
show up in most clients, but it needed fixing regardless. (r255)
|
||||
* Fixed: auth-int is now properly supported within HTTP Digest.
|
||||
* Fixed: Using application/xml for a mimetype vs. text/xml as per RFC4918
|
||||
sec 8.2.
|
||||
* Fixed: TemporaryFileFilter now lets through GET's if they actually
|
||||
exist on the backend. (r274)
|
||||
* FIxed: Some methods didn't get passed through in the FilterTree (r283).
|
||||
* Fixed: LockManager is now slightly more complex, Tree classes slightly
|
||||
less. (r287)
|
||||
|
||||
0.6-alpha (2009-02-16)
|
||||
* Added: Now uses streams for files, instead of strings.
|
||||
This means it won't require to hold entire files in memory, which can be
|
||||
an issue if you're dealing with big files. Note that this breaks
|
||||
compatibility for put() and createFile methods.
|
||||
* Added: HTTP Digest Authentication helper class.
|
||||
* Added: Support for HTTP Range header
|
||||
* Added: Support for ETags within If: headers
|
||||
* Added: The API can now return ETags and override the default Content-Type
|
||||
* Added: starting with basic framework for unittesting, using PHPUnit.
|
||||
* Added: 49 unittests.
|
||||
* Added: Abstraction for the HTTP request.
|
||||
* Updated: Using Clark Notation for tags in properties. This means tags
|
||||
are serialized as {namespace}tagName instead of namespace#tagName
|
||||
* Fixed: HTTP_BasicAuth class now works as expected.
|
||||
* Fixed: DAV_Server uses / for a default baseUrl.
|
||||
* Fixed: Last modification date is no longer ignored in PROPFIND.
|
||||
* Fixed: PROPFIND now sends back information about the requestUri even
|
||||
when "Depth: 1" is specified.
|
||||
|
||||
0.5-alpha (2009-01-14)
|
||||
* Added: Added a very simple example for implementing a mapping to PHP
|
||||
file streams. This should allow easy implementation of for example a
|
||||
WebDAV to FTP proxy.
|
||||
* Added: HTTP Basic Authentication helper class.
|
||||
* Added: Sabre_HTTP_Response class. This centralizes HTTP operations and
|
||||
will be a start towards the creating of a testing framework.
|
||||
* Updated: Backwards compatibility break: all require_once() statements
|
||||
are removed
|
||||
from all the files. It is now recommended to use autoloading of
|
||||
classes, or just including lib/Sabre.includes.php. This fix was made
|
||||
to allow easier integration into applications not using this standard
|
||||
inclusion model.
|
||||
* Updated: Better in-file documentation.
|
||||
* Updated: Sabre_DAV_Tree can now work with Sabre_DAV_LockManager.
|
||||
* Updated: Fixes a shared-lock bug.
|
||||
* Updated: Removed ?> from the bottom of each php file.
|
||||
* Updated: Split up some operations from Sabre_DAV_Server to
|
||||
Sabre_HTTP_Response.
|
||||
* Fixed: examples are now actually included in the pear package.
|
||||
|
||||
0.4-alpha (2008-11-05)
|
||||
* Passes all litmus tests!
|
||||
* Added: more examples
|
||||
* Added: Custom property support
|
||||
* Added: Shared lock support
|
||||
* Added: Depth support to locks
|
||||
* Added: Locking on unmapped urls (non-existent nodes)
|
||||
* Fixed: Advertising as WebDAV class 3 support
|
||||
|
||||
0.3-alpha (2008-06-29)
|
||||
* Fully working in MS Windows clients.
|
||||
* Added: temporary file filter: support for smultron files.
|
||||
* Added: Phing build scripts
|
||||
* Added: PEAR package
|
||||
* Fixed: MOVE bug identified using finder.
|
||||
* Fixed: Using gzuncompress instead of gzdecode in the temporary file
|
||||
filter. This seems more common.
|
||||
|
||||
0.2-alpha (2008-05-27)
|
||||
* Somewhat working in Windows clients
|
||||
* Added: Working PROPPATCH method (doesn't support custom properties yet)
|
||||
* Added: Temporary filename handling system
|
||||
* Added: Sabre_DAV_IQuota to return quota information
|
||||
* Added: PROPFIND now reads the request body and only supplies the
|
||||
requested properties
|
||||
|
||||
0.1-alpha (2008-04-04)
|
||||
* First release!
|
||||
* Passes litmus: basic, http and copymove test.
|
||||
* Fully working in Finder and DavFSv2
|
||||
|
||||
Project started: 2007-12-13
|
|
@ -0,0 +1,28 @@
|
|||
Copyright (C) 2007-2012 Rooftop Solutions.
|
||||
Copyright (C) 2007-2009 FileMobile inc.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* 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.
|
||||
* Neither the name of the SabreDAV nor the names of its contributors
|
||||
may 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.
|
|
@ -0,0 +1,248 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2006, 2007 Google Inc. All Rights Reserved.
|
||||
# Author: danderson@google.com (David Anderson)
|
||||
#
|
||||
# Script for uploading files to a Google Code project.
|
||||
#
|
||||
# This is intended to be both a useful script for people who want to
|
||||
# streamline project uploads and a reference implementation for
|
||||
# uploading files to Google Code projects.
|
||||
#
|
||||
# To upload a file to Google Code, you need to provide a path to the
|
||||
# file on your local machine, a small summary of what the file is, a
|
||||
# project name, and a valid account that is a member or owner of that
|
||||
# project. You can optionally provide a list of labels that apply to
|
||||
# the file. The file will be uploaded under the same name that it has
|
||||
# in your local filesystem (that is, the "basename" or last path
|
||||
# component). Run the script with '--help' to get the exact syntax
|
||||
# and available options.
|
||||
#
|
||||
# Note that the upload script requests that you enter your
|
||||
# googlecode.com password. This is NOT your Gmail account password!
|
||||
# This is the password you use on googlecode.com for committing to
|
||||
# Subversion and uploading files. You can find your password by going
|
||||
# to http://code.google.com/hosting/settings when logged in with your
|
||||
# Gmail account. If you have already committed to your project's
|
||||
# Subversion repository, the script will automatically retrieve your
|
||||
# credentials from there (unless disabled, see the output of '--help'
|
||||
# for details).
|
||||
#
|
||||
# If you are looking at this script as a reference for implementing
|
||||
# your own Google Code file uploader, then you should take a look at
|
||||
# the upload() function, which is the meat of the uploader. You
|
||||
# basically need to build a multipart/form-data POST request with the
|
||||
# right fields and send it to https://PROJECT.googlecode.com/files .
|
||||
# Authenticate the request using HTTP Basic authentication, as is
|
||||
# shown below.
|
||||
#
|
||||
# Licensed under the terms of the Apache Software License 2.0:
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Questions, comments, feature requests and patches are most welcome.
|
||||
# Please direct all of these to the Google Code users group:
|
||||
# http://groups.google.com/group/google-code-hosting
|
||||
|
||||
"""Google Code file uploader script.
|
||||
"""
|
||||
|
||||
__author__ = 'danderson@google.com (David Anderson)'
|
||||
|
||||
import httplib
|
||||
import os.path
|
||||
import optparse
|
||||
import getpass
|
||||
import base64
|
||||
import sys
|
||||
|
||||
|
||||
def upload(file, project_name, user_name, password, summary, labels=None):
|
||||
"""Upload a file to a Google Code project's file server.
|
||||
|
||||
Args:
|
||||
file: The local path to the file.
|
||||
project_name: The name of your project on Google Code.
|
||||
user_name: Your Google account name.
|
||||
password: The googlecode.com password for your account.
|
||||
Note that this is NOT your global Google Account password!
|
||||
summary: A small description for the file.
|
||||
labels: an optional list of label strings with which to tag the file.
|
||||
|
||||
Returns: a tuple:
|
||||
http_status: 201 if the upload succeeded, something else if an
|
||||
error occurred.
|
||||
http_reason: The human-readable string associated with http_status
|
||||
file_url: If the upload succeeded, the URL of the file on Google
|
||||
Code, None otherwise.
|
||||
"""
|
||||
# The login is the user part of user@gmail.com. If the login provided
|
||||
# is in the full user@domain form, strip it down.
|
||||
if user_name.endswith('@gmail.com'):
|
||||
user_name = user_name[:user_name.index('@gmail.com')]
|
||||
|
||||
form_fields = [('summary', summary)]
|
||||
if labels is not None:
|
||||
form_fields.extend([('label', l.strip()) for l in labels])
|
||||
|
||||
content_type, body = encode_upload_request(form_fields, file)
|
||||
|
||||
upload_host = '%s.googlecode.com' % project_name
|
||||
upload_uri = '/files'
|
||||
auth_token = base64.b64encode('%s:%s'% (user_name, password))
|
||||
headers = {
|
||||
'Authorization': 'Basic %s' % auth_token,
|
||||
'User-Agent': 'Googlecode.com uploader v0.9.4',
|
||||
'Content-Type': content_type,
|
||||
}
|
||||
|
||||
server = httplib.HTTPSConnection(upload_host)
|
||||
server.request('POST', upload_uri, body, headers)
|
||||
resp = server.getresponse()
|
||||
server.close()
|
||||
|
||||
if resp.status == 201:
|
||||
location = resp.getheader('Location', None)
|
||||
else:
|
||||
location = None
|
||||
return resp.status, resp.reason, location
|
||||
|
||||
|
||||
def encode_upload_request(fields, file_path):
|
||||
"""Encode the given fields and file into a multipart form body.
|
||||
|
||||
fields is a sequence of (name, value) pairs. file is the path of
|
||||
the file to upload. The file will be uploaded to Google Code with
|
||||
the same file name.
|
||||
|
||||
Returns: (content_type, body) ready for httplib.HTTP instance
|
||||
"""
|
||||
BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla'
|
||||
CRLF = '\r\n'
|
||||
|
||||
body = []
|
||||
|
||||
# Add the metadata about the upload first
|
||||
for key, value in fields:
|
||||
body.extend(
|
||||
['--' + BOUNDARY,
|
||||
'Content-Disposition: form-data; name="%s"' % key,
|
||||
'',
|
||||
value,
|
||||
])
|
||||
|
||||
# Now add the file itself
|
||||
file_name = os.path.basename(file_path)
|
||||
f = open(file_path, 'rb')
|
||||
file_content = f.read()
|
||||
f.close()
|
||||
|
||||
body.extend(
|
||||
['--' + BOUNDARY,
|
||||
'Content-Disposition: form-data; name="filename"; filename="%s"'
|
||||
% file_name,
|
||||
# The upload server determines the mime-type, no need to set it.
|
||||
'Content-Type: application/octet-stream',
|
||||
'',
|
||||
file_content,
|
||||
])
|
||||
|
||||
# Finalize the form body
|
||||
body.extend(['--' + BOUNDARY + '--', ''])
|
||||
|
||||
return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body)
|
||||
|
||||
|
||||
def upload_find_auth(file_path, project_name, summary, labels=None,
|
||||
user_name=None, password=None, tries=3):
|
||||
"""Find credentials and upload a file to a Google Code project's file server.
|
||||
|
||||
file_path, project_name, summary, and labels are passed as-is to upload.
|
||||
|
||||
Args:
|
||||
file_path: The local path to the file.
|
||||
project_name: The name of your project on Google Code.
|
||||
summary: A small description for the file.
|
||||
labels: an optional list of label strings with which to tag the file.
|
||||
config_dir: Path to Subversion configuration directory, 'none', or None.
|
||||
user_name: Your Google account name.
|
||||
tries: How many attempts to make.
|
||||
"""
|
||||
|
||||
while tries > 0:
|
||||
if user_name is None:
|
||||
# Read username if not specified or loaded from svn config, or on
|
||||
# subsequent tries.
|
||||
sys.stdout.write('Please enter your googlecode.com username: ')
|
||||
sys.stdout.flush()
|
||||
user_name = sys.stdin.readline().rstrip()
|
||||
if password is None:
|
||||
# Read password if not loaded from svn config, or on subsequent tries.
|
||||
print 'Please enter your googlecode.com password.'
|
||||
print '** Note that this is NOT your Gmail account password! **'
|
||||
print 'It is the password you use to access Subversion repositories,'
|
||||
print 'and can be found here: http://code.google.com/hosting/settings'
|
||||
password = getpass.getpass()
|
||||
|
||||
status, reason, url = upload(file_path, project_name, user_name, password,
|
||||
summary, labels)
|
||||
# Returns 403 Forbidden instead of 401 Unauthorized for bad
|
||||
# credentials as of 2007-07-17.
|
||||
if status in [httplib.FORBIDDEN, httplib.UNAUTHORIZED]:
|
||||
# Rest for another try.
|
||||
user_name = password = None
|
||||
tries = tries - 1
|
||||
else:
|
||||
# We're done.
|
||||
break
|
||||
|
||||
return status, reason, url
|
||||
|
||||
|
||||
def main():
|
||||
parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY '
|
||||
'-p PROJECT [options] FILE')
|
||||
parser.add_option('-s', '--summary', dest='summary',
|
||||
help='Short description of the file')
|
||||
parser.add_option('-p', '--project', dest='project',
|
||||
help='Google Code project name')
|
||||
parser.add_option('-u', '--user', dest='user',
|
||||
help='Your Google Code username')
|
||||
parser.add_option('-w', '--password', dest='password',
|
||||
help='Your Google Code password')
|
||||
parser.add_option('-l', '--labels', dest='labels',
|
||||
help='An optional list of comma-separated labels to attach '
|
||||
'to the file')
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if not options.summary:
|
||||
parser.error('File summary is missing.')
|
||||
elif not options.project:
|
||||
parser.error('Project name is missing.')
|
||||
elif len(args) < 1:
|
||||
parser.error('File to upload not provided.')
|
||||
elif len(args) > 1:
|
||||
parser.error('Only one file may be specified.')
|
||||
|
||||
file_path = args[0]
|
||||
|
||||
if options.labels:
|
||||
labels = options.labels.split(',')
|
||||
else:
|
||||
labels = None
|
||||
|
||||
status, reason, url = upload_find_auth(file_path, options.project,
|
||||
options.summary, labels,
|
||||
options.user, options.password)
|
||||
if url:
|
||||
print 'The file was uploaded successfully.'
|
||||
print 'URL: %s' % url
|
||||
return 0
|
||||
else:
|
||||
print 'An error occurred. Your file was not uploaded.'
|
||||
print 'Google Code upload server said: %s (%s)' % (reason, status)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
|
@ -0,0 +1,378 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Documentation generator
|
||||
*
|
||||
* This scripts scans all files in the lib/ directory, and generates
|
||||
* Google Code wiki documentation.
|
||||
*
|
||||
* This script is rather crappy. It does what it needs to do, but uses global
|
||||
* variables and it might be a hard to read.
|
||||
*
|
||||
* I'm not sure if I care though. Maybe one day this can become a separate
|
||||
* project
|
||||
*
|
||||
* To run this script, just execute on the command line. The script assumes
|
||||
* it's in the standard bin/ directory.
|
||||
*/
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
$libDir = realpath(__DIR__ . '/../lib');
|
||||
$outputDir = __DIR__ . '/../docs/wikidocs';
|
||||
|
||||
if (!is_dir($outputDir)) mkdir($outputDir);
|
||||
|
||||
$files = new RecursiveDirectoryIterator($libDir);
|
||||
$files = new RecursiveIteratorIterator($files, RecursiveIteratorIterator::LEAVES_ONLY);
|
||||
|
||||
include_once $libDir . '/Sabre/autoload.php';
|
||||
|
||||
// Finding all classnames
|
||||
$classNames = findClassNames($files);
|
||||
echo "Found: " . count($classNames) . " classes and interfaces\n";
|
||||
|
||||
echo "Generating class tree\n";
|
||||
$classTree = getClassTree($classNames);
|
||||
|
||||
$packageList = array();
|
||||
|
||||
foreach($classNames as $className) {
|
||||
|
||||
echo "Creating docs for: " . $className . "\n";
|
||||
|
||||
$output = createDoc($className,isset($classTree[$className])?$classTree[$className]:array());
|
||||
file_put_contents($outputDir . '/' . $className . '.wiki', $output);
|
||||
|
||||
}
|
||||
|
||||
echo "Creating indexes\n";
|
||||
$output = createSidebarIndex($packageList);
|
||||
file_put_contents($outputDir . '/APIIndex.wiki', $output);
|
||||
|
||||
|
||||
function findClassNames($files) {
|
||||
|
||||
$classNames = array();
|
||||
foreach($files as $fileName=>$fileInfo) {
|
||||
|
||||
$tokens = token_get_all(file_get_contents($fileName));
|
||||
foreach($tokens as $tokenIndex=>$token) {
|
||||
|
||||
if ($token[0]===T_CLASS || $token[0]===T_INTERFACE) {
|
||||
$classNames[] = $tokens[$tokenIndex+2][1];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $classNames;
|
||||
|
||||
}
|
||||
|
||||
function getClassTree($classNames) {
|
||||
|
||||
$classTree = array();
|
||||
|
||||
foreach($classNames as $className) {
|
||||
|
||||
if (!class_exists($className) && !interface_exists($className)) continue;
|
||||
$rClass = new ReflectionClass($className);
|
||||
|
||||
$parent = $rClass->getParentClass();
|
||||
if ($parent) $parent = $parent->name;
|
||||
|
||||
if (!isset($classTree[$parent])) $classTree[$parent] = array();
|
||||
$classTree[$parent][] = $className;
|
||||
|
||||
foreach($rClass->getInterfaceNames() as $interface) {
|
||||
|
||||
if (!isset($classTree[$interface])) {
|
||||
$classTree[$interface] = array();
|
||||
}
|
||||
$classTree[$interface][] = $className;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return $classTree;
|
||||
|
||||
}
|
||||
|
||||
function createDoc($className, $extendedBy) {
|
||||
|
||||
// ew
|
||||
global $packageList;
|
||||
|
||||
ob_start();
|
||||
$rClass = new ReflectionClass($className);
|
||||
|
||||
echo "#summary API documentation for: ", $rClass->getName() , "\n";
|
||||
echo "#labels APIDoc\n";
|
||||
echo "#sidebar APIIndex\n";
|
||||
echo "=`" . $rClass->getName() . "`=\n";
|
||||
echo "\n";
|
||||
|
||||
$docs = parseDocs($rClass->getDocComment());
|
||||
echo $docs['description'] . "\n";
|
||||
echo "\n";
|
||||
|
||||
$parentClass = $rClass->getParentClass();
|
||||
|
||||
if($parentClass) {
|
||||
echo " * Parent class: [" . $parentClass->getName() . "]\n";
|
||||
}
|
||||
if ($interfaces = $rClass->getInterfaceNames()) {
|
||||
$interfaces = array_map(function($int) { return '[' . $int . ']'; },$interfaces);
|
||||
echo " * Implements: " . implode(", ", $interfaces) . "\n";
|
||||
}
|
||||
$classType = $rClass->isInterface()?'interface':'class';
|
||||
if (isset($docs['deprecated'])) {
|
||||
echo " * *Warning: This $classType is deprecated, and should not longer be used.*\n";
|
||||
}
|
||||
if ($rClass->isInterface()) {
|
||||
echo " * This is an interface.\n";
|
||||
} elseif ($rClass->isAbstract()) {
|
||||
echo " * This is an abstract class.\n";
|
||||
}
|
||||
if (isset($docs['package'])) {
|
||||
$package = $docs['package'];
|
||||
if (isset($docs['subpackage'])) {
|
||||
$package.='_' . $docs['subpackage'];
|
||||
}
|
||||
if (!isset($packageList[$package])) {
|
||||
$packageList[$package] = array();
|
||||
}
|
||||
$packageList[$package][] = $rClass->getName();
|
||||
}
|
||||
|
||||
if ($extendedBy) {
|
||||
|
||||
echo "\n";
|
||||
if ($classType==='interface') {
|
||||
echo "This interface is extended by the following interfaces:\n";
|
||||
foreach($extendedBy as $className) {
|
||||
if (interface_exists($className)) {
|
||||
echo " * [" . $className . "]\n";
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
echo "This interface is implemented by the following classes:\n";
|
||||
} else {
|
||||
echo "This class is extended by the following classes:\n";
|
||||
}
|
||||
foreach($extendedBy as $className) {
|
||||
if (class_exists($className)) {
|
||||
echo " * [" . $className . "]\n";
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
echo "==Properties==\n";
|
||||
|
||||
echo "\n";
|
||||
|
||||
$properties = $rClass->getProperties(ReflectionProperty::IS_STATIC | ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED);
|
||||
|
||||
if (count($properties)>0) {
|
||||
foreach($properties as $rProperty) {
|
||||
|
||||
createPropertyDoc($rProperty);
|
||||
|
||||
}
|
||||
} else {
|
||||
echo "This $classType does not define any public or protected properties.\n";
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
echo "==Methods==\n";
|
||||
|
||||
echo "\n";
|
||||
|
||||
$methods = $rClass->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED);
|
||||
|
||||
if (count($methods)>0) {
|
||||
foreach($methods as $rMethod) {
|
||||
|
||||
createMethodDoc($rMethod, $rClass);
|
||||
|
||||
}
|
||||
} else {
|
||||
echo "\nThis $classType does not define any public or protected methods.\n";
|
||||
}
|
||||
|
||||
return ob_get_clean();
|
||||
|
||||
}
|
||||
|
||||
function createMethodDoc($rMethod, $rClass) {
|
||||
|
||||
echo "===`" . $rMethod->getName() . "`===\n";
|
||||
echo "\n";
|
||||
|
||||
$docs = parseDocs($rMethod->getDocComment());
|
||||
|
||||
$return = isset($docs['return'])?$docs['return']:'void';
|
||||
|
||||
echo "{{{\n";
|
||||
echo $return . " " . $rMethod->class . "::" . $rMethod->getName() . "(";
|
||||
foreach($rMethod->getParameters() as $parameter) {
|
||||
if ($parameter->getPosition()>0) echo ", ";
|
||||
if ($class = $parameter->getClass()) {
|
||||
echo $class->name . " ";
|
||||
} elseif (isset($docs['param'][$parameter->name])) {
|
||||
echo $docs['param'][$parameter->name] . " ";
|
||||
}
|
||||
|
||||
echo '$' . $parameter->name;
|
||||
|
||||
if ($parameter->isOptional() && $parameter->isDefaultValueAvailable()) {
|
||||
$default = $parameter->getDefaultValue();
|
||||
$default = var_export($default,true);
|
||||
$default = str_replace("\n","",$default);
|
||||
echo " = " . $default;
|
||||
|
||||
}
|
||||
}
|
||||
echo ")\n";
|
||||
echo "}}}\n";
|
||||
echo "\n";
|
||||
|
||||
echo $docs['description'] . "\n";
|
||||
|
||||
echo "\n";
|
||||
|
||||
$hasProp = false;
|
||||
if (isset($docs['deprecated'])) {
|
||||
echo " * *Warning: This method is deprecated, and should not longer be used.*\n";
|
||||
$hasProp = true;
|
||||
}
|
||||
if ($rMethod->isProtected()) {
|
||||
echo " * This method is protected.\n";
|
||||
$hasProp = true;
|
||||
}
|
||||
if ($rMethod->isPrivate()) {
|
||||
echo " * This method is private.\n";
|
||||
$hasProp = true;
|
||||
}
|
||||
if ($rMethod->isAbstract()) {
|
||||
echo " * This is an abstract method\n";
|
||||
$hasProp = true;
|
||||
}
|
||||
|
||||
if ($rMethod->class != $rClass->name) {
|
||||
echo " * Defined in [" . $rMethod->class . "]\n";
|
||||
$hasProp = true;
|
||||
}
|
||||
|
||||
if ($hasProp) echo "\n";
|
||||
|
||||
}
|
||||
|
||||
function createPropertyDoc($rProperty) {
|
||||
|
||||
echo "===`" . $rProperty->getName() . "`===\n";
|
||||
echo "\n";
|
||||
|
||||
$docs = parseDocs($rProperty->getDocComment());
|
||||
|
||||
$visibility = 'public';
|
||||
if ($rProperty->isProtected()) $visibility = 'protected';
|
||||
if ($rProperty->isPrivate()) $visibility = 'private';
|
||||
|
||||
echo "{{{\n";
|
||||
echo $visibility . " " . $rProperty->class . "::$" . $rProperty->getName();
|
||||
echo "\n}}}\n";
|
||||
echo "\n";
|
||||
|
||||
echo $docs['description'] . "\n";
|
||||
|
||||
echo "\n";
|
||||
|
||||
$hasProp = false;
|
||||
if (isset($docs['deprecated'])) {
|
||||
echo " * *Warning: This property is deprecated, and should not longer be used.*\n";
|
||||
$hasProp = true;
|
||||
}
|
||||
if ($rProperty->isProtected()) {
|
||||
echo " * This property is protected.\n";
|
||||
$hasProp = true;
|
||||
}
|
||||
if ($rProperty->isPrivate()) {
|
||||
echo " * This property is private.\n";
|
||||
$hasProp = true;
|
||||
}
|
||||
if ($rProperty->isStatic()) {
|
||||
echo " * This property is static.\n";
|
||||
$hasProp = true;
|
||||
}
|
||||
|
||||
if ($hasProp) echo "\n";
|
||||
|
||||
}
|
||||
|
||||
function parseDocs($docString) {
|
||||
|
||||
$params = array();
|
||||
$description = array();
|
||||
|
||||
// Trimming all the comment characters
|
||||
$docString = trim($docString,"\n*/ ");
|
||||
$docString = explode("\n",$docString);
|
||||
|
||||
foreach($docString as $str) {
|
||||
|
||||
$str = ltrim($str,'* ');
|
||||
$str = trim($str);
|
||||
if ($str && $str[0]==='@') {
|
||||
$r = explode(' ',substr($str,1),2);
|
||||
$paramName = $r[0];
|
||||
$paramValue = (count($r)>1)?$r[1]:'';
|
||||
|
||||
// 'param' paramName is special. Confusing, I know.
|
||||
if ($paramName==='param') {
|
||||
if (!isset($params['param'])) $params['param'] = array();
|
||||
$paramValue = explode(' ', $paramValue,3);
|
||||
$params['param'][substr($paramValue[1],1)] = $paramValue[0];
|
||||
} else {
|
||||
$params[$paramName] = trim($paramValue);
|
||||
}
|
||||
} else {
|
||||
$description[]=$str;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$params['description'] = trim(implode("\n",$description),"\n ");
|
||||
|
||||
return $params;
|
||||
|
||||
}
|
||||
|
||||
function createSidebarIndex($packageList) {
|
||||
|
||||
ob_start();
|
||||
echo "#labels APIDocs\n";
|
||||
echo "#summary List of all classes, neatly organized\n";
|
||||
echo "=API Index=\n";
|
||||
|
||||
foreach($packageList as $package=>$classes) {
|
||||
|
||||
echo " * $package\n";
|
||||
sort($classes);
|
||||
foreach($classes as $class) {
|
||||
|
||||
echo " * [$class $class]\n";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ob_get_clean();
|
||||
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
<?php
|
||||
|
||||
echo "SabreDAV migrate script for version 1.7\n";
|
||||
|
||||
if ($argc<2) {
|
||||
|
||||
echo <<<HELLO
|
||||
|
||||
This script help you migrate from a pre-1.7 database to 1.7 and later\n
|
||||
It is important to note, that this script only touches the 'calendarobjects'
|
||||
table.
|
||||
|
||||
If you do not have this table, or don't use the default PDO CalDAV backend
|
||||
it's pointless to run this script.
|
||||
|
||||
Keep in mind that some processing will be done on every single record of this
|
||||
table and in addition, ALTER TABLE commands will be executed.
|
||||
If you have a large calendarobjects table, this may mean that this process
|
||||
takes a while.
|
||||
|
||||
Usage:
|
||||
|
||||
{$argv[0]} [pdo-dsn] [username] [password]
|
||||
|
||||
For example:
|
||||
|
||||
{$argv[0]} mysql:host=localhost;dbname=sabredav root password
|
||||
{$argv[0]} sqlite:data/sabredav.db
|
||||
|
||||
HELLO;
|
||||
|
||||
exit();
|
||||
|
||||
}
|
||||
|
||||
if (file_exists(__DIR__ . '/../lib/Sabre/VObject/includes.php')) {
|
||||
include __DIR__ . '/../lib/Sabre/VObject/includes.php';
|
||||
} else {
|
||||
// If, for some reason VObject was not found in the vicinity,
|
||||
// we'll try to grab it from the default path.
|
||||
require 'Sabre/VObject/includes.php';
|
||||
}
|
||||
|
||||
$dsn = $argv[1];
|
||||
$user = isset($argv[2])?$argv[2]:null;
|
||||
$pass = isset($argv[3])?$argv[3]:null;
|
||||
|
||||
echo "Connecting to database: " . $dsn . "\n";
|
||||
|
||||
$pdo = new PDO($dsn, $user, $pass);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
||||
|
||||
echo "Validating existing table layout\n";
|
||||
|
||||
// The only cross-db way to do this, is to just fetch a single record.
|
||||
$row = $pdo->query("SELECT * FROM calendarobjects LIMIT 1")->fetch();
|
||||
|
||||
if (!$row) {
|
||||
echo "Error: This database did not have any records in the calendarobjects table, you should just recreate the table.\n";
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
$requiredFields = array(
|
||||
'id',
|
||||
'calendardata',
|
||||
'uri',
|
||||
'calendarid',
|
||||
'lastmodified',
|
||||
);
|
||||
|
||||
foreach($requiredFields as $requiredField) {
|
||||
if (!array_key_exists($requiredField,$row)) {
|
||||
echo "Error: The current 'calendarobjects' table was missing a field we expected to exist.\n";
|
||||
echo "For safety reasons, this process is stopped.\n";
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
$fields17 = array(
|
||||
'etag',
|
||||
'size',
|
||||
'componenttype',
|
||||
'firstoccurence',
|
||||
'lastoccurence',
|
||||
);
|
||||
|
||||
$found = 0;
|
||||
foreach($fields17 as $field) {
|
||||
if (array_key_exists($field, $row)) {
|
||||
$found++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($found === 0) {
|
||||
echo "The database had the 1.6 schema. Table will now be altered.\n";
|
||||
echo "This may take some time for large tables\n";
|
||||
$pdo->exec(<<<SQL
|
||||
ALTER TABLE calendarobjects
|
||||
ADD etag VARCHAR(32),
|
||||
ADD size INT(11) UNSIGNED,
|
||||
ADD componenttype VARCHAR(8),
|
||||
ADD firstoccurence INT(11) UNSIGNED,
|
||||
ADD lastoccurence INT(11) UNSIGNED
|
||||
SQL
|
||||
);
|
||||
echo "Database schema upgraded.\n";
|
||||
|
||||
} elseif ($found === 5) {
|
||||
|
||||
echo "Database already had the 1.7 schema\n";
|
||||
|
||||
} else {
|
||||
|
||||
echo "The database had $found out of 5 from the changes for 1.7. This is scary and unusual, so we have to abort.\n";
|
||||
echo "You can manually try to upgrade the schema, and then run this script again.\n";
|
||||
exit(-1);
|
||||
|
||||
}
|
||||
|
||||
echo "Now, we need to parse every record and pull out some information.\n";
|
||||
|
||||
$result = $pdo->query('SELECT id, calendardata FROM calendarobjects');
|
||||
$stmt = $pdo->prepare('UPDATE calendarobjects SET etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ? WHERE id = ?');
|
||||
|
||||
echo "Total records found: " . $result->rowCount() . "\n";
|
||||
$done = 0;
|
||||
$total = $result->rowCount();
|
||||
while($row = $result->fetch()) {
|
||||
|
||||
try {
|
||||
$newData = getDenormalizedData($row['calendardata']);
|
||||
} catch (Exception $e) {
|
||||
echo "===\nException caught will trying to parser calendarobject.\n";
|
||||
echo "Error message: " . $e->getMessage() . "\n";
|
||||
echo "Record id: " . $row['id'] . "\n";
|
||||
echo "This record is ignored, you should inspect it to see if there's anything wrong.\n===\n";
|
||||
continue;
|
||||
}
|
||||
$stmt->execute(array(
|
||||
$newData['etag'],
|
||||
$newData['size'],
|
||||
$newData['componentType'],
|
||||
$newData['firstOccurence'],
|
||||
$newData['lastOccurence'],
|
||||
$row['id'],
|
||||
));
|
||||
$done++;
|
||||
|
||||
if ($done % 500 === 0) {
|
||||
echo "Completed: $done / $total\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "Process completed!\n";
|
||||
|
||||
/**
|
||||
* Parses some information from calendar objects, used for optimized
|
||||
* calendar-queries.
|
||||
*
|
||||
* Blantently copied from Sabre_CalDAV_Backend_PDO
|
||||
*
|
||||
* Returns an array with the following keys:
|
||||
* * etag
|
||||
* * size
|
||||
* * componentType
|
||||
* * firstOccurence
|
||||
* * lastOccurence
|
||||
*
|
||||
* @param string $calendarData
|
||||
* @return array
|
||||
*/
|
||||
function getDenormalizedData($calendarData) {
|
||||
|
||||
$vObject = Sabre_VObject_Reader::read($calendarData);
|
||||
$componentType = null;
|
||||
$component = null;
|
||||
$firstOccurence = null;
|
||||
$lastOccurence = null;
|
||||
foreach($vObject->getComponents() as $component) {
|
||||
if ($component->name!=='VTIMEZONE') {
|
||||
$componentType = $component->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$componentType) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
|
||||
}
|
||||
if ($componentType === 'VEVENT') {
|
||||
$firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
|
||||
// Finding the last occurence is a bit harder
|
||||
if (!isset($component->RRULE)) {
|
||||
if (isset($component->DTEND)) {
|
||||
$lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
|
||||
} elseif (isset($component->DURATION)) {
|
||||
$endDate = clone $component->DTSTART->getDateTime();
|
||||
$endDate->add(Sabre_VObject_DateTimeParser::parse($component->DURATION->value));
|
||||
$lastOccurence = $endDate->getTimeStamp();
|
||||
} elseif ($component->DTSTART->getDateType()===Sabre_VObject_Property_DateTime::DATE) {
|
||||
$endDate = clone $component->DTSTART->getDateTime();
|
||||
$endDate->modify('+1 day');
|
||||
$lastOccurence = $endDate->getTimeStamp();
|
||||
} else {
|
||||
$lastOccurence = $firstOccurence;
|
||||
}
|
||||
} else {
|
||||
$it = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->UID);
|
||||
$maxDate = new DateTime(self::MAX_DATE);
|
||||
if ($it->isInfinite()) {
|
||||
$lastOccurence = $maxDate->getTimeStamp();
|
||||
} else {
|
||||
$end = $it->getDtEnd();
|
||||
while($it->valid() && $end < $maxDate) {
|
||||
$end = $it->getDtEnd();
|
||||
$it->next();
|
||||
|
||||
}
|
||||
$lastOccurence = $end->getTimeStamp();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'etag' => md5($calendarData),
|
||||
'size' => strlen($calendarData),
|
||||
'componentType' => $componentType,
|
||||
'firstOccurence' => $firstOccurence,
|
||||
'lastOccurence' => $lastOccurence,
|
||||
);
|
||||
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Copyright (c) 2009-2010 Evert Pot
|
||||
# All rights reserved.
|
||||
# http://www.rooftopsolutions.nl/
|
||||
#
|
||||
# This utility is distributed along with SabreDAV
|
||||
# license: http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
|
||||
import os
|
||||
from optparse import OptionParser
|
||||
import time
|
||||
|
||||
def getfreespace(path):
|
||||
stat = os.statvfs(path)
|
||||
return stat.f_frsize * stat.f_bavail
|
||||
|
||||
def getbytesleft(path,treshold):
|
||||
return getfreespace(path)-treshold
|
||||
|
||||
def run(cacheDir, treshold, sleep=5, simulate=False, min_erase = 0):
|
||||
|
||||
bytes = getbytesleft(cacheDir,treshold)
|
||||
if (bytes>0):
|
||||
print "Bytes to go before we hit treshhold:", bytes
|
||||
else:
|
||||
print "Treshold exceeded with:", -bytes, "bytes"
|
||||
dir = os.listdir(cacheDir)
|
||||
dir2 = []
|
||||
for file in dir:
|
||||
path = cacheDir + '/' + file
|
||||
dir2.append({
|
||||
"path" : path,
|
||||
"atime": os.stat(path).st_atime,
|
||||
"size" : os.stat(path).st_size
|
||||
})
|
||||
|
||||
dir2.sort(lambda x,y: int(x["atime"]-y["atime"]))
|
||||
|
||||
filesunlinked = 0
|
||||
gainedspace = 0
|
||||
|
||||
# Left is the amount of bytes that need to be freed up
|
||||
# The default is the 'min_erase setting'
|
||||
left = min_erase
|
||||
|
||||
# If the min_erase setting is lower than the amount of bytes over
|
||||
# the treshold, we use that number instead.
|
||||
if left < -bytes :
|
||||
left = -bytes
|
||||
|
||||
print "Need to delete at least:", left;
|
||||
|
||||
for file in dir2:
|
||||
|
||||
# Only deleting files if we're not simulating
|
||||
if not simulate: os.unlink(file["path"])
|
||||
left = int(left - file["size"])
|
||||
gainedspace = gainedspace + file["size"]
|
||||
filesunlinked = filesunlinked + 1
|
||||
|
||||
if(left<0):
|
||||
break
|
||||
|
||||
print "%d files deleted (%d bytes)" % (filesunlinked, gainedspace)
|
||||
|
||||
|
||||
time.sleep(sleep)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
parser = OptionParser(
|
||||
version="naturalselecton v0.3",
|
||||
description="Cache directory manager. Deletes cache entries based on accesstime and free space tresholds.\n" +
|
||||
"This utility is distributed alongside SabreDAV.",
|
||||
usage="usage: %prog [options] cacheDirectory",
|
||||
)
|
||||
parser.add_option(
|
||||
'-s',
|
||||
dest="simulate",
|
||||
action="store_true",
|
||||
help="Don't actually make changes, but just simulate the behaviour",
|
||||
)
|
||||
parser.add_option(
|
||||
'-r','--runs',
|
||||
help="How many times to check before exiting. -1 is infinite, which is the default",
|
||||
type="int",
|
||||
dest="runs",
|
||||
default=-1
|
||||
)
|
||||
parser.add_option(
|
||||
'-n','--interval',
|
||||
help="Sleep time in seconds (default = 5)",
|
||||
type="int",
|
||||
dest="sleep",
|
||||
default=5
|
||||
)
|
||||
parser.add_option(
|
||||
'-l','--treshold',
|
||||
help="Treshhold in bytes (default = 10737418240, which is 10GB)",
|
||||
type="int",
|
||||
dest="treshold",
|
||||
default=10737418240
|
||||
)
|
||||
parser.add_option(
|
||||
'-m', '--min-erase',
|
||||
help="Minimum number of bytes to erase when the treshold is reached. " +
|
||||
"Setting this option higher will reduce the amount of times the cache directory will need to be scanned. " +
|
||||
"(the default is 1073741824, which is 1GB.)",
|
||||
type="int",
|
||||
dest="min_erase",
|
||||
default=1073741824
|
||||
)
|
||||
|
||||
options,args = parser.parse_args()
|
||||
if len(args)<1:
|
||||
parser.error("This utility requires at least 1 argument")
|
||||
cacheDir = args[0]
|
||||
|
||||
print "Natural Selection"
|
||||
print "Cache directory:", cacheDir
|
||||
free = getfreespace(cacheDir);
|
||||
print "Current free disk space:", free
|
||||
|
||||
runs = options.runs;
|
||||
while runs!=0 :
|
||||
run(
|
||||
cacheDir,
|
||||
sleep=options.sleep,
|
||||
simulate=options.simulate,
|
||||
treshold=options.treshold,
|
||||
min_erase=options.min_erase
|
||||
)
|
||||
if runs>0:
|
||||
runs = runs - 1
|
||||
|
||||
if __name__ == '__main__' :
|
||||
main()
|
|
@ -0,0 +1,321 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
$make = false;
|
||||
$packageName = null;
|
||||
|
||||
foreach($argv as $index=>$arg) {
|
||||
if ($index==0) continue;
|
||||
if ($arg=='make') {
|
||||
$make = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$packageName = $arg;
|
||||
}
|
||||
|
||||
if (is_null($packageName)) {
|
||||
echo "A packagename is required\n";
|
||||
die(1);
|
||||
}
|
||||
|
||||
if (!is_dir('build/' . $packageName)) {
|
||||
echo "Could not find package directory: build/$packageName\n";
|
||||
die(2);
|
||||
}
|
||||
|
||||
// We'll figure out something better for this one day
|
||||
|
||||
$dependencies = array(
|
||||
array(
|
||||
'type' => 'php',
|
||||
'min' => '5.3.1',
|
||||
),
|
||||
array(
|
||||
'type' => 'pearinstaller',
|
||||
'min' => '1.9',
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
switch($packageName) {
|
||||
|
||||
case 'Sabre' :
|
||||
$summary = 'Sabretooth base package.';
|
||||
$description = <<<TEXT
|
||||
The base package provides some functionality used by all packages.
|
||||
|
||||
Currently this is only an autoloader
|
||||
TEXT;
|
||||
$version = '1.0.0';
|
||||
$stability = 'stable';
|
||||
break;
|
||||
|
||||
case 'Sabre_DAV' :
|
||||
$summary = 'Sabre_DAV is a WebDAV framework for PHP.';
|
||||
$description = <<<TEXT
|
||||
SabreDAV allows you to easily integrate WebDAV access into your existing PHP application.
|
||||
|
||||
Feature List:
|
||||
* Fully WebDAV (class 1, 2, 3) compliant
|
||||
* Supports Windows clients, OS/X, DavFS, Cadaver, and pretty much everything we've come accross
|
||||
* Custom property support
|
||||
* RFC4918-compliant
|
||||
* Authentication support
|
||||
* Plugin system
|
||||
TEXT;
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.0.0',
|
||||
);
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre_HTTP',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.6.0',
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
case 'Sabre_HTTP' :
|
||||
$summary = 'Sabre_HTTP provides various HTTP helpers, for input and output and authentication';
|
||||
$description = <<<TEXT
|
||||
Sabre_HTTP effectively wraps around \$_SERVER, php://input, php://output and the headers method,
|
||||
allowing for a central interface to deal with this as well as easier unittesting.
|
||||
|
||||
In addition Sabre_HTTP provides classes for Basic, Digest and Amazon AWS authentication.
|
||||
TEXT;
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.0.0',
|
||||
);
|
||||
break;
|
||||
|
||||
case 'Sabre_DAVACL' :
|
||||
$summary = 'Sabre_DAVACL provides rfc3744 support.';
|
||||
$description = <<<TEXT
|
||||
Sabre_DAVACL is the RFC3744 implementation for SabreDAV. It provides principals
|
||||
(users and groups) and access control.
|
||||
TEXT;
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.0.0',
|
||||
);
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre_DAV',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.6.0',
|
||||
);
|
||||
break;
|
||||
|
||||
case 'Sabre_CalDAV' :
|
||||
$summary = 'Sabre_CalDAV provides CalDAV extensions to SabreDAV';
|
||||
$description = <<<TEXT
|
||||
Sabre_CalDAV provides RFC4791 (CalDAV) support to Sabre_DAV.
|
||||
|
||||
Feature list:
|
||||
* Multi-user Calendar Server
|
||||
* Support for Apple iCal, Evolution, Sunbird, Lightning
|
||||
TEXT;
|
||||
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.0.0',
|
||||
);
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre_HTTP',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.6.0',
|
||||
);
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre_DAV',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.6.0',
|
||||
);
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre_DAVACL',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.6.0',
|
||||
);
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre_VObject',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.3.0',
|
||||
);
|
||||
break;
|
||||
|
||||
case 'Sabre_CardDAV' :
|
||||
$summary = 'Sabre_CardDAV provides CardDAV extensions to SabreDAV';
|
||||
$description = <<<TEXT
|
||||
Sabre_CardDAV provides CardDAV support to Sabre_DAV.
|
||||
|
||||
Feature list:
|
||||
* Multi-user addressbook server
|
||||
* ACL support
|
||||
* Support for OS/X, iOS, Evolution and probably more
|
||||
* Hook-ins for creating a global \'directory\'.
|
||||
TEXT;
|
||||
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.0.0',
|
||||
);
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre_HTTP',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.6.0',
|
||||
);
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre_DAV',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.6.0',
|
||||
);
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre_DAVACL',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.6.0',
|
||||
);
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre_VObject',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.3.0',
|
||||
);
|
||||
break;
|
||||
|
||||
case 'Sabre_VObject' :
|
||||
$summary = 'Sabre_VObject is a natural-interface iCalendar and vCard reader';
|
||||
$description = <<<TEXT
|
||||
Sabre_VObject is an intuitive reader for iCalendar and vCard objects.
|
||||
|
||||
It provides a natural array/object accessor interface to the parsed tree, much like
|
||||
simplexml for XML files.
|
||||
TEXT;
|
||||
$dependencies[] = array(
|
||||
'type' => 'package',
|
||||
'name' => 'Sabre',
|
||||
'channel' => 'pear.sabredav.org',
|
||||
'min' => '1.0.0',
|
||||
);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (!isset($version)) {
|
||||
include 'lib/' . str_replace('_','/',$packageName) . '/Version.php';
|
||||
$versionClassName = $packageName . '_Version';
|
||||
$version = $versionClassName::VERSION;
|
||||
$stability = $versionClassName::STABILITY;
|
||||
}
|
||||
|
||||
$lead = 'Evert Pot';
|
||||
$lead_email = 'evert@rooftopsolutions.nl';
|
||||
$date = date('Y-m-d');
|
||||
|
||||
$license = 'Modified BSD';
|
||||
$licenseuri = 'http://code.google.com/p/sabredav/wiki/License';
|
||||
$notes = 'New release. Read the ChangeLog and announcement for more details';
|
||||
$channel = 'pear.sabredav.org';
|
||||
|
||||
/* This function is intended to generate the full file list */
|
||||
function parsePath($fullPath, $role, $padding = 4) {
|
||||
|
||||
$fileList = '';
|
||||
$file = basename($fullPath);
|
||||
if (is_dir($fullPath)) {
|
||||
$fileList .= str_repeat(' ', $padding) . "<dir name=\"{$file}\">\n";
|
||||
foreach(scandir($fullPath) as $subPath) {;
|
||||
if ($subPath==='.' || $subPath==='..') continue;
|
||||
$fileList .= parsePath($fullPath. '/' . $subPath,$role, $padding+2);
|
||||
}
|
||||
$fileList .= str_repeat(' ', $padding) . "</dir><!-- {$file} -->\n";
|
||||
} elseif (is_file($fullPath)) {
|
||||
$fileList .= str_repeat(' ', $padding) . "<file name=\"{$file}\" role=\"{$role}\" />\n";
|
||||
}
|
||||
|
||||
return $fileList;
|
||||
|
||||
}
|
||||
|
||||
$rootDir = realpath('build/' . $packageName);
|
||||
|
||||
$fileList = parsePath($rootDir . '/Sabre', 'php');
|
||||
$fileList .= parsePath($rootDir . '/examples', 'doc');
|
||||
$fileList .= parsePath($rootDir . '/ChangeLog', 'doc');
|
||||
$fileList .= parsePath($rootDir . '/LICENSE', 'doc');
|
||||
|
||||
$dependenciesXML = "\n";
|
||||
foreach($dependencies as $dep) {
|
||||
$pad = 8;
|
||||
$dependenciesXML.=str_repeat(' ',$pad) . '<' . $dep['type'] . ">\n";
|
||||
foreach($dep as $key=>$value) {
|
||||
if ($key=='type') continue;
|
||||
$dependenciesXML.=str_repeat(' ',$pad+2) . "<$key>$value</$key>\n";
|
||||
}
|
||||
$dependenciesXML.=str_repeat(' ',$pad) . '</' . $dep['type'] . ">\n";
|
||||
}
|
||||
|
||||
$package = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<package version="2.0"
|
||||
xmlns="http://pear.php.net/dtd/package-2.0">
|
||||
|
||||
<name>{$packageName}</name>
|
||||
<channel>{$channel}</channel>
|
||||
<summary>{$summary}</summary>
|
||||
<description>{$description}</description>
|
||||
<lead>
|
||||
<name>{$lead}</name>
|
||||
<user>{$lead}</user>
|
||||
<email>{$lead_email}</email>
|
||||
<active>true</active>
|
||||
</lead>
|
||||
<date>{$date}</date>
|
||||
<version>
|
||||
<release>{$version}</release>
|
||||
<api>{$version}</api>
|
||||
</version>
|
||||
<stability>
|
||||
<release>{$stability}</release>
|
||||
<api>{$stability}</api>
|
||||
</stability>
|
||||
<license uri="{$licenseuri}">{$license}</license>
|
||||
<notes>{$notes}</notes>
|
||||
<contents>
|
||||
<dir name="/">{$fileList}
|
||||
</dir>
|
||||
</contents>
|
||||
<dependencies>
|
||||
<required>{$dependenciesXML}
|
||||
</required>
|
||||
</dependencies>
|
||||
<phprelease />
|
||||
</package>
|
||||
XML;
|
||||
|
||||
if (isset($argv) && in_array('make',$argv)) {
|
||||
file_put_contents($rootDir . '/package.xml',$package);
|
||||
} else {
|
||||
echo $package;
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
<?xml version="1.0"?>
|
||||
<project name="SabreDAV" default="build" basedir=".">
|
||||
|
||||
<!-- Any default properties -->
|
||||
<property file="build.properties" />
|
||||
|
||||
<!-- Where to write api documentation -->
|
||||
<property name="sabredav.apidocspath" value="docs/api" />
|
||||
|
||||
<target name="build" depends="init, test, clean">
|
||||
<mkdir dir="build" />
|
||||
|
||||
<echo msg="Building Sabre pear package" />
|
||||
<mkdir dir="build/Sabre" />
|
||||
<copy todir="build/Sabre">
|
||||
<fileset dir="lib">
|
||||
<include name="Sabre/autoload.php" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="build/Sabre">
|
||||
<fileset dir=".">
|
||||
<include name="LICENSE" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<exec command="bin/pearpackage3.php make Sabre" checkreturn="true" />
|
||||
<exec command="pear package" dir="build/Sabre" checkreturn="true" />
|
||||
|
||||
<echo msg="Building Sabre_HTTP pear package" />
|
||||
<mkdir dir="build/Sabre_HTTP" />
|
||||
<mkdir dir="build/Sabre_HTTP/Sabre" />
|
||||
<copy todir="build/Sabre_HTTP" includeemptydirs="true" >
|
||||
<fileset dir="lib">
|
||||
<include name="Sabre/HTTP/**" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="build/Sabre_HTTP">
|
||||
<fileset dir=".">
|
||||
<include name="LICENSE" />
|
||||
<include name="ChangeLog" />
|
||||
<include name="examples/basicauth.php" />
|
||||
<include name="examples/digestauth.php" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<exec command="bin/pearpackage3.php make Sabre_HTTP" checkreturn="true" />
|
||||
<exec command="pear package" dir="build/Sabre_HTTP" checkreturn="true" />
|
||||
|
||||
<echo msg="Building Sabre_DAV pear package" />
|
||||
<mkdir dir="build/Sabre_DAV" />
|
||||
<mkdir dir="build/Sabre_DAV/Sabre" />
|
||||
<copy todir="build/Sabre_DAV" includeemptydirs="true" >
|
||||
<fileset dir="lib">
|
||||
<include name="Sabre/DAV/**" />
|
||||
<exclude name="Sabre/DAVACL/**" />
|
||||
<include name="Sabre.includes.php" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="build/Sabre_DAV" includeemptydirs="true">
|
||||
<fileset dir="." >
|
||||
<include name="LICENSE" />
|
||||
<include name="ChangeLog" />
|
||||
<include name="examples/fileserver.php" />
|
||||
<include name="examples/simplefsserver.php" />
|
||||
<include name="examples/sql/*.locks.sql" />
|
||||
<include name="examples/sql/*.users.sql" />
|
||||
<include name="examples/webserver/*.conf" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<exec command="bin/pearpackage3.php make Sabre_DAV" checkreturn="true" />
|
||||
<exec command="pear package" dir="build/Sabre_DAV" checkreturn="true" />
|
||||
|
||||
<!-- DAVACL -->
|
||||
<echo msg="Building Sabre_DAVACL pear package" />
|
||||
<mkdir dir="build/Sabre_DAVACL" />
|
||||
<mkdir dir="build/Sabre_DAVACL/Sabre" />
|
||||
<copy todir="build/Sabre_DAVACL" includeemptydirs="true" >
|
||||
<fileset dir="lib">
|
||||
<include name="Sabre/DAVACL/**" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<mkdir dir="build/Sabre_DAVACL" />
|
||||
<copy todir="build/Sabre_DAVACL">
|
||||
<fileset dir=".">
|
||||
<include name="LICENSE" />
|
||||
<include name="ChangeLog" />
|
||||
<include name="examples/sql/*.principals.sql" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<exec command="bin/pearpackage3.php make Sabre_DAVACL" checkreturn="true" />
|
||||
<exec command="pear package" dir="build/Sabre_DAVACL" checkreturn="true" />
|
||||
|
||||
<!-- CalDAV -->
|
||||
<echo msg="Building Sabre_CalDAV pear package" />
|
||||
<mkdir dir="build/Sabre_CalDAV" />
|
||||
<mkdir dir="build/Sabre_CalDAV/Sabre" />
|
||||
<copy todir="build/Sabre_CalDAV" includeemptydirs="true" >
|
||||
<fileset dir="lib">
|
||||
<include name="Sabre/CalDAV/**" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<mkdir dir="build/Sabre_CalDAV" />
|
||||
<copy todir="build/Sabre_CalDAV">
|
||||
<fileset dir=".">
|
||||
<include name="LICENSE" />
|
||||
<include name="ChangeLog" />
|
||||
<include name="examples/calendarserver.php" />
|
||||
<include name="examples/sql/*.calendars.sql" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<exec command="bin/pearpackage3.php make Sabre_CalDAV" checkreturn="true" />
|
||||
<exec command="pear package" dir="build/Sabre_CalDAV" checkreturn="true" />
|
||||
|
||||
<!-- CardDAV -->
|
||||
<echo msg="Building Sabre_CardDAV pear package" />
|
||||
<mkdir dir="build/Sabre_CardDAV" />
|
||||
<mkdir dir="build/Sabre_CardDAV/Sabre" />
|
||||
<copy todir="build/Sabre_CardDAV" includeemptydirs="true" >
|
||||
<fileset dir="lib">
|
||||
<include name="Sabre/CardDAV/**" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<mkdir dir="build/Sabre_CardDAV" />
|
||||
<copy todir="build/Sabre_CardDAV">
|
||||
<fileset dir=".">
|
||||
<include name="LICENSE" />
|
||||
<include name="ChangeLog" />
|
||||
<include name="examples/addressbookserver.php" />
|
||||
<include name="examples/sql/*.addressbooks.sql" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<exec command="bin/pearpackage3.php make Sabre_CardDAV" checkreturn="true" />
|
||||
<exec command="pear package" dir="build/Sabre_CardDAV" checkreturn="true" />
|
||||
|
||||
<!-- VObject -->
|
||||
<echo msg="Building Sabre_VObject pear package" />
|
||||
<mkdir dir="build/Sabre_VObject" />
|
||||
<mkdir dir="build/Sabre_VObject/Sabre" />
|
||||
<copy todir="build/Sabre_VObject" includeemptydirs="true" >
|
||||
<fileset dir="lib">
|
||||
<include name="Sabre/VObject/**" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<mkdir dir="build/Sabre_VObject" />
|
||||
<copy todir="build/Sabre_VObject">
|
||||
<fileset dir=".">
|
||||
<include name="LICENSE" />
|
||||
<include name="ChangeLog" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<exec command="bin/pearpackage3.php make Sabre_VObject" checkreturn="true" />
|
||||
<exec command="pear package" dir="build/Sabre_VObject" checkreturn="true" />
|
||||
|
||||
<!-- moving tgz files -->
|
||||
<move todir="build">
|
||||
<mapper type="flatten" />
|
||||
<fileset dir="build/">
|
||||
<include name="**/*.tgz" />
|
||||
</fileset>
|
||||
</move>
|
||||
|
||||
|
||||
<echo>Creating combined SabreDAV build</echo>
|
||||
<mkdir dir="build/SabreDAV" />
|
||||
<mkdir dir="build/SabreDAV/lib" />
|
||||
<mkdir dir="build/SabreDAV/lib/Sabre" />
|
||||
<mkdir dir="build/SabreDAV/lib/Sabre/CalDAV" />
|
||||
<mkdir dir="build/SabreDAV/lib/Sabre/DAV" />
|
||||
<mkdir dir="build/SabreDAV/lib/Sabre/DAV/Auth" />
|
||||
<mkdir dir="build/SabreDAV/lib/Sabre/DAV/Locks" />
|
||||
<mkdir dir="build/SabreDAV/lib/Sabre/HTTP" />
|
||||
<mkdir dir="build/SabreDAV/lib/Sabre/VObject" />
|
||||
<mkdir dir="build/SabreDAV/tests" />
|
||||
<mkdir dir="build/SabreDAV/tests/Sabre" />
|
||||
<mkdir dir="build/SabreDAV/tests/Sabre/CalDAV" />
|
||||
<mkdir dir="build/SabreDAV/tests/Sabre/DAV" />
|
||||
<mkdir dir="build/SabreDAV/tests/Sabre/HTTP" />
|
||||
<mkdir dir="build/SabreDAV/tests/Sabre/DAV/Auth" />
|
||||
<mkdir dir="build/SabreDAV/tests/Sabre/DAV/Locks" />
|
||||
<mkdir dir="build/SabreDAV/tests/Sabre/VObject" />
|
||||
<copy todir="build/SabreDAV" includeemptydirs="true">
|
||||
<fileset dir=".">
|
||||
<include name="lib/**/*.php" />
|
||||
<include name="lib/Sabre/DAV/Browser/assets/**" />
|
||||
<include name="ChangeLog" />
|
||||
<include name="LICENSE" />
|
||||
<include name="examples/**.php" />
|
||||
<include name="examples/**/*.sql" />
|
||||
<include name="bin/naturalselection.py" />
|
||||
<include name="bin/migrateto17.php" />
|
||||
<include name="tests/**/*.xml" />
|
||||
<include name="tests/**/*.php" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<mkdir dir="build/SabreDAV/tests/temp" />
|
||||
<zip destfile="build/SabreDAV-${sabredav.version}.zip" basedir="build/SabreDAV" prefix="SabreDAV/" />
|
||||
|
||||
</target>
|
||||
|
||||
<target name="clean" depends="init">
|
||||
<echo msg="Removing build files (cleaning up distribution)" />
|
||||
<delete dir="docs/api" />
|
||||
<delete dir="build" />
|
||||
</target>
|
||||
|
||||
<target name="release" depends="init,clean,test,build">
|
||||
<echo>Creating Git release tag</echo>
|
||||
<exec command="git tag ${sabredav.version}" checkreturn="false" passthru="1" />
|
||||
<echo>Uploading to Google Code</echo>
|
||||
<propertyprompt propertyName="googlecode.username" promptText="Enter your googlecode username" useExistingValue="true" />
|
||||
<propertyprompt propertyName="googlecode.password" promptText="Enter your googlecode password" useExistingValue="true" />
|
||||
<exec command="bin/googlecode_upload.py -s 'SabreDAV ${sabredav.version}' -p sabredav --labels=${sabredav.ucstability} -u '${googlecode.username}' -w '${googlecode.password}' build/SabreDAV-${sabredav.version}.zip" checkreturn="true" />
|
||||
</target>
|
||||
|
||||
<target name="test">
|
||||
<phpunit haltonfailure="1" haltonerror="1" bootstrap="tests/bootstrap.php" haltonskipped="1" printsummary="1">
|
||||
<batchtest>
|
||||
<fileset dir="tests">
|
||||
<include name="**/*.php"/>
|
||||
</fileset>
|
||||
</batchtest>
|
||||
</phpunit>
|
||||
</target>
|
||||
|
||||
<target name="apidocs" depends="init">
|
||||
|
||||
<echo>Creating api documentation using PHP documentor</echo>
|
||||
<echo>Writing to ${sabredav.apidocspath}</echo>
|
||||
<phpdoc title="SabreDAV API documentation"
|
||||
destdir="${sabredav.apidocspath}"
|
||||
sourcecode="false"
|
||||
output="HTML:frames:phphtmllib">
|
||||
|
||||
<fileset dir="./lib">
|
||||
<include name="**/*.php" />
|
||||
</fileset>
|
||||
<projdocfileset dir=".">
|
||||
<include name="ChangeLog" />
|
||||
<include name="LICENSE" />
|
||||
</projdocfileset>
|
||||
|
||||
</phpdoc>
|
||||
|
||||
</target>
|
||||
|
||||
<target name="init">
|
||||
|
||||
<!-- This sets SabreDAV version information -->
|
||||
<adhoc-task name="sabredav-version"><![CDATA[
|
||||
|
||||
class SabreDAV_VersionTask extends Task {
|
||||
|
||||
public function main() {
|
||||
|
||||
include_once 'lib/Sabre/DAV/Version.php';
|
||||
$this->getProject()->setNewProperty('sabredav.version',Sabre_DAV_Version::VERSION);
|
||||
$this->getProject()->setNewProperty('sabredav.stability',Sabre_DAV_Version::STABILITY);
|
||||
$this->getProject()->setNewProperty('sabredav.ucstability',ucwords(Sabre_DAV_Version::STABILITY));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
]]></adhoc-task>
|
||||
<sabredav-version />
|
||||
<echo>SabreDAV version ${sabredav.version}</echo>
|
||||
|
||||
</target>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "evert/sabredav",
|
||||
"type": "library",
|
||||
"description": "WebDAV Framework for PHP",
|
||||
"keywords": ["Framework", "WebDAV", "CalDAV", "CardDAV", "iCalendar"],
|
||||
"homepage": "http://code.google.com/p/sabredav/",
|
||||
"license": "New BSD License",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Evert Pot",
|
||||
"email": "evert@rooftopsolutions.nl",
|
||||
"homepage" : "http://www.rooftopsolutions.nl/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Sabre": "lib/" }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,336 @@
|
|||
|
||||
|
||||
|
||||
Calendar Server Extension C. Daboo
|
||||
Apple
|
||||
May 3, 2007
|
||||
|
||||
|
||||
Calendar Collection Entity Tag (CTag) in CalDAV
|
||||
caldav-ctag-02
|
||||
|
||||
Abstract
|
||||
|
||||
This specification defines an extension to CalDAV that provides a
|
||||
fast way for a client to determine whether the contents of a calendar
|
||||
collection may have changed.
|
||||
|
||||
|
||||
Table of Contents
|
||||
|
||||
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 2
|
||||
2. Conventions Used in This Document . . . . . . . . . . . . . . . 2
|
||||
3. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
|
||||
3.1. Server . . . . . . . . . . . . . . . . . . . . . . . . . . 3
|
||||
3.2. Client . . . . . . . . . . . . . . . . . . . . . . . . . . 3
|
||||
4. New features in CalDAV . . . . . . . . . . . . . . . . . . . . 3
|
||||
4.1. getctag WebDAV Property . . . . . . . . . . . . . . . . . . 4
|
||||
5. Security Considerations . . . . . . . . . . . . . . . . . . . . 4
|
||||
6. IANA Considerations . . . . . . . . . . . . . . . . . . . . . . 5
|
||||
7. Normative References . . . . . . . . . . . . . . . . . . . . . 5
|
||||
Appendix A. Acknowledgments . . . . . . . . . . . . . . . . . . . 5
|
||||
Appendix B. Change History . . . . . . . . . . . . . . . . . . . . 5
|
||||
Author's Address . . . . . . . . . . . . . . . . . . . . . . . . . 6
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 1]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
1. Introduction
|
||||
|
||||
In CalDAV [RFC4791] calendar data is stored in calendar collection
|
||||
resources. Clients need to "poll" calendar collections in order to
|
||||
find out what has changed since the last time they examined it.
|
||||
Currently that involves having to do a PROPFIND Depth:1 HTTP request,
|
||||
or a CALDAV:calendar-query REPORT request. When a calendar
|
||||
collection contains a large number of calendar resources those
|
||||
operations become expensive on the server.
|
||||
|
||||
Calendar users often configure their clients to poll at short time
|
||||
intervals. So polling traffic to the server will be high, even
|
||||
though the frequency at which changes actually occur to a calendar is
|
||||
typically low.
|
||||
|
||||
To improve on performance, this specification defines a new "calendar
|
||||
collection entity tag" (CTag) WebDAV property that is defined on
|
||||
calendar collections. When the calendar collection changes, the CTag
|
||||
value changes. Thus a client can cache the CTag at some point in
|
||||
time, then poll the collection only (i.e. PROPFIND Depth:0 HTTP
|
||||
requests) and determine if a change has happened based on the
|
||||
returned CTag value. If there is a change, it can then fall back to
|
||||
doing the full (Depth:1) poll of the collection to actually determine
|
||||
which resources in the collection changed.
|
||||
|
||||
This extension also defines CTag's on CalDAV scheduling
|
||||
[I-D.desruisseaux-caldav-sched] Inbox and Outbox collections.
|
||||
|
||||
|
||||
2. Conventions Used in This Document
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
||||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
|
||||
document are to be interpreted as described in [RFC2119].
|
||||
|
||||
When XML element types in the namespaces "DAV:" and
|
||||
"urn:ietf:params:xml:ns:caldav" are referenced in this document
|
||||
outside of the context of an XML fragment, the string "DAV:" and
|
||||
"CALDAV:" will be prefixed to the element type names respectively.
|
||||
|
||||
The namespace "http://calendarserver.org/ns/" is used for XML
|
||||
elements defined in this specification. When XML element types in
|
||||
this namespace are referenced in this document outside of the context
|
||||
of an XML fragment, the string "CS:" will be prefixed to the element
|
||||
type names respectively.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 2]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
3. Overview
|
||||
|
||||
3.1. Server
|
||||
|
||||
For each calendar or scheduling Inbox or Outbox collection on the
|
||||
server, a new CS:getctag WebDAV property is present.
|
||||
|
||||
The property value is an "opaque" token whose value is guaranteed to
|
||||
be unique over the lifetime of any calendar or scheduling Inbox or
|
||||
Outbox collection at a specific URI.
|
||||
|
||||
Whenever a calendar resource is added to, modified or deleted from
|
||||
the calendar collection, the value of the CS:getctag property MUST
|
||||
change. Typically this change will occur when the DAV:getetag
|
||||
property on a child resource changes due to some protocol action. It
|
||||
could be the result of a change to the body or properties of the
|
||||
resource.
|
||||
|
||||
3.2. Client
|
||||
|
||||
The client starts off with an empty string as the initial value for
|
||||
the cached CTag of a calendar or scheduling Inbox or Outbox
|
||||
collection that it intends to synchronize with.
|
||||
|
||||
When polling a calendar or scheduling Inbox or Outbox collection, the
|
||||
client issues a PROPFIND Depth:0 HTTP request, asking for the CS:
|
||||
getctag property to be returned.
|
||||
|
||||
If the returned value of CS:getctag property matches the one
|
||||
currently cached for the calendar or scheduling Inbox or Outbox
|
||||
collection, then the collection contents have not changed and no
|
||||
further action is required until the next poll.
|
||||
|
||||
If the returned value of CS:getctag property does not match the one
|
||||
found previously, then the contents of the calendar or scheduling
|
||||
Inbox or Outbox collection have changed. At that point the client
|
||||
should re-issue the PROPFIND Depth:1 request to get the collection
|
||||
changes in detail and the CS:getctag property value corresponding to
|
||||
the new state. The new CSgetctag property value should replace the
|
||||
one currently cached for that calendar or scheduling Inbox or Outbox
|
||||
collection.
|
||||
|
||||
|
||||
4. New features in CalDAV
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 3]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
4.1. getctag WebDAV Property
|
||||
|
||||
Name: getctag
|
||||
|
||||
Namespace: http://calendarserver.org/ns/
|
||||
|
||||
Purpose: Specifies a "synchronization" token used to indicate when
|
||||
the contents of a calendar or scheduling Inbox or Outbox
|
||||
collection have changed.
|
||||
|
||||
Conformance: This property MUST be defined on a calendar or
|
||||
scheduling Inbox or Outbox collection resource. It MUST be
|
||||
protected and SHOULD be returned by a PROPFIND DAV:allprop request
|
||||
(as defined in Section 12.14.1 of [RFC2518]).
|
||||
|
||||
Description: The CS:getctag property allows clients to quickly
|
||||
determine if the contents of a calendar or scheduling Inbox or
|
||||
Outbox collection have changed since the last time a
|
||||
"synchronization" operation was done. The CS:getctag property
|
||||
value MUST change each time the contents of the calendar or
|
||||
scheduling Inbox or Outbox collection change, and each change MUST
|
||||
result in a value that is different from any other used with that
|
||||
collection URI.
|
||||
|
||||
Definition:
|
||||
|
||||
<!ELEMENT getctag #PCDATA>
|
||||
|
||||
Example:
|
||||
|
||||
<T:getctag xmlns:T="http://calendarserver.org/ns/"
|
||||
>ABCD-GUID-IN-THIS-COLLECTION-20070228T122324010340</T:getctag>
|
||||
|
||||
|
||||
5. Security Considerations
|
||||
|
||||
The CS:getctag property value changes whenever any resource in the
|
||||
collection or scheduling Inbox or Outbox changes. Thus a change to a
|
||||
resource that a user does not have read access to will result in a
|
||||
change in the CTag and the user will know that a change occurred.
|
||||
However, that user will not able to get additional details about
|
||||
exactly what changed as WebDAV ACLs [RFC3744] will prevent that. So
|
||||
this does expose the fact that there are potentially "hidden"
|
||||
resources in a calendar collection, but it does not expose any
|
||||
details about them.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 4]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
6. IANA Considerations
|
||||
|
||||
This document does not require any actions on the part of IANA.
|
||||
|
||||
|
||||
7. Normative References
|
||||
|
||||
[I-D.desruisseaux-caldav-sched]
|
||||
Desruisseaux, B., "Scheduling Extensions to CalDAV",
|
||||
draft-desruisseaux-caldav-sched-03 (work in progress),
|
||||
January 2007.
|
||||
|
||||
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
|
||||
Requirement Levels", BCP 14, RFC 2119, March 1997.
|
||||
|
||||
[RFC2518] Goland, Y., Whitehead, E., Faizi, A., Carter, S., and D.
|
||||
Jensen, "HTTP Extensions for Distributed Authoring --
|
||||
WEBDAV", RFC 2518, February 1999.
|
||||
|
||||
[RFC3744] Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web
|
||||
Distributed Authoring and Versioning (WebDAV) Access
|
||||
Control Protocol", RFC 3744, May 2004.
|
||||
|
||||
[RFC4791] Daboo, C., Desruisseaux, B., and L. Dusseault,
|
||||
"Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,
|
||||
March 2007.
|
||||
|
||||
|
||||
Appendix A. Acknowledgments
|
||||
|
||||
This specification is the result of discussions between the Apple
|
||||
calendar server and client teams.
|
||||
|
||||
|
||||
Appendix B. Change History
|
||||
|
||||
Changes from -01:
|
||||
|
||||
1. Updated to RFC4791 reference.
|
||||
|
||||
2. Added text indicating that ctag applies to schedule Inbox and
|
||||
Outbox as well.
|
||||
|
||||
Changes from -00:
|
||||
|
||||
1. Relaxed requirement so that any type of change to a child
|
||||
resource can trigger a CTag change (similar behavior to ETag).
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 5]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
Author's Address
|
||||
|
||||
Cyrus Daboo
|
||||
Apple Inc.
|
||||
1 Infinite Loop
|
||||
Cupertino, CA 95014
|
||||
USA
|
||||
|
||||
Email: cyrus@daboo.name
|
||||
URI: http://www.apple.com/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 6]
|
||||
|
|
@ -0,0 +1,560 @@
|
|||
|
||||
|
||||
|
||||
Calendar Server Extension C. Daboo
|
||||
Apple Computer
|
||||
May 3, 2007
|
||||
|
||||
|
||||
Calendar User Proxy Functionality in CalDAV
|
||||
caldav-cu-proxy-02
|
||||
|
||||
Abstract
|
||||
|
||||
This specification defines an extension to CalDAV that makes it easy
|
||||
for clients to setup and manage calendar user proxies, using the
|
||||
WebDAV Access Control List extension as a basis.
|
||||
|
||||
|
||||
Table of Contents
|
||||
|
||||
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 2
|
||||
2. Conventions Used in This Document . . . . . . . . . . . . . . 2
|
||||
3. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
|
||||
3.1. Server . . . . . . . . . . . . . . . . . . . . . . . . . . 3
|
||||
3.2. Client . . . . . . . . . . . . . . . . . . . . . . . . . . 3
|
||||
4. Open Issues . . . . . . . . . . . . . . . . . . . . . . . . . 4
|
||||
5. New features in CalDAV . . . . . . . . . . . . . . . . . . . . 4
|
||||
5.1. Proxy Principal Resource . . . . . . . . . . . . . . . . . 4
|
||||
5.2. Privilege Provisioning . . . . . . . . . . . . . . . . . . 8
|
||||
6. Security Considerations . . . . . . . . . . . . . . . . . . . 9
|
||||
7. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 9
|
||||
8. Normative References . . . . . . . . . . . . . . . . . . . . . 9
|
||||
Appendix A. Acknowledgments . . . . . . . . . . . . . . . . . . . 9
|
||||
Appendix B. Change History . . . . . . . . . . . . . . . . . . . 10
|
||||
Author's Address . . . . . . . . . . . . . . . . . . . . . . . . . 10
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 1]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
1. Introduction
|
||||
|
||||
CalDAV [RFC4791] provides a way for calendar users to store calendar
|
||||
data and exchange this data via scheduling operations. Based on the
|
||||
WebDAV protocol [RFC2518], it also includes the ability to manage
|
||||
access to calendar data via the WebDAV ACL extension [RFC3744].
|
||||
|
||||
It is often common for a calendar user to delegate some form of
|
||||
responsibility for their calendar and schedules to another calendar
|
||||
user (e.g., a boss allows an assistant to check a calendar or to send
|
||||
and accept scheduling invites on his behalf). The user handling the
|
||||
calendar data on behalf of someone else is often referred to as a
|
||||
"calendar user proxy".
|
||||
|
||||
Whilst CalDAV does have fine-grained access control features that can
|
||||
be used to setup complex sharing and management of calendars, often
|
||||
the proxy behavior required is an "all-or-nothing" approach - i.e.
|
||||
the proxy has access to all the calendars or to no calendars (in
|
||||
which case they are of course not a proxy). So a simple way to
|
||||
manage access to an entire set of calendars and scheduling ability
|
||||
would be handy.
|
||||
|
||||
In addition, calendar user agents will often want to display to a
|
||||
user who has proxy access to their calendars, or to whom they are
|
||||
acting as a proxy. Again, CalDAV's access control discovery and
|
||||
report features can be used to do that, but with fine-grained control
|
||||
that exists, it can be hard to tell who is a "real" proxy as opposed
|
||||
to someone just granted rights to some subset of calendars. Again, a
|
||||
simple way to discover proxy information would be handy.
|
||||
|
||||
|
||||
2. Conventions Used in This Document
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
||||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
|
||||
document are to be interpreted as described in [RFC2119].
|
||||
|
||||
When XML element types in the namespace "DAV:" are referenced in this
|
||||
document outside of the context of an XML fragment, the string "DAV:"
|
||||
will be prefixed to the element type names.
|
||||
|
||||
When XML element types in the namespaces "DAV:" and
|
||||
"urn:ietf:params:xml:ns:caldav" are referenced in this document
|
||||
outside of the context of an XML fragment, the string "DAV:" and
|
||||
"CALDAV:" will be prefixed to the element type names respectively.
|
||||
|
||||
The namespace "http://calendarserver.org/ns/" is used for XML
|
||||
elements defined in this specification. When XML element types in
|
||||
|
||||
|
||||
|
||||
Daboo [Page 2]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
this namespace are referenced in this document outside of the context
|
||||
of an XML fragment, the string "CS:" will be prefixed to the element
|
||||
type names respectively.
|
||||
|
||||
|
||||
3. Overview
|
||||
|
||||
3.1. Server
|
||||
|
||||
For each calendar user principal on the server, the server will
|
||||
generate two group principals - "proxy groups". One is used to hold
|
||||
the list of principals who have read-only proxy access to the main
|
||||
principal's calendars, the other holds the list of principals who
|
||||
have read-write and scheduling proxy access. NB these new group
|
||||
principals would have no equivalent in Open Directory.
|
||||
|
||||
Privileges on each "proxy group" principal will be set so that the
|
||||
"owner" has the ability to change property values.
|
||||
|
||||
The "proxy group" principals will be child resources of the user
|
||||
principal resource with specific resource types and thus are easy to
|
||||
discover. As a result the user principal resources will also be
|
||||
collection resources.
|
||||
|
||||
When provisioning the calendar user home collection, the server will:
|
||||
|
||||
a. Add an ACE to the calendar home collection giving the read-only
|
||||
"proxy group" inheritable read access.
|
||||
|
||||
b. Add an ACE to the calendar home collection giving the read-write
|
||||
"proxy group" inheritable read-write access.
|
||||
|
||||
c. Add an ACE to each of the calendar Inbox and Outbox collections
|
||||
giving the CALDAV:schedule privilege
|
||||
[I-D.desruisseaux-caldav-sched] to the read-write "proxy group".
|
||||
|
||||
3.2. Client
|
||||
|
||||
A client can see who the proxies are for the current principal by
|
||||
examining the principal resource for the two "proxy group" properties
|
||||
and then looking at the DAV:group-member-set property of each.
|
||||
|
||||
The client can edit the list of proxies for the current principal by
|
||||
editing the DAV:group-member-set property on the relevant "proxy
|
||||
group" principal resource.
|
||||
|
||||
The client can find out who the current principal is a proxy for by
|
||||
running a DAV:principal-match REPORT on the principal collection.
|
||||
|
||||
|
||||
|
||||
Daboo [Page 3]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
Alternatively, the client can find out who the current principal is a
|
||||
proxy for by examining the DAV:group-membership property on the
|
||||
current principal resource looking for membership in other users'
|
||||
"proxy groups".
|
||||
|
||||
|
||||
4. Open Issues
|
||||
|
||||
1. Do we want to separate read-write access to calendars vs the
|
||||
ability to schedule as a proxy?
|
||||
|
||||
2. We may want to restrict changing properties on the proxy group
|
||||
collections to just the DAV:group-member-set property?
|
||||
|
||||
3. There is no way for a proxy to be able to manage the list of
|
||||
proxies. We could allow the main calendar user DAV:write-acl on
|
||||
their "proxy group" principals, in which case they could grant
|
||||
others the right to modify the group membership.
|
||||
|
||||
4. Should the "proxy group" principals also be collections given
|
||||
that the regular principal resources will be?
|
||||
|
||||
|
||||
5. New features in CalDAV
|
||||
|
||||
5.1. Proxy Principal Resource
|
||||
|
||||
Each "regular" principal resource that needs to allow calendar user
|
||||
proxy support MUST be a collection resource. i.e. in addition to
|
||||
including the DAV:principal XML element in the DAV:resourcetype
|
||||
property on the resource, it MUST also include the DAV:collection XML
|
||||
element.
|
||||
|
||||
Each "regular" principal resource MUST contain two child resources
|
||||
with names "calendar-proxy-read" and "calendar-proxy-write" (note
|
||||
that these are only suggested names - the server could choose any
|
||||
unique name for these). These resources are themselves principal
|
||||
resources that are groups contain the list of principals for calendar
|
||||
users who can act as a read-only or read-write proxy respectively.
|
||||
|
||||
The server MUST include the CS:calendar-proxy-read or CS:calendar-
|
||||
proxy-write XML elements in the DAV:resourcetype property of the
|
||||
child resources, respectively. This allows clients to discover the
|
||||
"proxy group" principals by using a PROPFIND, Depth:1 request on the
|
||||
current user's principal resource and requesting the DAV:resourcetype
|
||||
property be returned. The element type declarations are:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 4]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
<!ELEMENT calendar-proxy-read EMPTY>
|
||||
|
||||
<!ELEMENT calendar-proxy-write EMPTY>
|
||||
|
||||
The server MUST allow the "parent" principal to change the DAV:group-
|
||||
member-set property on each of the "child" "proxy group" principal
|
||||
resources. When a principal is listed as a member of the "child"
|
||||
resource, the server MUST include the "child" resource URI in the
|
||||
DAV:group-membership property on the included principal resource.
|
||||
Note that this is just "normal" behavior for a group principal.
|
||||
|
||||
An example principal resource layout might be:
|
||||
|
||||
+ /
|
||||
+ principals/
|
||||
+ users/
|
||||
+ cyrus/
|
||||
calendar-proxy-read
|
||||
calendar-proxy-write
|
||||
+ red/
|
||||
calendar-proxy-read
|
||||
calendar-proxy-write
|
||||
+ wilfredo/
|
||||
calendar-proxy-read
|
||||
calendar-proxy-write
|
||||
|
||||
If the principal "cyrus" wishes to have the principal "red" act as a
|
||||
calendar user proxy on his behalf and have the ability to change
|
||||
items on his calendar or schedule meetings on his behalf, then he
|
||||
would add the principal resource URI for "red" to the DAV:group-
|
||||
member-set property of the principal resource /principals/users/
|
||||
cyrus/calendar-proxy-write, giving:
|
||||
|
||||
<DAV:group-member-set>
|
||||
<DAV:href>/principals/users/red/</DAV:href>
|
||||
</DAV:group-member-set>
|
||||
|
||||
The DAV:group-membership property on the resource /principals/users/
|
||||
red/ would be:
|
||||
|
||||
<DAV:group-membership>
|
||||
<DAV:href>/principals/users/cyrus/calendar-proxy-write</DAV:href>
|
||||
</DAV:group-membership>
|
||||
|
||||
If the principal "red" was also a read-only proxy for the principal
|
||||
"wilfredo", then the DA:group-membership property on the resource
|
||||
/principals/users/red/ would be:
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 5]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
<DAV:group-membership>
|
||||
<DAV:href>/principals/users/cyrus/calendar-proxy-write</DAV:href>
|
||||
<DAV:href>/principals/users/wilfredo/calendar-proxy-read</DAV:href>
|
||||
</DAV:group-membership>
|
||||
|
||||
Thus a client can discover to which principals a particular principal
|
||||
is acting as a calendar user proxy for by examining the DAV:group-
|
||||
membership property.
|
||||
|
||||
An alternative to discovering which principals a user can proxy as is
|
||||
to use the WebDAV ACL principal-match report, targeted at the
|
||||
principal collections available on the server.
|
||||
|
||||
Example:
|
||||
|
||||
>> Request <<
|
||||
|
||||
REPORT /principals/ HTTP/1.1
|
||||
Host: cal.example.com
|
||||
Depth: 0
|
||||
Content-Type: application/xml; charset="utf-8"
|
||||
Content-Length: xxxx
|
||||
Authorization: Digest username="red",
|
||||
realm="cal.example.com", nonce="...",
|
||||
uri="/principals/", response="...", opaque="..."
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:principal-match xmlns:D="DAV:">
|
||||
<D:self/>
|
||||
<D:prop>
|
||||
<D:resourcetype/>
|
||||
</D:prop>
|
||||
</D:principal-match>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 6]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
>> Response <<
|
||||
|
||||
HTTP/1.1 207 Multi-Status
|
||||
Date: Fri, 10 Nov 2006 09:32:12 GMT
|
||||
Content-Type: application/xml; charset="utf-8"
|
||||
Content-Length: xxxx
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:multistatus xmlns:D="DAV:"
|
||||
xmlns:A="http://calendarserver.org/ns/">
|
||||
<D:response>
|
||||
<D:href>/principals/users/red/</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype>
|
||||
<D:principal/>
|
||||
<D:collection/>
|
||||
</D:resourcetype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/principals/users/cyrus/calendar-proxy-write</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype>
|
||||
<D:principal/>
|
||||
<A:calendar-proxy-write/>
|
||||
</D:resourcetype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/principals/users/wilfredo/calendar-proxy-read</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype>
|
||||
<D:principal/>
|
||||
<A:calendar-proxy-read/>
|
||||
</D:resourcetype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
</D:multistatus>
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 7]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
5.2. Privilege Provisioning
|
||||
|
||||
In order for a calendar user proxy to be able to access the calendars
|
||||
of the user they are proxying for the server MUST ensure that the
|
||||
privileges on the relevant calendars are setup accordingly:
|
||||
|
||||
The DAV:read privilege MUST be granted for read-only and read-
|
||||
write calendar user proxy principals
|
||||
|
||||
The DAV:write privilege MUST be granted for read-write calendar
|
||||
user proxy principals.
|
||||
|
||||
Additionally, the CalDAV scheduling Inbox and Outbox calendar
|
||||
collections for the user allowing proxy access, MUST have the CALDAV:
|
||||
schedule privilege [I-D.desruisseaux-caldav-sched] granted for read-
|
||||
write calendar user proxy principals.
|
||||
|
||||
Note that with a suitable repository layout, a server may be able to
|
||||
grant the appropriate privileges on a parent collection and ensure
|
||||
that all the contained collections and resources inherit that. For
|
||||
example, given the following repository layout:
|
||||
|
||||
+ /
|
||||
+ calendars/
|
||||
+ users/
|
||||
+ cyrus/
|
||||
inbox
|
||||
outbox
|
||||
home
|
||||
work
|
||||
+ red/
|
||||
inbox
|
||||
outbox
|
||||
work
|
||||
soccer
|
||||
+ wilfredo/
|
||||
inbox
|
||||
outbox
|
||||
home
|
||||
work
|
||||
flying
|
||||
|
||||
In order for the principal "red" to act as a read-write proxy for the
|
||||
principal "cyrus", the following WebDAV ACE will need to be granted
|
||||
on the resource /calendars/users/cyrus/ and all children of that
|
||||
resource:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 8]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
<DAV:ace>
|
||||
<DAV:principal>
|
||||
<DAV:href>/principals/users/cyrus/calendar-proxy-write</DAV:href>
|
||||
</DAV:principal>
|
||||
<DAV:privileges>
|
||||
<DAV:grant><DAV:read/><DAV:write/></DAV:grant>
|
||||
</DAV:privileges>
|
||||
</DAV:ace>
|
||||
|
||||
|
||||
6. Security Considerations
|
||||
|
||||
TBD
|
||||
|
||||
|
||||
7. IANA Considerations
|
||||
|
||||
This document does not require any actions on the part of IANA.
|
||||
|
||||
|
||||
8. Normative References
|
||||
|
||||
[I-D.desruisseaux-caldav-sched]
|
||||
Desruisseaux, B., "Scheduling Extensions to CalDAV",
|
||||
draft-desruisseaux-caldav-sched-03 (work in progress),
|
||||
January 2007.
|
||||
|
||||
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
|
||||
Requirement Levels", BCP 14, RFC 2119, March 1997.
|
||||
|
||||
[RFC2518] Goland, Y., Whitehead, E., Faizi, A., Carter, S., and D.
|
||||
Jensen, "HTTP Extensions for Distributed Authoring --
|
||||
WEBDAV", RFC 2518, February 1999.
|
||||
|
||||
[RFC3744] Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web
|
||||
Distributed Authoring and Versioning (WebDAV) Access
|
||||
Control Protocol", RFC 3744, May 2004.
|
||||
|
||||
[RFC4791] Daboo, C., Desruisseaux, B., and L. Dusseault,
|
||||
"Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,
|
||||
March 2007.
|
||||
|
||||
|
||||
Appendix A. Acknowledgments
|
||||
|
||||
This specification is the result of discussions between the Apple
|
||||
calendar server and client teams.
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 9]
|
||||
|
||||
CalDAV Proxy May 2007
|
||||
|
||||
|
||||
Appendix B. Change History
|
||||
|
||||
Changes from -00:
|
||||
|
||||
1. Updated to RFC 4791 reference.
|
||||
|
||||
Changes from -00:
|
||||
|
||||
1. Added more details on actual CalDAV protocol changes.
|
||||
|
||||
2. Changed namespace from http://apple.com/ns/calendarserver/ to
|
||||
http://calendarserver.org/ns/.
|
||||
|
||||
3. Made "proxy group" principals child resources of their "owner"
|
||||
principals.
|
||||
|
||||
4. The "proxy group" principals now have their own resourcetype.
|
||||
|
||||
|
||||
Author's Address
|
||||
|
||||
Cyrus Daboo
|
||||
Apple Computer, Inc.
|
||||
1 Infinite Loop
|
||||
Cupertino, CA 95014
|
||||
USA
|
||||
|
||||
Email: cyrus@daboo.name
|
||||
URI: http://www.apple.com/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo [Page 10]
|
||||
|
|
@ -0,0 +1,560 @@
|
|||
|
||||
|
||||
|
||||
Network Working Group C. Daboo
|
||||
Internet-Draft Apple Inc.
|
||||
Updates: XXXX-CardDAV August 24, 2010
|
||||
(if approved)
|
||||
Intended status: Standards Track
|
||||
Expires: February 25, 2011
|
||||
|
||||
|
||||
CardDAV Directory Gateway Extension
|
||||
draft-daboo-carddav-directory-gateway-02
|
||||
|
||||
Abstract
|
||||
|
||||
This document defines an extension to the vCard Extensions to WebDAV
|
||||
(CardDAV) protocol that allows a server to expose a directory as a
|
||||
read-only address book collection.
|
||||
|
||||
Status of this Memo
|
||||
|
||||
This Internet-Draft is submitted in full conformance with the
|
||||
provisions of BCP 78 and BCP 79.
|
||||
|
||||
Internet-Drafts are working documents of the Internet Engineering
|
||||
Task Force (IETF). Note that other groups may also distribute
|
||||
working documents as Internet-Drafts. The list of current Internet-
|
||||
Drafts is at http://datatracker.ietf.org/drafts/current/.
|
||||
|
||||
Internet-Drafts are draft documents valid for a maximum of six months
|
||||
and may be updated, replaced, or obsoleted by other documents at any
|
||||
time. It is inappropriate to use Internet-Drafts as reference
|
||||
material or to cite them other than as "work in progress."
|
||||
|
||||
This Internet-Draft will expire on February 25, 2011.
|
||||
|
||||
Copyright Notice
|
||||
|
||||
Copyright (c) 2010 IETF Trust and the persons identified as the
|
||||
document authors. All rights reserved.
|
||||
|
||||
This document is subject to BCP 78 and the IETF Trust's Legal
|
||||
Provisions Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info) in effect on the date of
|
||||
publication of this document. Please review these documents
|
||||
carefully, as they describe your rights and restrictions with respect
|
||||
to this document. Code Components extracted from this document must
|
||||
include Simplified BSD License text as described in Section 4.e of
|
||||
the Trust Legal Provisions and are provided without warranty as
|
||||
described in the Simplified BSD License.
|
||||
|
||||
|
||||
|
||||
Daboo Expires February 25, 2011 [Page 1]
|
||||
|
||||
Internet-Draft CardDAV Directory Gateway Extension August 2010
|
||||
|
||||
|
||||
Table of Contents
|
||||
|
||||
1. Introduction and Overview . . . . . . . . . . . . . . . . . . 3
|
||||
2. Conventions . . . . . . . . . . . . . . . . . . . . . . . . . 3
|
||||
3. CARDDAV:directory-gateway Property . . . . . . . . . . . . . . 4
|
||||
4. XML Element Definitions . . . . . . . . . . . . . . . . . . . 5
|
||||
4.1. CARDDAV:directory . . . . . . . . . . . . . . . . . . . . 5
|
||||
5. Client Guidelines . . . . . . . . . . . . . . . . . . . . . . 5
|
||||
6. Server Guidelines . . . . . . . . . . . . . . . . . . . . . . 6
|
||||
7. Security Considerations . . . . . . . . . . . . . . . . . . . 7
|
||||
8. IANA Consideration . . . . . . . . . . . . . . . . . . . . . . 8
|
||||
9. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 8
|
||||
10. References . . . . . . . . . . . . . . . . . . . . . . . . . . 8
|
||||
10.1. Normative References . . . . . . . . . . . . . . . . . . . 8
|
||||
10.2. Informative References . . . . . . . . . . . . . . . . . . 9
|
||||
Appendix A. Change History (to be removed prior to
|
||||
publication as an RFC) . . . . . . . . . . . . . . . 9
|
||||
Author's Address . . . . . . . . . . . . . . . . . . . . . . . . . 10
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Expires February 25, 2011 [Page 2]
|
||||
|
||||
Internet-Draft CardDAV Directory Gateway Extension August 2010
|
||||
|
||||
|
||||
1. Introduction and Overview
|
||||
|
||||
The CardDAV [I-D.ietf-vcarddav-carddav] protocol defines a standard
|
||||
way of accessing, managing, and sharing contact information based on
|
||||
the vCard [RFC2426] format. Often, in an enterprise or service
|
||||
provider environment, a directory of all users hosted on the server
|
||||
(or elsewhere) is available (for example via Lightweight Directory
|
||||
Access Protocol (LDAP) [RFC4510] or some direct database access). It
|
||||
would be convenient for CardDAV clients if this directory were
|
||||
exposed as a "global" address book on the CardDAV server so it could
|
||||
be searched in the same way as personal address books are. This
|
||||
specification defines a "directory gateway" feature extension to
|
||||
CardDAV to enable this.
|
||||
|
||||
This specification adds one new WebDAV property to principal
|
||||
resources that contains the URL to one or more directory gateway
|
||||
address book collection resources. It is important for clients to be
|
||||
able to distinguish this address book collection from others because
|
||||
there are specific limitations involved in using it as described
|
||||
below. To aid that, this specification defines an XML element that
|
||||
can be included as a child element of the DAV:resourcetype property
|
||||
of address book collections to identify them as directory gateways.
|
||||
|
||||
Note that this feature is in no way intended to replace full
|
||||
directory access - it is meant to simply provide a convenient way for
|
||||
CardDAV clients to query contact-related attributes in directory
|
||||
records.
|
||||
|
||||
|
||||
2. Conventions
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
||||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
|
||||
document are to be interpreted as described in [RFC2119].
|
||||
|
||||
The term "protected" is used in the Conformance field of property
|
||||
definitions as defined in Section 15 of [RFC4918].
|
||||
|
||||
This document uses XML DTD fragments ([W3C.REC-xml-20081126], Section
|
||||
3.2) as a purely notational convention. WebDAV request and response
|
||||
bodies cannot be validated by a DTD due to the specific extensibility
|
||||
rules defined in Section 17 of [RFC4918] and due to the fact that all
|
||||
XML elements defined by this specification use the XML namespace name
|
||||
"DAV:". In particular:
|
||||
|
||||
1. element names use the "DAV:" namespace,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Expires February 25, 2011 [Page 3]
|
||||
|
||||
Internet-Draft CardDAV Directory Gateway Extension August 2010
|
||||
|
||||
|
||||
2. element ordering is irrelevant unless explicitly stated,
|
||||
|
||||
3. extension elements (elements not already defined as valid child
|
||||
elements) may be added anywhere, except when explicitly stated
|
||||
otherwise,
|
||||
|
||||
4. extension attributes (attributes not already defined as valid for
|
||||
this element) may be added anywhere, except when explicitly
|
||||
stated otherwise.
|
||||
|
||||
When XML element types in the namespaces "DAV:" and
|
||||
"urn:ietf:params:xml:ns:carddav" are referenced in this document
|
||||
outside of the context of an XML fragment, the strings "DAV:" and
|
||||
"CARDDAV:" will be prefixed to the element types, respectively.
|
||||
|
||||
|
||||
3. CARDDAV:directory-gateway Property
|
||||
|
||||
Name: directory-gateway
|
||||
|
||||
Namespace: urn:ietf:params:xml:ns:carddav
|
||||
|
||||
Purpose: Identifies URLs of CardDAV address book collections acting
|
||||
as a directory gateway for the server.
|
||||
|
||||
Protected: MUST be protected.
|
||||
|
||||
allprop behavior: SHOULD NOT be returned by a PROPFIND DAV:allprop
|
||||
request.
|
||||
|
||||
Description: The CARDDAV:directory-gateway identifies address book
|
||||
collection resources that are directory gateway address books for
|
||||
the server.
|
||||
|
||||
Definition:
|
||||
|
||||
<!ELEMENT directory-gateway (DAV:href*)>
|
||||
|
||||
Example:
|
||||
|
||||
<C:directory-gateway xmlns:D="DAV:"
|
||||
xmlns:C="urn:ietf:params:xml:ns:carddav">
|
||||
<D:href>/directory</D:href>
|
||||
</C:directory-gateway>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Expires February 25, 2011 [Page 4]
|
||||
|
||||
Internet-Draft CardDAV Directory Gateway Extension August 2010
|
||||
|
||||
|
||||
4. XML Element Definitions
|
||||
|
||||
4.1. CARDDAV:directory
|
||||
|
||||
Name: directory
|
||||
|
||||
Namespace: urn:ietf:params:xml:ns:carddav
|
||||
|
||||
Purpose: Used to indicate that an address book collection is a
|
||||
directory gateway.
|
||||
|
||||
Description: This element appears in the DAV:resourcetype property
|
||||
on a address book collection resources that are directory
|
||||
gateways. Clients can use the presence of this element to
|
||||
identify directory gateway collections when doing PROPFINDs to
|
||||
list collection contents.
|
||||
|
||||
Definition:
|
||||
|
||||
<!ELEMENT directory EMPTY>
|
||||
|
||||
Example:
|
||||
|
||||
<D:resourcetype xmlns:D="DAV:"
|
||||
xmlns:C="urn:ietf:params:xml:ns:carddav">
|
||||
<D:collection/>
|
||||
<C:addressbook/>
|
||||
<C:directory/>
|
||||
</D:resourcetype>
|
||||
|
||||
|
||||
5. Client Guidelines
|
||||
|
||||
Clients wishing to make use of directory gateway address books can
|
||||
request the CARDDAV:directory-gateway property (Section 3) when
|
||||
examining other properties on the principal resource for the user.
|
||||
If the property is not present, then the directory gateway feature is
|
||||
not supported by the server at that time.
|
||||
|
||||
Clients can also detect the presence of directory gateway address
|
||||
book collections by retrieving the DAV:resourcetype property on
|
||||
collections that it lists, and look for the presence of the CARDDAV:
|
||||
directory element (Section 4.1).
|
||||
|
||||
Since the directory being exposed via a directory gateway address
|
||||
book collection could be large, clients SHOULD limit the number of
|
||||
results returned in an CARDDAV:addressbook-query REPORT as defined in
|
||||
Section 8.6.1 of [I-D.ietf-vcarddav-carddav].
|
||||
|
||||
|
||||
|
||||
Daboo Expires February 25, 2011 [Page 5]
|
||||
|
||||
Internet-Draft CardDAV Directory Gateway Extension August 2010
|
||||
|
||||
|
||||
Clients MUST treat the directory gateway address book collection as a
|
||||
read-only collection, so HTTP methods that modify resource data or
|
||||
properties in the address book collection MUST NOT be used.
|
||||
|
||||
Clients SHOULD NOT attempt to cache the entire contents of the
|
||||
directory gateway address book collection resource by retrieving all
|
||||
resources, or trying to examine all the properties of all resources
|
||||
(e.g., via a PROPFIND Depth:1 request). Instead, CARDDAV:
|
||||
addressbook-query REPORTs are used to search for specific address
|
||||
book object resources, and CARDDAV:multiget REPORTs and individual
|
||||
GET requests can be made to retrieve the actual vCard data for
|
||||
address book object resources found via a query.
|
||||
|
||||
When presenting directory gateway collections to the user, clients
|
||||
SHOULD use the DAV:displayname property on the corresponding address
|
||||
book collections as the name of the directory gateway. This is
|
||||
important in the case where more than one directory gateway is
|
||||
available. Clients MAY also provide descriptive information about
|
||||
each directory gateway by examining the CARDDAV:addressbook-
|
||||
description property (see Section 6.2.1 of
|
||||
[I-D.ietf-vcarddav-carddav]) on the resource.
|
||||
|
||||
|
||||
6. Server Guidelines
|
||||
|
||||
Servers wishing to expose a directory gateway as an address book
|
||||
collection MUST include the CARDDAV:directory-gateway property on all
|
||||
principal resources of users expected to use the feature.
|
||||
|
||||
Since the directory being exposed via the directory gateway address
|
||||
book collection could be large, servers SHOULD truncate the number of
|
||||
results returned in an CARDDAV:addressbook-query REPORT as defined in
|
||||
Section 8.6.2 of [I-D.ietf-vcarddav-carddav]. In addition, servers
|
||||
SHOULD disallow requests that effectively enumerate the collection
|
||||
contents (e.g., PROPFIND Depth:1, trivial CARDDAV:addressbook-query,
|
||||
DAV:sync-collection REPORT).
|
||||
|
||||
Servers need to expose the directory information as a set of address
|
||||
book object resources in the directory gateway address book
|
||||
collection resource. To do that, a mapping between the directory
|
||||
record format and the vCard data has to be applied. In general, only
|
||||
directory record attributes that have a direct equivalent in vCard
|
||||
SHOULD be mapped. It is up to individual implementations to
|
||||
determine which attributes to map. But in all cases servers MUST
|
||||
generate valid vCard data as returned to the client. In addition, as
|
||||
required by CardDAV, the UID vCard property MUST be present in the
|
||||
vCard data, and this value MUST be persistent from query to query for
|
||||
the same directory record.
|
||||
|
||||
|
||||
|
||||
Daboo Expires February 25, 2011 [Page 6]
|
||||
|
||||
Internet-Draft CardDAV Directory Gateway Extension August 2010
|
||||
|
||||
|
||||
Multiple directory sources could be available to the server. The
|
||||
server MAY use a single directory gateway resource to aggregate
|
||||
results from each directory source. When doing so care is needed
|
||||
when dealing with potential records that refer to the same entity.
|
||||
Servers MAY suppress any duplicates that they are able to determine
|
||||
themselves. Alternatively, multiple directory sources can be exposed
|
||||
as separate directory gateway resources.
|
||||
|
||||
For any directory source, a server MAY expose multiple directory
|
||||
gateway resources where each represents a different query "scope" for
|
||||
the directory. Different scopes MAY be offered to different
|
||||
principals on the server. For example, the server might expose an
|
||||
entire company directory for searching as the resource "/directory-
|
||||
all" to all principals, but then provide "/directory-department-XYZ"
|
||||
as another directory gateway that has a search scope that implicitly
|
||||
limits the search results to just the "XYZ" department. Users in
|
||||
that department would then have a CARDDAV:directory-gateway property
|
||||
on their principal resource that included the "/directory-department-
|
||||
XYZ" resource. Users in other departments would have corresponding
|
||||
directory gateway resources available to them.
|
||||
|
||||
Records in a directory can include data for more than just people,
|
||||
e.g, resources such as rooms or projectors, groups, computer systems
|
||||
etc. It is up to individual implementations to determine the most
|
||||
appropriate "scope" for the data returned via the directory gateway
|
||||
by filtering the appropriate record types. As above, servers could
|
||||
choose to expose people and resources under different directory
|
||||
gateway resources by implicitly limiting the search "scope" for each
|
||||
of those.
|
||||
|
||||
Servers MAY apply implementation defined access rules to determine,
|
||||
on a per-user basis, what records are returned to a particularly user
|
||||
and the content of those records exposed via vCard data. This per-
|
||||
user behavior is in addition to the general security requirements
|
||||
detailed below.
|
||||
|
||||
When multiple directory gateway collections are present, servers
|
||||
SHOULD provide a DAV:displayname property on each that disambiguates
|
||||
them. Servers MAY include a CARDDAV:addressbook-description property
|
||||
(see Section 6.2.1 of [I-D.ietf-vcarddav-carddav]) on each directory
|
||||
gateway resource to provide a description of the directory and any
|
||||
search "scope" that might be used, or any other useful information
|
||||
for users.
|
||||
|
||||
|
||||
7. Security Considerations
|
||||
|
||||
Servers MUST ensure that client requests against the directory
|
||||
|
||||
|
||||
|
||||
Daboo Expires February 25, 2011 [Page 7]
|
||||
|
||||
Internet-Draft CardDAV Directory Gateway Extension August 2010
|
||||
|
||||
|
||||
gateway address book collection cannot use excessive resources (CPU,
|
||||
memory, network bandwidth etc), given that the directory could be
|
||||
large.
|
||||
|
||||
Servers MUST take care not to expose sensitive directory record
|
||||
attributes in the vCard data via the directory gateway address book.
|
||||
In general only those properties that have direct correspondence in
|
||||
vCard SHOULD be exposed.
|
||||
|
||||
Servers need to determine whether it is appropriate for the directory
|
||||
information to be available via CardDAV to unauthenticated users. If
|
||||
not, servers MUST ensure that unauthenticated users do not have
|
||||
access to the directory gateway address book object resource and its
|
||||
contents. If unauthenticated access is allowed, servers MAY choose
|
||||
to limit the set of vCard properties that are searchable or returned
|
||||
in the address book object resources when unauthenticated requests
|
||||
are made.
|
||||
|
||||
|
||||
8. IANA Consideration
|
||||
|
||||
This document does not require any actions on the part of IANA.
|
||||
|
||||
|
||||
9. Acknowledgments
|
||||
|
||||
|
||||
10. References
|
||||
|
||||
10.1. Normative References
|
||||
|
||||
[I-D.ietf-vcarddav-carddav]
|
||||
Daboo, C., "vCard Extensions to WebDAV (CardDAV)",
|
||||
draft-ietf-vcarddav-carddav-10 (work in progress),
|
||||
November 2009.
|
||||
|
||||
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
|
||||
Requirement Levels", BCP 14, RFC 2119, March 1997.
|
||||
|
||||
[RFC2426] Dawson, F. and T. Howes, "vCard MIME Directory Profile",
|
||||
RFC 2426, September 1998.
|
||||
|
||||
[RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed
|
||||
Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
|
||||
|
||||
[W3C.REC-xml-20081126]
|
||||
Paoli, J., Yergeau, F., Bray, T., Sperberg-McQueen, C.,
|
||||
and E. Maler, "Extensible Markup Language (XML) 1.0 (Fifth
|
||||
|
||||
|
||||
|
||||
Daboo Expires February 25, 2011 [Page 8]
|
||||
|
||||
Internet-Draft CardDAV Directory Gateway Extension August 2010
|
||||
|
||||
|
||||
Edition)", World Wide Web Consortium Recommendation REC-
|
||||
xml-20081126, November 2008,
|
||||
<http://www.w3.org/TR/2008/REC-xml-20081126>.
|
||||
|
||||
10.2. Informative References
|
||||
|
||||
[RFC4510] Zeilenga, K., "Lightweight Directory Access Protocol
|
||||
(LDAP): Technical Specification Road Map", RFC 4510,
|
||||
June 2006.
|
||||
|
||||
|
||||
Appendix A. Change History (to be removed prior to publication as an
|
||||
RFC)
|
||||
|
||||
Changes in -02
|
||||
|
||||
1. Added CARDDAV:directory element for use in DAV:resourcetype
|
||||
|
||||
2. Allow CARDDAV:directory-gateway to be multi-valued
|
||||
|
||||
3. Explain how a server could implicit "scope" queries on different
|
||||
directory gateway resources
|
||||
|
||||
Changes in -01
|
||||
|
||||
1. Remove duplicated text in a couple of sections
|
||||
|
||||
2. Add example of LDAP/generic database as possible directory
|
||||
"sources"
|
||||
|
||||
3. Add text to explain why the client needs to treat this as special
|
||||
and thus the need for a property
|
||||
|
||||
4. Added text to server guidelines indicating requirements for
|
||||
handling vCard UID properties
|
||||
|
||||
5. Added text to server guidelines explain that different record
|
||||
"types" may exist in the directory and the server is free to
|
||||
filter those as appropriate
|
||||
|
||||
6. Added text to server guidelines indicating that server are free
|
||||
to aggregate directory records from multiple sources
|
||||
|
||||
7. Added text to server guidelines indicating that servers are free
|
||||
to apply implementation defined access control to the returned
|
||||
data on a per-user basis
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Expires February 25, 2011 [Page 9]
|
||||
|
||||
Internet-Draft CardDAV Directory Gateway Extension August 2010
|
||||
|
||||
|
||||
Author's Address
|
||||
|
||||
Cyrus Daboo
|
||||
Apple Inc.
|
||||
1 Infinite Loop
|
||||
Cupertino, CA 95014
|
||||
USA
|
||||
|
||||
Email: cyrus@daboo.name
|
||||
URI: http://www.apple.com/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Expires February 25, 2011 [Page 10]
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,560 @@
|
|||
|
||||
|
||||
|
||||
Network Working Group M. Nottingham
|
||||
Internet-Draft Rackspace
|
||||
Updates: 2616 (if approved) R. Fielding
|
||||
Intended status: Standards Track Adobe
|
||||
Expires: August 7, 2012 February 4, 2012
|
||||
|
||||
|
||||
Additional HTTP Status Codes
|
||||
draft-nottingham-http-new-status-04
|
||||
|
||||
Abstract
|
||||
|
||||
This document specifies additional HyperText Transfer Protocol (HTTP)
|
||||
status codes for a variety of common situations.
|
||||
|
||||
Editorial Note (To be removed by RFC Editor before publication)
|
||||
|
||||
Distribution of this document is unlimited. Although this is not a
|
||||
work item of the HTTPbis Working Group, comments should be sent to
|
||||
the Hypertext Transfer Protocol (HTTP) mailing list at
|
||||
ietf-http-wg@w3.org [1], which may be joined by sending a message
|
||||
with subject "subscribe" to ietf-http-wg-request@w3.org [2].
|
||||
|
||||
Discussions of the HTTPbis Working Group are archived at
|
||||
<http://lists.w3.org/Archives/Public/ietf-http-wg/>.
|
||||
|
||||
Status of this Memo
|
||||
|
||||
This Internet-Draft is submitted in full conformance with the
|
||||
provisions of BCP 78 and BCP 79.
|
||||
|
||||
Internet-Drafts are working documents of the Internet Engineering
|
||||
Task Force (IETF). Note that other groups may also distribute
|
||||
working documents as Internet-Drafts. The list of current Internet-
|
||||
Drafts is at http://datatracker.ietf.org/drafts/current/.
|
||||
|
||||
Internet-Drafts are draft documents valid for a maximum of six months
|
||||
and may be updated, replaced, or obsoleted by other documents at any
|
||||
time. It is inappropriate to use Internet-Drafts as reference
|
||||
material or to cite them other than as "work in progress."
|
||||
|
||||
This Internet-Draft will expire on August 7, 2012.
|
||||
|
||||
Copyright Notice
|
||||
|
||||
Copyright (c) 2012 IETF Trust and the persons identified as the
|
||||
document authors. All rights reserved.
|
||||
|
||||
|
||||
|
||||
|
||||
Nottingham & Fielding Expires August 7, 2012 [Page 1]
|
||||
|
||||
Internet-Draft Additional HTTP Status Codes February 2012
|
||||
|
||||
|
||||
This document is subject to BCP 78 and the IETF Trust's Legal
|
||||
Provisions Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info) in effect on the date of
|
||||
publication of this document. Please review these documents
|
||||
carefully, as they describe your rights and restrictions with respect
|
||||
to this document. Code Components extracted from this document must
|
||||
include Simplified BSD License text as described in Section 4.e of
|
||||
the Trust Legal Provisions and are provided without warranty as
|
||||
described in the Simplified BSD License.
|
||||
|
||||
|
||||
Table of Contents
|
||||
|
||||
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3
|
||||
2. Requirements . . . . . . . . . . . . . . . . . . . . . . . . . 3
|
||||
3. 428 Precondition Required . . . . . . . . . . . . . . . . . . . 3
|
||||
4. 429 Too Many Requests . . . . . . . . . . . . . . . . . . . . . 4
|
||||
5. 431 Request Header Fields Too Large . . . . . . . . . . . . . . 4
|
||||
6. 511 Network Authentication Required . . . . . . . . . . . . . . 5
|
||||
7. Security Considerations . . . . . . . . . . . . . . . . . . . . 6
|
||||
8. IANA Considerations . . . . . . . . . . . . . . . . . . . . . . 7
|
||||
9. References . . . . . . . . . . . . . . . . . . . . . . . . . . 8
|
||||
9.1. Normative References . . . . . . . . . . . . . . . . . . . 8
|
||||
9.2. Informative References . . . . . . . . . . . . . . . . . . 8
|
||||
Appendix A. Acknowledgements . . . . . . . . . . . . . . . . . . . 8
|
||||
Appendix B. Issues Raised by Captive Portals . . . . . . . . . . . 8
|
||||
Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 9
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Nottingham & Fielding Expires August 7, 2012 [Page 2]
|
||||
|
||||
Internet-Draft Additional HTTP Status Codes February 2012
|
||||
|
||||
|
||||
1. Introduction
|
||||
|
||||
This document specifies additional HTTP [RFC2616] status codes for a
|
||||
variety of common situations, to improve interoperability and avoid
|
||||
confusion when other, less precise status codes are used.
|
||||
|
||||
Note that these status codes are optional; servers cannot be required
|
||||
to support them. However, because clients will treat unknown status
|
||||
codes as a generic error of the same class (e.g., 499 is treated as
|
||||
400 if it is not recognized), they can be safely deployed by existing
|
||||
servers (see [RFC2616] Section 6.1.1 for more information).
|
||||
|
||||
|
||||
2. Requirements
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
||||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
|
||||
document are to be interpreted as described in [RFC2119].
|
||||
|
||||
|
||||
3. 428 Precondition Required
|
||||
|
||||
The 428 status code indicates that the origin server requires the
|
||||
request to be conditional.
|
||||
|
||||
Its typical use is to avoid the "lost update" problem, where a client
|
||||
GETs a resource's state, modifies it, and PUTs it back to the server,
|
||||
when meanwhile a third party has modified the state on the server,
|
||||
leading to a conflict. By requiring requests to be conditional, the
|
||||
server can assure that clients are working with the correct copies.
|
||||
|
||||
Responses using this status code SHOULD explain how to resubmit the
|
||||
request successfully. For example:
|
||||
|
||||
HTTP/1.1 428 Precondition Required
|
||||
Content-Type: text/html
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Precondition Required</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Precondition Required</h1>
|
||||
<p>This request is required to be conditional;
|
||||
try using "If-Match".</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
Nottingham & Fielding Expires August 7, 2012 [Page 3]
|
||||
|
||||
Internet-Draft Additional HTTP Status Codes February 2012
|
||||
|
||||
|
||||
Responses with the 428 status code MUST NOT be stored by a cache.
|
||||
|
||||
|
||||
4. 429 Too Many Requests
|
||||
|
||||
The 429 status code indicates that the user has sent too many
|
||||
requests in a given amount of time ("rate limiting").
|
||||
|
||||
The response representations SHOULD include details explaining the
|
||||
condition, and MAY include a Retry-After header indicating how long
|
||||
to wait before making a new request.
|
||||
|
||||
For example:
|
||||
|
||||
HTTP/1.1 429 Too Many Requests
|
||||
Content-Type: text/html
|
||||
Retry-After: 3600
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Too Many Requests</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Too Many Requests</h1>
|
||||
<p>I only allow 50 requests per hour to this Web site per
|
||||
logged in user. Try again soon.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Note that this specification does not define how the origin server
|
||||
identifies the user, nor how it counts requests. For example, an
|
||||
origin server that is limiting request rates can do so based upon
|
||||
counts of requests on a per-resource basis, across the entire server,
|
||||
or even among a set of servers. Likewise, it might identify the user
|
||||
by its authentication credentials, or a stateful cookie.
|
||||
|
||||
Responses with the 429 status code MUST NOT be stored by a cache.
|
||||
|
||||
|
||||
5. 431 Request Header Fields Too Large
|
||||
|
||||
The 431 status code indicates that the server is unwilling to process
|
||||
the request because its header fields are too large. The request MAY
|
||||
be resubmitted after reducing the size of the request header fields.
|
||||
|
||||
It can be used both when the set of request header fields in total
|
||||
are too large, and when a single header field is at fault. In the
|
||||
latter case, the response representation SHOULD specify which header
|
||||
|
||||
|
||||
|
||||
Nottingham & Fielding Expires August 7, 2012 [Page 4]
|
||||
|
||||
Internet-Draft Additional HTTP Status Codes February 2012
|
||||
|
||||
|
||||
field was too large.
|
||||
|
||||
For example:
|
||||
|
||||
HTTP/1.1 431 Request Header Fields Too Large
|
||||
Content-Type: text/html
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Request Header Fields Too Large</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Request Header Fields Too Large</h1>
|
||||
<p>The "Example" header was too large.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Responses with the 431 status code MUST NOT be stored by a cache.
|
||||
|
||||
|
||||
6. 511 Network Authentication Required
|
||||
|
||||
The 511 status code indicates that the client needs to authenticate
|
||||
to gain network access.
|
||||
|
||||
The response representation SHOULD contain a link to a resource that
|
||||
allows the user to submit credentials (e.g. with a HTML form).
|
||||
|
||||
Note that the 511 response SHOULD NOT contain a challenge or the
|
||||
login interface itself, because browsers would show the login
|
||||
interface as being associated with the originally requested URL,
|
||||
which may cause confusion.
|
||||
|
||||
The 511 status SHOULD NOT be generated by origin servers; it is
|
||||
intended for use by intercepting proxies that are interposed as a
|
||||
means of controlling access to the network.
|
||||
|
||||
Responses with the 511 status code MUST NOT be stored by a cache.
|
||||
|
||||
6.1. The 511 Status Code and Captive Portals
|
||||
|
||||
The 511 status code is designed to mitigate problems caused by
|
||||
"captive portals" to software (especially non-browser agents) that is
|
||||
expecting a response from the server that a request was made to, not
|
||||
the intervening network infrastructure. It is not intended to
|
||||
encouraged deployment of captive portals, only to limit the damage
|
||||
caused by them.
|
||||
|
||||
|
||||
|
||||
|
||||
Nottingham & Fielding Expires August 7, 2012 [Page 5]
|
||||
|
||||
Internet-Draft Additional HTTP Status Codes February 2012
|
||||
|
||||
|
||||
A network operator wishing to require some authentication, acceptance
|
||||
of terms or other user interaction before granting access usually
|
||||
does so by identifing clients who have not done so ("unknown
|
||||
clients") using their MAC addresses.
|
||||
|
||||
Unknown clients then have all traffic blocked, except for that on TCP
|
||||
port 80, which is sent to a HTTP server (the "login server")
|
||||
dedicated to "logging in" unknown clients, and of course traffic to
|
||||
the login server itself.
|
||||
|
||||
For example, a user agent might connect to a network and make the
|
||||
following HTTP request on TCP port 80:
|
||||
|
||||
GET /index.htm HTTP/1.1
|
||||
Host: www.example.com
|
||||
|
||||
Upon receiving such a request, the login server would generate a 511
|
||||
response:
|
||||
|
||||
HTTP/1.1 511 Network Authentication Required
|
||||
Content-Type: text/html
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Network Authentication Required</title>
|
||||
<meta http-equiv="refresh"
|
||||
content="0; url=https://login.example.net/">
|
||||
</head>
|
||||
<body>
|
||||
<p>You need to <a href="https://login.example.net/">
|
||||
authenticate with the local network</a> in order to gain
|
||||
access.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Here, the 511 status code assures that non-browser clients will not
|
||||
interpret the response as being from the origin server, and the META
|
||||
HTML element redirects the user agent to the login server.
|
||||
|
||||
|
||||
7. Security Considerations
|
||||
|
||||
7.1. 428 Precondition Required
|
||||
|
||||
The 428 status code is optional; clients cannot rely upon its use to
|
||||
prevent "lost update" conflicts.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Nottingham & Fielding Expires August 7, 2012 [Page 6]
|
||||
|
||||
Internet-Draft Additional HTTP Status Codes February 2012
|
||||
|
||||
|
||||
7.2. 429 Too Many Requests
|
||||
|
||||
When a server is under attack or just receiving a very large number
|
||||
of requests from a single party, responding to each with a 429 status
|
||||
code will consume resources.
|
||||
|
||||
Therefore, servers are not required to use the 429 status code; when
|
||||
limiting resource usage, it may be more appropriate to just drop
|
||||
connections, or take other steps.
|
||||
|
||||
7.3. 431 Request Header Fields Too Large
|
||||
|
||||
Servers are not required to use the 431 status code; when under
|
||||
attack, it may be more appropriate to just drop connections, or take
|
||||
other steps.
|
||||
|
||||
7.4. 511 Network Authentication Required
|
||||
|
||||
In common use, a response carrying the 511 status code will not come
|
||||
from the origin server indicated in the request's URL. This presents
|
||||
many security issues; e.g., an attacking intermediary may be
|
||||
inserting cookies into the original domain's name space, may be
|
||||
observing cookies or HTTP authentication credentials sent from the
|
||||
user agent, and so on.
|
||||
|
||||
However, these risks are not unique to the 511 status code; in other
|
||||
words, a captive portal that is not using this status code introduces
|
||||
the same issues.
|
||||
|
||||
Also, note that captive portals using this status code on an SSL or
|
||||
TLS connection (commonly, port 443) will generate a certificate error
|
||||
on the client.
|
||||
|
||||
|
||||
8. IANA Considerations
|
||||
|
||||
The HTTP Status Codes Registry should be updated with the following
|
||||
entries:
|
||||
|
||||
o Code: 428
|
||||
o Description: Precondition Required
|
||||
o Specification: [ this document ]
|
||||
|
||||
o Code: 429
|
||||
o Description: Too Many Requests
|
||||
o Specification: [ this document ]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Nottingham & Fielding Expires August 7, 2012 [Page 7]
|
||||
|
||||
Internet-Draft Additional HTTP Status Codes February 2012
|
||||
|
||||
|
||||
o Code: 431
|
||||
o Description: Request Header Fields Too Large
|
||||
o Specification: [ this document ]
|
||||
|
||||
o Code: 511
|
||||
o Description: Network Authentication Required
|
||||
o Specification: [ this document ]
|
||||
|
||||
|
||||
9. References
|
||||
|
||||
9.1. Normative References
|
||||
|
||||
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
|
||||
Requirement Levels", BCP 14, RFC 2119, March 1997.
|
||||
|
||||
[RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H.,
|
||||
Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext
|
||||
Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
|
||||
|
||||
9.2. Informative References
|
||||
|
||||
[RFC4791] Daboo, C., Desruisseaux, B., and L. Dusseault,
|
||||
"Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,
|
||||
March 2007.
|
||||
|
||||
[RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed
|
||||
Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
|
||||
|
||||
URIs
|
||||
|
||||
[1] <mailto:ietf-http-wg@w3.org>
|
||||
|
||||
[2] <mailto:ietf-http-wg-request@w3.org?subject=subscribe>
|
||||
|
||||
|
||||
Appendix A. Acknowledgements
|
||||
|
||||
Thanks to Jan Algermissen and Julian Reschke for their suggestions
|
||||
and feedback.
|
||||
|
||||
|
||||
Appendix B. Issues Raised by Captive Portals
|
||||
|
||||
Since clients cannot differentiate between a portal's response and
|
||||
that of the HTTP server that they intended to communicate with, a
|
||||
number of issues arise. The 511 status code is intended to help
|
||||
mitigate some of them.
|
||||
|
||||
|
||||
|
||||
Nottingham & Fielding Expires August 7, 2012 [Page 8]
|
||||
|
||||
Internet-Draft Additional HTTP Status Codes February 2012
|
||||
|
||||
|
||||
One example is the "favicon.ico"
|
||||
<http://en.wikipedia.org/wiki/Favicon> commonly used by browsers to
|
||||
identify the site being accessed. If the favicon for a given site is
|
||||
fetched from a captive portal instead of the intended site (e.g.,
|
||||
because the user is unauthenticated), it will often "stick" in the
|
||||
browser's cache (most implementations cache favicons aggressively)
|
||||
beyond the portal session, so that it seems as if the portal's
|
||||
favicon has "taken over" the legitimate site.
|
||||
|
||||
Another browser-based issue comes about when P3P
|
||||
<http://www.w3.org/TR/P3P/> is supported. Depending on how it is
|
||||
implemented, it's possible a browser might interpret a portal's
|
||||
response for the p3p.xml file as the server's, resulting in the
|
||||
privacy policy (or lack thereof) advertised by the portal being
|
||||
interpreted as applying to the intended site. Other Web-based
|
||||
protocols such as WebFinger
|
||||
<http://code.google.com/p/webfinger/wiki/WebFingerProtocol>, CORS
|
||||
<http://www.w3.org/TR/cors/> and OAuth
|
||||
<http://tools.ietf.org/html/draft-ietf-oauth-v2> may also be
|
||||
vulnerable to such issues.
|
||||
|
||||
Although HTTP is most widely used with Web browsers, a growing number
|
||||
of non-browsing applications use it as a substrate protocol. For
|
||||
example, WebDAV [RFC4918] and CalDAV [RFC4791] both use HTTP as the
|
||||
basis (for remote authoring and calendaring, respectively). Using
|
||||
these applications from behind a captive portal can result in
|
||||
spurious errors being presented to the user, and might result in
|
||||
content corruption, in extreme cases.
|
||||
|
||||
Similarly, other non-browser applications using HTTP can be affected
|
||||
as well; e.g., widgets <http://www.w3.org/TR/widgets/>, software
|
||||
updates, and other specialised software such as Twitter clients and
|
||||
the iTunes Music Store.
|
||||
|
||||
It should be noted that it's sometimes believed that using HTTP
|
||||
redirection to direct traffic to the portal addresses these issues.
|
||||
However, since many of these uses "follow" redirects, this is not a
|
||||
good solution.
|
||||
|
||||
|
||||
Authors' Addresses
|
||||
|
||||
Mark Nottingham
|
||||
Rackspace
|
||||
|
||||
Email: mnot@mnot.net
|
||||
URI: http://www.mnot.net/
|
||||
|
||||
|
||||
|
||||
|
||||
Nottingham & Fielding Expires August 7, 2012 [Page 9]
|
||||
|
||||
Internet-Draft Additional HTTP Status Codes February 2012
|
||||
|
||||
|
||||
Roy T. Fielding
|
||||
Adobe Systems Incorporated
|
||||
345 Park Ave
|
||||
San Jose, CA 95110
|
||||
USA
|
||||
|
||||
Email: fielding@gbiv.com
|
||||
URI: http://roy.gbiv.com/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Nottingham & Fielding Expires August 7, 2012 [Page 10]
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,395 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Network Working Group M. Crispin
|
||||
Request for Comments: 5051 University of Washington
|
||||
Category: Standards Track October 2007
|
||||
|
||||
|
||||
i;unicode-casemap - Simple Unicode Collation Algorithm
|
||||
|
||||
Status of This Memo
|
||||
|
||||
This document specifies an Internet standards track protocol for the
|
||||
Internet community, and requests discussion and suggestions for
|
||||
improvements. Please refer to the current edition of the "Internet
|
||||
Official Protocol Standards" (STD 1) for the standardization state
|
||||
and status of this protocol. Distribution of this memo is unlimited.
|
||||
|
||||
Abstract
|
||||
|
||||
This document describes "i;unicode-casemap", a simple case-
|
||||
insensitive collation for Unicode strings. It provides equality,
|
||||
substring, and ordering operations.
|
||||
|
||||
1. Introduction
|
||||
|
||||
The "i;ascii-casemap" collation described in [COMPARATOR] is quite
|
||||
simple to implement and provides case-independent comparisons for the
|
||||
26 Latin alphabetics. It is specified as the default and/or baseline
|
||||
comparator in some application protocols, e.g., [IMAP-SORT].
|
||||
|
||||
However, the "i;ascii-casemap" collation does not produce
|
||||
satisfactory results with non-ASCII characters. It is possible, with
|
||||
a modest extension, to provide a more sophisticated collation with
|
||||
greater multilingual applicability than "i;ascii-casemap". This
|
||||
extension provides case-independent comparisons for a much greater
|
||||
number of characters. It also collates characters with diacriticals
|
||||
with the non-diacritical character forms.
|
||||
|
||||
This collation, "i;unicode-casemap", is intended to be an alternative
|
||||
to, and preferred over, "i;ascii-casemap". It does not replace the
|
||||
"i;basic" collation described in [BASIC].
|
||||
|
||||
2. Unicode Casemap Collation Description
|
||||
|
||||
The "i;unicode-casemap" collation is a simple collation which is
|
||||
case-insensitive in its treatment of characters. It provides
|
||||
equality, substring, and ordering operations. The validity test
|
||||
operation returns "valid" for any input.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Crispin Standards Track [Page 1]
|
||||
|
||||
RFC 5051 i;unicode-casemap October 2007
|
||||
|
||||
|
||||
This collation allows strings in arbitrary (and mixed) character
|
||||
sets, as long as the character set for each string is identified and
|
||||
it is possible to convert the string to Unicode. Strings which have
|
||||
an unidentified character set and/or cannot be converted to Unicode
|
||||
are not rejected, but are treated as binary.
|
||||
|
||||
Each input string is prepared by converting it to a "titlecased
|
||||
canonicalized UTF-8" string according to the following steps, using
|
||||
UnicodeData.txt ([UNICODE-DATA]):
|
||||
|
||||
(1) A Unicode codepoint is obtained from the input string.
|
||||
|
||||
(a) If the input string is in a known charset that can be
|
||||
converted to Unicode, a sequence in the string's charset
|
||||
is read and checked for validity according to the rules of
|
||||
that charset. If the sequence is valid, it is converted
|
||||
to a Unicode codepoint. Note that for input strings in
|
||||
UTF-8, the UTF-8 sequence must be valid according to the
|
||||
rules of [UTF-8]; e.g., overlong UTF-8 sequences are
|
||||
invalid.
|
||||
|
||||
(b) If the input string is in an unknown charset, or an
|
||||
invalid sequence occurs in step (1)(a), conversion ceases.
|
||||
No further preparation is performed, and any partial
|
||||
preparation results are discarded. The original string is
|
||||
used unchanged with the i;octet comparator.
|
||||
|
||||
(2) The following steps, using UnicodeData.txt ([UNICODE-DATA]),
|
||||
are performed on the resulting codepoint from step (1)(a).
|
||||
|
||||
(a) If the codepoint has a titlecase property in
|
||||
UnicodeData.txt (this is normally the same as the
|
||||
uppercase property), the codepoint is converted to the
|
||||
codepoints in the titlecase property.
|
||||
|
||||
(b) If the resulting codepoint from (2)(a) has a decomposition
|
||||
property of any type in UnicodeData.txt, the codepoint is
|
||||
converted to the codepoints in the decomposition property.
|
||||
This step is recursively applied to each of the resulting
|
||||
codepoints until no more decomposition is possible
|
||||
(effectively Normalization Form KD).
|
||||
|
||||
Example: codepoint U+01C4 (LATIN CAPITAL LETTER DZ WITH CARON)
|
||||
has a titlecase property of U+01C5 (LATIN CAPITAL LETTER D
|
||||
WITH SMALL LETTER Z WITH CARON). Codepoint U+01C5 has a
|
||||
decomposition property of U+0044 (LATIN CAPITAL LETTER D)
|
||||
U+017E (LATIN SMALL LETTER Z WITH CARON). U+017E has a
|
||||
decomposition property of U+007A (LATIN SMALL LETTER Z) U+030c
|
||||
|
||||
|
||||
|
||||
Crispin Standards Track [Page 2]
|
||||
|
||||
RFC 5051 i;unicode-casemap October 2007
|
||||
|
||||
|
||||
(COMBINING CARON). Neither U+0044, U+007A, nor U+030C have
|
||||
any decomposition properties. Therefore, U+01C4 is converted
|
||||
to U+0044 U+007A U+030C by this step.
|
||||
|
||||
(3) The resulting codepoint(s) from step (2) is/are appended, in
|
||||
UTF-8 format, to the "titlecased canonicalized UTF-8" string.
|
||||
|
||||
(4) Repeat from step (1) until there is no more data in the input
|
||||
string.
|
||||
|
||||
Following the above preparation process on each string, the equality,
|
||||
ordering, and substring operations are as for i;octet.
|
||||
|
||||
It is permitted to use an alternative implementation of the above
|
||||
preparation process if it produces the same results. For example, it
|
||||
may be more convenient for an implementation to convert all input
|
||||
strings to a sequence of UTF-16 or UTF-32 values prior to performing
|
||||
any of the step (2) actions. Similarly, if all input strings are (or
|
||||
are convertible to) Unicode, it may be possible to use UTF-32 as an
|
||||
alternative to UTF-8 in step (3).
|
||||
|
||||
Note: UTF-16 is unsuitable as an alternative to UTF-8 in step (3),
|
||||
because UTF-16 surrogates will cause i;octet to collate codepoints
|
||||
U+E0000 through U+FFFF after non-BMP codepoints.
|
||||
|
||||
This collation is not locale sensitive. Consequently, care should be
|
||||
taken when using OS-supplied functions to implement this collation.
|
||||
Functions such as strcasecmp and toupper are sometimes locale
|
||||
sensitive and may inconsistently casemap letters.
|
||||
|
||||
The i;unicode-casemap collation is well suited to use with many
|
||||
Internet protocols and computer languages. Use with natural language
|
||||
is often inappropriate; even though the collation apparently supports
|
||||
languages such as Swahili and English, in real-world use it tends to
|
||||
mis-sort a number of types of string:
|
||||
|
||||
o people and place names containing scripts that are not collated
|
||||
according to "alphabetical order".
|
||||
o words with characters that have diacriticals. However,
|
||||
i;unicode-casemap generally does a better job than i;ascii-casemap
|
||||
for most (but not all) languages. For example, German umlaut
|
||||
letters will sort correctly, but some Scandinavian letters will
|
||||
not.
|
||||
o names such as "Lloyd" (which in Welsh sorts after "Lyon", unlike
|
||||
in English),
|
||||
o strings containing other non-letter symbols; e.g., euro and pound
|
||||
sterling symbols, quotation marks other than '"', dashes/hyphens,
|
||||
etc.
|
||||
|
||||
|
||||
|
||||
Crispin Standards Track [Page 3]
|
||||
|
||||
RFC 5051 i;unicode-casemap October 2007
|
||||
|
||||
|
||||
3. Unicode Casemap Collation Registration
|
||||
|
||||
<?xml version='1.0'?>
|
||||
<!DOCTYPE collation SYSTEM 'collationreg.dtd'>
|
||||
<collation rfc="5051" scope="global" intendedUse="common">
|
||||
<identifier>i;unicode-casemap</identifier>
|
||||
<title>Unicode Casemap</title>
|
||||
<operations>equality order substring</operations>
|
||||
<specification>RFC 5051</specification>
|
||||
<owner>IETF</owner>
|
||||
<submitter>mrc@cac.washington.edu</submitter>
|
||||
</collation>
|
||||
|
||||
4. Security Considerations
|
||||
|
||||
The security considerations for [UTF-8], [STRINGPREP], and [UNICODE-
|
||||
SECURITY] apply and are normative to this specification.
|
||||
|
||||
The results from this comparator will vary depending upon the
|
||||
implementation for several reasons. Implementations MUST consider
|
||||
whether these possibilities are a problem for their use case:
|
||||
|
||||
1) New characters added in Unicode may have decomposition or
|
||||
titlecase properties that will not be known to an implementation
|
||||
based upon an older revision of Unicode. This impacts step (2).
|
||||
|
||||
2) Step (2)(b) defines a subset of Normalization Form KD (NFKD) that
|
||||
does not require normalization of out-of-order diacriticals.
|
||||
However, an implementation MAY use an NFKD library routine that
|
||||
does such normalization. This impacts step (2)(b) and possibly
|
||||
also step (1)(a), and is an issue only with ill-formed UTF-8
|
||||
input.
|
||||
|
||||
3) The set of charsets handled in step (1)(a) is open-ended. UTF-8
|
||||
(and, by extension, US-ASCII) are the only mandatory-to-implement
|
||||
charsets. This impacts step (1)(a).
|
||||
|
||||
Implementations SHOULD, as far as feasible, support all the
|
||||
charsets they are likely to encounter in the input data, in order
|
||||
to avoid poor collation caused by the fall through to the (1)(b)
|
||||
rule.
|
||||
|
||||
4) Other charsets may have revisions which add new characters that
|
||||
are not known to an implementation based upon an older revision.
|
||||
This impacts step (1)(a) and possibly also step (1)(b).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Crispin Standards Track [Page 4]
|
||||
|
||||
RFC 5051 i;unicode-casemap October 2007
|
||||
|
||||
|
||||
An attacker may create input that is ill-formed or in an unknown
|
||||
charset, with the intention of impacting the results of this
|
||||
comparator or exploiting other parts of the system which process this
|
||||
input in different ways. Note, however, that even well-formed data
|
||||
in a known charset can impact the result of this comparator in
|
||||
unexpected ways. For example, an attacker can substitute U+0041
|
||||
(LATIN CAPITAL LETTER A) with U+0391 (GREEK CAPITAL LETTER ALPHA) or
|
||||
U+0410 (CYRILLIC CAPITAL LETTER A) in the intention of causing a
|
||||
non-match of strings which visually appear the same and/or causing
|
||||
the string to appear elsewhere in a sort.
|
||||
|
||||
5. IANA Considerations
|
||||
|
||||
The i;unicode-casemap collation defined in section 2 has been added
|
||||
to the registry of collations defined in [COMPARATOR].
|
||||
|
||||
6. Normative References
|
||||
|
||||
[COMPARATOR] Newman, C., Duerst, M., and A. Gulbrandsen,
|
||||
"Internet Application Protocol Collation
|
||||
Registry", RFC 4790, February 2007.
|
||||
|
||||
[STRINGPREP] Hoffman, P. and M. Blanchet, "Preparation of
|
||||
Internationalized Strings ("stringprep")", RFC
|
||||
3454, December 2002.
|
||||
|
||||
[UTF-8] Yergeau, F., "UTF-8, a transformation format of
|
||||
ISO 10646", STD 63, RFC 3629, November 2003.
|
||||
|
||||
[UNICODE-DATA] <http://www.unicode.org/Public/UNIDATA/
|
||||
UnicodeData.txt>
|
||||
|
||||
Although the UnicodeData.txt file referenced
|
||||
here is part of the Unicode standard, it is
|
||||
subject to change as new characters are added
|
||||
to Unicode and errors are corrected in Unicode
|
||||
revisions. As a result, it may be less stable
|
||||
than might otherwise be implied by the
|
||||
standards status of this specification.
|
||||
|
||||
[UNICODE-SECURITY] Davis, M. and M. Suignard, "Unicode Security
|
||||
Considerations", February 2006,
|
||||
<http://www.unicode.org/reports/tr36/>.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Crispin Standards Track [Page 5]
|
||||
|
||||
RFC 5051 i;unicode-casemap October 2007
|
||||
|
||||
|
||||
7. Informative References
|
||||
|
||||
[BASIC] Newman, C., Duerst, M., and A. Gulbrandsen,
|
||||
"i;basic - the Unicode Collation Algorithm",
|
||||
Work in Progress, March 2007.
|
||||
|
||||
[IMAP-SORT] Crispin, M. and K. Murchison, "Internet Message
|
||||
Access Protocol - SORT and THREAD Extensions",
|
||||
Work in Progress, September 2007.
|
||||
|
||||
Author's Address
|
||||
|
||||
Mark R. Crispin
|
||||
Networks and Distributed Computing
|
||||
University of Washington
|
||||
4545 15th Avenue NE
|
||||
Seattle, WA 98105-4527
|
||||
|
||||
Phone: +1 (206) 543-5762
|
||||
EMail: MRC@CAC.Washington.EDU
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Crispin Standards Track [Page 6]
|
||||
|
||||
RFC 5051 i;unicode-casemap October 2007
|
||||
|
||||
|
||||
Full Copyright Statement
|
||||
|
||||
Copyright (C) The IETF Trust (2007).
|
||||
|
||||
This document is subject to the rights, licenses and restrictions
|
||||
contained in BCP 78, and except as set forth therein, the authors
|
||||
retain all their rights.
|
||||
|
||||
This document and the information contained herein are provided on an
|
||||
"AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
|
||||
OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND
|
||||
THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF
|
||||
THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
Intellectual Property
|
||||
|
||||
The IETF takes no position regarding the validity or scope of any
|
||||
Intellectual Property Rights or other rights that might be claimed to
|
||||
pertain to the implementation or use of the technology described in
|
||||
this document or the extent to which any license under such rights
|
||||
might or might not be available; nor does it represent that it has
|
||||
made any independent effort to identify any such rights. Information
|
||||
on the procedures with respect to rights in RFC documents can be
|
||||
found in BCP 78 and BCP 79.
|
||||
|
||||
Copies of IPR disclosures made to the IETF Secretariat and any
|
||||
assurances of licenses to be made available, or the result of an
|
||||
attempt made to obtain a general license or permission for the use of
|
||||
such proprietary rights by implementers or users of this
|
||||
specification can be obtained from the IETF on-line IPR repository at
|
||||
http://www.ietf.org/ipr.
|
||||
|
||||
The IETF invites any interested party to bring to its attention any
|
||||
copyrights, patents or patent applications, or other proprietary
|
||||
rights that may cover technology that may be required to implement
|
||||
this standard. Please address the information to the IETF at
|
||||
ietf-ipr@ietf.org.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Crispin Standards Track [Page 7]
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
|
||||
|
||||
|
||||
Network Working Group W. Sanchez
|
||||
Request for Comments: 5397 C. Daboo
|
||||
Category: Standards Track Apple Inc.
|
||||
December 2008
|
||||
|
||||
|
||||
WebDAV Current Principal Extension
|
||||
|
||||
Status of This Memo
|
||||
|
||||
This document specifies an Internet standards track protocol for the
|
||||
Internet community, and requests discussion and suggestions for
|
||||
improvements. Please refer to the current edition of the "Internet
|
||||
Official Protocol Standards" (STD 1) for the standardization state
|
||||
and status of this protocol. Distribution of this memo is unlimited.
|
||||
|
||||
Copyright Notice
|
||||
|
||||
Copyright (c) 2008 IETF Trust and the persons identified as the
|
||||
document authors. All rights reserved.
|
||||
|
||||
This document is subject to BCP 78 and the IETF Trust's Legal
|
||||
Provisions Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info) in effect on the date of
|
||||
publication of this document. Please review these documents
|
||||
carefully, as they describe your rights and restrictions with respect
|
||||
to this document.
|
||||
|
||||
Abstract
|
||||
|
||||
This specification defines a new WebDAV property that allows clients
|
||||
to quickly determine the principal corresponding to the current
|
||||
authenticated user.
|
||||
|
||||
Table of Contents
|
||||
|
||||
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 2
|
||||
2. Conventions Used in This Document . . . . . . . . . . . . . . . 2
|
||||
3. DAV:current-user-principal . . . . . . . . . . . . . . . . . . 3
|
||||
4. Security Considerations . . . . . . . . . . . . . . . . . . . . 4
|
||||
5. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . 4
|
||||
6. Normative References . . . . . . . . . . . . . . . . . . . . . 4
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Sanchez & Daboo Standards Track [Page 1]
|
||||
|
||||
RFC 5397 WebDAV Current Principal December 2008
|
||||
|
||||
|
||||
1. Introduction
|
||||
|
||||
WebDAV [RFC4918] is an extension to HTTP [RFC2616] to support
|
||||
improved document authoring capabilities. The WebDAV Access Control
|
||||
Protocol ("WebDAV ACL") [RFC3744] extension adds access control
|
||||
capabilities to WebDAV. It introduces the concept of a "principal"
|
||||
resource, which is used to represent information about authenticated
|
||||
entities on the system.
|
||||
|
||||
Some clients have a need to determine which [RFC3744] principal a
|
||||
server is associating with the currently authenticated HTTP user.
|
||||
While [RFC3744] defines a DAV:current-user-privilege-set property for
|
||||
retrieving the privileges granted to that principal, there is no
|
||||
recommended way to identify the principal in question, which is
|
||||
necessary to perform other useful operations. For example, a client
|
||||
may wish to determine which groups the current user is a member of,
|
||||
or modify a property of the principal resource associated with the
|
||||
current user.
|
||||
|
||||
The DAV:principal-match REPORT provides some useful functionality,
|
||||
but there are common situations where the results from that query can
|
||||
be ambiguous. For example, not only is an individual user principal
|
||||
returned, but also every group principal that the user is a member
|
||||
of, and there is no clear way to distinguish which is which.
|
||||
|
||||
This specification proposes an extension to WebDAV ACL that adds a
|
||||
DAV:current-user-principal property to resources under access control
|
||||
on the server. This property provides a URL to a principal resource
|
||||
corresponding to the currently authenticated user. This allows a
|
||||
client to "bootstrap" itself by performing additional queries on the
|
||||
principal resource to obtain additional information from that
|
||||
resource, which is the purpose of this extension. Note that while it
|
||||
is possible for multiple URLs to refer to the same principal
|
||||
resource, or for multiple principal resources to correspond to a
|
||||
single principal, this specification only allows for a single http(s)
|
||||
URL in the DAV:current-user-principal property. If a client wishes
|
||||
to obtain alternate URLs for the principal, it can query the
|
||||
principal resource for this information; it is not the purpose of
|
||||
this extension to provide a complete list of such URLs, but simply to
|
||||
provide a means to locate a resource which contains that (and other)
|
||||
information.
|
||||
|
||||
2. Conventions Used in This Document
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
||||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
|
||||
document are to be interpreted as described in [RFC2119].
|
||||
|
||||
|
||||
|
||||
|
||||
Sanchez & Daboo Standards Track [Page 2]
|
||||
|
||||
RFC 5397 WebDAV Current Principal December 2008
|
||||
|
||||
|
||||
When XML element types in the namespace "DAV:" are referenced in this
|
||||
document outside of the context of an XML fragment, the string "DAV:"
|
||||
will be prefixed to the element type names.
|
||||
|
||||
Processing of XML by clients and servers MUST follow the rules
|
||||
defined in Section 17 of WebDAV [RFC4918].
|
||||
|
||||
Some of the declarations refer to XML elements defined by WebDAV
|
||||
[RFC4918].
|
||||
|
||||
3. DAV:current-user-principal
|
||||
|
||||
Name: current-user-principal
|
||||
|
||||
Namespace: DAV:
|
||||
|
||||
Purpose: Indicates a URL for the currently authenticated user's
|
||||
principal resource on the server.
|
||||
|
||||
Value: A single DAV:href or DAV:unauthenticated element.
|
||||
|
||||
Protected: This property is computed on a per-request basis, and
|
||||
therefore is protected.
|
||||
|
||||
Description: The DAV:current-user-principal property contains either
|
||||
a DAV:href or DAV:unauthenticated XML element. The DAV:href
|
||||
element contains a URL to a principal resource corresponding to
|
||||
the currently authenticated user. That URL MUST be one of the
|
||||
URLs in the DAV:principal-URL or DAV:alternate-URI-set properties
|
||||
defined on the principal resource and MUST be an http(s) scheme
|
||||
URL. When authentication has not been done or has failed, this
|
||||
property MUST contain the DAV:unauthenticated pseudo-principal.
|
||||
|
||||
In some cases, there may be multiple principal resources
|
||||
corresponding to the same authenticated principal. In that case,
|
||||
the server is free to choose any one of the principal resource
|
||||
URIs for the value of the DAV:current-user-principal property.
|
||||
However, servers SHOULD be consistent and use the same principal
|
||||
resource URI for each authenticated principal.
|
||||
|
||||
COPY/MOVE behavior: This property is computed on a per-request
|
||||
basis, and is thus never copied or moved.
|
||||
|
||||
Definition:
|
||||
|
||||
<!ELEMENT current-user-principal (unauthenticated | href)>
|
||||
<!-- href value: a URL to a principal resource -->
|
||||
|
||||
|
||||
|
||||
|
||||
Sanchez & Daboo Standards Track [Page 3]
|
||||
|
||||
RFC 5397 WebDAV Current Principal December 2008
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
<D:current-user-principal xmlns:D="DAV:">
|
||||
<D:href>/principals/users/cdaboo</D:href>
|
||||
</D:current-user-principal>
|
||||
|
||||
4. Security Considerations
|
||||
|
||||
This specification does not introduce any additional security issues
|
||||
beyond those defined for HTTP [RFC2616], WebDAV [RFC4918], and WebDAV
|
||||
ACL [RFC3744].
|
||||
|
||||
5. Acknowledgments
|
||||
|
||||
This specification is based on discussions that took place within the
|
||||
Calendaring and Scheduling Consortium's CalDAV Technical Committee.
|
||||
The authors thank the participants of that group for their input.
|
||||
|
||||
The authors thank Julian Reschke for his valuable input via the
|
||||
WebDAV working group mailing list.
|
||||
|
||||
6. Normative References
|
||||
|
||||
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
|
||||
Requirement Levels", BCP 14, RFC 2119, March 1997.
|
||||
|
||||
[RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H.,
|
||||
Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext
|
||||
Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
|
||||
|
||||
[RFC3744] Clemm, G., Reschke, J., Sedlar, E., and J. Whitehead, "Web
|
||||
Distributed Authoring and Versioning (WebDAV)
|
||||
Access Control Protocol", RFC 3744, May 2004.
|
||||
|
||||
[RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed
|
||||
Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
|
||||
|
||||
Authors' Addresses
|
||||
|
||||
Wilfredo Sanchez
|
||||
Apple Inc.
|
||||
1 Infinite Loop
|
||||
Cupertino, CA 95014
|
||||
USA
|
||||
|
||||
EMail: wsanchez@wsanchez.net
|
||||
URI: http://www.apple.com/
|
||||
|
||||
|
||||
|
||||
|
||||
Sanchez & Daboo Standards Track [Page 4]
|
||||
|
||||
RFC 5397 WebDAV Current Principal December 2008
|
||||
|
||||
|
||||
Cyrus Daboo
|
||||
Apple Inc.
|
||||
1 Infinite Loop
|
||||
Cupertino, CA 95014
|
||||
USA
|
||||
|
||||
EMail: cyrus@daboo.name
|
||||
URI: http://www.apple.com/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Sanchez & Daboo Standards Track [Page 5]
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,675 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Network Working Group C. Daboo
|
||||
Request for Comments: 5689 Apple Inc.
|
||||
Updates: 4791, 4918 September 2009
|
||||
Category: Standards Track
|
||||
|
||||
|
||||
Extended MKCOL for Web Distributed Authoring and Versioning (WebDAV)
|
||||
|
||||
Abstract
|
||||
|
||||
This specification extends the Web Distributed Authoring and
|
||||
Versioning (WebDAV) MKCOL (Make Collection) method to allow
|
||||
collections of arbitrary resourcetype to be created and to allow
|
||||
properties to be set at the same time.
|
||||
|
||||
Status of This Memo
|
||||
|
||||
This document specifies an Internet standards track protocol for the
|
||||
Internet community, and requests discussion and suggestions for
|
||||
improvements. Please refer to the current edition of the "Internet
|
||||
Official Protocol Standards" (STD 1) for the standardization state
|
||||
and status of this protocol. Distribution of this memo is unlimited.
|
||||
|
||||
Copyright Notice
|
||||
|
||||
Copyright (c) 2009 IETF Trust and the persons identified as the
|
||||
document authors. All rights reserved.
|
||||
|
||||
This document is subject to BCP 78 and the IETF Trust's Legal
|
||||
Provisions Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info) in effect on the date of
|
||||
publication of this document. Please review these documents
|
||||
carefully, as they describe your rights and restrictions with respect
|
||||
to this document. Code Components extracted from this document must
|
||||
include Simplified BSD License text as described in Section 4.e of
|
||||
the Trust Legal Provisions and are provided without warranty as
|
||||
described in the BSD License.
|
||||
|
||||
This document may contain material from IETF Documents or IETF
|
||||
Contributions published or made publicly available before November
|
||||
10, 2008. The person(s) controlling the copyright in some of this
|
||||
material may not have granted the IETF Trust the right to allow
|
||||
modifications of such material outside the IETF Standards Process.
|
||||
Without obtaining an adequate license from the person(s) controlling
|
||||
the copyright in such materials, this document may not be modified
|
||||
outside the IETF Standards Process, and derivative works of it may
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 1]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
not be created outside the IETF Standards Process, except to format
|
||||
it for publication as an RFC or to translate it into languages other
|
||||
than English.
|
||||
|
||||
Table of Contents
|
||||
|
||||
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3
|
||||
2. Conventions Used in This Document . . . . . . . . . . . . . . 3
|
||||
3. WebDAV Extended MKCOL . . . . . . . . . . . . . . . . . . . . 4
|
||||
3.1. Extended MKCOL Support . . . . . . . . . . . . . . . . . . 5
|
||||
3.1.1. Example: Using OPTIONS for the Discovery of
|
||||
Support for Extended MKCOL . . . . . . . . . . . . . . 5
|
||||
3.2. Status Codes . . . . . . . . . . . . . . . . . . . . . . . 5
|
||||
3.3. Additional Precondition for Extended MKCOL . . . . . . . . 5
|
||||
3.4. Example: Successful Extended MKCOL Request . . . . . . . . 6
|
||||
3.5. Example: Unsuccessful Extended MKCOL Request . . . . . . . 6
|
||||
4. Using Extended MKCOL as an Alternative for MKxxx Methods . . . 8
|
||||
4.1. MKCALENDAR Alternative . . . . . . . . . . . . . . . . . . 8
|
||||
4.1.1. Example: Using MKCOL Instead of MKCALENDAR . . . . . . 8
|
||||
5. XML Element Definitions . . . . . . . . . . . . . . . . . . . 10
|
||||
5.1. mkcol XML Element . . . . . . . . . . . . . . . . . . . . 10
|
||||
5.2. mkcol-response XML Element . . . . . . . . . . . . . . . . 10
|
||||
6. Security Considerations . . . . . . . . . . . . . . . . . . . 11
|
||||
7. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 11
|
||||
8. Normative References . . . . . . . . . . . . . . . . . . . . . 11
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 2]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
1. Introduction
|
||||
|
||||
WebDAV [RFC4918] defines the HTTP [RFC2616] method MKCOL. This
|
||||
method is used to create WebDAV collections on the server. However,
|
||||
several WebDAV-based specifications (e.g., CalDAV [RFC4791]) define
|
||||
"special" collections -- ones that are identified by additional
|
||||
values in the DAV:resourcetype property assigned to the collection
|
||||
resource or by other means. These "special" collections are created
|
||||
by new methods (e.g., MKCALENDAR). The addition of a new MKxxx
|
||||
method for each new "special" collection adds to server complexity
|
||||
and is detrimental to overall reliability due to the need to make
|
||||
sure intermediaries are aware of these methods.
|
||||
|
||||
This specification defines an extension to the WebDAV MKCOL method
|
||||
that adds a request body allowing a client to specify WebDAV
|
||||
properties to be set on the newly created collection or resource. In
|
||||
particular, the DAV:resourcetype property can be used to create a
|
||||
"special" collection; alternatively, other properties can be used to
|
||||
create a "special" resource. This avoids the need to invent new
|
||||
MKxxx methods.
|
||||
|
||||
2. Conventions Used in This Document
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
||||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
|
||||
document are to be interpreted as described in [RFC2119].
|
||||
|
||||
This document uses XML DTD fragments (Section 3.2 of
|
||||
[W3C.REC-xml-20081126]) as a purely notational convention. WebDAV
|
||||
request and response bodies cannot be validated by a DTD due to the
|
||||
specific extensibility rules defined in Section 17 of [RFC4918] and
|
||||
due to the fact that all XML elements defined by this specification
|
||||
use the XML namespace name "DAV:". In particular:
|
||||
|
||||
1. Element names use the "DAV:" namespace.
|
||||
|
||||
2. Element ordering is irrelevant unless explicitly stated.
|
||||
|
||||
3. Extension elements (elements not already defined as valid child
|
||||
elements) may be added anywhere, except when explicitly stated
|
||||
otherwise.
|
||||
|
||||
4. Extension attributes (attributes not already defined as valid for
|
||||
this element) may be added anywhere, except when explicitly
|
||||
stated otherwise.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 3]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
When an XML element type in the "DAV:" namespace is referenced in
|
||||
this document outside of the context of an XML fragment, the string
|
||||
"DAV:" will be prefixed to the element type.
|
||||
|
||||
This document inherits, and sometimes extends, DTD productions from
|
||||
Section 14 of [RFC4918].
|
||||
|
||||
3. WebDAV Extended MKCOL
|
||||
|
||||
The WebDAV MKCOL request is extended to allow the inclusion of a
|
||||
request body. The request body is an XML document containing a
|
||||
single DAV:mkcol XML element as the root element. The Content-Type
|
||||
request header MUST be set appropriately for an XML body (e.g., set
|
||||
to "text/xml" or "application/xml"). XML-typed bodies for an MKCOL
|
||||
request that do not have DAV:mkcol as the root element are reserved
|
||||
for future usage.
|
||||
|
||||
One or more DAV:set XML elements may be included in the DAV:mkcol XML
|
||||
element to allow setting properties on the collection as it is
|
||||
created. In particular, to create a collection of a particular type,
|
||||
the DAV:resourcetype XML element MUST be included in a DAV:set XML
|
||||
element and MUST specify the expected resource type elements for the
|
||||
new resource, which MUST include the DAV:collection element that
|
||||
needs to be present for any WebDAV collection.
|
||||
|
||||
As per the PROPPATCH method (Section 9.2 of [RFC4918]), servers MUST
|
||||
process any DAV:set instructions in document order (an exception to
|
||||
the normal rule that ordering is irrelevant). If any one instruction
|
||||
fails to execute successfully, all instructions MUST fail (i.e.,
|
||||
either all succeed or all fail). Thus, if any error occurs during
|
||||
processing, all executed instructions MUST be undone and a proper
|
||||
error result returned. Failure to set a property value on the
|
||||
collection MUST result in a failure of the overall MKCOL request --
|
||||
i.e., the collection is not created.
|
||||
|
||||
The response to an extended MKCOL request MUST be an XML document
|
||||
containing a single DAV:mkcol-response XML element, which MUST
|
||||
contain DAV:propstat XML elements with the status of each property
|
||||
when the request fails due to a failure to set one or more of the
|
||||
properties specified in the request body. The server MAY return a
|
||||
response body in the case where the request is successful, indicating
|
||||
success for setting each property specified in the request body.
|
||||
When an empty response body is returned with a success request status
|
||||
code, the client can assume that all properties were set.
|
||||
|
||||
In all other respects, the behavior of the extended MKCOL request
|
||||
follows that of the standard MKCOL request.
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 4]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
3.1. Extended MKCOL Support
|
||||
|
||||
A server supporting the features described in this document MUST
|
||||
include "extended-mkcol" as a field in the DAV response header from
|
||||
an OPTIONS request on any URI that supports use of the extended MKCOL
|
||||
method.
|
||||
|
||||
3.1.1. Example: Using OPTIONS for the Discovery of Support for Extended
|
||||
MKCOL
|
||||
|
||||
>> Request <<
|
||||
|
||||
OPTIONS /addressbooks/users/ HTTP/1.1
|
||||
Host: addressbook.example.com
|
||||
|
||||
>> Response <<
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE
|
||||
Allow: MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, REPORT, ACL
|
||||
DAV: 1, 2, 3, access-control, extended-mkcol
|
||||
Date: Sat, 11 Nov 2006 09:32:12 GMT
|
||||
Content-Length: 0
|
||||
|
||||
3.2. Status Codes
|
||||
|
||||
As per Section 9.3.1 of [RFC4918].
|
||||
|
||||
3.3. Additional Precondition for Extended MKCOL
|
||||
|
||||
WebDAV ([RFC4918], Section 16) defines preconditions and
|
||||
postconditions for request behavior. This specification adds the
|
||||
following precondition for the extended MKCOL request.
|
||||
|
||||
Name: valid-resourcetype
|
||||
|
||||
Namespace: DAV:
|
||||
|
||||
Use with: Typically 403 (Forbidden)
|
||||
|
||||
Purpose: (precondition) -- The server MUST support the specified
|
||||
resourcetype value for the specified collection.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 5]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
3.4. Example: Successful Extended MKCOL Request
|
||||
|
||||
This example shows how the extended MKCOL request is used to create a
|
||||
collection of a fictitious type "special-resource". The response
|
||||
body is empty as the request completed successfully.
|
||||
|
||||
>> Request <<
|
||||
|
||||
MKCOL /home/special/ HTTP/1.1
|
||||
Host: special.example.com
|
||||
Content-Type: application/xml; charset="utf-8"
|
||||
Content-Length: xxxx
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:mkcol xmlns:D="DAV:"
|
||||
xmlns:E="http://example.com/ns/">
|
||||
<D:set>
|
||||
<D:prop>
|
||||
<D:resourcetype>
|
||||
<D:collection/>
|
||||
<E:special-resource/>
|
||||
</D:resourcetype>
|
||||
<D:displayname>Special Resource</D:displayname>
|
||||
</D:prop>
|
||||
</D:set>
|
||||
</D:mkcol>
|
||||
|
||||
>> Response <<
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Cache-Control: no-cache
|
||||
Date: Sat, 11 Nov 2006 09:32:12 GMT
|
||||
|
||||
3.5. Example: Unsuccessful Extended MKCOL Request
|
||||
|
||||
This example shows an attempt to use the extended MKCOL request to
|
||||
create a collection of a fictitious type "special-resource", which is
|
||||
not actually supported by the server. The response body shows that
|
||||
an error occurred specifically with the DAV:resourcetype property.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 6]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
>> Request <<
|
||||
|
||||
MKCOL /home/special/ HTTP/1.1
|
||||
Host: special.example.com
|
||||
Content-Type: application/xml; charset="utf-8"
|
||||
Content-Length: xxxx
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:mkcol xmlns:D="DAV:"
|
||||
xmlns:E="http://example.com/ns/">
|
||||
<D:set>
|
||||
<D:prop>
|
||||
<D:resourcetype>
|
||||
<D:collection/>
|
||||
<E:special-resource/>
|
||||
</D:resourcetype>
|
||||
<D:displayname>Special Resource</D:displayname>
|
||||
</D:prop>
|
||||
</D:set>
|
||||
</D:mkcol>
|
||||
|
||||
>> Response <<
|
||||
|
||||
HTTP/1.1 403 Forbidden
|
||||
Cache-Control: no-cache
|
||||
Date: Sat, 11 Nov 2006 09:32:12 GMT
|
||||
Content-Type: application/xml; charset="utf-8"
|
||||
Content-Length: xxxx
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:mkcol-response xmlns:D="DAV:">
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype/>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 403 Forbidden</D:status>
|
||||
<D:error><D:valid-resourcetype /></D:error>
|
||||
<D:responsedescription>Resource type is not
|
||||
supported by this server</D:responsedescription>
|
||||
</D:propstat>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:displayname/>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 424 Failed Dependency</D:status>
|
||||
</D:propstat>
|
||||
</D:mkcol-response>
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 7]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
4. Using Extended MKCOL as an Alternative for MKxxx Methods
|
||||
|
||||
One of the goals of this extension is to eliminate the need for other
|
||||
extensions to define their own variant of MKCOL to create the special
|
||||
collections they need. This extension can be used as an alternative
|
||||
to existing MKxxx methods in other extensions as detailed below. If
|
||||
a server supports this extension and the other extension listed, then
|
||||
the server MUST support use of the extended MKCOL method to achieve
|
||||
the same result as the MKxxx method of the other extension.
|
||||
|
||||
4.1. MKCALENDAR Alternative
|
||||
|
||||
CalDAV defines the MKCALENDAR method to create a calendar collection
|
||||
as well as to set properties during creation (Section 5.3.1 of
|
||||
[RFC4791]).
|
||||
|
||||
The extended MKCOL method can be used instead by specifying both DAV:
|
||||
collection and CALDAV:calendar-collection XML elements in the DAV:
|
||||
resourcetype property, set during the extended MKCOL request.
|
||||
|
||||
4.1.1. Example: Using MKCOL Instead of MKCALENDAR
|
||||
|
||||
The first example below shows an MKCALENDAR request containing a
|
||||
CALDAV:mkcalendar XML element in the request body and returning a
|
||||
CALDAV:mkcalendar-response XML element in the response body.
|
||||
|
||||
>> MKCALENDAR Request <<
|
||||
|
||||
MKCALENDAR /home/lisa/calendars/events/ HTTP/1.1
|
||||
Host: calendar.example.com
|
||||
Content-Type: application/xml; charset="utf-8"
|
||||
Content-Length: xxxx
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<C:mkcalendar xmlns:D="DAV:"
|
||||
xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
<D:set>
|
||||
<D:prop>
|
||||
<D:displayname>Lisa's Events</D:displayname>
|
||||
</D:prop>
|
||||
</D:set>
|
||||
</C:mkcalendar>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 8]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
>> MKCALENDAR Response <<
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Cache-Control: no-cache
|
||||
Date: Sat, 11 Nov 2006 09:32:12 GMT
|
||||
Content-Type: application/xml; charset="utf-8"
|
||||
Content-Length: xxxx
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<C:mkcalendar-response xmlns:D="DAV:"
|
||||
xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:displayname/>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</C:mkcalendar-response>
|
||||
|
||||
The second example shows the equivalent extended MKCOL request with
|
||||
the same request and response XML elements.
|
||||
|
||||
>> MKCOL Request <<
|
||||
|
||||
MKCOL /home/lisa/calendars/events/ HTTP/1.1
|
||||
Host: calendar.example.com
|
||||
Content-Type: application/xml; charset="utf-8"
|
||||
Content-Length: xxxx
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:mkcol xmlns:D="DAV:"
|
||||
xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
<D:set>
|
||||
<D:prop>
|
||||
<D:resourcetype>
|
||||
<D:collection/>
|
||||
<C:calendar/>
|
||||
</D:resourcetype>
|
||||
<D:displayname>Lisa's Events</D:displayname>
|
||||
</D:prop>
|
||||
</D:set>
|
||||
</D:mkcol>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 9]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
>> MKCOL Response <<
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Cache-Control: no-cache
|
||||
Date: Sat, 11 Nov 2006 09:32:12 GMT
|
||||
Content-Type: application/xml; charset="utf-8"
|
||||
Content-Length: xxxx
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:mkcol-response xmlns:D="DAV:"
|
||||
xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype/>
|
||||
<D:displayname/>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:mkcol-response>
|
||||
|
||||
5. XML Element Definitions
|
||||
|
||||
5.1. mkcol XML Element
|
||||
|
||||
Name: mkcol
|
||||
|
||||
Namespace: DAV:
|
||||
|
||||
Purpose: Used in a request to specify properties to be set in an
|
||||
extended MKCOL request, as well as any additional information
|
||||
needed when creating the resource.
|
||||
|
||||
Description: This XML element is a container for the information
|
||||
required to modify the properties on a collection resource as it
|
||||
is created in an extended MKCOL request.
|
||||
|
||||
Definition:
|
||||
|
||||
<!ELEMENT mkcol (set+)>
|
||||
|
||||
5.2. mkcol-response XML Element
|
||||
|
||||
Name: mkcol-response
|
||||
|
||||
Namespace: DAV:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 10]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
Purpose: Used in a response to indicate the status of properties
|
||||
that were set or failed to be set during an extended MKCOL
|
||||
request.
|
||||
|
||||
Description: This XML element is a container for the information
|
||||
returned about a resource that has been created in an extended
|
||||
MKCOL request.
|
||||
|
||||
Definition:
|
||||
|
||||
<!ELEMENT mkcol-response (propstat+)>
|
||||
|
||||
6. Security Considerations
|
||||
|
||||
This extension does not introduce any new security concerns beyond
|
||||
those already described in HTTP [RFC2616] and WebDAV [RFC4918].
|
||||
|
||||
7. Acknowledgments
|
||||
|
||||
Thanks to Bernard Desruisseaux, Mike Douglass, Alexey Melnikov,
|
||||
Julian Reschke, and Simon Vaillancourt.
|
||||
|
||||
|
||||
8. Normative References
|
||||
|
||||
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
|
||||
Requirement Levels", BCP 14, RFC 2119, March 1997.
|
||||
|
||||
[RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H.,
|
||||
Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext
|
||||
Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
|
||||
|
||||
[RFC4791] Daboo, C., Desruisseaux, B., and L. Dusseault,
|
||||
"Calendaring Extensions to WebDAV (CalDAV)", RFC 4791,
|
||||
March 2007.
|
||||
|
||||
[RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed
|
||||
Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
|
||||
|
||||
[W3C.REC-xml-20081126]
|
||||
Maler, E., Yergeau, F., Paoli, J., Bray, T., and C.
|
||||
Sperberg-McQueen, "Extensible Markup Language (XML) 1.0
|
||||
(Fifth Edition)", World Wide Web Consortium
|
||||
Recommendation REC-xml-20081126, November 2008,
|
||||
<http://www.w3.org/TR/2008/REC-xml-20081126>.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 11]
|
||||
|
||||
RFC 5689 Extended MKCOL for WebDAV September 2009
|
||||
|
||||
|
||||
Author's Address
|
||||
|
||||
Cyrus Daboo
|
||||
Apple Inc.
|
||||
1 Infinite Loop
|
||||
Cupertino, CA 95014
|
||||
USA
|
||||
|
||||
EMail: cyrus@daboo.name
|
||||
URI: http://www.apple.com/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Daboo Standards Track [Page 12]
|
||||
|
|
@ -0,0 +1,563 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Internet Engineering Task Force (IETF) L. Dusseault
|
||||
Request for Comments: 5789 Linden Lab
|
||||
Category: Standards Track J. Snell
|
||||
ISSN: 2070-1721 March 2010
|
||||
|
||||
|
||||
PATCH Method for HTTP
|
||||
|
||||
Abstract
|
||||
|
||||
Several applications extending the Hypertext Transfer Protocol (HTTP)
|
||||
require a feature to do partial resource modification. The existing
|
||||
HTTP PUT method only allows a complete replacement of a document.
|
||||
This proposal adds a new HTTP method, PATCH, to modify an existing
|
||||
HTTP resource.
|
||||
|
||||
Status of This Memo
|
||||
|
||||
This is an Internet Standards Track document.
|
||||
|
||||
This document is a product of the Internet Engineering Task Force
|
||||
(IETF). It represents the consensus of the IETF community. It has
|
||||
received public review and has been approved for publication by the
|
||||
Internet Engineering Steering Group (IESG). Further information on
|
||||
Internet Standards is available in Section 2 of RFC 5741.
|
||||
|
||||
Information about the current status of this document, any errata,
|
||||
and how to provide feedback on it may be obtained at
|
||||
http://www.rfc-editor.org/info/rfc5789.
|
||||
|
||||
Copyright Notice
|
||||
|
||||
Copyright (c) 2010 IETF Trust and the persons identified as the
|
||||
document authors. All rights reserved.
|
||||
|
||||
This document is subject to BCP 78 and the IETF Trust's Legal
|
||||
Provisions Relating to IETF Documents
|
||||
(http://trustee.ietf.org/license-info) in effect on the date of
|
||||
publication of this document. Please review these documents
|
||||
carefully, as they describe your rights and restrictions with respect
|
||||
to this document. Code Components extracted from this document must
|
||||
include Simplified BSD License text as described in Section 4.e of
|
||||
the Trust Legal Provisions and are provided without warranty as
|
||||
described in the Simplified BSD License.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Dusseault & Snell Standards Track [Page 1]
|
||||
|
||||
RFC 5789 HTTP PATCH March 2010
|
||||
|
||||
|
||||
Table of Contents
|
||||
|
||||
1. Introduction ....................................................2
|
||||
2. The PATCH Method ................................................2
|
||||
2.1. A Simple PATCH Example .....................................4
|
||||
2.2. Error Handling .............................................5
|
||||
3. Advertising Support in OPTIONS ..................................7
|
||||
3.1. The Accept-Patch Header ....................................7
|
||||
3.2. Example OPTIONS Request and Response .......................7
|
||||
4. IANA Considerations .............................................8
|
||||
4.1. The Accept-Patch Response Header ...........................8
|
||||
5. Security Considerations .........................................8
|
||||
6. References ......................................................9
|
||||
6.1. Normative References .......................................9
|
||||
6.2. Informative References .....................................9
|
||||
Appendix A. Acknowledgements .....................................10
|
||||
|
||||
1. Introduction
|
||||
|
||||
This specification defines the new HTTP/1.1 [RFC2616] method, PATCH,
|
||||
which is used to apply partial modifications to a resource.
|
||||
|
||||
A new method is necessary to improve interoperability and prevent
|
||||
errors. The PUT method is already defined to overwrite a resource
|
||||
with a complete new body, and cannot be reused to do partial changes.
|
||||
Otherwise, proxies and caches, and even clients and servers, may get
|
||||
confused as to the result of the operation. POST is already used but
|
||||
without broad interoperability (for one, there is no standard way to
|
||||
discover patch format support). PATCH was mentioned in earlier HTTP
|
||||
specifications, but not completely defined.
|
||||
|
||||
In this document, the key words "MUST", "MUST NOT", "REQUIRED",
|
||||
"SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY",
|
||||
and "OPTIONAL" are to be interpreted as described in [RFC2119].
|
||||
|
||||
Furthermore, this document uses the ABNF syntax defined in Section
|
||||
2.1 of [RFC2616].
|
||||
|
||||
2. The PATCH Method
|
||||
|
||||
The PATCH method requests that a set of changes described in the
|
||||
request entity be applied to the resource identified by the Request-
|
||||
URI. The set of changes is represented in a format called a "patch
|
||||
document" identified by a media type. If the Request-URI does not
|
||||
point to an existing resource, the server MAY create a new resource,
|
||||
depending on the patch document type (whether it can logically modify
|
||||
a null resource) and permissions, etc.
|
||||
|
||||
|
||||
|
||||
|
||||
Dusseault & Snell Standards Track [Page 2]
|
||||
|
||||
RFC 5789 HTTP PATCH March 2010
|
||||
|
||||
|
||||
The difference between the PUT and PATCH requests is reflected in the
|
||||
way the server processes the enclosed entity to modify the resource
|
||||
identified by the Request-URI. In a PUT request, the enclosed entity
|
||||
is considered to be a modified version of the resource stored on the
|
||||
origin server, and the client is requesting that the stored version
|
||||
be replaced. With PATCH, however, the enclosed entity contains a set
|
||||
of instructions describing how a resource currently residing on the
|
||||
origin server should be modified to produce a new version. The PATCH
|
||||
method affects the resource identified by the Request-URI, and it
|
||||
also MAY have side effects on other resources; i.e., new resources
|
||||
may be created, or existing ones modified, by the application of a
|
||||
PATCH.
|
||||
|
||||
PATCH is neither safe nor idempotent as defined by [RFC2616], Section
|
||||
9.1.
|
||||
|
||||
A PATCH request can be issued in such a way as to be idempotent,
|
||||
which also helps prevent bad outcomes from collisions between two
|
||||
PATCH requests on the same resource in a similar time frame.
|
||||
Collisions from multiple PATCH requests may be more dangerous than
|
||||
PUT collisions because some patch formats need to operate from a
|
||||
known base-point or else they will corrupt the resource. Clients
|
||||
using this kind of patch application SHOULD use a conditional request
|
||||
such that the request will fail if the resource has been updated
|
||||
since the client last accessed the resource. For example, the client
|
||||
can use a strong ETag [RFC2616] in an If-Match header on the PATCH
|
||||
request.
|
||||
|
||||
There are also cases where patch formats do not need to operate from
|
||||
a known base-point (e.g., appending text lines to log files, or non-
|
||||
colliding rows to database tables), in which case the same care in
|
||||
client requests is not needed.
|
||||
|
||||
The server MUST apply the entire set of changes atomically and never
|
||||
provide (e.g., in response to a GET during this operation) a
|
||||
partially modified representation. If the entire patch document
|
||||
cannot be successfully applied, then the server MUST NOT apply any of
|
||||
the changes. The determination of what constitutes a successful
|
||||
PATCH can vary depending on the patch document and the type of
|
||||
resource(s) being modified. For example, the common 'diff' utility
|
||||
can generate a patch document that applies to multiple files in a
|
||||
directory hierarchy. The atomicity requirement holds for all
|
||||
directly affected files. See "Error Handling", Section 2.2, for
|
||||
details on status codes and possible error conditions.
|
||||
|
||||
If the request passes through a cache and the Request-URI identifies
|
||||
one or more currently cached entities, those entries SHOULD be
|
||||
treated as stale. A response to this method is only cacheable if it
|
||||
|
||||
|
||||
|
||||
Dusseault & Snell Standards Track [Page 3]
|
||||
|
||||
RFC 5789 HTTP PATCH March 2010
|
||||
|
||||
|
||||
contains explicit freshness information (such as an Expires header or
|
||||
"Cache-Control: max-age" directive) as well as the Content-Location
|
||||
header matching the Request-URI, indicating that the PATCH response
|
||||
body is a resource representation. A cached PATCH response can only
|
||||
be used to respond to subsequent GET and HEAD requests; it MUST NOT
|
||||
be used to respond to other methods (in particular, PATCH).
|
||||
|
||||
Note that entity-headers contained in the request apply only to the
|
||||
contained patch document and MUST NOT be applied to the resource
|
||||
being modified. Thus, a Content-Language header could be present on
|
||||
the request, but it would only mean (for whatever that's worth) that
|
||||
the patch document had a language. Servers SHOULD NOT store such
|
||||
headers except as trace information, and SHOULD NOT use such header
|
||||
values the same way they might be used on PUT requests. Therefore,
|
||||
this document does not specify a way to modify a document's Content-
|
||||
Type or Content-Language value through headers, though a mechanism
|
||||
could well be designed to achieve this goal through a patch document.
|
||||
|
||||
There is no guarantee that a resource can be modified with PATCH.
|
||||
Further, it is expected that different patch document formats will be
|
||||
appropriate for different types of resources and that no single
|
||||
format will be appropriate for all types of resources. Therefore,
|
||||
there is no single default patch document format that implementations
|
||||
are required to support. Servers MUST ensure that a received patch
|
||||
document is appropriate for the type of resource identified by the
|
||||
Request-URI.
|
||||
|
||||
Clients need to choose when to use PATCH rather than PUT. For
|
||||
example, if the patch document size is larger than the size of the
|
||||
new resource data that would be used in a PUT, then it might make
|
||||
sense to use PUT instead of PATCH. A comparison to POST is even more
|
||||
difficult, because POST is used in widely varying ways and can
|
||||
encompass PUT and PATCH-like operations if the server chooses. If
|
||||
the operation does not modify the resource identified by the Request-
|
||||
URI in a predictable way, POST should be considered instead of PATCH
|
||||
or PUT.
|
||||
|
||||
2.1. A Simple PATCH Example
|
||||
|
||||
PATCH /file.txt HTTP/1.1
|
||||
Host: www.example.com
|
||||
Content-Type: application/example
|
||||
If-Match: "e0023aa4e"
|
||||
Content-Length: 100
|
||||
|
||||
[description of changes]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Dusseault & Snell Standards Track [Page 4]
|
||||
|
||||
RFC 5789 HTTP PATCH March 2010
|
||||
|
||||
|
||||
This example illustrates use of a hypothetical patch document on an
|
||||
existing resource.
|
||||
|
||||
Successful PATCH response to existing text file:
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Content-Location: /file.txt
|
||||
ETag: "e0023aa4f"
|
||||
|
||||
The 204 response code is used because the response does not carry a
|
||||
message body (which a response with the 200 code would have). Note
|
||||
that other success codes could be used as well.
|
||||
|
||||
Furthermore, the ETag response header field contains the ETag for the
|
||||
entity created by applying the PATCH, available at
|
||||
http://www.example.com/file.txt, as indicated by the Content-Location
|
||||
response header field.
|
||||
|
||||
2.2. Error Handling
|
||||
|
||||
There are several known conditions under which a PATCH request can
|
||||
fail.
|
||||
|
||||
Malformed patch document: When the server determines that the patch
|
||||
document provided by the client is not properly formatted, it
|
||||
SHOULD return a 400 (Bad Request) response. The definition of
|
||||
badly formatted depends on the patch document chosen.
|
||||
|
||||
Unsupported patch document: Can be specified using a 415
|
||||
(Unsupported Media Type) response when the client sends a patch
|
||||
document format that the server does not support for the resource
|
||||
identified by the Request-URI. Such a response SHOULD include an
|
||||
Accept-Patch response header as described in Section 3.1 to notify
|
||||
the client what patch document media types are supported.
|
||||
|
||||
Unprocessable request: Can be specified with a 422 (Unprocessable
|
||||
Entity) response ([RFC4918], Section 11.2) when the server
|
||||
understands the patch document and the syntax of the patch
|
||||
document appears to be valid, but the server is incapable of
|
||||
processing the request. This might include attempts to modify a
|
||||
resource in a way that would cause the resource to become invalid;
|
||||
for instance, a modification to a well-formed XML document that
|
||||
would cause it to no longer be well-formed. There may also be
|
||||
more specific errors like "Conflicting State" that could be
|
||||
signaled with this status code, but the more specific error would
|
||||
generally be more helpful.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Dusseault & Snell Standards Track [Page 5]
|
||||
|
||||
RFC 5789 HTTP PATCH March 2010
|
||||
|
||||
|
||||
Resource not found: Can be specified with a 404 (Not Found) status
|
||||
code when the client attempted to apply a patch document to a non-
|
||||
existent resource, but the patch document chosen cannot be applied
|
||||
to a non-existent resource.
|
||||
|
||||
Conflicting state: Can be specified with a 409 (Conflict) status
|
||||
code when the request cannot be applied given the state of the
|
||||
resource. For example, if the client attempted to apply a
|
||||
structural modification and the structures assumed to exist did
|
||||
not exist (with XML, a patch might specify changing element 'foo'
|
||||
to element 'bar' but element 'foo' might not exist).
|
||||
|
||||
Conflicting modification: When a client uses either the If-Match or
|
||||
If-Unmodified-Since header to define a precondition, and that
|
||||
precondition failed, then the 412 (Precondition Failed) error is
|
||||
most helpful to the client. However, that response makes no sense
|
||||
if there was no precondition on the request. In cases when the
|
||||
server detects a possible conflicting modification and no
|
||||
precondition was defined in the request, the server can return a
|
||||
409 (Conflict) response.
|
||||
|
||||
Concurrent modification: Some applications of PATCH might require
|
||||
the server to process requests in the order in which they are
|
||||
received. If a server is operating under those restrictions, and
|
||||
it receives concurrent requests to modify the same resource, but
|
||||
is unable to queue those requests, the server can usefully
|
||||
indicate this error by using a 409 (Conflict) response.
|
||||
|
||||
Note that the 409 Conflict response gives reasonably consistent
|
||||
information to clients. Depending on the application and the nature
|
||||
of the patch format, the client might be able to reissue the request
|
||||
as is (e.g., an instruction to append a line to a log file), have to
|
||||
retrieve the resource content to recalculate a patch, or have to fail
|
||||
the operation.
|
||||
|
||||
Other HTTP status codes can also be used under the appropriate
|
||||
circumstances.
|
||||
|
||||
The entity body of error responses SHOULD contain enough information
|
||||
to communicate the nature of the error to the client. The content-
|
||||
type of the response entity can vary across implementations.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Dusseault & Snell Standards Track [Page 6]
|
||||
|
||||
RFC 5789 HTTP PATCH March 2010
|
||||
|
||||
|
||||
3. Advertising Support in OPTIONS
|
||||
|
||||
A server can advertise its support for the PATCH method by adding it
|
||||
to the listing of allowed methods in the "Allow" OPTIONS response
|
||||
header defined in HTTP/1.1. The PATCH method MAY appear in the
|
||||
"Allow" header even if the Accept-Patch header is absent, in which
|
||||
case the list of allowed patch documents is not advertised.
|
||||
|
||||
3.1. The Accept-Patch Header
|
||||
|
||||
This specification introduces a new response header Accept-Patch used
|
||||
to specify the patch document formats accepted by the server.
|
||||
Accept-Patch SHOULD appear in the OPTIONS response for any resource
|
||||
that supports the use of the PATCH method. The presence of the
|
||||
Accept-Patch header in response to any method is an implicit
|
||||
indication that PATCH is allowed on the resource identified by the
|
||||
Request-URI. The presence of a specific patch document format in
|
||||
this header indicates that that specific format is allowed on the
|
||||
resource identified by the Request-URI.
|
||||
|
||||
Accept-Patch = "Accept-Patch" ":" 1#media-type
|
||||
|
||||
The Accept-Patch header specifies a comma-separated listing of media-
|
||||
types (with optional parameters) as defined by [RFC2616], Section
|
||||
3.7.
|
||||
|
||||
Example:
|
||||
|
||||
Accept-Patch: text/example;charset=utf-8
|
||||
|
||||
3.2. Example OPTIONS Request and Response
|
||||
|
||||
[request]
|
||||
|
||||
OPTIONS /example/buddies.xml HTTP/1.1
|
||||
Host: www.example.com
|
||||
|
||||
[response]
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Allow: GET, PUT, POST, OPTIONS, HEAD, DELETE, PATCH
|
||||
Accept-Patch: application/example, text/example
|
||||
|
||||
The examples show a server that supports PATCH generally using two
|
||||
hypothetical patch document formats.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Dusseault & Snell Standards Track [Page 7]
|
||||
|
||||
RFC 5789 HTTP PATCH March 2010
|
||||
|
||||
|
||||
4. IANA Considerations
|
||||
|
||||
4.1. The Accept-Patch Response Header
|
||||
|
||||
The Accept-Patch response header has been added to the permanent
|
||||
registry (see [RFC3864]).
|
||||
|
||||
Header field name: Accept-Patch
|
||||
|
||||
Applicable Protocol: HTTP
|
||||
|
||||
Author/Change controller: IETF
|
||||
|
||||
Specification document: this specification
|
||||
|
||||
5. Security Considerations
|
||||
|
||||
The security considerations for PATCH are nearly identical to the
|
||||
security considerations for PUT ([RFC2616], Section 9.6). These
|
||||
include authorizing requests (possibly through access control and/or
|
||||
authentication) and ensuring that data is not corrupted through
|
||||
transport errors or through accidental overwrites. Whatever
|
||||
mechanisms are used for PUT can be used for PATCH as well. The
|
||||
following considerations apply especially to PATCH.
|
||||
|
||||
A document that is patched might be more likely to be corrupted than
|
||||
a document that is overridden in entirety, but that concern can be
|
||||
addressed through the use of mechanisms such as conditional requests
|
||||
using ETags and the If-Match request header as described in
|
||||
Section 2. If a PATCH request fails, the client can issue a GET
|
||||
request to the resource to see what state it is in. In some cases,
|
||||
the client might be able to check the contents of the resource to see
|
||||
if the PATCH request can be resent, but in other cases, the attempt
|
||||
will just fail and/or a user will have to verify intent. In the case
|
||||
of a failure of the underlying transport channel, where a PATCH
|
||||
response is not received before the channel fails or some other
|
||||
timeout happens, the client might have to issue a GET request to see
|
||||
whether the request was applied. The client might want to ensure
|
||||
that the GET request bypasses caches using mechanisms described in
|
||||
HTTP specifications (see, for example, Section 13.1.6 of [RFC2616]).
|
||||
|
||||
Sometimes an HTTP intermediary might try to detect viruses being sent
|
||||
via HTTP by checking the body of the PUT/POST request or GET
|
||||
response. The PATCH method complicates such watch-keeping because
|
||||
neither the source document nor the patch document might be a virus,
|
||||
yet the result could be. This security consideration is not
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Dusseault & Snell Standards Track [Page 8]
|
||||
|
||||
RFC 5789 HTTP PATCH March 2010
|
||||
|
||||
|
||||
materially different from those already introduced by byte-range
|
||||
downloads, downloading patch documents, uploading zipped (compressed)
|
||||
files, and so on.
|
||||
|
||||
Individual patch documents will have their own specific security
|
||||
considerations that will likely vary depending on the types of
|
||||
resources being patched. The considerations for patched binary
|
||||
resources, for instance, will be different than those for patched XML
|
||||
documents. Servers MUST take adequate precautions to ensure that
|
||||
malicious clients cannot consume excessive server resources (e.g.,
|
||||
CPU, disk I/O) through the client's use of PATCH.
|
||||
|
||||
6. References
|
||||
|
||||
6.1. Normative References
|
||||
|
||||
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
|
||||
Requirement Levels", BCP 14, RFC 2119, March 1997.
|
||||
|
||||
[RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H.,
|
||||
Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext
|
||||
Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
|
||||
|
||||
[RFC3864] Klyne, G., Nottingham, M., and J. Mogul, "Registration
|
||||
Procedures for Message Header Fields", BCP 90, RFC 3864,
|
||||
September 2004.
|
||||
|
||||
6.2. Informative References
|
||||
|
||||
[RFC4918] Dusseault, L., "HTTP Extensions for Web Distributed
|
||||
Authoring and Versioning (WebDAV)", RFC 4918, June 2007.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Dusseault & Snell Standards Track [Page 9]
|
||||
|
||||
RFC 5789 HTTP PATCH March 2010
|
||||
|
||||
|
||||
Appendix A. Acknowledgements
|
||||
|
||||
PATCH is not a new concept, it first appeared in HTTP in drafts of
|
||||
version 1.1 written by Roy Fielding and Henrik Frystyk and also
|
||||
appears in Section 19.6.1.1 of RFC 2068.
|
||||
|
||||
Thanks to Adam Roach, Chris Sharp, Julian Reschke, Geoff Clemm, Scott
|
||||
Lawrence, Jeffrey Mogul, Roy Fielding, Greg Stein, Jim Luther, Alex
|
||||
Rousskov, Jamie Lokier, Joe Hildebrand, Mark Nottingham, Michael
|
||||
Balloni, Cyrus Daboo, Brian Carpenter, John Klensin, Eliot Lear, SM,
|
||||
and Bernie Hoeneisen for review and advice on this document. In
|
||||
particular, Julian Reschke did repeated reviews, made many useful
|
||||
suggestions, and was critical to the publication of this document.
|
||||
|
||||
Authors' Addresses
|
||||
|
||||
Lisa Dusseault
|
||||
Linden Lab
|
||||
945 Battery Street
|
||||
San Francisco, CA 94111
|
||||
USA
|
||||
|
||||
EMail: lisa.dusseault@gmail.com
|
||||
|
||||
|
||||
James M. Snell
|
||||
|
||||
EMail: jasnell@gmail.com
|
||||
URI: http://www.snellspace.com
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Dusseault & Snell Standards Track [Page 10]
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
|
||||
Addressbook/CardDAV server example
|
||||
|
||||
This server features CardDAV support
|
||||
|
||||
*/
|
||||
|
||||
// settings
|
||||
date_default_timezone_set('Canada/Eastern');
|
||||
|
||||
// Make sure this setting is turned on and reflect the root url for your WebDAV server.
|
||||
// This can be for example the root / or a complete path to your server script
|
||||
$baseUri = '/';
|
||||
|
||||
/* Database */
|
||||
$pdo = new PDO('sqlite:data/db.sqlite');
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
//Mapping PHP errors to exceptions
|
||||
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
|
||||
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
||||
}
|
||||
set_error_handler("exception_error_handler");
|
||||
|
||||
// Autoloader
|
||||
require_once 'lib/Sabre/autoload.php';
|
||||
|
||||
// Backends
|
||||
$authBackend = new Sabre_DAV_Auth_Backend_PDO($pdo);
|
||||
$principalBackend = new Sabre_DAVACL_PrincipalBackend_PDO($pdo);
|
||||
$carddavBackend = new Sabre_CardDAV_Backend_PDO($pdo);
|
||||
//$caldavBackend = new Sabre_CalDAV_Backend_PDO($pdo);
|
||||
|
||||
// Setting up the directory tree //
|
||||
$nodes = array(
|
||||
new Sabre_DAVACL_PrincipalCollection($principalBackend),
|
||||
// new Sabre_CalDAV_CalendarRootNode($authBackend, $caldavBackend),
|
||||
new Sabre_CardDAV_AddressBookRoot($principalBackend, $carddavBackend),
|
||||
);
|
||||
|
||||
// The object tree needs in turn to be passed to the server class
|
||||
$server = new Sabre_DAV_Server($nodes);
|
||||
$server->setBaseUri($baseUri);
|
||||
|
||||
// Plugins
|
||||
$server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend,'SabreDAV'));
|
||||
$server->addPlugin(new Sabre_DAV_Browser_Plugin());
|
||||
//$server->addPlugin(new Sabre_CalDAV_Plugin());
|
||||
$server->addPlugin(new Sabre_CardDAV_Plugin());
|
||||
$server->addPlugin(new Sabre_DAVACL_Plugin());
|
||||
|
||||
// And off we go!
|
||||
$server->exec();
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
// !!!! Make sure the Sabre directory is in the include_path !!!
|
||||
// example:
|
||||
// set_include_path('lib/' . PATH_SEPARATOR . get_include_path());
|
||||
|
||||
// settings
|
||||
date_default_timezone_set('Canada/Eastern');
|
||||
|
||||
// Files we need
|
||||
require_once 'Sabre/autoload.php';
|
||||
|
||||
$u = 'admin';
|
||||
$p = '1234';
|
||||
|
||||
$auth = new Sabre_HTTP_BasicAuth();
|
||||
|
||||
$result = $auth->getUserPass();
|
||||
|
||||
if (!$result || $result[0]!=$u || $result[1]!=$p) {
|
||||
|
||||
$auth->requireLogin();
|
||||
echo "Authentication required\n";
|
||||
die();
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
|
||||
CalendarServer example
|
||||
|
||||
This server features CalDAV support
|
||||
|
||||
*/
|
||||
|
||||
// settings
|
||||
date_default_timezone_set('Canada/Eastern');
|
||||
|
||||
// If you want to run the SabreDAV server in a custom location (using mod_rewrite for instance)
|
||||
// You can override the baseUri here.
|
||||
// $baseUri = '/';
|
||||
|
||||
/* Database */
|
||||
$pdo = new PDO('sqlite:data/db.sqlite');
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
//Mapping PHP errors to exceptions
|
||||
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
|
||||
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
||||
}
|
||||
set_error_handler("exception_error_handler");
|
||||
|
||||
// Files we need
|
||||
require_once 'lib/Sabre/autoload.php';
|
||||
|
||||
// Backends
|
||||
$authBackend = new Sabre_DAV_Auth_Backend_PDO($pdo);
|
||||
$calendarBackend = new Sabre_CalDAV_Backend_PDO($pdo);
|
||||
$principalBackend = new Sabre_DAVACL_PrincipalBackend_PDO($pdo);
|
||||
|
||||
// Directory structure
|
||||
$tree = array(
|
||||
new Sabre_CalDAV_Principal_Collection($principalBackend),
|
||||
new Sabre_CalDAV_CalendarRootNode($principalBackend, $calendarBackend),
|
||||
);
|
||||
|
||||
$server = new Sabre_DAV_Server($tree);
|
||||
|
||||
if (isset($baseUri))
|
||||
$server->setBaseUri($baseUri);
|
||||
|
||||
/* Server Plugins */
|
||||
$authPlugin = new Sabre_DAV_Auth_Plugin($authBackend,'SabreDAV');
|
||||
$server->addPlugin($authPlugin);
|
||||
|
||||
$aclPlugin = new Sabre_DAVACL_Plugin();
|
||||
$server->addPlugin($aclPlugin);
|
||||
|
||||
$caldavPlugin = new Sabre_CalDAV_Plugin();
|
||||
$server->addPlugin($caldavPlugin);
|
||||
|
||||
// Support for html frontend
|
||||
$browser = new Sabre_DAV_Browser_Plugin();
|
||||
$server->addPlugin($browser);
|
||||
|
||||
// And off we go!
|
||||
$server->exec();
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
// !!!! Make sure the Sabre directory is in the include_path !!!
|
||||
// example:
|
||||
// set_include_path('lib/' . PATH_SEPARATOR . get_include_path());
|
||||
|
||||
// settings
|
||||
date_default_timezone_set('Canada/Eastern');
|
||||
|
||||
// Files we need
|
||||
require_once 'Sabre/autoload.php';
|
||||
|
||||
$u = 'admin';
|
||||
$p = '1234';
|
||||
|
||||
$auth = new Sabre_HTTP_DigestAuth();
|
||||
$auth->init();
|
||||
|
||||
if ($auth->getUsername() != $u || !$auth->validatePassword($p)) {
|
||||
|
||||
$auth->requireLogin();
|
||||
echo "Authentication required\n";
|
||||
die();
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
// !!!! Make sure the Sabre directory is in the include_path !!!
|
||||
// example:
|
||||
set_include_path('lib/' . PATH_SEPARATOR . get_include_path());
|
||||
|
||||
/*
|
||||
|
||||
This is the best starting point if you're just interested in setting up a fileserver.
|
||||
|
||||
Make sure that the 'public' and 'tmpdata' exists, with write permissions
|
||||
for your server.
|
||||
|
||||
*/
|
||||
|
||||
// settings
|
||||
date_default_timezone_set('Canada/Eastern');
|
||||
$publicDir = 'public';
|
||||
$tmpDir = 'tmpdata';
|
||||
|
||||
// If you want to run the SabreDAV server in a custom location (using mod_rewrite for instance)
|
||||
// You can override the baseUri here.
|
||||
// $baseUri = '/';
|
||||
|
||||
|
||||
// Files we need
|
||||
require_once 'Sabre/autoload.php';
|
||||
|
||||
// Create the root node
|
||||
$root = new Sabre_DAV_FS_Directory($publicDir);
|
||||
|
||||
// The rootnode needs in turn to be passed to the server class
|
||||
$server = new Sabre_DAV_Server($root);
|
||||
|
||||
if (isset($baseUri))
|
||||
$server->setBaseUri($baseUri);
|
||||
|
||||
// Support for LOCK and UNLOCK
|
||||
$lockBackend = new Sabre_DAV_Locks_Backend_File($tmpDir . '/locksdb');
|
||||
$lockPlugin = new Sabre_DAV_Locks_Plugin($lockBackend);
|
||||
$server->addPlugin($lockPlugin);
|
||||
|
||||
// Support for html frontend
|
||||
$browser = new Sabre_DAV_Browser_Plugin();
|
||||
$server->addPlugin($browser);
|
||||
|
||||
// Automatically guess (some) contenttypes, based on extesion
|
||||
$server->addPlugin(new Sabre_DAV_Browser_GuessContentType());
|
||||
|
||||
// Authentication backend
|
||||
$authBackend = new Sabre_DAV_Auth_Backend_File('.htdigest');
|
||||
$auth = new Sabre_DAV_Auth_Plugin($authBackend,'SabreDAV');
|
||||
$server->addPlugin($auth);
|
||||
|
||||
// Temporary file filter
|
||||
$tempFF = new Sabre_DAV_TemporaryFileFilterPlugin($tmpDir);
|
||||
$server->addPlugin($tempFF);
|
||||
|
||||
// And off we go!
|
||||
$server->exec();
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This server combines both CardDAV and CalDAV functionality into a single
|
||||
* server. It is assumed that the server runs at the root of a HTTP domain (be
|
||||
* that a domainname-based vhost or a specific TCP port.
|
||||
*
|
||||
* This example also assumes that you're using SQLite and the database has
|
||||
* already been setup (along with the database tables).
|
||||
*
|
||||
* You may choose to use MySQL instead, just change the PDO connection
|
||||
* statement.
|
||||
*/
|
||||
|
||||
/**
|
||||
* UTC or GMT is easy to work with, and usually recommended for any
|
||||
* application.
|
||||
*/
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
/**
|
||||
* Make sure this setting is turned on and reflect the root url for your WebDAV
|
||||
* server.
|
||||
*
|
||||
* This can be for example the root / or a complete path to your server script.
|
||||
*/
|
||||
$baseUri = '/';
|
||||
|
||||
/**
|
||||
* Database
|
||||
*
|
||||
* Feel free to switch this to MySQL, it will definitely be better for higher
|
||||
* concurrency.
|
||||
*/
|
||||
$pdo = new PDO('sqlite:data/db.sqlite');
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
/**
|
||||
* Mapping PHP errors to exceptions.
|
||||
*
|
||||
* While this is not strictly needed, it makes a lot of sense to do so. If an
|
||||
* E_NOTICE or anything appears in your code, this allows SabreDAV to intercept
|
||||
* the issue and send a proper response back to the client (HTTP/1.1 500).
|
||||
*/
|
||||
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
|
||||
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
||||
}
|
||||
set_error_handler("exception_error_handler");
|
||||
|
||||
// Autoloader
|
||||
require_once 'lib/Sabre/autoload.php';
|
||||
|
||||
/**
|
||||
* The backends. Yes we do really need all of them.
|
||||
*
|
||||
* This allows any developer to subclass just any of them and hook into their
|
||||
* own backend systems.
|
||||
*/
|
||||
$authBackend = new Sabre_DAV_Auth_Backend_PDO($pdo);
|
||||
$principalBackend = new Sabre_DAVACL_PrincipalBackend_PDO($pdo);
|
||||
$carddavBackend = new Sabre_CardDAV_Backend_PDO($pdo);
|
||||
$caldavBackend = new Sabre_CalDAV_Backend_PDO($pdo);
|
||||
|
||||
/**
|
||||
* The directory tree
|
||||
*
|
||||
* Basically this is an array which contains the 'top-level' directories in the
|
||||
* WebDAV server.
|
||||
*/
|
||||
$nodes = array(
|
||||
// /principals
|
||||
new Sabre_CalDAV_Principal_Collection($principalBackend),
|
||||
// /calendars
|
||||
new Sabre_CalDAV_CalendarRootNode($principalBackend, $caldavBackend),
|
||||
// /addressbook
|
||||
new Sabre_CardDAV_AddressBookRoot($principalBackend, $carddavBackend),
|
||||
);
|
||||
|
||||
// The object tree needs in turn to be passed to the server class
|
||||
$server = new Sabre_DAV_Server($nodes);
|
||||
$server->setBaseUri($baseUri);
|
||||
|
||||
// Plugins
|
||||
$server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend,'SabreDAV'));
|
||||
$server->addPlugin(new Sabre_DAV_Browser_Plugin());
|
||||
$server->addPlugin(new Sabre_CalDAV_Plugin());
|
||||
$server->addPlugin(new Sabre_CardDAV_Plugin());
|
||||
$server->addPlugin(new Sabre_DAVACL_Plugin());
|
||||
|
||||
// And off we go!
|
||||
$server->exec();
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
// !!!! Make sure the Sabre directory is in the include_path !!!
|
||||
// example:
|
||||
// set_include_path('lib/' . PATH_SEPARATOR . get_include_path());
|
||||
|
||||
/*
|
||||
|
||||
This example demonstrates a simple way to create your own virtual filesystems.
|
||||
By extending the _File and Directory classes, you can easily create a tree
|
||||
based on various datasources.
|
||||
|
||||
The most obvious example is the filesystem itself. A more complete and documented
|
||||
example can be found in:
|
||||
|
||||
lib/Sabre/DAV/FS/Node.php
|
||||
lib/Sabre/DAV/FS/Directory.php
|
||||
lib/Sabre/DAV/FS/File.php
|
||||
|
||||
*/
|
||||
|
||||
// settings
|
||||
date_default_timezone_set('Canada/Eastern');
|
||||
$publicDir = 'public';
|
||||
|
||||
// Files we need
|
||||
require_once 'Sabre/autoload.php';
|
||||
|
||||
class MyDirectory extends Sabre_DAV_Directory {
|
||||
|
||||
private $myPath;
|
||||
|
||||
function __construct($myPath) {
|
||||
|
||||
$this->myPath = $myPath;
|
||||
|
||||
}
|
||||
|
||||
function getChildren() {
|
||||
|
||||
$children = array();
|
||||
// Loop through the directory, and create objects for each node
|
||||
foreach(scandir($this->myPath) as $node) {
|
||||
|
||||
// Ignoring files staring with .
|
||||
if ($node[0]==='.') continue;
|
||||
|
||||
$children[] = $this->getChild($node);
|
||||
|
||||
}
|
||||
|
||||
return $children;
|
||||
|
||||
}
|
||||
|
||||
function getChild($name) {
|
||||
|
||||
$path = $this->myPath . '/' . $name;
|
||||
|
||||
// We have to throw a NotFound exception if the file didn't exist
|
||||
if (!file_exists($this->myPath)) throw new Sabre_DAV_Exception_NotFound('The file with name: ' . $name . ' could not be found');
|
||||
// Some added security
|
||||
|
||||
if ($name[0]=='.') throw new Sabre_DAV_Exception_NotFound('Access denied');
|
||||
|
||||
if (is_dir($path)) {
|
||||
|
||||
return new MyDirectory($name);
|
||||
|
||||
} else {
|
||||
|
||||
return new MyFile($path);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getName() {
|
||||
|
||||
return basename($this->myPath);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MyFile extends Sabre_DAV_File {
|
||||
|
||||
private $myPath;
|
||||
|
||||
function __construct($myPath) {
|
||||
|
||||
$this->myPath = $myPath;
|
||||
|
||||
}
|
||||
|
||||
function getName() {
|
||||
|
||||
return basename($this->myPath);
|
||||
|
||||
}
|
||||
|
||||
function get() {
|
||||
|
||||
return fopen($this->myPath,'r');
|
||||
|
||||
}
|
||||
|
||||
function getSize() {
|
||||
|
||||
return filesize($this->myPath);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Make sure there is a directory in your current directory named 'public'. We will be exposing that directory to WebDAV
|
||||
$rootNode = new MyDirectory($publicDir);
|
||||
|
||||
// The rootNode needs to be passed to the server object.
|
||||
$server = new Sabre_DAV_Server($rootNode);
|
||||
|
||||
// And off we go!
|
||||
$server->exec();
|
|
@ -0,0 +1,18 @@
|
|||
CREATE TABLE addressbooks (
|
||||
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
principaluri VARCHAR(255),
|
||||
displayname VARCHAR(255),
|
||||
uri VARCHAR(200),
|
||||
description TEXT,
|
||||
ctag INT(11) UNSIGNED NOT NULL DEFAULT '1',
|
||||
UNIQUE(principaluri, uri)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||
|
||||
CREATE TABLE cards (
|
||||
id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
addressbookid INT(11) UNSIGNED NOT NULL,
|
||||
carddata MEDIUMBLOB,
|
||||
uri VARCHAR(200),
|
||||
lastmodified INT(11) UNSIGNED
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
CREATE TABLE calendarobjects (
|
||||
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
calendardata MEDIUMBLOB,
|
||||
uri VARCHAR(200),
|
||||
calendarid INTEGER UNSIGNED NOT NULL,
|
||||
lastmodified INT(11) UNSIGNED,
|
||||
etag VARCHAR(32),
|
||||
size INT(11) UNSIGNED NOT NULL,
|
||||
componenttype VARCHAR(8),
|
||||
firstoccurence INT(11) UNSIGNED,
|
||||
lastoccurence INT(11) UNSIGNED,
|
||||
UNIQUE(calendarid, uri)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||
|
||||
CREATE TABLE calendars (
|
||||
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
principaluri VARCHAR(100),
|
||||
displayname VARCHAR(100),
|
||||
uri VARCHAR(200),
|
||||
ctag INTEGER UNSIGNED NOT NULL DEFAULT '0',
|
||||
description TEXT,
|
||||
calendarorder INTEGER UNSIGNED NOT NULL DEFAULT '0',
|
||||
calendarcolor VARCHAR(10),
|
||||
timezone TEXT,
|
||||
components VARCHAR(20),
|
||||
UNIQUE(principaluri, uri)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE locks (
|
||||
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
owner VARCHAR(100),
|
||||
timeout INTEGER UNSIGNED,
|
||||
created INTEGER,
|
||||
token VARCHAR(100),
|
||||
scope TINYINT,
|
||||
depth TINYINT,
|
||||
uri text
|
||||
);
|
|
@ -0,0 +1,22 @@
|
|||
CREATE TABLE principals (
|
||||
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
uri VARCHAR(200) NOT NULL,
|
||||
email VARCHAR(80),
|
||||
displayname VARCHAR(80),
|
||||
vcardurl VARCHAR(80),
|
||||
UNIQUE(uri)
|
||||
);
|
||||
|
||||
CREATE TABLE groupmembers (
|
||||
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
principal_id INTEGER UNSIGNED NOT NULL,
|
||||
member_id INTEGER UNSIGNED NOT NULL,
|
||||
UNIQUE(principal_id, member_id)
|
||||
);
|
||||
|
||||
|
||||
INSERT INTO principals (uri,email,displayname) VALUES
|
||||
('principals/admin', 'admin@example.org','Administrator'),
|
||||
('principals/admin/calendar-proxy-read', null, null),
|
||||
('principals/admin/calendar-proxy-write', null, null);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE users (
|
||||
id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(50),
|
||||
digesta1 VARCHAR(32),
|
||||
UNIQUE(username)
|
||||
);
|
||||
|
||||
INSERT INTO users (username,digesta1) VALUES
|
||||
('admin', '87fd274b7b6c01e48d7c2f965da8ddf7');
|
|
@ -0,0 +1,33 @@
|
|||
CREATE TABLE addressbooks (
|
||||
id SERIAL NOT NULL,
|
||||
principaluri VARCHAR(255),
|
||||
displayname VARCHAR(255),
|
||||
uri VARCHAR(200),
|
||||
description TEXT,
|
||||
ctag INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY addressbooks
|
||||
ADD CONSTRAINT addressbooks_pkey PRIMARY KEY (id);
|
||||
|
||||
CREATE UNIQUE INDEX addressbooks_ukey
|
||||
ON addressbooks USING btree (principaluri, uri);
|
||||
|
||||
CREATE TABLE cards (
|
||||
id SERIAL NOT NULL,
|
||||
addressbookid INTEGER NOT NULL,
|
||||
carddata TEXT,
|
||||
uri VARCHAR(200),
|
||||
lastmodified INTEGER
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY cards
|
||||
ADD CONSTRAINT cards_pkey PRIMARY KEY (id);
|
||||
|
||||
CREATE UNIQUE INDEX cards_ukey
|
||||
ON cards USING btree (addressbookid, uri);
|
||||
|
||||
ALTER TABLE ONLY cards
|
||||
ADD CONSTRAINT cards_addressbookid_fkey FOREIGN KEY (addressbookid) REFERENCES addressbooks(id)
|
||||
ON DELETE CASCADE;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
CREATE TABLE calendars (
|
||||
id SERIAL NOT NULL,
|
||||
principaluri VARCHAR(100),
|
||||
displayname VARCHAR(100),
|
||||
uri VARCHAR(200),
|
||||
ctag INTEGER NOT NULL DEFAULT 0,
|
||||
description TEXT,
|
||||
calendarorder INTEGER NOT NULL DEFAULT 0,
|
||||
calendarcolor VARCHAR(10),
|
||||
timezone TEXT,
|
||||
components VARCHAR(20)
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY calendars
|
||||
ADD CONSTRAINT calendars_pkey PRIMARY KEY (id);
|
||||
|
||||
CREATE UNIQUE INDEX calendars_ukey
|
||||
ON calendars USING btree (principaluri, uri);
|
||||
|
||||
CREATE TABLE calendarobjects (
|
||||
id SERIAL NOT NULL,
|
||||
calendarid INTEGER NOT NULL,
|
||||
calendardata TEXT,
|
||||
uri VARCHAR(200),
|
||||
etag VARCHAR(32),
|
||||
size INTEGER NOT NULL,
|
||||
componenttype VARCHAR(8),
|
||||
lastmodified INTEGER
|
||||
firstoccurence INTEGER,
|
||||
lastoccurence INTEGER
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY calendarobjects
|
||||
ADD CONSTRAINT calendarobjects_pkey PRIMARY KEY (id);
|
||||
|
||||
CREATE UNIQUE INDEX calendarobjects_ukey
|
||||
ON calendarobjects USING btree (calendarid, uri);
|
||||
|
||||
ALTER TABLE ONLY calendarobjects
|
||||
ADD CONSTRAINT calendarobjects_calendarid_fkey FOREIGN KEY (calendarid) REFERENCES calendars(id)
|
||||
ON DELETE CASCADE;
|
|
@ -0,0 +1,13 @@
|
|||
CREATE TABLE locks (
|
||||
id SERIAL NOT NULL,
|
||||
owner VARCHAR(100),
|
||||
timeout INTEGER,
|
||||
created INTEGER,
|
||||
token VARCHAR(100),
|
||||
scope smallint,
|
||||
depth smallint,
|
||||
uri text
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY locks
|
||||
ADD CONSTRAINT locks_pkey PRIMARY KEY (id);
|
|
@ -0,0 +1,40 @@
|
|||
CREATE TABLE principals (
|
||||
id SERIAL NOT NULL,
|
||||
uri VARCHAR(100) NOT NULL,
|
||||
email VARCHAR(80),
|
||||
displayname VARCHAR(80),
|
||||
vcardurl VARCHAR(80)
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY principals
|
||||
ADD CONSTRAINT principals_pkey PRIMARY KEY (id);
|
||||
|
||||
CREATE UNIQUE INDEX principals_ukey
|
||||
ON principals USING btree (uri);
|
||||
|
||||
CREATE TABLE groupmembers (
|
||||
id SERIAL NOT NULL,
|
||||
principal_id INTEGER NOT NULL,
|
||||
member_id INTEGER NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY groupmembers
|
||||
ADD CONSTRAINT groupmembers_pkey PRIMARY KEY (id);
|
||||
|
||||
CREATE UNIQUE INDEX groupmembers_ukey
|
||||
ON groupmembers USING btree (principal_id, member_id);
|
||||
|
||||
ALTER TABLE ONLY groupmembers
|
||||
ADD CONSTRAINT groupmembers_principal_id_fkey FOREIGN KEY (principal_id) REFERENCES principals(id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
-- Is this correct correct link ... or not?
|
||||
-- ALTER TABLE ONLY groupmembers
|
||||
-- ADD CONSTRAINT groupmembers_member_id_id_fkey FOREIGN KEY (member_id) REFERENCES users(id)
|
||||
-- ON DELETE CASCADE;
|
||||
|
||||
INSERT INTO principals (uri,email,displayname) VALUES
|
||||
('principals/admin', 'admin@example.org','Administrator'),
|
||||
('principals/admin/calendar-proxy-read', null, null),
|
||||
('principals/admin/calendar-proxy-write', null, null);
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
CREATE TABLE users (
|
||||
id SERIAL NOT NULL,
|
||||
username VARCHAR(50),
|
||||
digesta1 VARCHAR(32),
|
||||
UNIQUE(username)
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY users
|
||||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
|
||||
CREATE UNIQUE INDEX users_ukey
|
||||
ON users USING btree (username);
|
||||
|
||||
INSERT INTO users (username,digesta1) VALUES
|
||||
('admin', '87fd274b7b6c01e48d7c2f965da8ddf7');
|
|
@ -0,0 +1,17 @@
|
|||
CREATE TABLE addressbooks (
|
||||
id integer primary key asc,
|
||||
principaluri text,
|
||||
displayname text,
|
||||
uri text,
|
||||
description text,
|
||||
ctag integer
|
||||
);
|
||||
|
||||
CREATE TABLE cards (
|
||||
id integer primary key asc,
|
||||
addressbookid integer,
|
||||
carddata blob,
|
||||
uri text,
|
||||
lastmodified integer
|
||||
);
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
CREATE TABLE calendarobjects (
|
||||
id integer primary key asc,
|
||||
calendardata blob,
|
||||
uri text,
|
||||
calendarid integer,
|
||||
lastmodified integer,
|
||||
etag text,
|
||||
size integer,
|
||||
componenttype text,
|
||||
firstoccurence integer,
|
||||
lastoccurence integer
|
||||
);
|
||||
|
||||
CREATE TABLE calendars (
|
||||
id integer primary key asc,
|
||||
principaluri text,
|
||||
displayname text,
|
||||
uri text,
|
||||
ctag integer,
|
||||
description text,
|
||||
calendarorder integer,
|
||||
calendarcolor text,
|
||||
timezone text,
|
||||
components text
|
||||
);
|
|
@ -0,0 +1,12 @@
|
|||
BEGIN TRANSACTION;
|
||||
CREATE TABLE locks (
|
||||
id integer primary key asc,
|
||||
owner text,
|
||||
timeout integer,
|
||||
created integer,
|
||||
token text,
|
||||
scope integer,
|
||||
depth integer,
|
||||
uri text
|
||||
);
|
||||
COMMIT;
|
|
@ -0,0 +1,21 @@
|
|||
CREATE TABLE principals (
|
||||
id INTEGER PRIMARY KEY ASC,
|
||||
uri TEXT,
|
||||
email TEXT,
|
||||
displayname TEXT,
|
||||
vcardurl TEXT,
|
||||
UNIQUE(uri)
|
||||
);
|
||||
|
||||
CREATE TABLE groupmembers (
|
||||
id INTEGER PRIMARY KEY ASC,
|
||||
principal_id INTEGER,
|
||||
member_id INTEGER,
|
||||
UNIQUE(principal_id, member_id)
|
||||
);
|
||||
|
||||
|
||||
INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin', 'admin@example.org','Administrator');
|
||||
INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-read', null, null);
|
||||
INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-write', null, null);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE users (
|
||||
id integer primary key asc,
|
||||
username TEXT,
|
||||
digesta1 TEXT,
|
||||
UNIQUE(username)
|
||||
);
|
||||
|
||||
INSERT INTO users (username,digesta1) VALUES
|
||||
('admin', '87fd274b7b6c01e48d7c2f965da8ddf7');
|
|
@ -0,0 +1,16 @@
|
|||
RewriteEngine On
|
||||
# This makes every request go to server.php
|
||||
RewriteRule (.*) server.php [L]
|
||||
|
||||
# Output buffering needs to be off, to prevent high memory usage
|
||||
php_flag output_buffering off
|
||||
|
||||
# This is also to prevent high memory usage
|
||||
php_flag always_populate_raw_post_data off
|
||||
|
||||
# This is almost a given, but magic quotes is *still* on on some
|
||||
# linux distributions
|
||||
php_flag magic_quotes_gpc off
|
||||
|
||||
# SabreDAV is not compatible with mbstring function overloading
|
||||
php_flag mbstring.func_overload off
|
|
@ -0,0 +1,33 @@
|
|||
# This is a sample configuration to setup a dedicated Apache vhost for WebDAV.
|
||||
#
|
||||
# The main thing that should be configured is the servername, and the path to
|
||||
# your SabreDAV installation (DocumentRoot).
|
||||
#
|
||||
# This configuration assumed mod_php5 is used, as it sets a few default php
|
||||
# settings as well.
|
||||
<VirtualHost *:*>
|
||||
|
||||
# Don't forget to change the server name
|
||||
# ServerName dav.example.org
|
||||
|
||||
# The DocumentRoot is also required
|
||||
# DocumentRoot /home/sabredav/
|
||||
|
||||
RewriteEngine On
|
||||
# This makes every request go to server.php
|
||||
RewriteRule ^/(.*)$ /server.php [L]
|
||||
|
||||
# Output buffering needs to be off, to prevent high memory usage
|
||||
php_flag output_buffering off
|
||||
|
||||
# This is also to prevent high memory usage
|
||||
php_flag always_populate_raw_post_data off
|
||||
|
||||
# This is almost a given, but magic quotes is *still* on on some
|
||||
# linux distributions
|
||||
php_flag magic_quotes_gpc off
|
||||
|
||||
# SabreDAV is not compatible with mbstring function overloading
|
||||
php_flag mbstring.func_overload off
|
||||
|
||||
</VirtualHost *:*>
|
|
@ -0,0 +1,21 @@
|
|||
# This is a sample configuration to setup a dedicated Apache vhost for WebDAV.
|
||||
#
|
||||
# The main thing that should be configured is the servername, and the path to
|
||||
# your SabreDAV installation (DocumentRoot).
|
||||
#
|
||||
# This configuration assumes CGI or FastCGI is used.
|
||||
<VirtualHost *:*>
|
||||
|
||||
# Don't forget to change the server name
|
||||
# ServerName dav.example.org
|
||||
|
||||
# The DocumentRoot is also required
|
||||
# DocumentRoot /home/sabredav/
|
||||
|
||||
# This makes every request go to server.php. This also makes sure
|
||||
# the Authentication information is available. If your server script is
|
||||
# not called server.php, be sure to change it.
|
||||
RewriteEngine On
|
||||
RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
</VirtualHost *:*>
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Library include file
|
||||
*
|
||||
* This file is deprecated, don't use it!
|
||||
* Instead, use the specific includes files that are in the sub-packages.
|
||||
*
|
||||
* Sabre/DAV/includes.php
|
||||
* Sabre/HTTP/includes.php
|
||||
*
|
||||
* etc..
|
||||
*
|
||||
* This file contains all includes to the rest of the SabreDAV library
|
||||
* Make sure the lib/ directory is in PHP's include_path.
|
||||
*
|
||||
* @package Sabre
|
||||
* @deprecated Don't use this file, it will be remove in a future version
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
|
||||
include 'Sabre/HTTP/includes.php';
|
||||
include 'Sabre/DAV/includes.php';
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Abstract Calendaring backend. Extend this class to create your own backends.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
abstract class Sabre_CalDAV_Backend_Abstract {
|
||||
|
||||
/**
|
||||
* Returns a list of calendars for a principal.
|
||||
*
|
||||
* Every project is an array with the following keys:
|
||||
* * id, a unique id that will be used by other functions to modify the
|
||||
* calendar. This can be the same as the uri or a database key.
|
||||
* * uri, which the basename of the uri with which the calendar is
|
||||
* accessed.
|
||||
* * principaluri. The owner of the calendar. Almost always the same as
|
||||
* principalUri passed to this method.
|
||||
*
|
||||
* Furthermore it can contain webdav properties in clark notation. A very
|
||||
* common one is '{DAV:}displayname'.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @return array
|
||||
*/
|
||||
abstract function getCalendarsForUser($principalUri);
|
||||
|
||||
/**
|
||||
* Creates a new calendar for a principal.
|
||||
*
|
||||
* If the creation was a success, an id must be returned that can be used to reference
|
||||
* this calendar in other methods, such as updateCalendar.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $calendarUri
|
||||
* @param array $properties
|
||||
* @return void
|
||||
*/
|
||||
abstract function createCalendar($principalUri,$calendarUri,array $properties);
|
||||
|
||||
/**
|
||||
* Updates properties for a calendar.
|
||||
*
|
||||
* The mutations array uses the propertyName in clark-notation as key,
|
||||
* and the array value for the property value. In the case a property
|
||||
* should be deleted, the property value will be null.
|
||||
*
|
||||
* This method must be atomic. If one property cannot be changed, the
|
||||
* entire operation must fail.
|
||||
*
|
||||
* If the operation was successful, true can be returned.
|
||||
* If the operation failed, false can be returned.
|
||||
*
|
||||
* Deletion of a non-existent property is always successful.
|
||||
*
|
||||
* Lastly, it is optional to return detailed information about any
|
||||
* failures. In this case an array should be returned with the following
|
||||
* structure:
|
||||
*
|
||||
* array(
|
||||
* 403 => array(
|
||||
* '{DAV:}displayname' => null,
|
||||
* ),
|
||||
* 424 => array(
|
||||
* '{DAV:}owner' => null,
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* In this example it was forbidden to update {DAV:}displayname.
|
||||
* (403 Forbidden), which in turn also caused {DAV:}owner to fail
|
||||
* (424 Failed Dependency) because the request needs to be atomic.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param array $mutations
|
||||
* @return bool|array
|
||||
*/
|
||||
public function updateCalendar($calendarId, array $mutations) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a calendar and all it's objects
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @return void
|
||||
*/
|
||||
abstract function deleteCalendar($calendarId);
|
||||
|
||||
/**
|
||||
* Returns all calendar objects within a calendar.
|
||||
*
|
||||
* Every item contains an array with the following keys:
|
||||
* * id - unique identifier which will be used for subsequent updates
|
||||
* * calendardata - The iCalendar-compatible calendar data
|
||||
* * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
|
||||
* * lastmodified - a timestamp of the last modification time
|
||||
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
|
||||
* ' "abcdef"')
|
||||
* * calendarid - The calendarid as it was passed to this function.
|
||||
* * size - The size of the calendar objects, in bytes.
|
||||
*
|
||||
* Note that the etag is optional, but it's highly encouraged to return for
|
||||
* speed reasons.
|
||||
*
|
||||
* The calendardata is also optional. If it's not returned
|
||||
* 'getCalendarObject' will be called later, which *is* expected to return
|
||||
* calendardata.
|
||||
*
|
||||
* If neither etag or size are specified, the calendardata will be
|
||||
* used/fetched to determine these numbers. If both are specified the
|
||||
* amount of times this is needed is reduced by a great degree.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @return array
|
||||
*/
|
||||
abstract function getCalendarObjects($calendarId);
|
||||
|
||||
/**
|
||||
* Returns information from a single calendar object, based on it's object
|
||||
* uri.
|
||||
*
|
||||
* The returned array must have the same keys as getCalendarObjects. The
|
||||
* 'calendardata' object is required here though, while it's not required
|
||||
* for getCalendarObjects.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
* @return array
|
||||
*/
|
||||
abstract function getCalendarObject($calendarId,$objectUri);
|
||||
|
||||
/**
|
||||
* Creates a new calendar object.
|
||||
*
|
||||
* It is possible return an etag from this function, which will be used in
|
||||
* the response to this PUT request. Note that the ETag must be surrounded
|
||||
* by double-quotes.
|
||||
*
|
||||
* However, you should only really return this ETag if you don't mangle the
|
||||
* calendar-data. If the result of a subsequent GET to this object is not
|
||||
* the exact same as this request body, you should omit the ETag.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
* @param string $calendarData
|
||||
* @return string|null
|
||||
*/
|
||||
abstract function createCalendarObject($calendarId,$objectUri,$calendarData);
|
||||
|
||||
/**
|
||||
* Updates an existing calendarobject, based on it's uri.
|
||||
*
|
||||
* It is possible return an etag from this function, which will be used in
|
||||
* the response to this PUT request. Note that the ETag must be surrounded
|
||||
* by double-quotes.
|
||||
*
|
||||
* However, you should only really return this ETag if you don't mangle the
|
||||
* calendar-data. If the result of a subsequent GET to this object is not
|
||||
* the exact same as this request body, you should omit the ETag.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
* @param string $calendarData
|
||||
* @return string|null
|
||||
*/
|
||||
abstract function updateCalendarObject($calendarId,$objectUri,$calendarData);
|
||||
|
||||
/**
|
||||
* Deletes an existing calendar object.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
* @return void
|
||||
*/
|
||||
abstract function deleteCalendarObject($calendarId,$objectUri);
|
||||
|
||||
/**
|
||||
* Performs a calendar-query on the contents of this calendar.
|
||||
*
|
||||
* The calendar-query is defined in RFC4791 : CalDAV. Using the
|
||||
* calendar-query it is possible for a client to request a specific set of
|
||||
* object, based on contents of iCalendar properties, date-ranges and
|
||||
* iCalendar component types (VTODO, VEVENT).
|
||||
*
|
||||
* This method should just return a list of (relative) urls that match this
|
||||
* query.
|
||||
*
|
||||
* The list of filters are specified as an array. The exact array is
|
||||
* documented by Sabre_CalDAV_CalendarQueryParser.
|
||||
*
|
||||
* Note that it is extremely likely that getCalendarObject for every path
|
||||
* returned from this method will be called almost immediately after. You
|
||||
* may want to anticipate this to speed up these requests.
|
||||
*
|
||||
* This method provides a default implementation, which parses *all* the
|
||||
* iCalendar objects in the specified calendar.
|
||||
*
|
||||
* This default may well be good enough for personal use, and calendars
|
||||
* that aren't very large. But if you anticipate high usage, big calendars
|
||||
* or high loads, you are strongly adviced to optimize certain paths.
|
||||
*
|
||||
* The best way to do so is override this method and to optimize
|
||||
* specifically for 'common filters'.
|
||||
*
|
||||
* Requests that are extremely common are:
|
||||
* * requests for just VEVENTS
|
||||
* * requests for just VTODO
|
||||
* * requests with a time-range-filter on either VEVENT or VTODO.
|
||||
*
|
||||
* ..and combinations of these requests. It may not be worth it to try to
|
||||
* handle every possible situation and just rely on the (relatively
|
||||
* easy to use) CalendarQueryValidator to handle the rest.
|
||||
*
|
||||
* Note that especially time-range-filters may be difficult to parse. A
|
||||
* time-range filter specified on a VEVENT must for instance also handle
|
||||
* recurrence rules correctly.
|
||||
* A good example of how to interprete all these filters can also simply
|
||||
* be found in Sabre_CalDAV_CalendarQueryFilter. This class is as correct
|
||||
* as possible, so it gives you a good idea on what type of stuff you need
|
||||
* to think of.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param array $filters
|
||||
* @return array
|
||||
*/
|
||||
public function calendarQuery($calendarId, array $filters) {
|
||||
|
||||
$result = array();
|
||||
$objects = $this->getCalendarObjects($calendarId);
|
||||
|
||||
$validator = new Sabre_CalDAV_CalendarQueryValidator();
|
||||
|
||||
foreach($objects as $object) {
|
||||
|
||||
if ($this->validateFilterForObject($object, $filters)) {
|
||||
$result[] = $object['uri'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method validates if a filters (as passed to calendarQuery) matches
|
||||
* the given object.
|
||||
*
|
||||
* @param array $object
|
||||
* @param array $filter
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateFilterForObject(array $object, array $filters) {
|
||||
|
||||
// Unfortunately, setting the 'calendardata' here is optional. If
|
||||
// it was excluded, we actually need another call to get this as
|
||||
// well.
|
||||
if (!isset($object['calendardata'])) {
|
||||
$object = $this->getCalendarObject($object['calendarid'], $object['uri']);
|
||||
}
|
||||
|
||||
$vObject = Sabre_VObject_Reader::read($object['calendardata']);
|
||||
|
||||
$validator = new Sabre_CalDAV_CalendarQueryValidator();
|
||||
return $validator->validate($vObject, $filters);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,657 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PDO CalDAV backend
|
||||
*
|
||||
* This backend is used to store calendar-data in a PDO database, such as
|
||||
* sqlite or MySQL
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Backend_PDO extends Sabre_CalDAV_Backend_Abstract {
|
||||
|
||||
/**
|
||||
* We need to specify a max date, because we need to stop *somewhere*
|
||||
*/
|
||||
const MAX_DATE = '2040-01-01';
|
||||
|
||||
/**
|
||||
* pdo
|
||||
*
|
||||
* @var PDO
|
||||
*/
|
||||
protected $pdo;
|
||||
|
||||
/**
|
||||
* The table name that will be used for calendars
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $calendarTableName;
|
||||
|
||||
/**
|
||||
* The table name that will be used for calendar objects
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $calendarObjectTableName;
|
||||
|
||||
/**
|
||||
* List of CalDAV properties, and how they map to database fieldnames
|
||||
*
|
||||
* Add your own properties by simply adding on to this array
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $propertyMap = array(
|
||||
'{DAV:}displayname' => 'displayname',
|
||||
'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
|
||||
'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
|
||||
'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
|
||||
'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates the backend
|
||||
*
|
||||
* @param PDO $pdo
|
||||
* @param string $calendarTableName
|
||||
* @param string $calendarObjectTableName
|
||||
*/
|
||||
public function __construct(PDO $pdo, $calendarTableName = 'calendars', $calendarObjectTableName = 'calendarobjects') {
|
||||
|
||||
$this->pdo = $pdo;
|
||||
$this->calendarTableName = $calendarTableName;
|
||||
$this->calendarObjectTableName = $calendarObjectTableName;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of calendars for a principal.
|
||||
*
|
||||
* Every project is an array with the following keys:
|
||||
* * id, a unique id that will be used by other functions to modify the
|
||||
* calendar. This can be the same as the uri or a database key.
|
||||
* * uri, which the basename of the uri with which the calendar is
|
||||
* accessed.
|
||||
* * principaluri. The owner of the calendar. Almost always the same as
|
||||
* principalUri passed to this method.
|
||||
*
|
||||
* Furthermore it can contain webdav properties in clark notation. A very
|
||||
* common one is '{DAV:}displayname'.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @return array
|
||||
*/
|
||||
public function getCalendarsForUser($principalUri) {
|
||||
|
||||
$fields = array_values($this->propertyMap);
|
||||
$fields[] = 'id';
|
||||
$fields[] = 'uri';
|
||||
$fields[] = 'ctag';
|
||||
$fields[] = 'components';
|
||||
$fields[] = 'principaluri';
|
||||
|
||||
// Making fields a comma-delimited list
|
||||
$fields = implode(', ', $fields);
|
||||
$stmt = $this->pdo->prepare("SELECT " . $fields . " FROM ".$this->calendarTableName." WHERE principaluri = ? ORDER BY calendarorder ASC");
|
||||
$stmt->execute(array($principalUri));
|
||||
|
||||
$calendars = array();
|
||||
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
||||
$components = array();
|
||||
if ($row['components']) {
|
||||
$components = explode(',',$row['components']);
|
||||
}
|
||||
|
||||
$calendar = array(
|
||||
'id' => $row['id'],
|
||||
'uri' => $row['uri'],
|
||||
'principaluri' => $row['principaluri'],
|
||||
'{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}getctag' => $row['ctag']?$row['ctag']:'0',
|
||||
'{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre_CalDAV_Property_SupportedCalendarComponentSet($components),
|
||||
);
|
||||
|
||||
|
||||
foreach($this->propertyMap as $xmlName=>$dbName) {
|
||||
$calendar[$xmlName] = $row[$dbName];
|
||||
}
|
||||
|
||||
$calendars[] = $calendar;
|
||||
|
||||
}
|
||||
|
||||
return $calendars;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new calendar for a principal.
|
||||
*
|
||||
* If the creation was a success, an id must be returned that can be used to reference
|
||||
* this calendar in other methods, such as updateCalendar
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $calendarUri
|
||||
* @param array $properties
|
||||
* @return string
|
||||
*/
|
||||
public function createCalendar($principalUri, $calendarUri, array $properties) {
|
||||
|
||||
$fieldNames = array(
|
||||
'principaluri',
|
||||
'uri',
|
||||
'ctag',
|
||||
);
|
||||
$values = array(
|
||||
':principaluri' => $principalUri,
|
||||
':uri' => $calendarUri,
|
||||
':ctag' => 1,
|
||||
);
|
||||
|
||||
// Default value
|
||||
$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
|
||||
$fieldNames[] = 'components';
|
||||
if (!isset($properties[$sccs])) {
|
||||
$values[':components'] = 'VEVENT,VTODO';
|
||||
} else {
|
||||
if (!($properties[$sccs] instanceof Sabre_CalDAV_Property_SupportedCalendarComponentSet)) {
|
||||
throw new Sabre_DAV_Exception('The ' . $sccs . ' property must be of type: Sabre_CalDAV_Property_SupportedCalendarComponentSet');
|
||||
}
|
||||
$values[':components'] = implode(',',$properties[$sccs]->getValue());
|
||||
}
|
||||
|
||||
foreach($this->propertyMap as $xmlName=>$dbName) {
|
||||
if (isset($properties[$xmlName])) {
|
||||
|
||||
$values[':' . $dbName] = $properties[$xmlName];
|
||||
$fieldNames[] = $dbName;
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare("INSERT INTO ".$this->calendarTableName." (".implode(', ', $fieldNames).") VALUES (".implode(', ',array_keys($values)).")");
|
||||
$stmt->execute($values);
|
||||
|
||||
return $this->pdo->lastInsertId();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates properties for a calendar.
|
||||
*
|
||||
* The mutations array uses the propertyName in clark-notation as key,
|
||||
* and the array value for the property value. In the case a property
|
||||
* should be deleted, the property value will be null.
|
||||
*
|
||||
* This method must be atomic. If one property cannot be changed, the
|
||||
* entire operation must fail.
|
||||
*
|
||||
* If the operation was successful, true can be returned.
|
||||
* If the operation failed, false can be returned.
|
||||
*
|
||||
* Deletion of a non-existent property is always successful.
|
||||
*
|
||||
* Lastly, it is optional to return detailed information about any
|
||||
* failures. In this case an array should be returned with the following
|
||||
* structure:
|
||||
*
|
||||
* array(
|
||||
* 403 => array(
|
||||
* '{DAV:}displayname' => null,
|
||||
* ),
|
||||
* 424 => array(
|
||||
* '{DAV:}owner' => null,
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* In this example it was forbidden to update {DAV:}displayname.
|
||||
* (403 Forbidden), which in turn also caused {DAV:}owner to fail
|
||||
* (424 Failed Dependency) because the request needs to be atomic.
|
||||
*
|
||||
* @param string $calendarId
|
||||
* @param array $mutations
|
||||
* @return bool|array
|
||||
*/
|
||||
public function updateCalendar($calendarId, array $mutations) {
|
||||
|
||||
$newValues = array();
|
||||
$result = array(
|
||||
200 => array(), // Ok
|
||||
403 => array(), // Forbidden
|
||||
424 => array(), // Failed Dependency
|
||||
);
|
||||
|
||||
$hasError = false;
|
||||
|
||||
foreach($mutations as $propertyName=>$propertyValue) {
|
||||
|
||||
// We don't know about this property.
|
||||
if (!isset($this->propertyMap[$propertyName])) {
|
||||
$hasError = true;
|
||||
$result[403][$propertyName] = null;
|
||||
unset($mutations[$propertyName]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldName = $this->propertyMap[$propertyName];
|
||||
$newValues[$fieldName] = $propertyValue;
|
||||
|
||||
}
|
||||
|
||||
// If there were any errors we need to fail the request
|
||||
if ($hasError) {
|
||||
// Properties has the remaining properties
|
||||
foreach($mutations as $propertyName=>$propertyValue) {
|
||||
$result[424][$propertyName] = null;
|
||||
}
|
||||
|
||||
// Removing unused statuscodes for cleanliness
|
||||
foreach($result as $status=>$properties) {
|
||||
if (is_array($properties) && count($properties)===0) unset($result[$status]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
// Success
|
||||
|
||||
// Now we're generating the sql query.
|
||||
$valuesSql = array();
|
||||
foreach($newValues as $fieldName=>$value) {
|
||||
$valuesSql[] = $fieldName . ' = ?';
|
||||
}
|
||||
$valuesSql[] = 'ctag = ctag + 1';
|
||||
|
||||
$stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ',$valuesSql) . " WHERE id = ?");
|
||||
$newValues['id'] = $calendarId;
|
||||
$stmt->execute(array_values($newValues));
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a calendar and all it's objects
|
||||
*
|
||||
* @param string $calendarId
|
||||
* @return void
|
||||
*/
|
||||
public function deleteCalendar($calendarId) {
|
||||
|
||||
$stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?');
|
||||
$stmt->execute(array($calendarId));
|
||||
|
||||
$stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarTableName.' WHERE id = ?');
|
||||
$stmt->execute(array($calendarId));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all calendar objects within a calendar.
|
||||
*
|
||||
* Every item contains an array with the following keys:
|
||||
* * id - unique identifier which will be used for subsequent updates
|
||||
* * calendardata - The iCalendar-compatible calendar data
|
||||
* * uri - a unique key which will be used to construct the uri. This can be any arbitrary string.
|
||||
* * lastmodified - a timestamp of the last modification time
|
||||
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
|
||||
* ' "abcdef"')
|
||||
* * calendarid - The calendarid as it was passed to this function.
|
||||
* * size - The size of the calendar objects, in bytes.
|
||||
*
|
||||
* Note that the etag is optional, but it's highly encouraged to return for
|
||||
* speed reasons.
|
||||
*
|
||||
* The calendardata is also optional. If it's not returned
|
||||
* 'getCalendarObject' will be called later, which *is* expected to return
|
||||
* calendardata.
|
||||
*
|
||||
* If neither etag or size are specified, the calendardata will be
|
||||
* used/fetched to determine these numbers. If both are specified the
|
||||
* amount of times this is needed is reduced by a great degree.
|
||||
*
|
||||
* @param string $calendarId
|
||||
* @return array
|
||||
*/
|
||||
public function getCalendarObjects($calendarId) {
|
||||
|
||||
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?');
|
||||
$stmt->execute(array($calendarId));
|
||||
|
||||
$result = array();
|
||||
foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
|
||||
$result[] = array(
|
||||
'id' => $row['id'],
|
||||
'uri' => $row['uri'],
|
||||
'lastmodified' => $row['lastmodified'],
|
||||
'etag' => '"' . $row['etag'] . '"',
|
||||
'calendarid' => $row['calendarid'],
|
||||
'size' => (int)$row['size'],
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information from a single calendar object, based on it's object
|
||||
* uri.
|
||||
*
|
||||
* The returned array must have the same keys as getCalendarObjects. The
|
||||
* 'calendardata' object is required here though, while it's not required
|
||||
* for getCalendarObjects.
|
||||
*
|
||||
* @param string $calendarId
|
||||
* @param string $objectUri
|
||||
* @return array
|
||||
*/
|
||||
public function getCalendarObject($calendarId,$objectUri) {
|
||||
|
||||
$stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?');
|
||||
$stmt->execute(array($calendarId, $objectUri));
|
||||
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if(!$row) return null;
|
||||
|
||||
return array(
|
||||
'id' => $row['id'],
|
||||
'uri' => $row['uri'],
|
||||
'lastmodified' => $row['lastmodified'],
|
||||
'etag' => '"' . $row['etag'] . '"',
|
||||
'calendarid' => $row['calendarid'],
|
||||
'size' => (int)$row['size'],
|
||||
'calendardata' => $row['calendardata'],
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new calendar object.
|
||||
*
|
||||
* It is possible return an etag from this function, which will be used in
|
||||
* the response to this PUT request. Note that the ETag must be surrounded
|
||||
* by double-quotes.
|
||||
*
|
||||
* However, you should only really return this ETag if you don't mangle the
|
||||
* calendar-data. If the result of a subsequent GET to this object is not
|
||||
* the exact same as this request body, you should omit the ETag.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
* @param string $calendarData
|
||||
* @return string|null
|
||||
*/
|
||||
public function createCalendarObject($calendarId,$objectUri,$calendarData) {
|
||||
|
||||
$extraData = $this->getDenormalizedData($calendarData);
|
||||
|
||||
$stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarObjectTableName.' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence) VALUES (?,?,?,?,?,?,?,?,?)');
|
||||
$stmt->execute(array(
|
||||
$calendarId,
|
||||
$objectUri,
|
||||
$calendarData,
|
||||
time(),
|
||||
$extraData['etag'],
|
||||
$extraData['size'],
|
||||
$extraData['componentType'],
|
||||
$extraData['firstOccurence'],
|
||||
$extraData['lastOccurence'],
|
||||
));
|
||||
$stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?');
|
||||
$stmt->execute(array($calendarId));
|
||||
|
||||
return '"' . $extraData['etag'] . '"';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing calendarobject, based on it's uri.
|
||||
*
|
||||
* It is possible return an etag from this function, which will be used in
|
||||
* the response to this PUT request. Note that the ETag must be surrounded
|
||||
* by double-quotes.
|
||||
*
|
||||
* However, you should only really return this ETag if you don't mangle the
|
||||
* calendar-data. If the result of a subsequent GET to this object is not
|
||||
* the exact same as this request body, you should omit the ETag.
|
||||
*
|
||||
* @param mixed $calendarId
|
||||
* @param string $objectUri
|
||||
* @param string $calendarData
|
||||
* @return string|null
|
||||
*/
|
||||
public function updateCalendarObject($calendarId,$objectUri,$calendarData) {
|
||||
|
||||
$extraData = $this->getDenormalizedData($calendarData);
|
||||
|
||||
$stmt = $this->pdo->prepare('UPDATE '.$this->calendarObjectTableName.' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ? WHERE calendarid = ? AND uri = ?');
|
||||
$stmt->execute(array($calendarData,time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'] ,$calendarId,$objectUri));
|
||||
$stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?');
|
||||
$stmt->execute(array($calendarId));
|
||||
|
||||
return '"' . $extraData['etag'] . '"';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses some information from calendar objects, used for optimized
|
||||
* calendar-queries.
|
||||
*
|
||||
* Returns an array with the following keys:
|
||||
* * etag
|
||||
* * size
|
||||
* * componentType
|
||||
* * firstOccurence
|
||||
* * lastOccurence
|
||||
*
|
||||
* @param string $calendarData
|
||||
* @return array
|
||||
*/
|
||||
protected function getDenormalizedData($calendarData) {
|
||||
|
||||
$vObject = Sabre_VObject_Reader::read($calendarData);
|
||||
$componentType = null;
|
||||
$component = null;
|
||||
$firstOccurence = null;
|
||||
$lastOccurence = null;
|
||||
foreach($vObject->getComponents() as $component) {
|
||||
if ($component->name!=='VTIMEZONE') {
|
||||
$componentType = $component->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$componentType) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
|
||||
}
|
||||
if ($componentType === 'VEVENT') {
|
||||
$firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
|
||||
// Finding the last occurence is a bit harder
|
||||
if (!isset($component->RRULE)) {
|
||||
if (isset($component->DTEND)) {
|
||||
$lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
|
||||
} elseif (isset($component->DURATION)) {
|
||||
$endDate = clone $component->DTSTART->getDateTime();
|
||||
$endDate->add(Sabre_VObject_DateTimeParser::parse($component->DURATION->value));
|
||||
$lastOccurence = $endDate->getTimeStamp();
|
||||
} elseif ($component->DTSTART->getDateType()===Sabre_VObject_Property_DateTime::DATE) {
|
||||
$endDate = clone $component->DTSTART->getDateTime();
|
||||
$endDate->modify('+1 day');
|
||||
$lastOccurence = $endDate->getTimeStamp();
|
||||
} else {
|
||||
$lastOccurence = $firstOccurence;
|
||||
}
|
||||
} else {
|
||||
$it = new Sabre_VObject_RecurrenceIterator($vObject, (string)$component->UID);
|
||||
$maxDate = new DateTime(self::MAX_DATE);
|
||||
if ($it->isInfinite()) {
|
||||
$lastOccurence = $maxDate->getTimeStamp();
|
||||
} else {
|
||||
$end = $it->getDtEnd();
|
||||
while($it->valid() && $end < $maxDate) {
|
||||
$end = $it->getDtEnd();
|
||||
$it->next();
|
||||
|
||||
}
|
||||
$lastOccurence = $end->getTimeStamp();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'etag' => md5($calendarData),
|
||||
'size' => strlen($calendarData),
|
||||
'componentType' => $componentType,
|
||||
'firstOccurence' => $firstOccurence,
|
||||
'lastOccurence' => $lastOccurence,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing calendar object.
|
||||
*
|
||||
* @param string $calendarId
|
||||
* @param string $objectUri
|
||||
* @return void
|
||||
*/
|
||||
public function deleteCalendarObject($calendarId,$objectUri) {
|
||||
|
||||
$stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?');
|
||||
$stmt->execute(array($calendarId,$objectUri));
|
||||
$stmt = $this->pdo->prepare('UPDATE '. $this->calendarTableName .' SET ctag = ctag + 1 WHERE id = ?');
|
||||
$stmt->execute(array($calendarId));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a calendar-query on the contents of this calendar.
|
||||
*
|
||||
* The calendar-query is defined in RFC4791 : CalDAV. Using the
|
||||
* calendar-query it is possible for a client to request a specific set of
|
||||
* object, based on contents of iCalendar properties, date-ranges and
|
||||
* iCalendar component types (VTODO, VEVENT).
|
||||
*
|
||||
* This method should just return a list of (relative) urls that match this
|
||||
* query.
|
||||
*
|
||||
* The list of filters are specified as an array. The exact array is
|
||||
* documented by Sabre_CalDAV_CalendarQueryParser.
|
||||
*
|
||||
* Note that it is extremely likely that getCalendarObject for every path
|
||||
* returned from this method will be called almost immediately after. You
|
||||
* may want to anticipate this to speed up these requests.
|
||||
*
|
||||
* This method provides a default implementation, which parses *all* the
|
||||
* iCalendar objects in the specified calendar.
|
||||
*
|
||||
* This default may well be good enough for personal use, and calendars
|
||||
* that aren't very large. But if you anticipate high usage, big calendars
|
||||
* or high loads, you are strongly adviced to optimize certain paths.
|
||||
*
|
||||
* The best way to do so is override this method and to optimize
|
||||
* specifically for 'common filters'.
|
||||
*
|
||||
* Requests that are extremely common are:
|
||||
* * requests for just VEVENTS
|
||||
* * requests for just VTODO
|
||||
* * requests with a time-range-filter on a VEVENT.
|
||||
*
|
||||
* ..and combinations of these requests. It may not be worth it to try to
|
||||
* handle every possible situation and just rely on the (relatively
|
||||
* easy to use) CalendarQueryValidator to handle the rest.
|
||||
*
|
||||
* Note that especially time-range-filters may be difficult to parse. A
|
||||
* time-range filter specified on a VEVENT must for instance also handle
|
||||
* recurrence rules correctly.
|
||||
* A good example of how to interprete all these filters can also simply
|
||||
* be found in Sabre_CalDAV_CalendarQueryFilter. This class is as correct
|
||||
* as possible, so it gives you a good idea on what type of stuff you need
|
||||
* to think of.
|
||||
*
|
||||
* This specific implementation (for the PDO) backend optimizes filters on
|
||||
* specific components, and VEVENT time-ranges.
|
||||
*
|
||||
* @param string $calendarId
|
||||
* @param array $filters
|
||||
* @return array
|
||||
*/
|
||||
public function calendarQuery($calendarId, array $filters) {
|
||||
|
||||
$result = array();
|
||||
$validator = new Sabre_CalDAV_CalendarQueryValidator();
|
||||
|
||||
$componentType = null;
|
||||
$requirePostFilter = true;
|
||||
$timeRange = null;
|
||||
|
||||
// if no filters were specified, we don't need to filter after a query
|
||||
if (!$filters['prop-filters'] && !$filters['comp-filters']) {
|
||||
$requirePostFilter = false;
|
||||
}
|
||||
|
||||
// Figuring out if there's a component filter
|
||||
if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
|
||||
$componentType = $filters['comp-filters'][0]['name'];
|
||||
|
||||
// Checking if we need post-filters
|
||||
if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
|
||||
$requirePostFilter = false;
|
||||
}
|
||||
// There was a time-range filter
|
||||
if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
|
||||
$timeRange = $filters['comp-filters'][0]['time-range'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($requirePostFilter) {
|
||||
$query = "SELECT uri, calendardata FROM ".$this->calendarObjectTableName." WHERE calendarid = :calendarid";
|
||||
} else {
|
||||
$query = "SELECT uri FROM ".$this->calendarObjectTableName." WHERE calendarid = :calendarid";
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'calendarid' => $calendarId,
|
||||
);
|
||||
|
||||
if ($componentType) {
|
||||
$query.=" AND componenttype = :componenttype";
|
||||
$values['componenttype'] = $componentType;
|
||||
}
|
||||
|
||||
if ($timeRange && $timeRange['start']) {
|
||||
$query.=" AND lastoccurence > :startdate";
|
||||
$values['startdate'] = $timeRange['start']->getTimeStamp();
|
||||
}
|
||||
if ($timeRange && $timeRange['end']) {
|
||||
$query.=" AND firstoccurence < :enddate";
|
||||
$values['enddate'] = $timeRange['end']->getTimeStamp();
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$stmt->execute($values);
|
||||
|
||||
$result = array();
|
||||
while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
if ($requirePostFilter) {
|
||||
if (!$this->validateFilterForObject($row, $filters)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$result[] = $row['uri'];
|
||||
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,366 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This object represents a CalDAV calendar.
|
||||
*
|
||||
* A calendar can contain multiple TODO and or Events. These are represented
|
||||
* as Sabre_CalDAV_CalendarObject objects.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Calendar implements Sabre_CalDAV_ICalendar, Sabre_DAV_IProperties, Sabre_DAVACL_IACL {
|
||||
|
||||
/**
|
||||
* This is an array with calendar information
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $calendarInfo;
|
||||
|
||||
/**
|
||||
* CalDAV backend
|
||||
*
|
||||
* @var Sabre_CalDAV_Backend_Abstract
|
||||
*/
|
||||
protected $caldavBackend;
|
||||
|
||||
/**
|
||||
* Principal backend
|
||||
*
|
||||
* @var Sabre_DAVACL_IPrincipalBackend
|
||||
*/
|
||||
protected $principalBackend;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
|
||||
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
|
||||
* @param array $calendarInfo
|
||||
*/
|
||||
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $calendarInfo) {
|
||||
|
||||
$this->caldavBackend = $caldavBackend;
|
||||
$this->principalBackend = $principalBackend;
|
||||
$this->calendarInfo = $calendarInfo;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the calendar
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
|
||||
return $this->calendarInfo['uri'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates properties such as the display name and description
|
||||
*
|
||||
* @param array $mutations
|
||||
* @return array
|
||||
*/
|
||||
public function updateProperties($mutations) {
|
||||
|
||||
return $this->caldavBackend->updateCalendar($this->calendarInfo['id'],$mutations);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of properties
|
||||
*
|
||||
* @param array $requestedProperties
|
||||
* @return array
|
||||
*/
|
||||
public function getProperties($requestedProperties) {
|
||||
|
||||
$response = array();
|
||||
|
||||
foreach($requestedProperties as $prop) switch($prop) {
|
||||
|
||||
case '{urn:ietf:params:xml:ns:caldav}supported-calendar-data' :
|
||||
$response[$prop] = new Sabre_CalDAV_Property_SupportedCalendarData();
|
||||
break;
|
||||
case '{urn:ietf:params:xml:ns:caldav}supported-collation-set' :
|
||||
$response[$prop] = new Sabre_CalDAV_Property_SupportedCollationSet();
|
||||
break;
|
||||
case '{DAV:}owner' :
|
||||
$response[$prop] = new Sabre_DAVACL_Property_Principal(Sabre_DAVACL_Property_Principal::HREF,$this->calendarInfo['principaluri']);
|
||||
break;
|
||||
default :
|
||||
if (isset($this->calendarInfo[$prop])) $response[$prop] = $this->calendarInfo[$prop];
|
||||
break;
|
||||
|
||||
}
|
||||
return $response;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a calendar object
|
||||
*
|
||||
* The contained calendar objects are for example Events or Todo's.
|
||||
*
|
||||
* @param string $name
|
||||
* @return Sabre_CalDAV_ICalendarObject
|
||||
*/
|
||||
public function getChild($name) {
|
||||
|
||||
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name);
|
||||
if (!$obj) throw new Sabre_DAV_Exception_NotFound('Calendar object not found');
|
||||
return new Sabre_CalDAV_CalendarObject($this->caldavBackend,$this->calendarInfo,$obj);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full list of calendar objects
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildren() {
|
||||
|
||||
$objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']);
|
||||
$children = array();
|
||||
foreach($objs as $obj) {
|
||||
$children[] = new Sabre_CalDAV_CalendarObject($this->caldavBackend,$this->calendarInfo,$obj);
|
||||
}
|
||||
return $children;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a child-node exists.
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function childExists($name) {
|
||||
|
||||
$obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name);
|
||||
if (!$obj)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new directory
|
||||
*
|
||||
* We actually block this, as subdirectories are not allowed in calendars.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function createDirectory($name) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Creating collections in calendar objects is not allowed');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file
|
||||
*
|
||||
* The contents of the new file must be a valid ICalendar string.
|
||||
*
|
||||
* @param string $name
|
||||
* @param resource $calendarData
|
||||
* @return string|null
|
||||
*/
|
||||
public function createFile($name,$calendarData = null) {
|
||||
|
||||
if (is_resource($calendarData)) {
|
||||
$calendarData = stream_get_contents($calendarData);
|
||||
}
|
||||
return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'],$name,$calendarData);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the calendar.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete() {
|
||||
|
||||
$this->caldavBackend->deleteCalendar($this->calendarInfo['id']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the calendar. Note that most calendars use the
|
||||
* {DAV:}displayname to display a name to display a name.
|
||||
*
|
||||
* @param string $newName
|
||||
* @return void
|
||||
*/
|
||||
public function setName($newName) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Renaming calendars is not yet supported');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification date as a unix timestamp.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getLastModified() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOwner() {
|
||||
|
||||
return $this->calendarInfo['principaluri'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a group principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getGroup() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of ACE's for this node.
|
||||
*
|
||||
* Each ACE has the following properties:
|
||||
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
|
||||
* currently the only supported privileges
|
||||
* * 'principal', a url to the principal who owns the node
|
||||
* * 'protected' (optional), indicating that this ACE is not allowed to
|
||||
* be updated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getACL() {
|
||||
|
||||
return array(
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy',
|
||||
'principal' => '{DAV:}authenticated',
|
||||
'protected' => true,
|
||||
),
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ACL
|
||||
*
|
||||
* This method will receive a list of new ACE's.
|
||||
*
|
||||
* @param array $acl
|
||||
* @return void
|
||||
*/
|
||||
public function setACL(array $acl) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of supported privileges for this node.
|
||||
*
|
||||
* The returned data structure is a list of nested privileges.
|
||||
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
|
||||
* standard structure.
|
||||
*
|
||||
* If null is returned from this method, the default privilege set is used,
|
||||
* which is fine for most common usecases.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getSupportedPrivilegeSet() {
|
||||
|
||||
$default = Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet();
|
||||
|
||||
// We need to inject 'read-free-busy' in the tree, aggregated under
|
||||
// {DAV:}read.
|
||||
foreach($default['aggregates'] as &$agg) {
|
||||
|
||||
if ($agg['privilege'] !== '{DAV:}read') continue;
|
||||
|
||||
$agg['aggregates'][] = array(
|
||||
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}read-free-busy',
|
||||
);
|
||||
|
||||
}
|
||||
return $default;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a calendar-query on the contents of this calendar.
|
||||
*
|
||||
* The calendar-query is defined in RFC4791 : CalDAV. Using the
|
||||
* calendar-query it is possible for a client to request a specific set of
|
||||
* object, based on contents of iCalendar properties, date-ranges and
|
||||
* iCalendar component types (VTODO, VEVENT).
|
||||
*
|
||||
* This method should just return a list of (relative) urls that match this
|
||||
* query.
|
||||
*
|
||||
* The list of filters are specified as an array. The exact array is
|
||||
* documented by Sabre_CalDAV_CalendarQueryParser.
|
||||
*
|
||||
* @param array $filters
|
||||
* @return array
|
||||
*/
|
||||
public function calendarQuery(array $filters) {
|
||||
|
||||
return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* The CalendarObject represents a single VEVENT or VTODO within a Calendar.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_CalendarObject extends Sabre_DAV_File implements Sabre_CalDAV_ICalendarObject, Sabre_DAVACL_IACL {
|
||||
|
||||
/**
|
||||
* Sabre_CalDAV_Backend_Abstract
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $caldavBackend;
|
||||
|
||||
/**
|
||||
* Array with information about this CalendarObject
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $objectData;
|
||||
|
||||
/**
|
||||
* Array with information about the containing calendar
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $calendarInfo;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
|
||||
* @param array $calendarInfo
|
||||
* @param array $objectData
|
||||
*/
|
||||
public function __construct(Sabre_CalDAV_Backend_Abstract $caldavBackend,array $calendarInfo,array $objectData) {
|
||||
|
||||
$this->caldavBackend = $caldavBackend;
|
||||
|
||||
if (!isset($objectData['calendarid'])) {
|
||||
throw new InvalidArgumentException('The objectData argument must contain a \'calendarid\' property');
|
||||
}
|
||||
if (!isset($objectData['uri'])) {
|
||||
throw new InvalidArgumentException('The objectData argument must contain an \'uri\' property');
|
||||
}
|
||||
|
||||
$this->calendarInfo = $calendarInfo;
|
||||
$this->objectData = $objectData;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the uri for this object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
|
||||
return $this->objectData['uri'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ICalendar-formatted object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get() {
|
||||
|
||||
// Pre-populating the 'calendardata' is optional, if we don't have it
|
||||
// already we fetch it from the backend.
|
||||
if (!isset($this->objectData['calendardata'])) {
|
||||
$this->objectData = $this->caldavBackend->getCalendarObject($this->objectData['calendarid'], $this->objectData['uri']);
|
||||
}
|
||||
return $this->objectData['calendardata'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ICalendar-formatted object
|
||||
*
|
||||
* @param string|resource $calendarData
|
||||
* @return string
|
||||
*/
|
||||
public function put($calendarData) {
|
||||
|
||||
if (is_resource($calendarData)) {
|
||||
$calendarData = stream_get_contents($calendarData);
|
||||
}
|
||||
$etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'],$this->objectData['uri'],$calendarData);
|
||||
$this->objectData['calendardata'] = $calendarData;
|
||||
$this->objectData['etag'] = $etag;
|
||||
|
||||
return $etag;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the calendar object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete() {
|
||||
|
||||
$this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'],$this->objectData['uri']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime content-type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType() {
|
||||
|
||||
return 'text/calendar; charset=utf-8';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ETag for this object.
|
||||
*
|
||||
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getETag() {
|
||||
|
||||
if (isset($this->objectData['etag'])) {
|
||||
return $this->objectData['etag'];
|
||||
} else {
|
||||
return '"' . md5($this->get()). '"';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification date as a unix timestamp
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModified() {
|
||||
|
||||
return $this->objectData['lastmodified'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of this object in bytes
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSize() {
|
||||
|
||||
if (array_key_exists('size',$this->objectData)) {
|
||||
return $this->objectData['size'];
|
||||
} else {
|
||||
return strlen($this->get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOwner() {
|
||||
|
||||
return $this->calendarInfo['principaluri'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a group principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getGroup() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of ACE's for this node.
|
||||
*
|
||||
* Each ACE has the following properties:
|
||||
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
|
||||
* currently the only supported privileges
|
||||
* * 'principal', a url to the principal who owns the node
|
||||
* * 'protected' (optional), indicating that this ACE is not allowed to
|
||||
* be updated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getACL() {
|
||||
|
||||
return array(
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->calendarInfo['principaluri'],
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
),
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ACL
|
||||
*
|
||||
* This method will receive a list of new ACE's.
|
||||
*
|
||||
* @param array $acl
|
||||
* @return void
|
||||
*/
|
||||
public function setACL(array $acl) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of supported privileges for this node.
|
||||
*
|
||||
* The returned data structure is a list of nested privileges.
|
||||
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
|
||||
* standard structure.
|
||||
*
|
||||
* If null is returned from this method, the default privilege set is used,
|
||||
* which is fine for most common usecases.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getSupportedPrivilegeSet() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,296 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Parses the calendar-query report request body.
|
||||
*
|
||||
* Whoever designed this format, and the CalDAV equivalent even more so,
|
||||
* has no feel for design.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_CalendarQueryParser {
|
||||
|
||||
/**
|
||||
* List of requested properties the client wanted
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $requestedProperties;
|
||||
|
||||
/**
|
||||
* List of property/component filters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $filters;
|
||||
|
||||
/**
|
||||
* This property will contain null if CALDAV:expand was not specified,
|
||||
* otherwise it will contain an array with 2 elements (start, end). Each
|
||||
* contain a DateTime object.
|
||||
*
|
||||
* If expand is specified, recurring calendar objects are to be expanded
|
||||
* into their individual components, and only the components that fall
|
||||
* within the specified time-range are to be returned.
|
||||
*
|
||||
* For more details, see rfc4791, section 9.6.5.
|
||||
*
|
||||
* @var null|array
|
||||
*/
|
||||
public $expand;
|
||||
|
||||
/**
|
||||
* DOM Document
|
||||
*
|
||||
* @var DOMDocument
|
||||
*/
|
||||
protected $dom;
|
||||
|
||||
/**
|
||||
* DOM XPath object
|
||||
*
|
||||
* @var DOMXPath
|
||||
*/
|
||||
protected $xpath;
|
||||
|
||||
/**
|
||||
* Creates the parser
|
||||
*
|
||||
* @param DOMDocument $dom
|
||||
*/
|
||||
public function __construct(DOMDocument $dom) {
|
||||
|
||||
$this->dom = $dom;
|
||||
|
||||
$this->xpath = new DOMXPath($dom);
|
||||
$this->xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV);
|
||||
$this->xpath->registerNameSpace('dav','urn:DAV');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parse() {
|
||||
|
||||
$filterNode = null;
|
||||
|
||||
$filter = $this->xpath->query('/cal:calendar-query/cal:filter');
|
||||
if ($filter->length !== 1) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed');
|
||||
}
|
||||
|
||||
$compFilters = $this->parseCompFilters($filter->item(0));
|
||||
if (count($compFilters)!==1) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('There must be exactly 1 top-level comp-filter.');
|
||||
}
|
||||
|
||||
$this->filters = $compFilters[0];
|
||||
$this->requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild));
|
||||
|
||||
$expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand');
|
||||
if ($expand->length>0) {
|
||||
$this->expand = $this->parseExpand($expand->item(0));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses all the 'comp-filter' elements from a node
|
||||
*
|
||||
* @param DOMElement $parentNode
|
||||
* @return array
|
||||
*/
|
||||
protected function parseCompFilters(DOMElement $parentNode) {
|
||||
|
||||
$compFilterNodes = $this->xpath->query('cal:comp-filter', $parentNode);
|
||||
$result = array();
|
||||
|
||||
for($ii=0; $ii < $compFilterNodes->length; $ii++) {
|
||||
|
||||
$compFilterNode = $compFilterNodes->item($ii);
|
||||
|
||||
$compFilter = array();
|
||||
$compFilter['name'] = $compFilterNode->getAttribute('name');
|
||||
$compFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $compFilterNode)->length>0;
|
||||
$compFilter['comp-filters'] = $this->parseCompFilters($compFilterNode);
|
||||
$compFilter['prop-filters'] = $this->parsePropFilters($compFilterNode);
|
||||
$compFilter['time-range'] = $this->parseTimeRange($compFilterNode);
|
||||
|
||||
if ($compFilter['time-range'] && !in_array($compFilter['name'],array(
|
||||
'VEVENT',
|
||||
'VTODO',
|
||||
'VJOURNAL',
|
||||
'VFREEBUSY',
|
||||
'VALARM',
|
||||
))) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The time-range filter is not defined for the ' . $compFilter['name'] . ' component');
|
||||
};
|
||||
|
||||
$result[] = $compFilter;
|
||||
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses all the prop-filter elements from a node
|
||||
*
|
||||
* @param DOMElement $parentNode
|
||||
* @return array
|
||||
*/
|
||||
protected function parsePropFilters(DOMElement $parentNode) {
|
||||
|
||||
$propFilterNodes = $this->xpath->query('cal:prop-filter', $parentNode);
|
||||
$result = array();
|
||||
|
||||
for ($ii=0; $ii < $propFilterNodes->length; $ii++) {
|
||||
|
||||
$propFilterNode = $propFilterNodes->item($ii);
|
||||
$propFilter = array();
|
||||
$propFilter['name'] = $propFilterNode->getAttribute('name');
|
||||
$propFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $propFilterNode)->length>0;
|
||||
$propFilter['param-filters'] = $this->parseParamFilters($propFilterNode);
|
||||
$propFilter['text-match'] = $this->parseTextMatch($propFilterNode);
|
||||
$propFilter['time-range'] = $this->parseTimeRange($propFilterNode);
|
||||
|
||||
$result[] = $propFilter;
|
||||
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the param-filter element
|
||||
*
|
||||
* @param DOMElement $parentNode
|
||||
* @return array
|
||||
*/
|
||||
protected function parseParamFilters(DOMElement $parentNode) {
|
||||
|
||||
$paramFilterNodes = $this->xpath->query('cal:param-filter', $parentNode);
|
||||
$result = array();
|
||||
|
||||
for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
|
||||
|
||||
$paramFilterNode = $paramFilterNodes->item($ii);
|
||||
$paramFilter = array();
|
||||
$paramFilter['name'] = $paramFilterNode->getAttribute('name');
|
||||
$paramFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $paramFilterNode)->length>0;
|
||||
$paramFilter['text-match'] = $this->parseTextMatch($paramFilterNode);
|
||||
|
||||
$result[] = $paramFilter;
|
||||
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the text-match element
|
||||
*
|
||||
* @param DOMElement $parentNode
|
||||
* @return array|null
|
||||
*/
|
||||
protected function parseTextMatch(DOMElement $parentNode) {
|
||||
|
||||
$textMatchNodes = $this->xpath->query('cal:text-match', $parentNode);
|
||||
|
||||
if ($textMatchNodes->length === 0)
|
||||
return null;
|
||||
|
||||
$textMatchNode = $textMatchNodes->item(0);
|
||||
$negateCondition = $textMatchNode->getAttribute('negate-condition');
|
||||
$negateCondition = $negateCondition==='yes';
|
||||
$collation = $textMatchNode->getAttribute('collation');
|
||||
if (!$collation) $collation = 'i;ascii-casemap';
|
||||
|
||||
return array(
|
||||
'negate-condition' => $negateCondition,
|
||||
'collation' => $collation,
|
||||
'value' => $textMatchNode->nodeValue
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the time-range element
|
||||
*
|
||||
* @param DOMElement $parentNode
|
||||
* @return array|null
|
||||
*/
|
||||
protected function parseTimeRange(DOMElement $parentNode) {
|
||||
|
||||
$timeRangeNodes = $this->xpath->query('cal:time-range', $parentNode);
|
||||
if ($timeRangeNodes->length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$timeRangeNode = $timeRangeNodes->item(0);
|
||||
|
||||
if ($start = $timeRangeNode->getAttribute('start')) {
|
||||
$start = Sabre_VObject_DateTimeParser::parseDateTime($start);
|
||||
} else {
|
||||
$start = null;
|
||||
}
|
||||
if ($end = $timeRangeNode->getAttribute('end')) {
|
||||
$end = Sabre_VObject_DateTimeParser::parseDateTime($end);
|
||||
} else {
|
||||
$end = null;
|
||||
}
|
||||
|
||||
if (!is_null($start) && !is_null($end) && $end <= $start) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the time-range filter');
|
||||
}
|
||||
|
||||
return array(
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the CALDAV:expand element
|
||||
*
|
||||
* @param DOMElement $parentNode
|
||||
* @return void
|
||||
*/
|
||||
protected function parseExpand(DOMElement $parentNode) {
|
||||
|
||||
$start = $parentNode->getAttribute('start');
|
||||
if(!$start) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The "start" attribute is required for the CALDAV:expand element');
|
||||
}
|
||||
$start = Sabre_VObject_DateTimeParser::parseDateTime($start);
|
||||
|
||||
$end = $parentNode->getAttribute('end');
|
||||
if(!$end) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The "end" attribute is required for the CALDAV:expand element');
|
||||
}
|
||||
$end = Sabre_VObject_DateTimeParser::parseDateTime($end);
|
||||
|
||||
if ($end <= $start) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.');
|
||||
}
|
||||
|
||||
return array(
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,369 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CalendarQuery Validator
|
||||
*
|
||||
* This class is responsible for checking if an iCalendar object matches a set
|
||||
* of filters. The main function to do this is 'validate'.
|
||||
*
|
||||
* This is used to determine which icalendar objects should be returned for a
|
||||
* calendar-query REPORT request.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_CalendarQueryValidator {
|
||||
|
||||
/**
|
||||
* Verify if a list of filters applies to the calendar data object
|
||||
*
|
||||
* The list of filters must be formatted as parsed by Sabre_CalDAV_CalendarQueryParser
|
||||
*
|
||||
* @param Sabre_VObject_Component $vObject
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function validate(Sabre_VObject_Component $vObject,array $filters) {
|
||||
|
||||
// The top level object is always a component filter.
|
||||
// We'll parse it manually, as it's pretty simple.
|
||||
if ($vObject->name !== $filters['name']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
$this->validateCompFilters($vObject, $filters['comp-filters']) &&
|
||||
$this->validatePropFilters($vObject, $filters['prop-filters']);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks the validity of comp-filters.
|
||||
*
|
||||
* A list of comp-filters needs to be specified. Also the parent of the
|
||||
* component we're checking should be specified, not the component to check
|
||||
* itself.
|
||||
*
|
||||
* @param Sabre_VObject_Component $parent
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateCompFilters(Sabre_VObject_Component $parent, array $filters) {
|
||||
|
||||
foreach($filters as $filter) {
|
||||
|
||||
$isDefined = isset($parent->$filter['name']);
|
||||
|
||||
if ($filter['is-not-defined']) {
|
||||
|
||||
if ($isDefined) {
|
||||
return false;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
if (!$isDefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($filter['time-range']) {
|
||||
foreach($parent->$filter['name'] as $subComponent) {
|
||||
if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$filter['comp-filters'] && !$filter['prop-filters']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there are sub-filters, we need to find at least one component
|
||||
// for which the subfilters hold true.
|
||||
foreach($parent->$filter['name'] as $subComponent) {
|
||||
|
||||
if (
|
||||
$this->validateCompFilters($subComponent, $filter['comp-filters']) &&
|
||||
$this->validatePropFilters($subComponent, $filter['prop-filters'])) {
|
||||
// We had a match, so this comp-filter succeeds
|
||||
continue 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If we got here it means there were sub-comp-filters or
|
||||
// sub-prop-filters and there was no match. This means this filter
|
||||
// needs to return false.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// If we got here it means we got through all comp-filters alive so the
|
||||
// filters were all true.
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks the validity of prop-filters.
|
||||
*
|
||||
* A list of prop-filters needs to be specified. Also the parent of the
|
||||
* property we're checking should be specified, not the property to check
|
||||
* itself.
|
||||
*
|
||||
* @param Sabre_VObject_Component $parent
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
protected function validatePropFilters(Sabre_VObject_Component $parent, array $filters) {
|
||||
|
||||
foreach($filters as $filter) {
|
||||
|
||||
$isDefined = isset($parent->$filter['name']);
|
||||
|
||||
if ($filter['is-not-defined']) {
|
||||
|
||||
if ($isDefined) {
|
||||
return false;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
if (!$isDefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($filter['time-range']) {
|
||||
foreach($parent->$filter['name'] as $subComponent) {
|
||||
if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$filter['param-filters'] && !$filter['text-match']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there are sub-filters, we need to find at least one property
|
||||
// for which the subfilters hold true.
|
||||
foreach($parent->$filter['name'] as $subComponent) {
|
||||
|
||||
if(
|
||||
$this->validateParamFilters($subComponent, $filter['param-filters']) &&
|
||||
(!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
|
||||
) {
|
||||
// We had a match, so this prop-filter succeeds
|
||||
continue 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If we got here it means there were sub-param-filters or
|
||||
// text-match filters and there was no match. This means the
|
||||
// filter needs to return false.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// If we got here it means we got through all prop-filters alive so the
|
||||
// filters were all true.
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks the validity of param-filters.
|
||||
*
|
||||
* A list of param-filters needs to be specified. Also the parent of the
|
||||
* parameter we're checking should be specified, not the parameter to check
|
||||
* itself.
|
||||
*
|
||||
* @param Sabre_VObject_Property $parent
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateParamFilters(Sabre_VObject_Property $parent, array $filters) {
|
||||
|
||||
foreach($filters as $filter) {
|
||||
|
||||
$isDefined = isset($parent[$filter['name']]);
|
||||
|
||||
if ($filter['is-not-defined']) {
|
||||
|
||||
if ($isDefined) {
|
||||
return false;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
if (!$isDefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$filter['text-match']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there are sub-filters, we need to find at least one parameter
|
||||
// for which the subfilters hold true.
|
||||
foreach($parent[$filter['name']] as $subParam) {
|
||||
|
||||
if($this->validateTextMatch($subParam,$filter['text-match'])) {
|
||||
// We had a match, so this param-filter succeeds
|
||||
continue 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If we got here it means there was a text-match filter and there
|
||||
// were no matches. This means the filter needs to return false.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// If we got here it means we got through all param-filters alive so the
|
||||
// filters were all true.
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks the validity of a text-match.
|
||||
*
|
||||
* A single text-match should be specified as well as the specific property
|
||||
* or parameter we need to validate.
|
||||
*
|
||||
* @param Sabre_VObject_Node $parent
|
||||
* @param array $textMatch
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateTextMatch(Sabre_VObject_Node $parent, array $textMatch) {
|
||||
|
||||
$value = (string)$parent;
|
||||
|
||||
$isMatching = Sabre_DAV_StringUtil::textMatch($value, $textMatch['value'], $textMatch['collation']);
|
||||
|
||||
return ($textMatch['negate-condition'] xor $isMatching);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a component matches the given time range.
|
||||
*
|
||||
* This is all based on the rules specified in rfc4791, which are quite
|
||||
* complex.
|
||||
*
|
||||
* @param Sabre_VObject_Node $component
|
||||
* @param DateTime $start
|
||||
* @param DateTime $end
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateTimeRange(Sabre_VObject_Node $component, $start, $end) {
|
||||
|
||||
if (is_null($start)) {
|
||||
$start = new DateTime('1900-01-01');
|
||||
}
|
||||
if (is_null($end)) {
|
||||
$end = new DateTime('3000-01-01');
|
||||
}
|
||||
|
||||
switch($component->name) {
|
||||
|
||||
case 'VEVENT' :
|
||||
case 'VTODO' :
|
||||
case 'VJOURNAL' :
|
||||
|
||||
return $component->isInTimeRange($start, $end);
|
||||
|
||||
case 'VALARM' :
|
||||
|
||||
// If the valarm is wrapped in a recurring event, we need to
|
||||
// expand the recursions, and validate each.
|
||||
//
|
||||
// Our datamodel doesn't easily allow us to do this straight
|
||||
// in the VALARM component code, so this is a hack, and an
|
||||
// expensive one too.
|
||||
if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
|
||||
|
||||
// Fire up the iterator!
|
||||
$it = new Sabre_VObject_RecurrenceIterator($component->parent->parent, (string)$component->parent->UID);
|
||||
while($it->valid()) {
|
||||
$expandedEvent = $it->getEventObject();
|
||||
|
||||
// We need to check from these expanded alarms, which
|
||||
// one is the first to trigger. Based on this, we can
|
||||
// determine if we can 'give up' expanding events.
|
||||
$firstAlarm = null;
|
||||
foreach($expandedEvent->VALARM as $expandedAlarm) {
|
||||
|
||||
$effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
|
||||
if ($expandedAlarm->isInTimeRange($start, $end)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
|
||||
// This is an alarm with a non-relative trigger
|
||||
// time, likely created by a buggy client. The
|
||||
// implication is that every alarm in this
|
||||
// recurring event trigger at the exact same
|
||||
// time. It doesn't make sense to traverse
|
||||
// further.
|
||||
} else {
|
||||
// We store the first alarm as a means to
|
||||
// figure out when we can stop traversing.
|
||||
if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
|
||||
$firstAlarm = $effectiveTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (is_null($firstAlarm)) {
|
||||
// No alarm was found.
|
||||
//
|
||||
// Or technically: No alarm that will change for
|
||||
// every instance of the recurrence was found,
|
||||
// which means we can assume there was no match.
|
||||
return false;
|
||||
}
|
||||
if ($firstAlarm > $end) {
|
||||
return false;
|
||||
}
|
||||
$it->next();
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return $component->isInTimeRange($start, $end);
|
||||
}
|
||||
|
||||
case 'VFREEBUSY' :
|
||||
throw new Sabre_DAV_Exception_NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
|
||||
|
||||
case 'COMPLETED' :
|
||||
case 'CREATED' :
|
||||
case 'DTEND' :
|
||||
case 'DTSTAMP' :
|
||||
case 'DTSTART' :
|
||||
case 'DUE' :
|
||||
case 'LAST-MODIFIED' :
|
||||
return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
|
||||
|
||||
|
||||
|
||||
default :
|
||||
throw new Sabre_DAV_Exception_BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Calendars collection
|
||||
*
|
||||
* This object is responsible for generating a list of calendar-homes for each
|
||||
* user.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_CalendarRootNode extends Sabre_DAVACL_AbstractPrincipalCollection {
|
||||
|
||||
/**
|
||||
* CalDAV backend
|
||||
*
|
||||
* @var Sabre_CalDAV_Backend_Abstract
|
||||
*/
|
||||
protected $caldavBackend;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* This constructor needs both an authentication and a caldav backend.
|
||||
*
|
||||
* By default this class will show a list of calendar collections for
|
||||
* principals in the 'principals' collection. If your main principals are
|
||||
* actually located in a different path, use the $principalPrefix argument
|
||||
* to override this.
|
||||
*
|
||||
*
|
||||
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
|
||||
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
|
||||
* @param string $principalPrefix
|
||||
*/
|
||||
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CalDAV_Backend_Abstract $caldavBackend, $principalPrefix = 'principals') {
|
||||
|
||||
parent::__construct($principalBackend, $principalPrefix);
|
||||
$this->caldavBackend = $caldavBackend;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nodename
|
||||
*
|
||||
* We're overriding this, because the default will be the 'principalPrefix',
|
||||
* and we want it to be Sabre_CalDAV_Plugin::CALENDAR_ROOT
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
|
||||
return Sabre_CalDAV_Plugin::CALENDAR_ROOT;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a node for a principal.
|
||||
*
|
||||
* The passed array contains principal information, and is guaranteed to
|
||||
* at least contain a uri item. Other properties may or may not be
|
||||
* supplied by the authentication backend.
|
||||
*
|
||||
* @param array $principal
|
||||
* @return Sabre_DAV_INode
|
||||
*/
|
||||
public function getChildForPrincipal(array $principal) {
|
||||
|
||||
return new Sabre_CalDAV_UserCalendars($this->principalBackend, $this->caldavBackend, $principal['uri']);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* ICS Exporter
|
||||
*
|
||||
* This plugin adds the ability to export entire calendars as .ics files.
|
||||
* This is useful for clients that don't support CalDAV yet. They often do
|
||||
* support ics files.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_ICSExportPlugin extends Sabre_DAV_ServerPlugin {
|
||||
|
||||
/**
|
||||
* Reference to Server class
|
||||
*
|
||||
* @var Sabre_DAV_Server
|
||||
*/
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* Initializes the plugin and registers event handlers
|
||||
*
|
||||
* @param Sabre_DAV_Server $server
|
||||
* @return void
|
||||
*/
|
||||
public function initialize(Sabre_DAV_Server $server) {
|
||||
|
||||
$this->server = $server;
|
||||
$this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'), 90);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 'beforeMethod' event handles. This event handles intercepts GET requests ending
|
||||
* with ?export
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $uri
|
||||
* @return bool
|
||||
*/
|
||||
public function beforeMethod($method, $uri) {
|
||||
|
||||
if ($method!='GET') return;
|
||||
if ($this->server->httpRequest->getQueryString()!='export') return;
|
||||
|
||||
// splitting uri
|
||||
list($uri) = explode('?',$uri,2);
|
||||
|
||||
$node = $this->server->tree->getNodeForPath($uri);
|
||||
|
||||
if (!($node instanceof Sabre_CalDAV_Calendar)) return;
|
||||
|
||||
// Checking ACL, if available.
|
||||
if ($aclPlugin = $this->server->getPlugin('acl')) {
|
||||
$aclPlugin->checkPrivileges($uri, '{DAV:}read');
|
||||
}
|
||||
|
||||
$this->server->httpResponse->setHeader('Content-Type','text/calendar');
|
||||
$this->server->httpResponse->sendStatus(200);
|
||||
|
||||
$nodes = $this->server->getPropertiesForPath($uri, array(
|
||||
'{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data',
|
||||
),1);
|
||||
|
||||
$this->server->httpResponse->sendBody($this->generateICS($nodes));
|
||||
|
||||
// Returning false to break the event chain
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges all calendar objects, and builds one big ics export
|
||||
*
|
||||
* @param array $nodes
|
||||
* @return string
|
||||
*/
|
||||
public function generateICS(array $nodes) {
|
||||
|
||||
$calendar = new Sabre_VObject_Component('vcalendar');
|
||||
$calendar->version = '2.0';
|
||||
if (Sabre_DAV_Server::$exposeVersion) {
|
||||
$calendar->prodid = '-//SabreDAV//SabreDAV ' . Sabre_DAV_Version::VERSION . '//EN';
|
||||
} else {
|
||||
$calendar->prodid = '-//SabreDAV//SabreDAV//EN';
|
||||
}
|
||||
$calendar->calscale = 'GREGORIAN';
|
||||
|
||||
$collectedTimezones = array();
|
||||
|
||||
$timezones = array();
|
||||
$objects = array();
|
||||
|
||||
foreach($nodes as $node) {
|
||||
|
||||
if (!isset($node[200]['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data'])) {
|
||||
continue;
|
||||
}
|
||||
$nodeData = $node[200]['{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data'];
|
||||
|
||||
$nodeComp = Sabre_VObject_Reader::read($nodeData);
|
||||
|
||||
foreach($nodeComp->children() as $child) {
|
||||
|
||||
switch($child->name) {
|
||||
case 'VEVENT' :
|
||||
case 'VTODO' :
|
||||
case 'VJOURNAL' :
|
||||
$objects[] = $child;
|
||||
break;
|
||||
|
||||
// VTIMEZONE is special, because we need to filter out the duplicates
|
||||
case 'VTIMEZONE' :
|
||||
// Naively just checking tzid.
|
||||
if (in_array((string)$child->TZID, $collectedTimezones)) continue;
|
||||
|
||||
$timezones[] = $child;
|
||||
$collectedTimezones[] = $child->TZID;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach($timezones as $tz) $calendar->add($tz);
|
||||
foreach($objects as $obj) $calendar->add($obj);
|
||||
|
||||
return $calendar->serialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Calendar interface
|
||||
*
|
||||
* Implement this interface to allow a node to be recognized as an calendar.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
interface Sabre_CalDAV_ICalendar extends Sabre_DAV_ICollection {
|
||||
|
||||
/**
|
||||
* Performs a calendar-query on the contents of this calendar.
|
||||
*
|
||||
* The calendar-query is defined in RFC4791 : CalDAV. Using the
|
||||
* calendar-query it is possible for a client to request a specific set of
|
||||
* object, based on contents of iCalendar properties, date-ranges and
|
||||
* iCalendar component types (VTODO, VEVENT).
|
||||
*
|
||||
* This method should just return a list of (relative) urls that match this
|
||||
* query.
|
||||
*
|
||||
* The list of filters are specified as an array. The exact array is
|
||||
* documented by Sabre_CalDAV_CalendarQueryParser.
|
||||
*
|
||||
* @param array $filters
|
||||
* @return array
|
||||
*/
|
||||
public function calendarQuery(array $filters);
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CalendarObject interface
|
||||
/**
|
||||
* Extend the ICalendarObject interface to allow your custom nodes to be picked up as
|
||||
* CalendarObjects.
|
||||
*
|
||||
* Calendar objects are resources such as Events, Todo's or Journals.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
interface Sabre_CalDAV_ICalendarObject extends Sabre_DAV_IFile {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,894 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CalDAV plugin
|
||||
*
|
||||
* This plugin provides functionality added by CalDAV (RFC 4791)
|
||||
* It implements new reports, and the MKCALENDAR method.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
|
||||
|
||||
/**
|
||||
* This is the official CalDAV namespace
|
||||
*/
|
||||
const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
|
||||
|
||||
/**
|
||||
* This is the namespace for the proprietary calendarserver extensions
|
||||
*/
|
||||
const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
|
||||
|
||||
/**
|
||||
* The hardcoded root for calendar objects. It is unfortunate
|
||||
* that we're stuck with it, but it will have to do for now
|
||||
*/
|
||||
const CALENDAR_ROOT = 'calendars';
|
||||
|
||||
/**
|
||||
* Reference to server object
|
||||
*
|
||||
* @var Sabre_DAV_Server
|
||||
*/
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* The email handler for invites and other scheduling messages.
|
||||
*
|
||||
* @var Sabre_CalDAV_Schedule_IMip
|
||||
*/
|
||||
protected $imipHandler;
|
||||
|
||||
/**
|
||||
* Sets the iMIP handler.
|
||||
*
|
||||
* iMIP = The email transport of iCalendar scheduling messages. Setting
|
||||
* this is optional, but if you want the server to allow invites to be sent
|
||||
* out, you must set a handler.
|
||||
*
|
||||
* Specifically iCal will plain assume that the server supports this. If
|
||||
* the server doesn't, iCal will display errors when inviting people to
|
||||
* events.
|
||||
*
|
||||
* @param Sabre_CalDAV_Schedule_IMip $imipHandler
|
||||
* @return void
|
||||
*/
|
||||
public function setIMipHandler(Sabre_CalDAV_Schedule_IMip $imipHandler) {
|
||||
|
||||
$this->imipHandler = $imipHandler;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to tell the server this plugin defines additional
|
||||
* HTTP methods.
|
||||
*
|
||||
* This method is passed a uri. It should only return HTTP methods that are
|
||||
* available for the specified uri.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return array
|
||||
*/
|
||||
public function getHTTPMethods($uri) {
|
||||
|
||||
// The MKCALENDAR is only available on unmapped uri's, whose
|
||||
// parents extend IExtendedCollection
|
||||
list($parent, $name) = Sabre_DAV_URLUtil::splitPath($uri);
|
||||
|
||||
$node = $this->server->tree->getNodeForPath($parent);
|
||||
|
||||
if ($node instanceof Sabre_DAV_IExtendedCollection) {
|
||||
try {
|
||||
$node->getChild($name);
|
||||
} catch (Sabre_DAV_Exception_NotFound $e) {
|
||||
return array('MKCALENDAR');
|
||||
}
|
||||
}
|
||||
return array();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of features for the DAV: HTTP header.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFeatures() {
|
||||
|
||||
return array('calendar-access', 'calendar-proxy');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a plugin name.
|
||||
*
|
||||
* Using this name other plugins will be able to access other plugins
|
||||
* using Sabre_DAV_Server::getPlugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPluginName() {
|
||||
|
||||
return 'caldav';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of reports this plugin supports.
|
||||
*
|
||||
* This will be used in the {DAV:}supported-report-set property.
|
||||
* Note that you still need to subscribe to the 'report' event to actually
|
||||
* implement them
|
||||
*
|
||||
* @param string $uri
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedReportSet($uri) {
|
||||
|
||||
$node = $this->server->tree->getNodeForPath($uri);
|
||||
|
||||
$reports = array();
|
||||
if ($node instanceof Sabre_CalDAV_ICalendar || $node instanceof Sabre_CalDAV_ICalendarObject) {
|
||||
$reports[] = '{' . self::NS_CALDAV . '}calendar-multiget';
|
||||
$reports[] = '{' . self::NS_CALDAV . '}calendar-query';
|
||||
}
|
||||
if ($node instanceof Sabre_CalDAV_ICalendar) {
|
||||
$reports[] = '{' . self::NS_CALDAV . '}free-busy-query';
|
||||
}
|
||||
return $reports;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the plugin
|
||||
*
|
||||
* @param Sabre_DAV_Server $server
|
||||
* @return void
|
||||
*/
|
||||
public function initialize(Sabre_DAV_Server $server) {
|
||||
|
||||
$this->server = $server;
|
||||
|
||||
$server->subscribeEvent('unknownMethod',array($this,'unknownMethod'));
|
||||
//$server->subscribeEvent('unknownMethod',array($this,'unknownMethod2'),1000);
|
||||
$server->subscribeEvent('report',array($this,'report'));
|
||||
$server->subscribeEvent('beforeGetProperties',array($this,'beforeGetProperties'));
|
||||
$server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel'));
|
||||
$server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction'));
|
||||
$server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent'));
|
||||
$server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile'));
|
||||
|
||||
$server->xmlNamespaces[self::NS_CALDAV] = 'cal';
|
||||
$server->xmlNamespaces[self::NS_CALENDARSERVER] = 'cs';
|
||||
|
||||
$server->propertyMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre_CalDAV_Property_SupportedCalendarComponentSet';
|
||||
|
||||
$server->resourceTypeMapping['Sabre_CalDAV_ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';
|
||||
$server->resourceTypeMapping['Sabre_CalDAV_Schedule_IOutbox'] = '{urn:ietf:params:xml:ns:caldav}schedule-outbox';
|
||||
$server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
|
||||
$server->resourceTypeMapping['Sabre_CalDAV_Principal_ProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';
|
||||
|
||||
array_push($server->protectedProperties,
|
||||
|
||||
'{' . self::NS_CALDAV . '}supported-calendar-component-set',
|
||||
'{' . self::NS_CALDAV . '}supported-calendar-data',
|
||||
'{' . self::NS_CALDAV . '}max-resource-size',
|
||||
'{' . self::NS_CALDAV . '}min-date-time',
|
||||
'{' . self::NS_CALDAV . '}max-date-time',
|
||||
'{' . self::NS_CALDAV . '}max-instances',
|
||||
'{' . self::NS_CALDAV . '}max-attendees-per-instance',
|
||||
'{' . self::NS_CALDAV . '}calendar-home-set',
|
||||
'{' . self::NS_CALDAV . '}supported-collation-set',
|
||||
'{' . self::NS_CALDAV . '}calendar-data',
|
||||
|
||||
// scheduling extension
|
||||
'{' . self::NS_CALDAV . '}schedule-inbox-URL',
|
||||
'{' . self::NS_CALDAV . '}schedule-outbox-URL',
|
||||
'{' . self::NS_CALDAV . '}calendar-user-address-set',
|
||||
'{' . self::NS_CALDAV . '}calendar-user-type',
|
||||
|
||||
// CalendarServer extensions
|
||||
'{' . self::NS_CALENDARSERVER . '}getctag',
|
||||
'{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
|
||||
'{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function handles support for the MKCALENDAR method
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $uri
|
||||
* @return bool
|
||||
*/
|
||||
public function unknownMethod($method, $uri) {
|
||||
|
||||
switch ($method) {
|
||||
case 'MKCALENDAR' :
|
||||
$this->httpMkCalendar($uri);
|
||||
// false is returned to stop the propagation of the
|
||||
// unknownMethod event.
|
||||
return false;
|
||||
case 'POST' :
|
||||
// Checking if we're talking to an outbox
|
||||
try {
|
||||
$node = $this->server->tree->getNodeForPath($uri);
|
||||
} catch (Sabre_DAV_Exception_NotFound $e) {
|
||||
return;
|
||||
}
|
||||
if (!$node instanceof Sabre_CalDAV_Schedule_IOutbox)
|
||||
return;
|
||||
|
||||
$this->outboxRequest($node);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions handles REPORT requests specific to CalDAV
|
||||
*
|
||||
* @param string $reportName
|
||||
* @param DOMNode $dom
|
||||
* @return bool
|
||||
*/
|
||||
public function report($reportName,$dom) {
|
||||
|
||||
switch($reportName) {
|
||||
case '{'.self::NS_CALDAV.'}calendar-multiget' :
|
||||
$this->calendarMultiGetReport($dom);
|
||||
return false;
|
||||
case '{'.self::NS_CALDAV.'}calendar-query' :
|
||||
$this->calendarQueryReport($dom);
|
||||
return false;
|
||||
case '{'.self::NS_CALDAV.'}free-busy-query' :
|
||||
$this->freeBusyQueryReport($dom);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function handles the MKCALENDAR HTTP method, which creates
|
||||
* a new calendar.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return void
|
||||
*/
|
||||
public function httpMkCalendar($uri) {
|
||||
|
||||
// Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support
|
||||
// for clients matching iCal in the user agent
|
||||
//$ua = $this->server->httpRequest->getHeader('User-Agent');
|
||||
//if (strpos($ua,'iCal/')!==false) {
|
||||
// throw new Sabre_DAV_Exception_Forbidden('iCal has major bugs in it\'s RFC3744 support. Therefore we are left with no other choice but disabling this feature.');
|
||||
//}
|
||||
|
||||
$body = $this->server->httpRequest->getBody(true);
|
||||
$properties = array();
|
||||
|
||||
if ($body) {
|
||||
|
||||
$dom = Sabre_DAV_XMLUtil::loadDOMDocument($body);
|
||||
|
||||
foreach($dom->firstChild->childNodes as $child) {
|
||||
|
||||
if (Sabre_DAV_XMLUtil::toClarkNotation($child)!=='{DAV:}set') continue;
|
||||
foreach(Sabre_DAV_XMLUtil::parseProperties($child,$this->server->propertyMap) as $k=>$prop) {
|
||||
$properties[$k] = $prop;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar');
|
||||
|
||||
$this->server->createCollection($uri,$resourceType,$properties);
|
||||
|
||||
$this->server->httpResponse->sendStatus(201);
|
||||
$this->server->httpResponse->setHeader('Content-Length',0);
|
||||
}
|
||||
|
||||
/**
|
||||
* beforeGetProperties
|
||||
*
|
||||
* 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 string $path
|
||||
* @param Sabre_DAV_INode $node
|
||||
* @param array $requestedProperties
|
||||
* @param array $returnedProperties
|
||||
* @return void
|
||||
*/
|
||||
public function beforeGetProperties($path, Sabre_DAV_INode $node, &$requestedProperties, &$returnedProperties) {
|
||||
|
||||
if ($node instanceof Sabre_DAVACL_IPrincipal) {
|
||||
|
||||
// calendar-home-set property
|
||||
$calHome = '{' . self::NS_CALDAV . '}calendar-home-set';
|
||||
if (in_array($calHome,$requestedProperties)) {
|
||||
$principalId = $node->getName();
|
||||
$calendarHomePath = self::CALENDAR_ROOT . '/' . $principalId . '/';
|
||||
unset($requestedProperties[$calHome]);
|
||||
$returnedProperties[200][$calHome] = new Sabre_DAV_Property_Href($calendarHomePath);
|
||||
}
|
||||
|
||||
// schedule-outbox-URL property
|
||||
$scheduleProp = '{' . self::NS_CALDAV . '}schedule-outbox-URL';
|
||||
if (in_array($scheduleProp,$requestedProperties)) {
|
||||
$principalId = $node->getName();
|
||||
$outboxPath = self::CALENDAR_ROOT . '/' . $principalId . '/outbox';
|
||||
unset($requestedProperties[$scheduleProp]);
|
||||
$returnedProperties[200][$scheduleProp] = new Sabre_DAV_Property_Href($outboxPath);
|
||||
}
|
||||
|
||||
// calendar-user-address-set property
|
||||
$calProp = '{' . self::NS_CALDAV . '}calendar-user-address-set';
|
||||
if (in_array($calProp,$requestedProperties)) {
|
||||
|
||||
$addresses = $node->getAlternateUriSet();
|
||||
$addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl();
|
||||
unset($requestedProperties[$calProp]);
|
||||
$returnedProperties[200][$calProp] = new Sabre_DAV_Property_HrefList($addresses, false);
|
||||
|
||||
}
|
||||
|
||||
// These two properties are shortcuts for ical to easily find
|
||||
// other principals this principal has access to.
|
||||
$propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for';
|
||||
$propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for';
|
||||
if (in_array($propRead,$requestedProperties) || in_array($propWrite,$requestedProperties)) {
|
||||
|
||||
$membership = $node->getGroupMembership();
|
||||
$readList = array();
|
||||
$writeList = array();
|
||||
|
||||
foreach($membership as $group) {
|
||||
|
||||
$groupNode = $this->server->tree->getNodeForPath($group);
|
||||
|
||||
// If the node is either ap proxy-read or proxy-write
|
||||
// group, we grab the parent principal and add it to the
|
||||
// list.
|
||||
if ($groupNode instanceof Sabre_CalDAV_Principal_ProxyRead) {
|
||||
list($readList[]) = Sabre_DAV_URLUtil::splitPath($group);
|
||||
}
|
||||
if ($groupNode instanceof Sabre_CalDAV_Principal_ProxyWrite) {
|
||||
list($writeList[]) = Sabre_DAV_URLUtil::splitPath($group);
|
||||
}
|
||||
|
||||
}
|
||||
if (in_array($propRead,$requestedProperties)) {
|
||||
unset($requestedProperties[$propRead]);
|
||||
$returnedProperties[200][$propRead] = new Sabre_DAV_Property_HrefList($readList);
|
||||
}
|
||||
if (in_array($propWrite,$requestedProperties)) {
|
||||
unset($requestedProperties[$propWrite]);
|
||||
$returnedProperties[200][$propWrite] = new Sabre_DAV_Property_HrefList($writeList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // instanceof IPrincipal
|
||||
|
||||
|
||||
if ($node instanceof Sabre_CalDAV_ICalendarObject) {
|
||||
// The calendar-data property is not supposed to be a 'real'
|
||||
// property, but in large chunks of the spec it does act as such.
|
||||
// Therefore we simply expose it as a property.
|
||||
$calDataProp = '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}calendar-data';
|
||||
if (in_array($calDataProp, $requestedProperties)) {
|
||||
unset($requestedProperties[$calDataProp]);
|
||||
$val = $node->get();
|
||||
if (is_resource($val))
|
||||
$val = stream_get_contents($val);
|
||||
|
||||
// Taking out \r to not screw up the xml output
|
||||
$returnedProperties[200][$calDataProp] = str_replace("\r","", $val);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function handles the calendar-multiget REPORT.
|
||||
*
|
||||
* This report is used by the client to fetch the content of a series
|
||||
* of urls. Effectively avoiding a lot of redundant requests.
|
||||
*
|
||||
* @param DOMNode $dom
|
||||
* @return void
|
||||
*/
|
||||
public function calendarMultiGetReport($dom) {
|
||||
|
||||
$properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
|
||||
$hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href');
|
||||
|
||||
$xpath = new DOMXPath($dom);
|
||||
$xpath->registerNameSpace('cal',Sabre_CalDAV_Plugin::NS_CALDAV);
|
||||
$xpath->registerNameSpace('dav','urn:DAV');
|
||||
|
||||
$expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand');
|
||||
if ($expand->length>0) {
|
||||
$expandElem = $expand->item(0);
|
||||
$start = $expandElem->getAttribute('start');
|
||||
$end = $expandElem->getAttribute('end');
|
||||
if(!$start || !$end) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The "start" and "end" attributes are required for the CALDAV:expand element');
|
||||
}
|
||||
$start = Sabre_VObject_DateTimeParser::parseDateTime($start);
|
||||
$end = Sabre_VObject_DateTimeParser::parseDateTime($end);
|
||||
|
||||
if ($end <= $start) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the expand element.');
|
||||
}
|
||||
|
||||
$expand = true;
|
||||
|
||||
} else {
|
||||
|
||||
$expand = false;
|
||||
|
||||
}
|
||||
|
||||
foreach($hrefElems as $elem) {
|
||||
$uri = $this->server->calculateUri($elem->nodeValue);
|
||||
list($objProps) = $this->server->getPropertiesForPath($uri,$properties);
|
||||
|
||||
if ($expand && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
|
||||
$vObject = Sabre_VObject_Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);
|
||||
$vObject->expand($start, $end);
|
||||
$objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
|
||||
}
|
||||
|
||||
$propertyList[]=$objProps;
|
||||
|
||||
}
|
||||
|
||||
$this->server->httpResponse->sendStatus(207);
|
||||
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
|
||||
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function handles the calendar-query REPORT
|
||||
*
|
||||
* This report is used by clients to request calendar objects based on
|
||||
* complex conditions.
|
||||
*
|
||||
* @param DOMNode $dom
|
||||
* @return void
|
||||
*/
|
||||
public function calendarQueryReport($dom) {
|
||||
|
||||
$parser = new Sabre_CalDAV_CalendarQueryParser($dom);
|
||||
$parser->parse();
|
||||
|
||||
$node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
|
||||
$depth = $this->server->getHTTPDepth(0);
|
||||
|
||||
// The default result is an empty array
|
||||
$result = array();
|
||||
|
||||
// The calendarobject was requested directly. In this case we handle
|
||||
// this locally.
|
||||
if ($depth == 0 && $node instanceof Sabre_CalDAV_ICalendarObject) {
|
||||
|
||||
$requestedCalendarData = true;
|
||||
$requestedProperties = $parser->requestedProperties;
|
||||
|
||||
if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {
|
||||
|
||||
// We always retrieve calendar-data, as we need it for filtering.
|
||||
$requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';
|
||||
|
||||
// If calendar-data wasn't explicitly requested, we need to remove
|
||||
// it after processing.
|
||||
$requestedCalendarData = false;
|
||||
}
|
||||
|
||||
$properties = $this->server->getPropertiesForPath(
|
||||
$this->server->getRequestUri(),
|
||||
$requestedProperties,
|
||||
0
|
||||
);
|
||||
|
||||
// This array should have only 1 element, the first calendar
|
||||
// object.
|
||||
$properties = current($properties);
|
||||
|
||||
// If there wasn't any calendar-data returned somehow, we ignore
|
||||
// this.
|
||||
if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
|
||||
|
||||
$validator = new Sabre_CalDAV_CalendarQueryValidator();
|
||||
$vObject = Sabre_VObject_Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
|
||||
if ($validator->validate($vObject,$parser->filters)) {
|
||||
|
||||
// If the client didn't require the calendar-data property,
|
||||
// we won't give it back.
|
||||
if (!$requestedCalendarData) {
|
||||
unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
|
||||
} else {
|
||||
if ($parser->expand) {
|
||||
$vObject->expand($parser->expand['start'], $parser->expand['end']);
|
||||
$properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
|
||||
}
|
||||
}
|
||||
|
||||
$result = array($properties);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// If we're dealing with a calendar, the calendar itself is responsible
|
||||
// for the calendar-query.
|
||||
if ($node instanceof Sabre_CalDAV_ICalendar && $depth = 1) {
|
||||
|
||||
$nodePaths = $node->calendarQuery($parser->filters);
|
||||
|
||||
foreach($nodePaths as $path) {
|
||||
|
||||
list($properties) =
|
||||
$this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $parser->requestedProperties);
|
||||
|
||||
if ($parser->expand) {
|
||||
// We need to do some post-processing
|
||||
$vObject = Sabre_VObject_Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
|
||||
$vObject->expand($parser->expand['start'], $parser->expand['end']);
|
||||
$properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
|
||||
}
|
||||
|
||||
$result[] = $properties;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->server->httpResponse->sendStatus(207);
|
||||
$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
|
||||
$this->server->httpResponse->sendBody($this->server->generateMultiStatus($result));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for parsing the request and generating the
|
||||
* response for the CALDAV:free-busy-query REPORT.
|
||||
*
|
||||
* @param DOMNode $dom
|
||||
* @return void
|
||||
*/
|
||||
protected function freeBusyQueryReport(DOMNode $dom) {
|
||||
|
||||
$start = null;
|
||||
$end = null;
|
||||
|
||||
foreach($dom->firstChild->childNodes as $childNode) {
|
||||
|
||||
$clark = Sabre_DAV_XMLUtil::toClarkNotation($childNode);
|
||||
if ($clark == '{' . self::NS_CALDAV . '}time-range') {
|
||||
$start = $childNode->getAttribute('start');
|
||||
$end = $childNode->getAttribute('end');
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if ($start) {
|
||||
$start = Sabre_VObject_DateTimeParser::parseDateTime($start);
|
||||
}
|
||||
if ($end) {
|
||||
$end = Sabre_VObject_DateTimeParser::parseDateTime($end);
|
||||
}
|
||||
|
||||
if (!$start && !$end) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The freebusy report must have a time-range filter');
|
||||
}
|
||||
$acl = $this->server->getPlugin('acl');
|
||||
|
||||
if (!$acl) {
|
||||
throw new Sabre_DAV_Exception('The ACL plugin must be loaded for free-busy queries to work');
|
||||
}
|
||||
$uri = $this->server->getRequestUri();
|
||||
$acl->checkPrivileges($uri,'{' . self::NS_CALDAV . '}read-free-busy');
|
||||
|
||||
$calendar = $this->server->tree->getNodeForPath($uri);
|
||||
if (!$calendar instanceof Sabre_CalDAV_ICalendar) {
|
||||
throw new Sabre_DAV_Exception_NotImplemented('The free-busy-query REPORT is only implemented on calendars');
|
||||
}
|
||||
|
||||
$objects = array_map(function($child) {
|
||||
$obj = $child->get();
|
||||
if (is_resource($obj)) {
|
||||
$obj = stream_get_contents($obj);
|
||||
}
|
||||
return $obj;
|
||||
}, $calendar->getChildren());
|
||||
|
||||
$generator = new Sabre_VObject_FreeBusyGenerator();
|
||||
$generator->setObjects($objects);
|
||||
$generator->setTimeRange($start, $end);
|
||||
$result = $generator->getResult();
|
||||
$result = $result->serialize();
|
||||
|
||||
$this->server->httpResponse->sendStatus(200);
|
||||
$this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
|
||||
$this->server->httpResponse->setHeader('Content-Length', strlen($result));
|
||||
$this->server->httpResponse->sendBody($result);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is triggered before a file gets updated with new content.
|
||||
*
|
||||
* This plugin uses this method to ensure that CalDAV objects receive
|
||||
* valid calendar data.
|
||||
*
|
||||
* @param string $path
|
||||
* @param Sabre_DAV_IFile $node
|
||||
* @param resource $data
|
||||
* @return void
|
||||
*/
|
||||
public function beforeWriteContent($path, Sabre_DAV_IFile $node, &$data) {
|
||||
|
||||
if (!$node instanceof Sabre_CalDAV_ICalendarObject)
|
||||
return;
|
||||
|
||||
$this->validateICalendar($data);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is triggered before a new file is created.
|
||||
*
|
||||
* This plugin uses this method to ensure that newly created calendar
|
||||
* objects contain valid calendar data.
|
||||
*
|
||||
* @param string $path
|
||||
* @param resource $data
|
||||
* @param Sabre_DAV_ICollection $parentNode
|
||||
* @return void
|
||||
*/
|
||||
public function beforeCreateFile($path, &$data, Sabre_DAV_ICollection $parentNode) {
|
||||
|
||||
if (!$parentNode instanceof Sabre_CalDAV_Calendar)
|
||||
return;
|
||||
|
||||
$this->validateICalendar($data);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the submitted iCalendar data is in fact, valid.
|
||||
*
|
||||
* An exception is thrown if it's not.
|
||||
*
|
||||
* @param resource|string $data
|
||||
* @return void
|
||||
*/
|
||||
protected function validateICalendar(&$data) {
|
||||
|
||||
// If it's a stream, we convert it to a string first.
|
||||
if (is_resource($data)) {
|
||||
$data = stream_get_contents($data);
|
||||
}
|
||||
|
||||
// Converting the data to unicode, if needed.
|
||||
$data = Sabre_DAV_StringUtil::ensureUTF8($data);
|
||||
|
||||
try {
|
||||
|
||||
$vobj = Sabre_VObject_Reader::read($data);
|
||||
|
||||
} catch (Sabre_VObject_ParseException $e) {
|
||||
|
||||
throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
|
||||
|
||||
}
|
||||
|
||||
if ($vobj->name !== 'VCALENDAR') {
|
||||
throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support iCalendar objects.');
|
||||
}
|
||||
|
||||
$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 (!isset($component->UID)) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('Every ' . $component->name . ' component must have an UID');
|
||||
}
|
||||
$foundUID = (string)$component->UID;
|
||||
} else {
|
||||
if ($foundType !== $component->name) {
|
||||
throw new Sabre_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 Sabre_DAV_Exception_BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
|
||||
}
|
||||
}
|
||||
break;
|
||||
default :
|
||||
throw new Sabre_DAV_Exception_BadRequest('You are not allowed to create components of type: ' . $component->name . ' here');
|
||||
|
||||
}
|
||||
}
|
||||
if (!$foundType)
|
||||
throw new Sabre_DAV_Exception_BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles POST requests to the schedule-outbox
|
||||
*
|
||||
* @param Sabre_CalDAV_Schedule_IOutbox $outboxNode
|
||||
* @return void
|
||||
*/
|
||||
public function outboxRequest(Sabre_CalDAV_Schedule_IOutbox $outboxNode) {
|
||||
|
||||
$originator = $this->server->httpRequest->getHeader('Originator');
|
||||
$recipients = $this->server->httpRequest->getHeader('Recipient');
|
||||
|
||||
if (!$originator) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The Originator: header must be specified when making POST requests');
|
||||
}
|
||||
if (!$recipients) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The Recipient: header must be specified when making POST requests');
|
||||
}
|
||||
|
||||
if (!preg_match('/^mailto:(.*)@(.*)$/', $originator)) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('Originator must start with mailto: and must be valid email address');
|
||||
}
|
||||
$originator = substr($originator,7);
|
||||
|
||||
$recipients = explode(',',$recipients);
|
||||
foreach($recipients as $k=>$recipient) {
|
||||
|
||||
$recipient = trim($recipient);
|
||||
if (!preg_match('/^mailto:(.*)@(.*)$/', $recipient)) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('Recipients must start with mailto: and must be valid email address');
|
||||
}
|
||||
$recipient = substr($recipient, 7);
|
||||
$recipients[$k] = $recipient;
|
||||
}
|
||||
|
||||
// We need to make sure that 'originator' matches one of the email
|
||||
// addresses of the selected principal.
|
||||
$principal = $outboxNode->getOwner();
|
||||
$props = $this->server->getProperties($principal,array(
|
||||
'{' . self::NS_CALDAV . '}calendar-user-address-set',
|
||||
));
|
||||
|
||||
$addresses = array();
|
||||
if (isset($props['{' . self::NS_CALDAV . '}calendar-user-address-set'])) {
|
||||
$addresses = $props['{' . self::NS_CALDAV . '}calendar-user-address-set']->getHrefs();
|
||||
}
|
||||
|
||||
if (!in_array('mailto:' . $originator, $addresses)) {
|
||||
throw new Sabre_DAV_Exception_Forbidden('The addresses specified in the Originator header did not match any addresses in the owners calendar-user-address-set header');
|
||||
}
|
||||
|
||||
try {
|
||||
$vObject = Sabre_VObject_Reader::read($this->server->httpRequest->getBody(true));
|
||||
} catch (Sabre_VObject_ParseException $e) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Checking for the object type
|
||||
$componentType = null;
|
||||
foreach($vObject->getComponents() as $component) {
|
||||
if ($component->name !== 'VTIMEZONE') {
|
||||
$componentType = $component->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_null($componentType)) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
|
||||
}
|
||||
|
||||
// Validating the METHOD
|
||||
$method = strtoupper((string)$vObject->METHOD);
|
||||
if (!$method) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('A METHOD property must be specified in iTIP messages');
|
||||
}
|
||||
|
||||
if (in_array($method, array('REQUEST','REPLY','ADD','CANCEL')) && $componentType==='VEVENT') {
|
||||
$this->iMIPMessage($originator, $recipients, $vObject, $principal);
|
||||
$this->server->httpResponse->sendStatus(200);
|
||||
$this->server->httpResponse->sendBody('Messages sent');
|
||||
} else {
|
||||
throw new Sabre_DAV_Exception_NotImplemented('This iTIP method is currently not implemented');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an iMIP message by email.
|
||||
*
|
||||
* @param string $originator
|
||||
* @param array $recipients
|
||||
* @param Sabre_VObject_Component $vObject
|
||||
* @param string $principal Principal url
|
||||
* @return void
|
||||
*/
|
||||
protected function iMIPMessage($originator, array $recipients, Sabre_VObject_Component $vObject, $principal) {
|
||||
|
||||
if (!$this->imipHandler) {
|
||||
throw new Sabre_DAV_Exception_NotImplemented('No iMIP handler is setup on this server.');
|
||||
}
|
||||
$this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to generate HTML output for the
|
||||
* Sabre_DAV_Browser_Plugin. This allows us to generate an interface users
|
||||
* can use to create new calendars.
|
||||
*
|
||||
* @param Sabre_DAV_INode $node
|
||||
* @param string $output
|
||||
* @return bool
|
||||
*/
|
||||
public function htmlActionsPanel(Sabre_DAV_INode $node, &$output) {
|
||||
|
||||
if (!$node instanceof Sabre_CalDAV_UserCalendars)
|
||||
return;
|
||||
|
||||
$output.= '<tr><td colspan="2"><form method="post" action="">
|
||||
<h3>Create new calendar</h3>
|
||||
<input type="hidden" name="sabreAction" value="mkcalendar" />
|
||||
<label>Name (uri):</label> <input type="text" name="name" /><br />
|
||||
<label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
|
||||
<input type="submit" value="create" />
|
||||
</form>
|
||||
</td></tr>';
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows us to intercept the 'mkcalendar' sabreAction. This
|
||||
* action enables the user to create new calendars from the browser plugin.
|
||||
*
|
||||
* @param string $uri
|
||||
* @param string $action
|
||||
* @param array $postVars
|
||||
* @return bool
|
||||
*/
|
||||
public function browserPostAction($uri, $action, array $postVars) {
|
||||
|
||||
if ($action!=='mkcalendar')
|
||||
return;
|
||||
|
||||
$resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar');
|
||||
$properties = array();
|
||||
if (isset($postVars['{DAV:}displayname'])) {
|
||||
$properties['{DAV:}displayname'] = $postVars['{DAV:}displayname'];
|
||||
}
|
||||
$this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Principal collection
|
||||
*
|
||||
* This is an alternative collection to the standard ACL principal collection.
|
||||
* This collection adds support for the calendar-proxy-read and
|
||||
* calendar-proxy-write sub-principals, as defined by the caldav-proxy
|
||||
* specification.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Principal_Collection extends Sabre_DAVACL_AbstractPrincipalCollection {
|
||||
|
||||
/**
|
||||
* Returns a child object based on principal information
|
||||
*
|
||||
* @param array $principalInfo
|
||||
* @return Sabre_CalDAV_Principal_User
|
||||
*/
|
||||
public function getChildForPrincipal(array $principalInfo) {
|
||||
|
||||
return new Sabre_CalDAV_Principal_User($this->principalBackend, $principalInfo);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* ProxyRead principal
|
||||
*
|
||||
* This class represents a principal group, hosted under the main principal.
|
||||
* This is needed to implement 'Calendar delegation' support. This class is
|
||||
* instantiated by Sabre_CalDAV_Principal_User.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Principal_ProxyRead implements Sabre_DAVACL_IPrincipal {
|
||||
|
||||
/**
|
||||
* Principal information from the parent principal.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $principalInfo;
|
||||
|
||||
/**
|
||||
* Principal backend
|
||||
*
|
||||
* @var Sabre_DAVACL_IPrincipalBackend
|
||||
*/
|
||||
protected $principalBackend;
|
||||
|
||||
/**
|
||||
* Creates the object.
|
||||
*
|
||||
* Note that you MUST supply the parent principal information.
|
||||
*
|
||||
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
|
||||
* @param array $principalInfo
|
||||
*/
|
||||
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, array $principalInfo) {
|
||||
|
||||
$this->principalInfo = $principalInfo;
|
||||
$this->principalBackend = $principalBackend;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this principals name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
|
||||
return 'calendar-proxy-read';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification time
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function getLastModified() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current node
|
||||
*
|
||||
* @throws Sabre_DAV_Exception_Forbidden
|
||||
* @return void
|
||||
*/
|
||||
public function delete() {
|
||||
|
||||
throw new Sabre_DAV_Exception_Forbidden('Permission denied to delete node');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the node
|
||||
*
|
||||
* @throws Sabre_DAV_Exception_Forbidden
|
||||
* @param string $name The new name
|
||||
* @return void
|
||||
*/
|
||||
public function setName($name) {
|
||||
|
||||
throw new Sabre_DAV_Exception_Forbidden('Permission denied to rename file');
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of alternative urls for a principal
|
||||
*
|
||||
* This can for example be an email address, or ldap url.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAlternateUriSet() {
|
||||
|
||||
return array();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full principal url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrincipalUrl() {
|
||||
|
||||
return $this->principalInfo['uri'] . '/' . $this->getName();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of group members
|
||||
*
|
||||
* If this principal is a group, this function should return
|
||||
* all member principal uri's for the group.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGroupMemberSet() {
|
||||
|
||||
return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of groups this principal is member of
|
||||
*
|
||||
* If this principal is a member of a (list of) groups, this function
|
||||
* should return a list of principal uri's for it's members.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGroupMembership() {
|
||||
|
||||
return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a list of group members
|
||||
*
|
||||
* If this principal is a group, this method sets all the group members.
|
||||
* The list of members is always overwritten, never appended to.
|
||||
*
|
||||
* This method should throw an exception if the members could not be set.
|
||||
*
|
||||
* @param array $principals
|
||||
* @return void
|
||||
*/
|
||||
public function setGroupMemberSet(array $principals) {
|
||||
|
||||
$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the displayname
|
||||
*
|
||||
* This should be a human readable name for the principal.
|
||||
* If none is available, return the nodename.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDisplayName() {
|
||||
|
||||
return $this->getName();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* ProxyWrite principal
|
||||
*
|
||||
* This class represents a principal group, hosted under the main principal.
|
||||
* This is needed to implement 'Calendar delegation' support. This class is
|
||||
* instantiated by Sabre_CalDAV_Principal_User.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Principal_ProxyWrite implements Sabre_DAVACL_IPrincipal {
|
||||
|
||||
/**
|
||||
* Parent principal information
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $principalInfo;
|
||||
|
||||
/**
|
||||
* Principal Backend
|
||||
*
|
||||
* @var Sabre_DAVACL_IPrincipalBackend
|
||||
*/
|
||||
protected $principalBackend;
|
||||
|
||||
/**
|
||||
* Creates the object
|
||||
*
|
||||
* Note that you MUST supply the parent principal information.
|
||||
*
|
||||
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
|
||||
* @param array $principalInfo
|
||||
*/
|
||||
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, array $principalInfo) {
|
||||
|
||||
$this->principalInfo = $principalInfo;
|
||||
$this->principalBackend = $principalBackend;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this principals name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
|
||||
return 'calendar-proxy-write';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification time
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function getLastModified() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current node
|
||||
*
|
||||
* @throws Sabre_DAV_Exception_Forbidden
|
||||
* @return void
|
||||
*/
|
||||
public function delete() {
|
||||
|
||||
throw new Sabre_DAV_Exception_Forbidden('Permission denied to delete node');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the node
|
||||
*
|
||||
* @throws Sabre_DAV_Exception_Forbidden
|
||||
* @param string $name The new name
|
||||
* @return void
|
||||
*/
|
||||
public function setName($name) {
|
||||
|
||||
throw new Sabre_DAV_Exception_Forbidden('Permission denied to rename file');
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of alternative urls for a principal
|
||||
*
|
||||
* This can for example be an email address, or ldap url.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAlternateUriSet() {
|
||||
|
||||
return array();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full principal url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrincipalUrl() {
|
||||
|
||||
return $this->principalInfo['uri'] . '/' . $this->getName();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of group members
|
||||
*
|
||||
* If this principal is a group, this function should return
|
||||
* all member principal uri's for the group.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGroupMemberSet() {
|
||||
|
||||
return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of groups this principal is member of
|
||||
*
|
||||
* If this principal is a member of a (list of) groups, this function
|
||||
* should return a list of principal uri's for it's members.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGroupMembership() {
|
||||
|
||||
return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a list of group members
|
||||
*
|
||||
* If this principal is a group, this method sets all the group members.
|
||||
* The list of members is always overwritten, never appended to.
|
||||
*
|
||||
* This method should throw an exception if the members could not be set.
|
||||
*
|
||||
* @param array $principals
|
||||
* @return void
|
||||
*/
|
||||
public function setGroupMemberSet(array $principals) {
|
||||
|
||||
$this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the displayname
|
||||
*
|
||||
* This should be a human readable name for the principal.
|
||||
* If none is available, return the nodename.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDisplayName() {
|
||||
|
||||
return $this->getName();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CalDAV principal
|
||||
*
|
||||
* This is a standard user-principal for CalDAV. This principal is also a
|
||||
* collection and returns the caldav-proxy-read and caldav-proxy-write child
|
||||
* principals.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Principal_User extends Sabre_DAVACL_Principal implements Sabre_DAV_ICollection {
|
||||
|
||||
/**
|
||||
* Creates a new file in the directory
|
||||
*
|
||||
* @param string $name Name of the file
|
||||
* @param resource $data Initial payload, passed as a readable stream resource.
|
||||
* @throws Sabre_DAV_Exception_Forbidden
|
||||
* @return void
|
||||
*/
|
||||
public function createFile($name, $data = null) {
|
||||
|
||||
throw new Sabre_DAV_Exception_Forbidden('Permission denied to create file (filename ' . $name . ')');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new subdirectory
|
||||
*
|
||||
* @param string $name
|
||||
* @throws Sabre_DAV_Exception_Forbidden
|
||||
* @return void
|
||||
*/
|
||||
public function createDirectory($name) {
|
||||
|
||||
throw new Sabre_DAV_Exception_Forbidden('Permission denied to create directory');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific child node, referenced by its name
|
||||
*
|
||||
* @param string $name
|
||||
* @return Sabre_DAV_INode
|
||||
*/
|
||||
public function getChild($name) {
|
||||
|
||||
$principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name);
|
||||
if (!$principal) {
|
||||
throw new Sabre_DAV_Exception_NotFound('Node with name ' . $name . ' was not found');
|
||||
}
|
||||
if ($name === 'calendar-proxy-read')
|
||||
return new Sabre_CalDAV_Principal_ProxyRead($this->principalBackend, $this->principalProperties);
|
||||
|
||||
if ($name === 'calendar-proxy-write')
|
||||
return new Sabre_CalDAV_Principal_ProxyWrite($this->principalBackend, $this->principalProperties);
|
||||
|
||||
throw new Sabre_DAV_Exception_NotFound('Node with name ' . $name . ' was not found');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all the child nodes
|
||||
*
|
||||
* @return Sabre_DAV_INode[]
|
||||
*/
|
||||
public function getChildren() {
|
||||
|
||||
$r = array();
|
||||
if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) {
|
||||
$r[] = new Sabre_CalDAV_Principal_ProxyRead($this->principalBackend, $this->principalProperties);
|
||||
}
|
||||
if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) {
|
||||
$r[] = new Sabre_CalDAV_Principal_ProxyWrite($this->principalBackend, $this->principalProperties);
|
||||
}
|
||||
|
||||
return $r;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the child node exists
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function childExists($name) {
|
||||
|
||||
try {
|
||||
$this->getChild($name);
|
||||
return true;
|
||||
} catch (Sabre_DAV_Exception_NotFound $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of ACE's for this node.
|
||||
*
|
||||
* Each ACE has the following properties:
|
||||
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
|
||||
* currently the only supported privileges
|
||||
* * 'principal', a url to the principal who owns the node
|
||||
* * 'protected' (optional), indicating that this ACE is not allowed to
|
||||
* be updated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getACL() {
|
||||
|
||||
$acl = parent::getACL();
|
||||
$acl[] = array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
);
|
||||
$acl[] = array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
);
|
||||
return $acl;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Supported component set property
|
||||
*
|
||||
* This property is a representation of the supported-calendar_component-set
|
||||
* property in the CalDAV namespace. It simply requires an array of components,
|
||||
* such as VEVENT, VTODO
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Property_SupportedCalendarComponentSet extends Sabre_DAV_Property {
|
||||
|
||||
/**
|
||||
* List of supported components, such as "VEVENT, VTODO"
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $components;
|
||||
|
||||
/**
|
||||
* Creates the property
|
||||
*
|
||||
* @param array $components
|
||||
*/
|
||||
public function __construct(array $components) {
|
||||
|
||||
$this->components = $components;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of supported components
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValue() {
|
||||
|
||||
return $this->components;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the property in a DOMDocument
|
||||
*
|
||||
* @param Sabre_DAV_Server $server
|
||||
* @param DOMElement $node
|
||||
* @return void
|
||||
*/
|
||||
public function serialize(Sabre_DAV_Server $server,DOMElement $node) {
|
||||
|
||||
$doc = $node->ownerDocument;
|
||||
foreach($this->components as $component) {
|
||||
|
||||
$xcomp = $doc->createElement('cal:comp');
|
||||
$xcomp->setAttribute('name',$component);
|
||||
$node->appendChild($xcomp);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserializes the DOMElement back into a Property class.
|
||||
*
|
||||
* @param DOMElement $node
|
||||
* @return Sabre_CalDAV_Property_SupportedCalendarComponentSet
|
||||
*/
|
||||
static function unserialize(DOMElement $node) {
|
||||
|
||||
$components = array();
|
||||
foreach($node->childNodes as $childNode) {
|
||||
if (Sabre_DAV_XMLUtil::toClarkNotation($childNode)==='{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}comp') {
|
||||
$components[] = $childNode->getAttribute('name');
|
||||
}
|
||||
}
|
||||
return new self($components);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Supported-calendar-data property
|
||||
*
|
||||
* This property is a representation of the supported-calendar-data property
|
||||
* in the CalDAV namespace. SabreDAV only has support for text/calendar;2.0
|
||||
* so the value is currently hardcoded.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Property_SupportedCalendarData extends Sabre_DAV_Property {
|
||||
|
||||
/**
|
||||
* Serializes the property in a DOMDocument
|
||||
*
|
||||
* @param Sabre_DAV_Server $server
|
||||
* @param DOMElement $node
|
||||
* @return void
|
||||
*/
|
||||
public function serialize(Sabre_DAV_Server $server,DOMElement $node) {
|
||||
|
||||
$doc = $node->ownerDocument;
|
||||
|
||||
$prefix = isset($server->xmlNamespaces[Sabre_CalDAV_Plugin::NS_CALDAV])?$server->xmlNamespaces[Sabre_CalDAV_Plugin::NS_CALDAV]:'cal';
|
||||
|
||||
$caldata = $doc->createElement($prefix . ':calendar-data');
|
||||
$caldata->setAttribute('content-type','text/calendar');
|
||||
$caldata->setAttribute('version','2.0');
|
||||
|
||||
$node->appendChild($caldata);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* supported-collation-set property
|
||||
*
|
||||
* This property is a representation of the supported-collation-set property
|
||||
* in the CalDAV namespace.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Property_SupportedCollationSet extends Sabre_DAV_Property {
|
||||
|
||||
/**
|
||||
* Serializes the property in a DOM document
|
||||
*
|
||||
* @param Sabre_DAV_Server $server
|
||||
* @param DOMElement $node
|
||||
* @return void
|
||||
*/
|
||||
public function serialize(Sabre_DAV_Server $server,DOMElement $node) {
|
||||
|
||||
$doc = $node->ownerDocument;
|
||||
|
||||
$prefix = $node->lookupPrefix('urn:ietf:params:xml:ns:caldav');
|
||||
if (!$prefix) $prefix = 'cal';
|
||||
|
||||
$node->appendChild(
|
||||
$doc->createElement($prefix . ':supported-collation','i;ascii-casemap')
|
||||
);
|
||||
$node->appendChild(
|
||||
$doc->createElement($prefix . ':supported-collation','i;octet')
|
||||
);
|
||||
$node->appendChild(
|
||||
$doc->createElement($prefix . ':supported-collation','i;unicode-casemap')
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* iMIP handler.
|
||||
*
|
||||
* This class is responsible for sending out iMIP messages. iMIP is the
|
||||
* email-based transport for iTIP. iTIP deals with scheduling operations for
|
||||
* iCalendar objects.
|
||||
*
|
||||
* If you want to customize the email that gets sent out, you can do so by
|
||||
* extending this class and overriding the sendMessage method.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Schedule_IMip {
|
||||
|
||||
/**
|
||||
* Email address used in From: header.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $senderEmail;
|
||||
|
||||
/**
|
||||
* Creates the email handler.
|
||||
*
|
||||
* @param string $senderEmail. The 'senderEmail' is the email that shows up
|
||||
* in the 'From:' address. This should
|
||||
* generally be some kind of no-reply email
|
||||
* address you own.
|
||||
*/
|
||||
public function __construct($senderEmail) {
|
||||
|
||||
$this->senderEmail = $senderEmail;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends one or more iTip messages through email.
|
||||
*
|
||||
* @param string $originator Originator Email
|
||||
* @param array $recipients Array of email addresses
|
||||
* @param Sabre_VObject_Component $vObject
|
||||
* @param string $principal Principal Url of the originator
|
||||
* @return void
|
||||
*/
|
||||
public function sendMessage($originator, array $recipients, Sabre_VObject_Component $vObject, $principal) {
|
||||
|
||||
foreach($recipients as $recipient) {
|
||||
|
||||
$to = $recipient;
|
||||
$replyTo = $originator;
|
||||
$subject = 'SabreDAV iTIP message';
|
||||
|
||||
switch(strtoupper($vObject->METHOD)) {
|
||||
case 'REPLY' :
|
||||
$subject = 'Response for: ' . $vObject->VEVENT->SUMMARY;
|
||||
break;
|
||||
case 'REQUEST' :
|
||||
$subject = 'Invitation for: ' .$vObject->VEVENT->SUMMARY;
|
||||
break;
|
||||
case 'CANCEL' :
|
||||
$subject = 'Cancelled event: ' . $vObject->VEVENT->SUMMARY;
|
||||
break;
|
||||
}
|
||||
|
||||
$headers = array();
|
||||
$headers[] = 'Reply-To: ' . $replyTo;
|
||||
$headers[] = 'From: ' . $this->senderEmail;
|
||||
$headers[] = 'Content-Type: text/calendar; method=' . (string)$vObject->method . '; charset=utf-8';
|
||||
if (Sabre_DAV_Server::$exposeVersion) {
|
||||
$headers[] = 'X-Sabre-Version: ' . Sabre_DAV_Version::VERSION . '-' . Sabre_DAV_Version::STABILITY;
|
||||
}
|
||||
|
||||
$vcalBody = $vObject->serialize();
|
||||
|
||||
$this->mail($to, $subject, $vcalBody, $headers);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is reponsible for sending the actual email.
|
||||
*
|
||||
* @param string $to Recipient email address
|
||||
* @param string $subject Subject of the email
|
||||
* @param string $body iCalendar body
|
||||
* @param array $headers List of headers
|
||||
* @return void
|
||||
*/
|
||||
protected function mail($to, $subject, $body, array $headers) {
|
||||
|
||||
mail($to, $subject, $body, implode("\r\n", $headers));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implement this interface to have a node be recognized as a CalDAV scheduling
|
||||
* outbox.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
interface Sabre_CalDAV_Schedule_IOutbox extends Sabre_DAV_ICollection, Sabre_DAVACL_IACL {
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* The CalDAV scheduling outbox
|
||||
*
|
||||
* The outbox is mainly used as an endpoint in the tree for a client to do
|
||||
* free-busy requests. This functionality is completely handled by the
|
||||
* Scheduling plugin, so this object is actually mostly static.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Schedule_Outbox extends Sabre_DAV_Collection implements Sabre_CalDAV_Schedule_IOutbox {
|
||||
|
||||
/**
|
||||
* The principal Uri
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $principalUri;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $principalUri
|
||||
*/
|
||||
public function __construct($principalUri) {
|
||||
|
||||
$this->principalUri = $principalUri;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the node.
|
||||
*
|
||||
* This is used to generate the url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
|
||||
return 'outbox';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all the child nodes
|
||||
*
|
||||
* @return Sabre_DAV_INode[]
|
||||
*/
|
||||
public function getChildren() {
|
||||
|
||||
return array();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOwner() {
|
||||
|
||||
return $this->principalUri;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a group principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getGroup() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of ACE's for this node.
|
||||
*
|
||||
* Each ACE has the following properties:
|
||||
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
|
||||
* currently the only supported privileges
|
||||
* * 'principal', a url to the principal who owns the node
|
||||
* * 'protected' (optional), indicating that this ACE is not allowed to
|
||||
* be updated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getACL() {
|
||||
|
||||
return array(
|
||||
array(
|
||||
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-query-freebusy',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ACL
|
||||
*
|
||||
* This method will receive a list of new ACE's.
|
||||
*
|
||||
* @param array $acl
|
||||
* @return void
|
||||
*/
|
||||
public function setACL(array $acl) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('You\'re not allowed to update the ACL');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of supported privileges for this node.
|
||||
*
|
||||
* The returned data structure is a list of nested privileges.
|
||||
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
|
||||
* standard structure.
|
||||
*
|
||||
* If null is returned from this method, the default privilege set is used,
|
||||
* which is fine for most common usecases.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getSupportedPrivilegeSet() {
|
||||
|
||||
$default = Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet();
|
||||
$default['aggregates'][] = array(
|
||||
'privilege' => '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}schedule-query-freebusy',
|
||||
);
|
||||
|
||||
return $default;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CalDAV server
|
||||
*
|
||||
* Deprecated! Warning: This class is now officially deprecated
|
||||
*
|
||||
* This script is a convenience script. It quickly sets up a WebDAV server
|
||||
* with caldav and ACL support, and it creates the root 'principals' and
|
||||
* 'calendars' collections.
|
||||
*
|
||||
* Note that if you plan to do anything moderately complex, you are advised to
|
||||
* not subclass this server, but use Sabre_DAV_Server directly instead. This
|
||||
* class is nothing more than an 'easy setup'.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @deprecated Don't use this class anymore, it will be removed in version 1.7.
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Server extends Sabre_DAV_Server {
|
||||
|
||||
/**
|
||||
* The authentication realm
|
||||
*
|
||||
* Note that if this changes, the hashes in the auth backend must also
|
||||
* be recalculated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $authRealm = 'SabreDAV';
|
||||
|
||||
/**
|
||||
* Sets up the object. A PDO object must be passed to setup all the backends.
|
||||
*
|
||||
* @param PDO $pdo
|
||||
*/
|
||||
public function __construct(PDO $pdo) {
|
||||
|
||||
/* Backends */
|
||||
$authBackend = new Sabre_DAV_Auth_Backend_PDO($pdo);
|
||||
$calendarBackend = new Sabre_CalDAV_Backend_PDO($pdo);
|
||||
$principalBackend = new Sabre_DAVACL_PrincipalBackend_PDO($pdo);
|
||||
|
||||
/* Directory structure */
|
||||
$tree = array(
|
||||
new Sabre_CalDAV_Principal_Collection($principalBackend),
|
||||
new Sabre_CalDAV_CalendarRootNode($principalBackend, $calendarBackend),
|
||||
);
|
||||
|
||||
/* Initializing server */
|
||||
parent::__construct($tree);
|
||||
|
||||
/* Server Plugins */
|
||||
$authPlugin = new Sabre_DAV_Auth_Plugin($authBackend,$this->authRealm);
|
||||
$this->addPlugin($authPlugin);
|
||||
|
||||
$aclPlugin = new Sabre_DAVACL_Plugin();
|
||||
$this->addPlugin($aclPlugin);
|
||||
|
||||
$caldavPlugin = new Sabre_CalDAV_Plugin();
|
||||
$this->addPlugin($caldavPlugin);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* The UserCalenders class contains all calendars associated to one user
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_UserCalendars implements Sabre_DAV_IExtendedCollection, Sabre_DAVACL_IACL {
|
||||
|
||||
/**
|
||||
* Principal backend
|
||||
*
|
||||
* @var Sabre_DAVACL_IPrincipalBackend
|
||||
*/
|
||||
protected $principalBackend;
|
||||
|
||||
/**
|
||||
* CalDAV backend
|
||||
*
|
||||
* @var Sabre_CalDAV_Backend_Abstract
|
||||
*/
|
||||
protected $caldavBackend;
|
||||
|
||||
/**
|
||||
* Principal information
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $principalInfo;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
|
||||
* @param Sabre_CalDAV_Backend_Abstract $caldavBackend
|
||||
* @param mixed $userUri
|
||||
*/
|
||||
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend, Sabre_CalDAV_Backend_Abstract $caldavBackend, $userUri) {
|
||||
|
||||
$this->principalBackend = $principalBackend;
|
||||
$this->caldavBackend = $caldavBackend;
|
||||
$this->principalInfo = $principalBackend->getPrincipalByPath($userUri);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
|
||||
list(,$name) = Sabre_DAV_URLUtil::splitPath($this->principalInfo['uri']);
|
||||
return $name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the name of this object
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function setName($name) {
|
||||
|
||||
throw new Sabre_DAV_Exception_Forbidden();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete() {
|
||||
|
||||
throw new Sabre_DAV_Exception_Forbidden();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification date
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModified() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file under this object.
|
||||
*
|
||||
* This is currently not allowed
|
||||
*
|
||||
* @param string $filename
|
||||
* @param resource $data
|
||||
* @return void
|
||||
*/
|
||||
public function createFile($filename, $data=null) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Creating new files in this collection is not supported');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new directory under this object.
|
||||
*
|
||||
* This is currently not allowed.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return void
|
||||
*/
|
||||
public function createDirectory($filename) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Creating new collections in this collection is not supported');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single calendar, by name
|
||||
*
|
||||
* @param string $name
|
||||
* @todo needs optimizing
|
||||
* @return Sabre_CalDAV_Calendar
|
||||
*/
|
||||
public function getChild($name) {
|
||||
|
||||
foreach($this->getChildren() as $child) {
|
||||
if ($name==$child->getName())
|
||||
return $child;
|
||||
|
||||
}
|
||||
throw new Sabre_DAV_Exception_NotFound('Calendar with name \'' . $name . '\' could not be found');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a calendar exists.
|
||||
*
|
||||
* @param string $name
|
||||
* @todo needs optimizing
|
||||
* @return bool
|
||||
*/
|
||||
public function childExists($name) {
|
||||
|
||||
foreach($this->getChildren() as $child) {
|
||||
if ($name==$child->getName())
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of calendars
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildren() {
|
||||
|
||||
$calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
|
||||
$objs = array();
|
||||
foreach($calendars as $calendar) {
|
||||
$objs[] = new Sabre_CalDAV_Calendar($this->principalBackend, $this->caldavBackend, $calendar);
|
||||
}
|
||||
$objs[] = new Sabre_CalDAV_Schedule_Outbox($this->principalInfo['uri']);
|
||||
return $objs;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new calendar
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $resourceType
|
||||
* @param array $properties
|
||||
* @return void
|
||||
*/
|
||||
public function createExtendedCollection($name, array $resourceType, array $properties) {
|
||||
|
||||
if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar',$resourceType) || count($resourceType)!==2) {
|
||||
throw new Sabre_DAV_Exception_InvalidResourceType('Unknown resourceType for this collection');
|
||||
}
|
||||
$this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOwner() {
|
||||
|
||||
return $this->principalInfo['uri'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a group principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getGroup() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of ACE's for this node.
|
||||
*
|
||||
* Each ACE has the following properties:
|
||||
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
|
||||
* currently the only supported privileges
|
||||
* * 'principal', a url to the principal who owns the node
|
||||
* * 'protected' (optional), indicating that this ACE is not allowed to
|
||||
* be updated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getACL() {
|
||||
|
||||
return array(
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalInfo['uri'],
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->principalInfo['uri'],
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read',
|
||||
'protected' => true,
|
||||
),
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ACL
|
||||
*
|
||||
* This method will receive a list of new ACE's.
|
||||
*
|
||||
* @param array $acl
|
||||
* @return void
|
||||
*/
|
||||
public function setACL(array $acl) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of supported privileges for this node.
|
||||
*
|
||||
* The returned data structure is a list of nested privileges.
|
||||
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
|
||||
* standard structure.
|
||||
*
|
||||
* If null is returned from this method, the default privilege set is used,
|
||||
* which is fine for most common usecases.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getSupportedPrivilegeSet() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This class contains the Sabre_CalDAV version constants.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CalDAV_Version {
|
||||
|
||||
/**
|
||||
* Full version number
|
||||
*/
|
||||
const VERSION = '1.7.0';
|
||||
|
||||
/**
|
||||
* Stability : alpha, beta, stable
|
||||
*/
|
||||
const STABILITY = 'alpha';
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Sabre_CalDAV includes file
|
||||
*
|
||||
* Including this file will automatically include all files from the
|
||||
* Sabre_CalDAV package.
|
||||
*
|
||||
* This often allows faster loadtimes, as autoload-speed is often quite slow.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CalDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
|
||||
// Begin includes
|
||||
include __DIR__ . '/Backend/Abstract.php';
|
||||
include __DIR__ . '/Backend/PDO.php';
|
||||
include __DIR__ . '/CalendarQueryParser.php';
|
||||
include __DIR__ . '/CalendarQueryValidator.php';
|
||||
include __DIR__ . '/CalendarRootNode.php';
|
||||
include __DIR__ . '/ICalendar.php';
|
||||
include __DIR__ . '/ICalendarObject.php';
|
||||
include __DIR__ . '/ICSExportPlugin.php';
|
||||
include __DIR__ . '/Plugin.php';
|
||||
include __DIR__ . '/Principal/Collection.php';
|
||||
include __DIR__ . '/Principal/ProxyRead.php';
|
||||
include __DIR__ . '/Principal/ProxyWrite.php';
|
||||
include __DIR__ . '/Principal/User.php';
|
||||
include __DIR__ . '/Property/SupportedCalendarComponentSet.php';
|
||||
include __DIR__ . '/Property/SupportedCalendarData.php';
|
||||
include __DIR__ . '/Property/SupportedCollationSet.php';
|
||||
include __DIR__ . '/Schedule/IMip.php';
|
||||
include __DIR__ . '/Schedule/IOutbox.php';
|
||||
include __DIR__ . '/Schedule/Outbox.php';
|
||||
include __DIR__ . '/Server.php';
|
||||
include __DIR__ . '/UserCalendars.php';
|
||||
include __DIR__ . '/Version.php';
|
||||
include __DIR__ . '/Calendar.php';
|
||||
include __DIR__ . '/CalendarObject.php';
|
||||
// End includes
|
|
@ -0,0 +1,312 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* The AddressBook class represents a CardDAV addressbook, owned by a specific user
|
||||
*
|
||||
* The AddressBook can contain multiple vcards
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CardDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_CardDAV_IAddressBook, Sabre_DAV_IProperties, Sabre_DAVACL_IACL {
|
||||
|
||||
/**
|
||||
* This is an array with addressbook information
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $addressBookInfo;
|
||||
|
||||
/**
|
||||
* CardDAV backend
|
||||
*
|
||||
* @var Sabre_CardDAV_Backend_Abstract
|
||||
*/
|
||||
private $carddavBackend;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Sabre_CardDAV_Backend_Abstract $carddavBackend
|
||||
* @param array $addressBookInfo
|
||||
*/
|
||||
public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend, array $addressBookInfo) {
|
||||
|
||||
$this->carddavBackend = $carddavBackend;
|
||||
$this->addressBookInfo = $addressBookInfo;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the addressbook
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
|
||||
return $this->addressBookInfo['uri'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a card
|
||||
*
|
||||
* @param string $name
|
||||
* @return Sabre_CardDAV_ICard
|
||||
*/
|
||||
public function getChild($name) {
|
||||
|
||||
$obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name);
|
||||
if (!$obj) throw new Sabre_DAV_Exception_NotFound('Card not found');
|
||||
return new Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full list of cards
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildren() {
|
||||
|
||||
$objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
|
||||
$children = array();
|
||||
foreach($objs as $obj) {
|
||||
$children[] = new Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj);
|
||||
}
|
||||
return $children;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new directory
|
||||
*
|
||||
* We actually block this, as subdirectories are not allowed in addressbooks.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function createDirectory($name) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Creating collections in addressbooks is not allowed');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new file
|
||||
*
|
||||
* The contents of the new file must be a valid VCARD.
|
||||
*
|
||||
* This method may return an ETag.
|
||||
*
|
||||
* @param string $name
|
||||
* @param resource $vcardData
|
||||
* @return string|null
|
||||
*/
|
||||
public function createFile($name,$vcardData = null) {
|
||||
|
||||
if (is_resource($vcardData)) {
|
||||
$vcardData = stream_get_contents($vcardData);
|
||||
}
|
||||
// Converting to UTF-8, if needed
|
||||
$vcardData = Sabre_DAV_StringUtil::ensureUTF8($vcardData);
|
||||
|
||||
return $this->carddavBackend->createCard($this->addressBookInfo['id'],$name,$vcardData);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the entire addressbook.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete() {
|
||||
|
||||
$this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the addressbook
|
||||
*
|
||||
* @param string $newName
|
||||
* @return void
|
||||
*/
|
||||
public function setName($newName) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Renaming addressbooks is not yet supported');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification date as a unix timestamp.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getLastModified() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates properties on this node,
|
||||
*
|
||||
* The properties array uses the propertyName in clark-notation as key,
|
||||
* and the array value for the property value. In the case a property
|
||||
* should be deleted, the property value will be null.
|
||||
*
|
||||
* This method must be atomic. If one property cannot be changed, the
|
||||
* entire operation must fail.
|
||||
*
|
||||
* If the operation was successful, true can be returned.
|
||||
* If the operation failed, false can be returned.
|
||||
*
|
||||
* Deletion of a non-existent property is always successful.
|
||||
*
|
||||
* Lastly, it is optional to return detailed information about any
|
||||
* failures. In this case an array should be returned with the following
|
||||
* structure:
|
||||
*
|
||||
* array(
|
||||
* 403 => array(
|
||||
* '{DAV:}displayname' => null,
|
||||
* ),
|
||||
* 424 => array(
|
||||
* '{DAV:}owner' => null,
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* In this example it was forbidden to update {DAV:}displayname.
|
||||
* (403 Forbidden), which in turn also caused {DAV:}owner to fail
|
||||
* (424 Failed Dependency) because the request needs to be atomic.
|
||||
*
|
||||
* @param array $mutations
|
||||
* @return bool|array
|
||||
*/
|
||||
public function updateProperties($mutations) {
|
||||
|
||||
return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $mutations);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of properties for this nodes.
|
||||
*
|
||||
* The properties list is a list of propertynames the client requested,
|
||||
* encoded in clark-notation {xmlnamespace}tagname
|
||||
*
|
||||
* If the array is empty, it means 'all properties' were requested.
|
||||
*
|
||||
* @param array $properties
|
||||
* @return array
|
||||
*/
|
||||
public function getProperties($properties) {
|
||||
|
||||
$response = array();
|
||||
foreach($properties as $propertyName) {
|
||||
|
||||
if (isset($this->addressBookInfo[$propertyName])) {
|
||||
|
||||
$response[$propertyName] = $this->addressBookInfo[$propertyName];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $response;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOwner() {
|
||||
|
||||
return $this->addressBookInfo['principaluri'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a group principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getGroup() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of ACE's for this node.
|
||||
*
|
||||
* Each ACE has the following properties:
|
||||
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
|
||||
* currently the only supported privileges
|
||||
* * 'principal', a url to the principal who owns the node
|
||||
* * 'protected' (optional), indicating that this ACE is not allowed to
|
||||
* be updated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getACL() {
|
||||
|
||||
return array(
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->addressBookInfo['principaluri'],
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->addressBookInfo['principaluri'],
|
||||
'protected' => true,
|
||||
),
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ACL
|
||||
*
|
||||
* This method will receive a list of new ACE's.
|
||||
*
|
||||
* @param array $acl
|
||||
* @return void
|
||||
*/
|
||||
public function setACL(array $acl) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of supported privileges for this node.
|
||||
*
|
||||
* The returned data structure is a list of nested privileges.
|
||||
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
|
||||
* standard structure.
|
||||
*
|
||||
* If null is returned from this method, the default privilege set is used,
|
||||
* which is fine for most common usecases.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getSupportedPrivilegeSet() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Parses the addressbook-query report request body.
|
||||
*
|
||||
* Whoever designed this format, and the CalDAV equivalent even more so,
|
||||
* has no feel for design.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CardDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CardDAV_AddressBookQueryParser {
|
||||
|
||||
const TEST_ANYOF = 'anyof';
|
||||
const TEST_ALLOF = 'allof';
|
||||
|
||||
/**
|
||||
* List of requested properties the client wanted
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $requestedProperties;
|
||||
|
||||
/**
|
||||
* The number of results the client wants
|
||||
*
|
||||
* null means it wasn't specified, which in most cases means 'all results'.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $limit;
|
||||
|
||||
/**
|
||||
* List of property filters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $filters;
|
||||
|
||||
/**
|
||||
* Either TEST_ANYOF or TEST_ALLOF
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $test;
|
||||
|
||||
/**
|
||||
* DOM Document
|
||||
*
|
||||
* @var DOMDocument
|
||||
*/
|
||||
protected $dom;
|
||||
|
||||
/**
|
||||
* DOM XPath object
|
||||
*
|
||||
* @var DOMXPath
|
||||
*/
|
||||
protected $xpath;
|
||||
|
||||
/**
|
||||
* Creates the parser
|
||||
*
|
||||
* @param DOMDocument $dom
|
||||
*/
|
||||
public function __construct(DOMDocument $dom) {
|
||||
|
||||
$this->dom = $dom;
|
||||
|
||||
$this->xpath = new DOMXPath($dom);
|
||||
$this->xpath->registerNameSpace('card',Sabre_CardDAV_Plugin::NS_CARDDAV);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parse() {
|
||||
|
||||
$filterNode = null;
|
||||
|
||||
$limit = $this->xpath->evaluate('number(/card:addressbook-query/card:limit/card:nresults)');
|
||||
if (is_nan($limit)) $limit = null;
|
||||
|
||||
$filter = $this->xpath->query('/card:addressbook-query/card:filter');
|
||||
|
||||
// According to the CardDAV spec there needs to be exactly 1 filter
|
||||
// element. However, KDE 4.8.2 contains a bug that will encode 0 filter
|
||||
// elements, so this is a workaround for that.
|
||||
//
|
||||
// See: https://bugs.kde.org/show_bug.cgi?id=300047
|
||||
if ($filter->length === 0) {
|
||||
$test = null;
|
||||
$filter = null;
|
||||
} elseif ($filter->length === 1) {
|
||||
$filter = $filter->item(0);
|
||||
$test = $this->xpath->evaluate('string(@test)', $filter);
|
||||
} else {
|
||||
throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed');
|
||||
}
|
||||
|
||||
if (!$test) $test = self::TEST_ANYOF;
|
||||
if ($test !== self::TEST_ANYOF && $test !== self::TEST_ALLOF) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('The test attribute must either hold "anyof" or "allof"');
|
||||
}
|
||||
|
||||
$propFilters = array();
|
||||
|
||||
$propFilterNodes = $this->xpath->query('card:prop-filter', $filter);
|
||||
for($ii=0; $ii < $propFilterNodes->length; $ii++) {
|
||||
|
||||
$propFilters[] = $this->parsePropFilterNode($propFilterNodes->item($ii));
|
||||
|
||||
|
||||
}
|
||||
|
||||
$this->filters = $propFilters;
|
||||
$this->limit = $limit;
|
||||
$this->requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild));
|
||||
$this->test = $test;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the prop-filter xml element
|
||||
*
|
||||
* @param DOMElement $propFilterNode
|
||||
* @return array
|
||||
*/
|
||||
protected function parsePropFilterNode(DOMElement $propFilterNode) {
|
||||
|
||||
$propFilter = array();
|
||||
$propFilter['name'] = $propFilterNode->getAttribute('name');
|
||||
$propFilter['test'] = $propFilterNode->getAttribute('test');
|
||||
if (!$propFilter['test']) $propFilter['test'] = 'anyof';
|
||||
|
||||
$propFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $propFilterNode)->length>0;
|
||||
|
||||
$paramFilterNodes = $this->xpath->query('card:param-filter', $propFilterNode);
|
||||
|
||||
$propFilter['param-filters'] = array();
|
||||
|
||||
|
||||
for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
|
||||
|
||||
$propFilter['param-filters'][] = $this->parseParamFilterNode($paramFilterNodes->item($ii));
|
||||
|
||||
}
|
||||
$propFilter['text-matches'] = array();
|
||||
$textMatchNodes = $this->xpath->query('card:text-match', $propFilterNode);
|
||||
|
||||
for($ii=0;$ii<$textMatchNodes->length;$ii++) {
|
||||
|
||||
$propFilter['text-matches'][] = $this->parseTextMatchNode($textMatchNodes->item($ii));
|
||||
|
||||
}
|
||||
|
||||
return $propFilter;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the param-filter element
|
||||
*
|
||||
* @param DOMElement $paramFilterNode
|
||||
* @return array
|
||||
*/
|
||||
public function parseParamFilterNode(DOMElement $paramFilterNode) {
|
||||
|
||||
$paramFilter = array();
|
||||
$paramFilter['name'] = $paramFilterNode->getAttribute('name');
|
||||
$paramFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $paramFilterNode)->length>0;
|
||||
$paramFilter['text-match'] = null;
|
||||
|
||||
$textMatch = $this->xpath->query('card:text-match', $paramFilterNode);
|
||||
if ($textMatch->length>0) {
|
||||
$paramFilter['text-match'] = $this->parseTextMatchNode($textMatch->item(0));
|
||||
}
|
||||
|
||||
return $paramFilter;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Text match
|
||||
*
|
||||
* @param DOMElement $textMatchNode
|
||||
* @return array
|
||||
*/
|
||||
public function parseTextMatchNode(DOMElement $textMatchNode) {
|
||||
|
||||
$matchType = $textMatchNode->getAttribute('match-type');
|
||||
if (!$matchType) $matchType = 'contains';
|
||||
|
||||
if (!in_array($matchType, array('contains', 'equals', 'starts-with', 'ends-with'))) {
|
||||
throw new Sabre_DAV_Exception_BadRequest('Unknown match-type: ' . $matchType);
|
||||
}
|
||||
|
||||
$negateCondition = $textMatchNode->getAttribute('negate-condition');
|
||||
$negateCondition = $negateCondition==='yes';
|
||||
$collation = $textMatchNode->getAttribute('collation');
|
||||
if (!$collation) $collation = 'i;unicode-casemap';
|
||||
|
||||
return array(
|
||||
'negate-condition' => $negateCondition,
|
||||
'collation' => $collation,
|
||||
'match-type' => $matchType,
|
||||
'value' => $textMatchNode->nodeValue
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* AddressBook rootnode
|
||||
*
|
||||
* This object lists a collection of users, which can contain addressbooks.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CardDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CardDAV_AddressBookRoot extends Sabre_DAVACL_AbstractPrincipalCollection {
|
||||
|
||||
/**
|
||||
* Principal Backend
|
||||
*
|
||||
* @var Sabre_DAVACL_IPrincipalBackend
|
||||
*/
|
||||
protected $principalBackend;
|
||||
|
||||
/**
|
||||
* CardDAV backend
|
||||
*
|
||||
* @var Sabre_CardDAV_Backend_Abstract
|
||||
*/
|
||||
protected $carddavBackend;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* This constructor needs both a principal and a carddav backend.
|
||||
*
|
||||
* By default this class will show a list of addressbook collections for
|
||||
* principals in the 'principals' collection. If your main principals are
|
||||
* actually located in a different path, use the $principalPrefix argument
|
||||
* to override this.
|
||||
*
|
||||
* @param Sabre_DAVACL_IPrincipalBackend $principalBackend
|
||||
* @param Sabre_CardDAV_Backend_Abstract $carddavBackend
|
||||
* @param string $principalPrefix
|
||||
*/
|
||||
public function __construct(Sabre_DAVACL_IPrincipalBackend $principalBackend,Sabre_CardDAV_Backend_Abstract $carddavBackend, $principalPrefix = 'principals') {
|
||||
|
||||
$this->carddavBackend = $carddavBackend;
|
||||
parent::__construct($principalBackend, $principalPrefix);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the node
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
|
||||
return Sabre_CardDAV_Plugin::ADDRESSBOOK_ROOT;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a node for a principal.
|
||||
*
|
||||
* The passed array contains principal information, and is guaranteed to
|
||||
* at least contain a uri item. Other properties may or may not be
|
||||
* supplied by the authentication backend.
|
||||
*
|
||||
* @param array $principal
|
||||
* @return Sabre_DAV_INode
|
||||
*/
|
||||
public function getChildForPrincipal(array $principal) {
|
||||
|
||||
return new Sabre_CardDAV_UserAddressBooks($this->carddavBackend, $principal['uri']);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Abstract Backend class
|
||||
*
|
||||
* This class serves as a base-class for addressbook backends
|
||||
*
|
||||
* Note that there are references to 'addressBookId' scattered throughout the
|
||||
* class. The value of the addressBookId is completely up to you, it can be any
|
||||
* arbitrary value you can use as an unique identifier.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CardDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
abstract class Sabre_CardDAV_Backend_Abstract {
|
||||
|
||||
/**
|
||||
* Returns the list of addressbooks for a specific user.
|
||||
*
|
||||
* Every addressbook should have the following properties:
|
||||
* id - an arbitrary unique id
|
||||
* uri - the 'basename' part of the url
|
||||
* principaluri - Same as the passed parameter
|
||||
*
|
||||
* Any additional clark-notation property may be passed besides this. Some
|
||||
* common ones are :
|
||||
* {DAV:}displayname
|
||||
* {urn:ietf:params:xml:ns:carddav}addressbook-description
|
||||
* {http://calendarserver.org/ns/}getctag
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @return array
|
||||
*/
|
||||
public abstract function getAddressBooksForUser($principalUri);
|
||||
|
||||
/**
|
||||
* Updates an addressbook's properties
|
||||
*
|
||||
* See Sabre_DAV_IProperties for a description of the mutations array, as
|
||||
* well as the return value.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param array $mutations
|
||||
* @see Sabre_DAV_IProperties::updateProperties
|
||||
* @return bool|array
|
||||
*/
|
||||
public abstract function updateAddressBook($addressBookId, array $mutations);
|
||||
|
||||
/**
|
||||
* Creates a new address book
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $url Just the 'basename' of the url.
|
||||
* @param array $properties
|
||||
* @return void
|
||||
*/
|
||||
abstract public function createAddressBook($principalUri, $url, array $properties);
|
||||
|
||||
/**
|
||||
* Deletes an entire addressbook and all its contents
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @return void
|
||||
*/
|
||||
abstract public function deleteAddressBook($addressBookId);
|
||||
|
||||
/**
|
||||
* Returns all cards for a specific addressbook id.
|
||||
*
|
||||
* This method should return the following properties for each card:
|
||||
* * carddata - raw vcard data
|
||||
* * uri - Some unique url
|
||||
* * lastmodified - A unix timestamp
|
||||
*
|
||||
* It's recommended to also return the following properties:
|
||||
* * etag - A unique etag. This must change every time the card changes.
|
||||
* * size - The size of the card in bytes.
|
||||
*
|
||||
* If these last two properties are provided, less time will be spent
|
||||
* calculating them. If they are specified, you can also ommit carddata.
|
||||
* This may speed up certain requests, especially with large cards.
|
||||
*
|
||||
* @param mixed $addressbookId
|
||||
* @return array
|
||||
*/
|
||||
public abstract function getCards($addressbookId);
|
||||
|
||||
/**
|
||||
* Returns a specfic card.
|
||||
*
|
||||
* The same set of properties must be returned as with getCards. The only
|
||||
* exception is that 'carddata' is absolutely required.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @return array
|
||||
*/
|
||||
public abstract function getCard($addressBookId, $cardUri);
|
||||
|
||||
/**
|
||||
* Creates a new card.
|
||||
*
|
||||
* The addressbook id will be passed as the first argument. This is the
|
||||
* same id as it is returned from the getAddressbooksForUser method.
|
||||
*
|
||||
* The cardUri is a base uri, and doesn't include the full path. The
|
||||
* cardData argument is the vcard body, and is passed as a string.
|
||||
*
|
||||
* It is possible to return an ETag from this method. This ETag is for the
|
||||
* newly created resource, and must be enclosed with double quotes (that
|
||||
* is, the string itself must contain the double quotes).
|
||||
*
|
||||
* You should only return the ETag if you store the carddata as-is. If a
|
||||
* subsequent GET request on the same card does not have the same body,
|
||||
* byte-by-byte and you did return an ETag here, clients tend to get
|
||||
* confused.
|
||||
*
|
||||
* If you don't return an ETag, you can just return null.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @param string $cardData
|
||||
* @return string|null
|
||||
*/
|
||||
abstract public function createCard($addressBookId, $cardUri, $cardData);
|
||||
|
||||
/**
|
||||
* Updates a card.
|
||||
*
|
||||
* The addressbook id will be passed as the first argument. This is the
|
||||
* same id as it is returned from the getAddressbooksForUser method.
|
||||
*
|
||||
* The cardUri is a base uri, and doesn't include the full path. The
|
||||
* cardData argument is the vcard body, and is passed as a string.
|
||||
*
|
||||
* It is possible to return an ETag from this method. This ETag should
|
||||
* match that of the updated resource, and must be enclosed with double
|
||||
* quotes (that is: the string itself must contain the actual quotes).
|
||||
*
|
||||
* You should only return the ETag if you store the carddata as-is. If a
|
||||
* subsequent GET request on the same card does not have the same body,
|
||||
* byte-by-byte and you did return an ETag here, clients tend to get
|
||||
* confused.
|
||||
*
|
||||
* If you don't return an ETag, you can just return null.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @param string $cardData
|
||||
* @return string|null
|
||||
*/
|
||||
abstract public function updateCard($addressBookId, $cardUri, $cardData);
|
||||
|
||||
/**
|
||||
* Deletes a card
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function deleteCard($addressBookId, $cardUri);
|
||||
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PDO CardDAV backend
|
||||
*
|
||||
* This CardDAV backend uses PDO to store addressbooks
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CardDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CardDAV_Backend_PDO extends Sabre_CardDAV_Backend_Abstract {
|
||||
|
||||
/**
|
||||
* PDO connection
|
||||
*
|
||||
* @var PDO
|
||||
*/
|
||||
protected $pdo;
|
||||
|
||||
/**
|
||||
* The PDO table name used to store addressbooks
|
||||
*/
|
||||
protected $addressBooksTableName;
|
||||
|
||||
/**
|
||||
* The PDO table name used to store cards
|
||||
*/
|
||||
protected $cardsTableName;
|
||||
|
||||
/**
|
||||
* Sets up the object
|
||||
*
|
||||
* @param PDO $pdo
|
||||
* @param string $addressBooksTableName
|
||||
* @param string $cardsTableName
|
||||
*/
|
||||
public function __construct(PDO $pdo, $addressBooksTableName = 'addressbooks', $cardsTableName = 'cards') {
|
||||
|
||||
$this->pdo = $pdo;
|
||||
$this->addressBooksTableName = $addressBooksTableName;
|
||||
$this->cardsTableName = $cardsTableName;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of addressbooks for a specific user.
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @return array
|
||||
*/
|
||||
public function getAddressBooksForUser($principalUri) {
|
||||
|
||||
$stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, ctag FROM '.$this->addressBooksTableName.' WHERE principaluri = ?');
|
||||
$stmt->execute(array($principalUri));
|
||||
|
||||
$addressBooks = array();
|
||||
|
||||
foreach($stmt->fetchAll() as $row) {
|
||||
|
||||
$addressBooks[] = array(
|
||||
'id' => $row['id'],
|
||||
'uri' => $row['uri'],
|
||||
'principaluri' => $row['principaluri'],
|
||||
'{DAV:}displayname' => $row['displayname'],
|
||||
'{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
|
||||
'{http://calendarserver.org/ns/}getctag' => $row['ctag'],
|
||||
'{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}supported-address-data' =>
|
||||
new Sabre_CardDAV_Property_SupportedAddressData(),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return $addressBooks;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates an addressbook's properties
|
||||
*
|
||||
* See Sabre_DAV_IProperties for a description of the mutations array, as
|
||||
* well as the return value.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param array $mutations
|
||||
* @see Sabre_DAV_IProperties::updateProperties
|
||||
* @return bool|array
|
||||
*/
|
||||
public function updateAddressBook($addressBookId, array $mutations) {
|
||||
|
||||
$updates = array();
|
||||
|
||||
foreach($mutations as $property=>$newValue) {
|
||||
|
||||
switch($property) {
|
||||
case '{DAV:}displayname' :
|
||||
$updates['displayname'] = $newValue;
|
||||
break;
|
||||
case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' :
|
||||
$updates['description'] = $newValue;
|
||||
break;
|
||||
default :
|
||||
// If any unsupported values were being updated, we must
|
||||
// let the entire request fail.
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No values are being updated?
|
||||
if (!$updates) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = 'UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 ';
|
||||
foreach($updates as $key=>$value) {
|
||||
$query.=', `' . $key . '` = :' . $key . ' ';
|
||||
}
|
||||
$query.=' WHERE id = :addressbookid';
|
||||
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$updates['addressbookid'] = $addressBookId;
|
||||
|
||||
$stmt->execute($updates);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new address book
|
||||
*
|
||||
* @param string $principalUri
|
||||
* @param string $url Just the 'basename' of the url.
|
||||
* @param array $properties
|
||||
* @return void
|
||||
*/
|
||||
public function createAddressBook($principalUri, $url, array $properties) {
|
||||
|
||||
$values = array(
|
||||
'displayname' => null,
|
||||
'description' => null,
|
||||
'principaluri' => $principalUri,
|
||||
'uri' => $url,
|
||||
);
|
||||
|
||||
foreach($properties as $property=>$newValue) {
|
||||
|
||||
switch($property) {
|
||||
case '{DAV:}displayname' :
|
||||
$values['displayname'] = $newValue;
|
||||
break;
|
||||
case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' :
|
||||
$values['description'] = $newValue;
|
||||
break;
|
||||
default :
|
||||
throw new Sabre_DAV_Exception_BadRequest('Unknown property: ' . $property);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, ctag) VALUES (:uri, :displayname, :description, :principaluri, 1)';
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$stmt->execute($values);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an entire addressbook and all its contents
|
||||
*
|
||||
* @param int $addressBookId
|
||||
* @return void
|
||||
*/
|
||||
public function deleteAddressBook($addressBookId) {
|
||||
|
||||
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
|
||||
$stmt->execute(array($addressBookId));
|
||||
|
||||
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
|
||||
$stmt->execute(array($addressBookId));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all cards for a specific addressbook id.
|
||||
*
|
||||
* This method should return the following properties for each card:
|
||||
* * carddata - raw vcard data
|
||||
* * uri - Some unique url
|
||||
* * lastmodified - A unix timestamp
|
||||
*
|
||||
* It's recommended to also return the following properties:
|
||||
* * etag - A unique etag. This must change every time the card changes.
|
||||
* * size - The size of the card in bytes.
|
||||
*
|
||||
* If these last two properties are provided, less time will be spent
|
||||
* calculating them. If they are specified, you can also ommit carddata.
|
||||
* This may speed up certain requests, especially with large cards.
|
||||
*
|
||||
* @param mixed $addressbookId
|
||||
* @return array
|
||||
*/
|
||||
public function getCards($addressbookId) {
|
||||
|
||||
$stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
|
||||
$stmt->execute(array($addressbookId));
|
||||
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specfic card.
|
||||
*
|
||||
* The same set of properties must be returned as with getCards. The only
|
||||
* exception is that 'carddata' is absolutely required.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @return array
|
||||
*/
|
||||
public function getCard($addressBookId, $cardUri) {
|
||||
|
||||
$stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ? LIMIT 1');
|
||||
$stmt->execute(array($addressBookId, $cardUri));
|
||||
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return (count($result)>0?$result[0]:false);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new card.
|
||||
*
|
||||
* The addressbook id will be passed as the first argument. This is the
|
||||
* same id as it is returned from the getCards method.
|
||||
*
|
||||
* The cardUri is a base uri, and doesn't include the full path. The
|
||||
* cardData argument is the vcard body, and is passed as a string.
|
||||
*
|
||||
* It is possible to return an ETag from this method. This ETag is for the
|
||||
* newly created resource, and must be enclosed with double quotes (that
|
||||
* is, the string itself must contain the double quotes).
|
||||
*
|
||||
* You should only return the ETag if you store the carddata as-is. If a
|
||||
* subsequent GET request on the same card does not have the same body,
|
||||
* byte-by-byte and you did return an ETag here, clients tend to get
|
||||
* confused.
|
||||
*
|
||||
* If you don't return an ETag, you can just return null.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @param string $cardData
|
||||
* @return string|null
|
||||
*/
|
||||
public function createCard($addressBookId, $cardUri, $cardData) {
|
||||
|
||||
$stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid) VALUES (?, ?, ?, ?)');
|
||||
|
||||
$result = $stmt->execute(array($cardData, $cardUri, time(), $addressBookId));
|
||||
|
||||
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
|
||||
$stmt2->execute(array($addressBookId));
|
||||
|
||||
return '"' . md5($cardData) . '"';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a card.
|
||||
*
|
||||
* The addressbook id will be passed as the first argument. This is the
|
||||
* same id as it is returned from the getAddressbooksForUser method.
|
||||
*
|
||||
* The cardUri is a base uri, and doesn't include the full path. The
|
||||
* cardData argument is the vcard body, and is passed as a string.
|
||||
*
|
||||
* It is possible to return an ETag from this method. This ETag should
|
||||
* match that of the updated resource, and must be enclosed with double
|
||||
* quotes (that is: the string itself must contain the actual quotes).
|
||||
*
|
||||
* You should only return the ETag if you store the carddata as-is. If a
|
||||
* subsequent GET request on the same card does not have the same body,
|
||||
* byte-by-byte and you did return an ETag here, clients tend to get
|
||||
* confused.
|
||||
*
|
||||
* If you don't return an ETag, you can just return null.
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @param string $cardData
|
||||
* @return string|null
|
||||
*/
|
||||
public function updateCard($addressBookId, $cardUri, $cardData) {
|
||||
|
||||
$stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ? WHERE uri = ? AND addressbookid =?');
|
||||
$stmt->execute(array($cardData, time(), $cardUri, $addressBookId));
|
||||
|
||||
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
|
||||
$stmt2->execute(array($addressBookId));
|
||||
|
||||
return '"' . md5($cardData) . '"';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a card
|
||||
*
|
||||
* @param mixed $addressBookId
|
||||
* @param string $cardUri
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteCard($addressBookId, $cardUri) {
|
||||
|
||||
$stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?');
|
||||
$stmt->execute(array($addressBookId, $cardUri));
|
||||
|
||||
$stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?');
|
||||
$stmt2->execute(array($addressBookId));
|
||||
|
||||
return $stmt->rowCount()===1;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* The Card object represents a single Card from an addressbook
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CardDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
class Sabre_CardDAV_Card extends Sabre_DAV_File implements Sabre_CardDAV_ICard, Sabre_DAVACL_IACL {
|
||||
|
||||
/**
|
||||
* CardDAV backend
|
||||
*
|
||||
* @var Sabre_CardDAV_Backend_Abstract
|
||||
*/
|
||||
private $carddavBackend;
|
||||
|
||||
/**
|
||||
* Array with information about this Card
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cardData;
|
||||
|
||||
/**
|
||||
* Array with information about the containing addressbook
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $addressBookInfo;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Sabre_CardDAV_Backend_Abstract $carddavBackend
|
||||
* @param array $addressBookInfo
|
||||
* @param array $cardData
|
||||
*/
|
||||
public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend,array $addressBookInfo,array $cardData) {
|
||||
|
||||
$this->carddavBackend = $carddavBackend;
|
||||
$this->addressBookInfo = $addressBookInfo;
|
||||
$this->cardData = $cardData;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the uri for this object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName() {
|
||||
|
||||
return $this->cardData['uri'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the VCard-formatted object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get() {
|
||||
|
||||
// Pre-populating 'carddata' is optional. If we don't yet have it
|
||||
// already, we fetch it from the backend.
|
||||
if (!isset($this->cardData['carddata'])) {
|
||||
$this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']);
|
||||
}
|
||||
return $this->cardData['carddata'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the VCard-formatted object
|
||||
*
|
||||
* @param string $cardData
|
||||
* @return string|null
|
||||
*/
|
||||
public function put($cardData) {
|
||||
|
||||
if (is_resource($cardData))
|
||||
$cardData = stream_get_contents($cardData);
|
||||
|
||||
// Converting to UTF-8, if needed
|
||||
$cardData = Sabre_DAV_StringUtil::ensureUTF8($cardData);
|
||||
|
||||
$etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'],$this->cardData['uri'],$cardData);
|
||||
$this->cardData['carddata'] = $cardData;
|
||||
$this->cardData['etag'] = $etag;
|
||||
|
||||
return $etag;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the card
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete() {
|
||||
|
||||
$this->carddavBackend->deleteCard($this->addressBookInfo['id'],$this->cardData['uri']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime content-type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType() {
|
||||
|
||||
return 'text/x-vcard; charset=utf-8';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ETag for this object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getETag() {
|
||||
|
||||
if (isset($this->cardData['etag'])) {
|
||||
return $this->cardData['etag'];
|
||||
} else {
|
||||
return '"' . md5($this->get()) . '"';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification date as a unix timestamp
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModified() {
|
||||
|
||||
return isset($this->cardData['lastmodified'])?$this->cardData['lastmodified']:null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of this object in bytes
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSize() {
|
||||
|
||||
if (array_key_exists('size', $this->cardData)) {
|
||||
return $this->cardData['size'];
|
||||
} else {
|
||||
return strlen($this->get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOwner() {
|
||||
|
||||
return $this->addressBookInfo['principaluri'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a group principal
|
||||
*
|
||||
* This must be a url to a principal, or null if there's no owner
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getGroup() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of ACE's for this node.
|
||||
*
|
||||
* Each ACE has the following properties:
|
||||
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
|
||||
* currently the only supported privileges
|
||||
* * 'principal', a url to the principal who owns the node
|
||||
* * 'protected' (optional), indicating that this ACE is not allowed to
|
||||
* be updated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getACL() {
|
||||
|
||||
return array(
|
||||
array(
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->addressBookInfo['principaluri'],
|
||||
'protected' => true,
|
||||
),
|
||||
array(
|
||||
'privilege' => '{DAV:}write',
|
||||
'principal' => $this->addressBookInfo['principaluri'],
|
||||
'protected' => true,
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ACL
|
||||
*
|
||||
* This method will receive a list of new ACE's.
|
||||
*
|
||||
* @param array $acl
|
||||
* @return void
|
||||
*/
|
||||
public function setACL(array $acl) {
|
||||
|
||||
throw new Sabre_DAV_Exception_MethodNotAllowed('Changing ACL is not yet supported');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of supported privileges for this node.
|
||||
*
|
||||
* The returned data structure is a list of nested privileges.
|
||||
* See Sabre_DAVACL_Plugin::getDefaultSupportedPrivilegeSet for a simple
|
||||
* standard structure.
|
||||
*
|
||||
* If null is returned from this method, the default privilege set is used,
|
||||
* which is fine for most common usecases.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getSupportedPrivilegeSet() {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* AddressBook interface
|
||||
*
|
||||
* Implement this interface to allow a node to be recognized as an addressbook.
|
||||
*
|
||||
* @package Sabre
|
||||
* @subpackage CardDAV
|
||||
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
|
||||
* @author Evert Pot (http://www.rooftopsolutions.nl/)
|
||||
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
|
||||
*/
|
||||
interface Sabre_CardDAV_IAddressBook extends Sabre_DAV_ICollection {
|
||||
|
||||
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user