Merge branch 'master' into hungerburg/davical-master

This commit is contained in:
Andrew Ruthven 2019-01-30 22:25:46 +13:00
commit dccd7997f7
132 changed files with 10784 additions and 449 deletions

1
.gitignore vendored
View File

@ -25,6 +25,7 @@ random_crap
testing/tests/x-*
testing/tests/local
testing/timing.stats*
testing/report.xml
.sync-cache
.buildpath
.cvsignore

73
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,73 @@
image: php:apache-stretch
build:
stage: build
script:
- apt-get -y update
- bash -c 'mkdir -p /usr/share/man/man{0..10}'
- apt-get -y install build-essential devscripts fakeroot dh-exec jdupes rst2pdf doxygen graphviz # todo build-dep instead, change this task's image to plain debian
- mv debian/changelog debian/changelog.old
- >
cat
<(echo "davical ($(cat VERSION)~git$(date +"%Y%m%d")-$(git rev-parse --short $CI_COMMIT_SHA)) unstable; urgency=medium")
<(echo "")
<(echo " * Build on CI")
<(echo "")
<(echo " -- ${GITLAB_USER_NAME} <${GITLAB_USER_EMAIL}> $(date -R)")
<(echo "")
debian/changelog.old
>debian/changelog
- rm debian/changelog.old
- debuild -us -uc -b -d
- 'mv ../davical_*_all.deb ./davical.deb'
artifacts:
paths:
- '*.deb'
test:
stage: test
artifacts:
paths:
- testing/report.xml
- apache2_log/*
reports:
junit: testing/report.xml
when:
always
script:
- apt-get -y update
- bash -c 'mkdir -p /usr/share/man/man{0..10}'
- apt-get -y install locales
- echo "en_NZ.UTF-8 UTF-8" >> /etc/locale.gen
- locale-gen
- echo "LANG=en_NZ.UTF-8" > /etc/default/locale
- apt-get -y install libdbd-pg-perl libyaml-perl perl postgresql postgresql-client libpq-dev xmlstarlet
- curl 'https://gitlab.com/davical-project/awl/-/archive/master/awl-master.tar.gz' | tar zxf -
- mv awl-master /usr/share/awl/
- chown -R www-data /usr/share/awl/
- dpkg --ignore-depends=php,php-pgsql,php-xml,libawl-php,php-cli -i *.deb
- docker-php-ext-install -j$(nproc) pgsql
- docker-php-ext-install -j$(nproc) pdo_pgsql
- docker-php-ext-install -j$(nproc) calendar
- echo '127.0.1.1 regression mycaldav myempty' >> /etc/hosts
- cp testing/apache-site.conf.example /etc/apache2/sites-enabled/davical-regression.conf
- sed -i 's/\/path\/to/\/usr\/share/g' /etc/apache2/sites-enabled/davical-regression.conf
- mkdir /usr/share/davical/testing/
- cp testing/*.php /usr/share/davical/testing/
- rm /etc/davical/config.php
- cp testing/regression-conf.php.example /etc/davical/regression-conf.php
- ln -s /etc/davical/regression-conf.php /etc/davical/mycaldav-conf.php
- ln -s /etc/davical/regression-conf.php /etc/davical/myempty-conf.php
- sed -i '/peer/d' /etc/postgresql/9.6/main/pg_hba.conf
- echo 'local all all trust' >> /etc/postgresql/9.6/main/pg_hba.conf
- pg_ctlcluster 9.6 main start
- su postgres -c 'createuser davical_dba --createdb --createrole --superuser'
- su postgres -c 'createuser davical_app --superuser'
- su postgres -c 'createuser testrunner --superuser'
- pg_ctlcluster 9.6 main restart
- a2enmod rewrite
- apache2ctl start
- useradd testrunner
- cd testing && su testrunner -c 'IS_CI=yes ALLSUITES="regression-suite binding carddav scheduling" ./run_regressions.sh all x'
after_script:
- cp -r /var/log/apache2 apache2_log

View File

@ -27,6 +27,7 @@ Aurelien Requiem <aurelien@menfin.net>
Benoît Bleuzé <benoit.bleuze@gmail.com>
Christian Kier <kier@isip.uni-luebeck.de>
Christoph Anton Mitterer <calestyo@scientia.net>
CSchulz <christian@schulz.re>
Cyril Giraud <cgiraud@free.fr>
Daniel Aleksandersen <code@daniel.priv.no>
DAViCal Administrator <davical@fry.dotcal.com>
@ -43,6 +44,7 @@ Henri Sivonen <hsivonen@iki.fi>
Jan Losinski <losinskij@gmail.com>
Ján Máté <jan.mate@inf-it.com>
Jason Alavaliant <alavaliant@gmail.com>
Jean-Baptiste Guerraz <jbguerraz@skilld.fr>
Jens Zahner <jens.zahner@servicereisen.de>
Jeppe Bob Dyrby <jeppe.dyrby@gmail.com>
Jeroen van Disseldorp <jdizzl@xs4all.nl>

View File

@ -1,3 +1,50 @@
2018-01-11 Florian Schlichting <fsfs@debian.org>
* switch to doxygen for api docs
* put the most important debug options in a more visible place
2016-01-21 Frank Steinberg <steinberg@ibr.cs.tu-bs.de>
* Improved handling of event modifications
2016-06-23 Andrew McMillan <andrew@mcmillan.net.nz>
* Fix common etag match code, use it everywhere.
* Tidy up some PHPdoc in DAVResource
2018-01-10 Jean-Baptiste Guerraz <jbguerraz@skilld.fr>
* use php ldap explode in order to be compatible with any dn
* sync ldap user - reactivate
2018-01-09 Andrew McMillan <andrew@mcmillan.net.nz>
* Correctly handle durations without units like "PT"
2018-01-09 Florian Schlichting <fsfs@debian.org>
* add regression tests for iCal handling calendar delegations
* drop tests/regression-suite/0528, same as 0527
* rename DAVResource->_is_proxy_request to _is_proxy_resource
* Finally: implement managing calendar delegations from iCal
* group-member-set and group-membership queries on proxy resources should be handled in DAVResource
* update regression tests due to FetchProxyGroups changes
* DAVPrincipal->FetchProxyGroups(): disable grants_proxy_access_from_p()
* DAVPrincipal->FetchProxyGroups(): invert arguments to pprivs()
2018-01-08 Florian Schlichting <fsfs@debian.org>
* add DAVResource->IsProxyCollection()
2018-01-07 Florian Schlichting <fsfs@debian.org>
* advertise support for principal-match REPORT
* clean up obsolete code: supported_methods and supported_reports was moved to DAVResource
2018-01-04 Florian Schlichting <fsfs@debian.org>
* update doc and fix a warning
2018-01-03 Florian Schlichting <fsfs@debian.org>
* UI: create external bindings with type set (fix: #132)
2017-11-20 CSchulz <christian@schulz.re>
* add PT to follow alias
2017-10-25 Florian Schlichting <fsfs@debian.org>
* Document $c->hide_bound and $c->disable_caldav_proxy_propfind_collections config options
2017-10-09 Florian Schlichting <fsfs@debian.org>
* 'perl update-davical-database' did not find the intended config file / patchdir

View File

@ -16,8 +16,8 @@ nodocs: htdocs/always.php built-locale
.PHONY: all
all: htdocs/always.php built-docs built-locale
built-docs: docs/phpdoc.ini htdocs/*.php inc/*.php docs/translation.rst
apigen generate --quiet --title=DAViCal --todo --tree --deprecated -s inc -s htdocs --exclude caldav-client.php -d docs/api || phpdoc -c docs/phpdoc.ini || echo "NOTICE: Failed to build API docs"
built-docs: docs/phpdoc.ini docs/Doxyfile inc/*.php docs/translation.rst
doxygen docs/Doxyfile || apigen generate --quiet --title=DAViCal --todo --tree --deprecated -s inc --exclude caldav-client.php -d docs/api || phpdoc -c docs/phpdoc.ini || echo "NOTICE: Failed to build API docs"
rst2pdf docs/translation.rst || echo "NOTICE: Failed to build ReST docs"
touch $@

View File

@ -1 +1 @@
1.1.6
1.1.7

View File

@ -25,6 +25,7 @@ $c->pg_connect[] = "dbname=davical user=davical_app";
/**
* "system_name" is used to specify the authentication realm of the server, as
* well as being used as a name to display in various places.
*
* Default: DAViCal CalDAV Server
*/
// $c->system_name = "DAViCal CalDAV Server";
@ -33,6 +34,7 @@ $c->pg_connect[] = "dbname=davical user=davical_app";
* The CalDAV specification does not define GET on a collection, but typically this is
* used as a .ics download for the whole collection. This will also enable a download
* link in the web interface for calendars with entries.
*
* Default: false
*/
// $c->get_includes_subcollections = true;
@ -45,6 +47,7 @@ $c->pg_connect[] = "dbname=davical user=davical_app";
* easier for people to PUT whole calendars or addressbooks as part of migrating
* their data. After this, it is recommended to turn it off so that clients
* which have been misconfigured are readily identifiable.
*
* Default: true
*/
// $c->readonly_webdav_collections = false;
@ -52,6 +55,7 @@ $c->pg_connect[] = "dbname=davical user=davical_app";
/**
* This will allow failure on import of collections to apply only to an
* individual event that is faulty, rather than failing the whole collection.
*
* Default: false (fail whole collection)
*/
// $c->skip_bad_event_on_import = true;
@ -74,6 +78,7 @@ $c->admin_email ='calendar-admin@example.com';
/**
* Set this to 'true' in order to restrict the /setup.php page (which contains
* the entire phpinfo() output) to 'Administrator' users.
*
* Default: false
*/
// $c->restrict_setup_to_admin = true;
@ -94,7 +99,8 @@ $c->admin_email ='calendar-admin@example.com';
* pages. Since this doesn't work in Konqueror you may want to set this
* to false if you expect people to be using Konqueror with the DAViCal
* administration pages.
* Default=true
*
* Default: true
*/
// $c->enable_row_linking = false;
@ -108,6 +114,44 @@ $c->admin_email ='calendar-admin@example.com';
// $c->print_styles = array();
/***************************************************************************
* *
* Debug Options *
* *
***************************************************************************/
/**
* Whenever you think you've found an issue with how DAViCal handles a situation,
* generating some data on what it's actually doing and whether it can find a
* certain event in the database etc. can be essential to allow others (or
* yourself) to understand what's going wrong.
*
* There are many different types of debug messages (for more details see
* debug-config.php), but this will log them all:
*/
// $c->dbg["ALL"] = 1;
/**
* While the above setting will quickly fill your PHP error log and should best
* be restricted to one or two requests, logging just the client interaction
* is almost as useful. This should be the minimum for any support request on
* the mailing list or issue tracker.
*/
// $c->dbg["request"] = 1; // The request headers & content
// $c->dbg['response'] = 1; // The response headers & content
/**
* Even on a moderately busy server, turning on debug logging for everyone can
* produce a lot of output in a short time that makes it hard to find the
* relevant lines. Debug filtering limits logging to certain IP addresses or
* usernames. (config values are arrays)
*/
// $c->dbg_filter["remoteIP"][] = '192.168.1.20';
// $c->dbg_filter["remoteIP"][] = '192.168.1.21';
// $c->dbg_filter["authenticatedUser"][] = 'peter';
// $c->dbg_filter["authenticatedUser"][] = 'john';
/***************************************************************************
* *
* Caldav Server *
@ -121,6 +165,7 @@ $c->admin_email ='calendar-admin@example.com';
* default (true) if people will be using Evolution or Sunbird /
* Lightning against this because that software does not support the
* creation of calendar collections.
*
* Default: true
*/
// $c->collections_always_exist = false;
@ -128,11 +173,26 @@ $c->admin_email ='calendar-admin@example.com';
/**
* The name of a user's "home" calendar and addressbook. These will be created
* for each new user.
* Default: 'calendar' / 'addresses'
*
* Defaults:
* home_calendar_name: 'calendar'
* home_addressbook_name: 'addresses'
*/
// $c->home_calendar_name = 'calendar';
// $c->home_addressbook_name = 'addresses';
/**
* Sets a numeric value indicating the maximum size in octets (bytes) of a resource
* that the server is willing to accept when an address object resource is stored
* in an address book collection (e.g. contacts with image attachments).
* Note that not all clients respect that property and that DAViCal won't deny creating
* or updating a resource that is larger than the specified limit if the client willingly or
* unwillingly ignores that property. Currently (late 2018) we only know of iOS devices to handle it properly.
*
* Default: 6550000
*/
// $c->carddav_max_resource_size = 6550000;
/**
* If the above options are not suitable for your new users, use this to create
* a more complex default collection management.
@ -153,6 +213,7 @@ $c->admin_email ='calendar-admin@example.com';
* array( 9 => 'R', 4 => 'A' )
* to create a 'read' relationship to user_no 9 and an 'all' relation
* with user_no 4.
*
* Default: none
*/
// $c->default_relationships = array();
@ -173,6 +234,7 @@ $c->admin_email ='calendar-admin@example.com';
/**
* An array of fields on the usr record which should be set to specific
* values when the users are created.
*
* Default: none
*/
// $c->template_usr = array( 'active' => true,
@ -186,6 +248,7 @@ $c->admin_email ='calendar-admin@example.com';
* admin or owner of a calendar will not get an answer. Often these todo are
* only relevant to the owner, but in some shared calendar situations they
* might not be in which case you should set this to false.
*
* Default: true
*/
// $c->hide_TODO = false;
@ -211,7 +274,9 @@ $c->admin_email ='calendar-admin@example.com';
* to tailor a working solution: bind all collections you want to see on iOS
* (emulation of delegation) and then hide these collections from other clients
* with real delegation support.
*
* Default: false/not set: always show bound collections
*
* If set to true: never show bound collections
* If set to an array: hide if any header => regex tuple matches
* Example: Hide bound collections from clients which send a User-Agent header
@ -222,6 +287,7 @@ $c->admin_email ='calendar-admin@example.com';
/**
* External subscription (BIND) minimum refresh interval
* Required if you want to enable remote binding ( webcal subscriptions )
*
* Default: none
*/
// $c->external_refresh = 60;
@ -229,6 +295,7 @@ $c->admin_email ='calendar-admin@example.com';
/**
* External subscription (BIND) user agent string
* Required if your remote calendar only delivers to known user agents.
*
* Default: none
*/
// $c->external_ua_string = '';
@ -245,6 +312,7 @@ $c->admin_email ='calendar-admin@example.com';
* Provide freebusy information to any (unauthenticated) user via the
* freebusy.php URL. Only events marked as PRIVATE will be excluded from the
* report.
*
* Default: false (authentication required)
*/
// $c->public_freebusy_url = true;
@ -257,6 +325,7 @@ $c->admin_email ='calendar-admin@example.com';
* adversely affected if you do so.
* Introduced in DAViCal version 1.1.4 in support of Issue #31 Database
* Performance Improvements.
*
* Default: false
*/
// $c->support_obsolete_free_busy_property = false;
@ -281,10 +350,11 @@ $c->admin_email ='calendar-admin@example.com';
// $c->default_locale = "en_NZ";
/**
* Default will be $_SERVER['SERVER_NAME'];
* This is used to construct URLs which are passed in the answers to the client. You may
* want to force this to a specific domain in responses if your system is accessed by
* multiple names, otherwise you probably won't need to change it.
*
* Default: $_SERVER['SERVER_NAME']
*/
// $c->domain_name = 'example.com';
@ -293,6 +363,7 @@ $c->admin_email ='calendar-admin@example.com';
* user login name if it does not contain the @ character. If email addresses
* are used as user names in Davical, this fixes a problem with MacOS X 10.6
* Addressbook that cannot login to CardDav account.
*
* Default: false
*/
// $c->login_append_domain_if_missing = true;
@ -312,6 +383,8 @@ $c->admin_email ='calendar-admin@example.com';
*
* If the only person that devious is your sysadmin then you probably already
* enabled this option...
*
* Default: false
*/
// $c->allow_get_email_visibility = false;
@ -322,7 +395,10 @@ $c->admin_email ='calendar-admin@example.com';
* users who share their collections) and most clients will never use it,
* or ask for it explicitly using an expand-property REPORT, which is not
* affected by this option.
* Default: false/unset (always show)
*
* Default: false/unset
*
* If set to false (or unset): always show
* If set to true: never show
* If set to an array: hide if any header => regex tuple matches
*/
@ -333,7 +409,9 @@ $c->admin_email ='calendar-admin@example.com';
* If true, names of groups (prefixed with "@") given as an event attendee
* will get resolved to a list of members of that group. Note that CalDAV
* clients might get confused by this server behavior until they get
* synced again. Default: false.
* synced again.
*
* Default: false.
*/
// $c->enable_attendee_group_resolution = true;
@ -348,6 +426,7 @@ $c->admin_email ='calendar-admin@example.com';
* If you want to turn off scheduling functions you can set this to 'false' and
* DAViCal will not advertise the ability to schedule, leaving it to calendar
* clients to send out and receive scheduling requests.
*
* Default: true
*/
// $c->enable_auto_schedule = false;
@ -369,6 +448,7 @@ $c->admin_email ='calendar-admin@example.com';
* DNS TXT record for signing outbound requests
* example:
* cal._domainkey.example.com. 86400 IN TXT "k=rsa\; t=s\; p=PUBKEY"
*
* Default: false
*/
// $c->enable_scheduling = true;
@ -378,6 +458,7 @@ $c->admin_email ='calendar-admin@example.com';
* is the domain with the public key in a TXT record as shown above.
*
* TODO: enable domain/signing by per user keys, patches welcome.
*
* Default: none
*/
// $c->scheduling_dkim_domain = '';
@ -386,6 +467,7 @@ $c->admin_email ='calendar-admin@example.com';
* Domain Key selector to use when signing outbound scheduling requests.
*
* TODO: enable selectors/signing by per user keys, patches welcome.
*
* Default: 'cal'
*/
// $c->scheduling_dkim_selector = 'cal';
@ -393,6 +475,7 @@ $c->admin_email ='calendar-admin@example.com';
/*
* Domain Key private key
* Required if you want to enable outbound remote server scheduling
*
* Default: none
*/
// $c->schedule_private_key = 'PRIVATE-KEY-BASE-64-DATA';
@ -413,19 +496,21 @@ $c->admin_email ='calendar-admin@example.com';
* X-Forwarded-Proto and X-Forwarded-Port headers. You can instruct DAViCal to
* attempt to "do the right thing" and use the content of these headers instead,
* when they are available.
*
* CAUTION: Malicious clients can spoof these headers. When you enable this, you
* need to make sure your reverse proxy erases any pre-existing values of all
* these headers, and that no untrusted requests can reach DAViCal without
* passing the proxy server.
*
* Default: false
*/
*/
// $c->trust_x_forwarded = true;
/**
* Instead or in addition to the above, you can compute, override or unset the
* relevant variables. This is a catch-all for non-standard or advanced
* environments.
*/
*/
/* Unset X-Real-IP, as it's not controlled by the reverse proxy. */
// unset( $_SERVER['HTTP_X_REAL_IP'] );

View File

@ -35,8 +35,8 @@ if [ -z "${AWLDIR}" ] ; then
exit 1
fi
export AWL_DBAUSER=davical_dba
export AWL_APPUSER=davical_app
AWL_DBAUSER="${3:-davical_dba}"
AWL_APPUSER="${4:-davical_app}"
# set -x

View File

@ -167,8 +167,8 @@ CREATE TABLE calendar_item (
completed TIMESTAMP WITH TIME ZONE,
dav_id INT8 UNIQUE,
collection_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE,
first_instance_start TIMESTAMP WITHOUT TIME ZONE DEFAULT NULL,
last_instance_end TIMESTAMP WITHOUT TIME ZONE DEFAULT NULL,
first_instance_start TIMESTAMP WITH TIME ZONE DEFAULT NULL,
last_instance_end TIMESTAMP WITH TIME ZONE DEFAULT NULL,
-- Cascade updates / deletes from the caldav_data table
CONSTRAINT caldav_exists FOREIGN KEY ( user_no, dav_name )
@ -489,4 +489,4 @@ CREATE SEQUENCE metrics_count_delticket;
CREATE SEQUENCE metrics_count_bind;
CREATE SEQUENCE metrics_count_unknown;
SELECT new_db_revision(1,3,2, 'Luty' );
SELECT new_db_revision(1,3,3, 'Marzec' );

23
dba/patches/1.3.3.sql Normal file
View File

@ -0,0 +1,23 @@
-- Notable enhancement: change first_instance_start and last_instance_end to be timezone-aware
BEGIN;
SELECT check_db_revision(1,3,2);
ALTER TABLE calendar_item
ALTER COLUMN first_instance_start
TYPE TIMESTAMP WITH TIME ZONE
-- I don't believe the column has ever been populated, but if it has, we want
-- to throw away that data anyway because we don't know its timezone
USING NULL::timestamptz;
ALTER TABLE calendar_item
ALTER COLUMN last_instance_end
TYPE TIMESTAMP WITH TIME ZONE
USING NULL::timestamptz; -- As above
-- http://blogs.transparent.com/polish/names-of-the-months-and-their-meaning/
SELECT new_db_revision(1,3,3, 'Marzec' );
COMMIT;
ROLLBACK;

View File

@ -24,8 +24,8 @@ if EXIST %AWLDIR%\awl-tables.sql (
)
rem Set DB user, web user, DB config directory, Windows DB config directory
set AWL_DBAUSER=davical_dba
set AWL_APPUSER=davical_app
set AWL_DBAUSER=%3-davical_dba
set AWL_APPUSER=%4-davical_app
set DBA=%AWL_DBAUSER%
rem Need PostgreSQL location

11
debian/changelog vendored
View File

@ -1,3 +1,12 @@
davical (1.1.7-1) unstable; urgency=medium
* New upstream release
* Bump dependency on AWL to 0.59
* Declare compliance with Debian Policy 4.1.3
* Update lintian-overrides
-- Florian Schlichting <fsfs@debian.org> Fri, 12 Jan 2018 00:04:06 +0100
davical (1.1.6-1) unstable; urgency=medium
* New upstream release (closes: #704069, #784282, #856467)
@ -49,7 +58,7 @@ davical (1.1.4-1) unstable; urgency=medium
references since it was moved to its own git repository.
* Don't run make / make built-docs during package build to avoid nasty
surprises; files should be up-to-date in git anyway
* Purge /etc/davical/.keep directory
* Purge /etc/davical/.keep directory
* Clean source tree before creating orig-source tarball
-- Florian Schlichting <fsfs@debian.org> Mon, 11 Jan 2016 23:56:48 +0100

8
debian/control vendored
View File

@ -4,13 +4,13 @@ Priority: optional
Maintainer: Davical Development Team <davical-devel@lists.sourceforge.net>
Uploaders: Andrew McMillan <awm@debian.org>,
Florian Schlichting <fsfs@debian.org>
Standards-Version: 4.1.1
Standards-Version: 4.1.3
Build-Depends: debhelper (>= 9),
dh-exec,
jdupes,
libawl-php (>= 0.58-1~), libawl-php (<< 0.59),
libawl-php (>= 0.59-1~), libawl-php (<< 0.60),
gettext,
php-apigen,
doxygen,
php-cli | php5-cli,
rst2pdf
Vcs-git: https://gitlab.com/davical-project/davical.git
@ -20,7 +20,7 @@ Homepage: https://www.davical.org/
Package: davical
Architecture: all
Depends: ${misc:Depends}, ${perl:Depends},
libawl-php (>= 0.58-1~), libawl-php (<< 0.59),
libawl-php (>= 0.59-1~), libawl-php (<< 0.60),
libdbd-pg-perl,
libyaml-perl,
php | php5,

View File

@ -1,8 +1,8 @@
# this is lintian being stupid and failing to detect our alternative php-cli dependency - remove when dropping php5
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/archive-old-events.php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/cron-sync-ldap.php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/export_calendar.php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/load_calendar.php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/refresh-alarms.php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/sync-remote-caldav.php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/tz-update.php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/archive-old-events.php #!php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/cron-sync-ldap.php #!php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/export_calendar.php #!php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/load_calendar.php #!php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/refresh-alarms.php #!php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/sync-remote-caldav.php #!/usr/bin/php
davical: php-script-but-no-php-cli-dep usr/share/davical/scripts/tz-update.php #!php

9
debian/rules vendored
View File

@ -13,12 +13,17 @@ get-orig-source:
override_dh_auto_build:
make all
override_dh_installchangelogs:
dh_installchangelogs ChangeLog
override_dh_install:
dh_install
rm debian/davical/usr/share/davical/htdocs/.htaccess || true
rm -f debian/davical/usr/share/doc/davical/examples/apache-davical.conf \
debian/davical/usr/share/doc/davical/examples/example-config.php
sed -i -e '1s|^#!/usr/bin/env perl|#!/usr/bin/perl|' debian/davical/usr/share/davical/dba/update-davical-database
override_dh_installchangelogs:
dh_installchangelogs ChangeLog
override_dh_installdocs:
dh_installdocs
find debian/davical-doc/ -name '*.md5' -delete
jdupes -rl debian/davical-doc/

2
debian/watch vendored
View File

@ -1,2 +1,2 @@
version=3
https://gitlab.com/davical-project/davical/tags \/davical-project\/davical\/repository\/r([\d.]*)\/archive.tar.gz
https://gitlab.com/davical-project/davical/tags?sort=updated_desc \/davical-project\/davical\/-\/archive\/r([\d.]+)\/davical-r[\d.]+\.tar\.gz

2494
docs/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@ -83,6 +83,7 @@ $c->permission_scan_depth = 2;
$c->expand_pdo_parameters = true;
$c->home_calendar_name = 'calendar';
$c->home_addressbook_name = 'addresses';
$c->carddav_max_resource_size = 6550000;
$c->enable_row_linking = true;
$c->enable_scheduling = false;
$c->http_auth_mode = 'Basic';
@ -244,8 +245,8 @@ if ( function_exists('awl_set_locale') ) {
*
*/
$c->code_version = 0;
$c->want_awl_version = '0.58';
$c->version_string = '1.1.6'; // The actual version # is replaced into that during the build /release process
$c->want_awl_version = '0.59';
$c->version_string = '1.1.7'; // The actual version # is replaced into that during the build /release process
if ( isset($c->version_string) && preg_match( '/(\d+)\.(\d+)\.(\d+)(.*)/', $c->version_string, $matches) ) {
$c->code_major = $matches[1];
$c->code_minor = $matches[2];

View File

@ -69,7 +69,7 @@ function checkiSchedule () {
$s->remote_selector = $c->scheduling_dkim_selector;
if (!$s->getTxt()) {
if (isset($c->schedule_private_key))
$ret .= '<p>' . translate('TXT record missing for "%s._domainkey.%s" or DNS failure, Private RSA key is configured', $s->remote_selector, $s->domain) . '</p>';
$ret .= '<p>' . sprintf(translate('TXT record missing for "%s._domainkey.%s" or DNS failure, Private RSA key is configured'), $s->remote_selector, $s->domain) . '</p>';
else {
$keys = generateKeys();
$config = '<p>' . translate('please add the following section to your DAViCal configuration file') . '<pre>$c->schedule_private_key = &lt;&lt;&lt;ENDOFKEY' . "\n";

View File

@ -268,6 +268,8 @@ function check_davical_version() {
else {
$result->setDescription( sprintf(i18n('Want: %s, Currently: %s'), $current_version, $c->version_string) );
}
} else {
$result->setDescription( sprintf(i18n('Passed: %s'), $c->version_string) );
}
return $result;
}
@ -282,6 +284,8 @@ function check_awl_version() {
if ( ! $result->getOK() ) {
$result->setDescription( sprintf(i18n('Want: %s, Currently: %s'), $c->want_awl_version, awl_version()) );
if ( $c->want_awl_version < awl_version() ) $result->setClass('dep_warning');
} else {
$result->setDescription( sprintf(i18n('Passed: %s'), $c->want_awl_version) );
}
return $result;

View File

@ -148,8 +148,11 @@ class DAVResource
* @param mixed $parameters If null, an empty Resourced is created.
* If it is an object then it is expected to be a record that was
* read elsewhere.
* @param object $prefetched_collection If provided, the internal collection
* field of the resource is populated with the given data, so it does not need
* to be queried again later
*/
function __construct( $parameters = null ) {
function __construct( $parameters = null, DAVResource $prefetched_collection = null ) {
$this->exists = null;
$this->bound_from = null;
$this->dav_name = null;
@ -172,6 +175,11 @@ class DAVResource
$this->_is_external = false;
$this->_is_addressbook = false;
$this->_is_proxy_resource = false;
if ( isset($prefetched_collection) ) {
$this->collection = $prefetched_collection;
}
if ( isset($parameters) && is_object($parameters) ) {
$this->FromRow($parameters);
}
@ -1287,6 +1295,15 @@ EOQRY;
}
/**
* Returns the name of the timezone for this collection, or the collection containing this resource
*/
function timezone_name() {
if ( !isset($this->collection) ) $this->FetchCollection();
return $this->collection->timezone;
}
/**
* Returns the database row for this resource
*/
@ -1533,8 +1550,8 @@ EOQRY;
* Return an array which is an expansion of the DAV::allprop
*/
function DAV_AllProperties() {
if ( isset($this->dead_properties) ) $this->FetchDeadProperties();
$allprop = array_merge( (isset($this->dead_properties)?$this->dead_properties:array()),
if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
$allprop = array_merge( (isset($this->dead_properties)?array_keys($this->dead_properties):array()),
(isset($include_properties)?$include_properties:array()),
array(
'DAV::getcontenttype', 'DAV::resourcetype', 'DAV::getcontentlength', 'DAV::displayname', 'DAV::getlastmodified',
@ -1842,7 +1859,7 @@ EOQRY;
case 'urn:ietf:params:xml:ns:carddav:max-resource-size':
if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
$reply->NSElement($prop, $tag, 65500 );
$reply->NSElement($prop, $tag, $c->carddav_max_resource_size );
break;
case 'urn:ietf:params:xml:ns:carddav:supported-address-data':

View File

@ -60,7 +60,7 @@ class HTTPAuthSession {
/**
* The constructor, which just calls the type supplied or configured
*/
function HTTPAuthSession() {
function __construct() {
global $c;
if ( ! empty($_SERVER['PHP_AUTH_DIGEST'])) {

View File

@ -59,7 +59,7 @@ class PublicSession {
/**
* The constructor, which just calls the actual type configured
*/
function PublicSession() {
function __construct() {
global $c;
$principal = new Principal('username','unauthenticated');

View File

@ -305,6 +305,18 @@ class RepeatRuleDateTime extends DateTime {
return $this;
}
public static function withFallbackTzid( $date, $fallback_tzid ) {
// Floating times or dates (either VALUE=DATE or with no TZID) can default to the collection's tzid, if one is set
if ($date->GetParameterValue('VALUE') == 'DATE' && isset($fallback_tzid)) {
return new RepeatRuleDateTime($date->Value()."T000000", new RepeatRuleTimeZone($fallback_tzid));
} else if ($date->GetParameterValue('TZID') === null && isset($fallback_tzid)) {
return new RepeatRuleDateTime($date->Value(), new RepeatRuleTimeZone($fallback_tzid));
} else {
return new RepeatRuleDateTime($date);
}
}
public function __toString() {
return (string)parent::format(self::$Format) . ' ' . parent::getTimeZone()->getName();
@ -562,7 +574,7 @@ class RepeatRuleDateRange {
if ( $this->until == null ) return ($other->until > $this->from);
if ( $this->from == null ) return ($other->from < $this->until);
if ( $other->until == null ) return ($this->until > $other->from);
if ( $other->from == null ) return ($thi->from < $other->until);
if ( $other->from == null ) return ($this->from < $other->until);
return !( $this->until < $other->from || $this->from > $other->until );
}
@ -1144,7 +1156,8 @@ function rdate_expand( $dtstart, $property, $component, $range_end = null, $is_d
*
* @return array An array keyed on the UTC dates, referring to the component
*/
function rrule_expand( $dtstart, $property, $component, $range_end, $is_date=null, $return_floating_times=false ) {
function rrule_expand( $dtstart, $property, $component, $range_end, $is_date=null, $return_floating_times=false, $fallback_tzid=null ) {
global $c;
$expansion = array();
$recur = $component->GetProperty($property);
@ -1153,7 +1166,7 @@ function rrule_expand( $dtstart, $property, $component, $range_end, $is_date=nul
$this_start = $component->GetProperty('DTSTART');
if ( isset($this_start) ) {
$this_start = new RepeatRuleDateTime($this_start);
$this_start = RepeatRuleDateTime::withFallbackTzid($this_start, $fallback_tzid);
}
else {
$this_start = clone($dtstart);
@ -1164,11 +1177,15 @@ function rrule_expand( $dtstart, $property, $component, $range_end, $is_date=nul
if ( DEBUG_RRULE ) printf( "RRULE: %s (floating: %s)\n", $recur, ($return_floating_times?"yes":"no") );
$rule = new RepeatRule( $this_start, $recur, $is_date, $return_floating_times );
$i = 0;
$result_limit = 1000;
if ( !isset($c->rrule_expansion_limit) ) $c->rrule_expansion_limit = 5000;
while( $date = $rule->next($return_floating_times) ) {
// if ( DEBUG_RRULE ) printf( "[%3d] %s\n", $i, $date->UTC() );
$expansion[$date->FloatOrUTC($return_floating_times)] = $component;
if ( $i++ >= $result_limit || $date > $range_end ) break;
if ( $date > $range_end ) break;
if ( $i++ >= $c->rrule_expansion_limit ) {
dbg_error_log( 'ERROR', "Hit rrule expansion limit of ".$c->rrule_expansion_limit." - increase rrule_expansion_limit in config to avoid events missing from freebusy" );
}
}
// if ( DEBUG_RRULE ) print_r( $expansion );
return $expansion;
@ -1215,6 +1232,26 @@ function expand_event_instances( vComponent $vResource, $range_start = null, $ra
$is_date = false;
$has_repeats = false;
$dtstart_type = 'DTSTART';
$components_prefix = [];
$components_base_events = [];
$components_override_events = [];
foreach ($components AS $k => $comp) {
if ( $comp->GetType() != 'VEVENT' && $comp->GetType() != 'VTODO' && $comp->GetType() != 'VJOURNAL' ) {
// Other types of component (such as VTIMEZONE) go first
$components_prefix[] = $comp;
} else if ($comp->GetProperty('RECURRENCE-ID') === null) {
// This is the base event, we need to handle it first
$components_base_events[] = $comp;
} else {
// This is an override of an event instance, handle it last
$components_override_events[] = $comp;
}
}
$components = array_merge($components_prefix, $components_base_events, $components_override_events);
foreach( $components AS $k => $comp ) {
if ( $comp->GetType() != 'VEVENT' && $comp->GetType() != 'VTODO' && $comp->GetType() != 'VJOURNAL' ) {
continue;
@ -1248,6 +1285,8 @@ function expand_event_instances( vComponent $vResource, $range_start = null, $ra
}
else {
unset($instances[$recur_utc]);
// This is a single instance of a recurring event, it can not in itself produce extra instances due to RRULE etc
continue;
}
}
else if ( DEBUG_RRULE ) {
@ -1359,36 +1398,43 @@ function expand_event_instances( vComponent $vResource, $range_start = null, $ra
$p = $comp->GetProperty('RECURRENCE-ID');
if ( isset($p) && $p->Value() != '') {
$recurrence_id = $p->Value();
if ( !isset($new_components[$recurrence_id]) ) {
// The component we're replacing is outside the range. Unless the replacement
// is *in* the range we will move along to the next one.
$dtstart_prop = $comp->GetProperty($dtstart_type);
if ( !isset($dtstart_prop) ) continue; // No start: no expansion. Note that we consider 'DUE' to be a start if DTSTART is missing
$dtstart = new RepeatRuleDateTime( $dtstart_prop );
$is_date = $dtstart->isDate();
if ( $return_floating_times ) $dtstart->setAsFloat();
$dtstart = $dtstart->FloatOrUTC($return_floating_times);
if ( $dtstart > $end_utc ) continue; // Start after end of range, skip it
$end_type = ($comp->GetType() == 'VTODO' ? 'DUE' : 'DTEND');
$duration = $comp->GetProperty('DURATION');
if ( !isset($duration) || $duration->Value() == '' ) {
$instance_end = $comp->GetProperty($end_type);
if ( isset($instance_end) ) {
$dtend = new RepeatRuleDateTime( $instance_end );
if ( $return_floating_times ) $dtend->setAsFloat();
$dtend = $dtend->FloatOrUTC($return_floating_times);
}
else {
$dtend = $dtstart + ($is_date ? $dtstart + 86400 : 0 );
}
$dtstart_prop = $comp->GetProperty('DTSTART');
if ( !isset($dtstart_prop) && $comp->GetType() != 'VTODO' ) {
$dtstart_prop = $comp->GetProperty('DUE');
}
if ( !isset($new_components[$recurrence_id]) && !isset($dtstart_prop) ) continue; // No start: no expansion. Note that we consider 'DUE' to be a start if DTSTART is missing
$dtstart_rrdt = new RepeatRuleDateTime( $dtstart_prop );
$is_date = $dtstart_rrdt->isDate();
if ( $return_floating_times ) $dtstart_rrdt->setAsFloat();
$dtstart = $dtstart_rrdt->FloatOrUTC($return_floating_times);
if ( !isset($new_components[$recurrence_id]) && $dtstart > $end_utc ) continue; // Start after end of range, skip it
$end_type = ($comp->GetType() == 'VTODO' ? 'DUE' : 'DTEND');
$duration = $comp->GetProperty('DURATION');
if ( !isset($duration) || $duration->Value() == '' ) {
$instance_end = $comp->GetProperty($end_type);
if ( isset($instance_end) ) {
$dtend_rrdt = new RepeatRuleDateTime( $instance_end );
if ( $return_floating_times ) $dtend_rrdt->setAsFloat();
$dtend = $dtend_rrdt->FloatOrUTC($return_floating_times);
$comp->AddProperty('DURATION', Rfc5545Duration::fromTwoDates($dtstart_rrdt, $dtend_rrdt) );
}
else {
$duration = new Rfc5545Duration($duration->Value());
$dtend = $dtstart + $duration->asSeconds();
$dtend = $dtstart + ($is_date ? $dtstart + 86400 : 0 );
}
if ( $dtend < $start_utc ) continue; // End before start of range: skip that too.
}
else {
$duration = new Rfc5545Duration($duration->Value());
$dtend = $dtstart + $duration->asSeconds();
}
if ( !isset($new_components[$recurrence_id]) && $dtend < $start_utc ) continue; // End before start of range: skip that too.
if ( DEBUG_RRULE ) printf( "Replacing overridden instance at %s\n", $recurrence_id);
$new_components[$recurrence_id] = $comp;
}
@ -1406,12 +1452,12 @@ function expand_event_instances( vComponent $vResource, $range_start = null, $ra
* @throws Exception (1) When DTSTART is not present but the RFC says MUST and (2) when we get an unsupported component
* @return RepeatRuleDateRange
*/
function getComponentRange(vComponent $comp) {
function getComponentRange(vComponent $comp, string $fallback_tzid = null) {
$dtstart_prop = $comp->GetProperty('DTSTART');
$duration_prop = $comp->GetProperty('DURATION');
if ( isset($duration_prop) ) {
if ( !isset($dtstart_prop) ) throw new Exception('Invalid '.$comp->GetType().' containing DURATION without DTSTART', 0);
$dtstart = new RepeatRuleDateTime($dtstart_prop);
$dtstart = RepeatRuleDateTime::withFallbackTzid($dtstart_prop, $fallback_tzid);
$dtend = clone($dtstart);
$dtend->modify(new Rfc5545Duration($duration_prop->Value()));
}
@ -1435,17 +1481,17 @@ function getComponentRange(vComponent $comp) {
}
if ( isset($dtstart_prop) )
$dtstart = new RepeatRuleDateTime($dtstart_prop);
$dtstart = RepeatRuleDateTime::withFallbackTzid($dtstart_prop, $fallback_tzid);
else
$dtstart = null;
if ( isset($dtend_prop) )
$dtend = new RepeatRuleDateTime($dtend_prop);
$dtend = RepeatRuleDateTime::withFallbackTzid($dtend_prop, $fallback_tzid);
else
$dtend = null;
if ( isset($completed_prop) ) {
$completed = new RepeatRuleDateTime($completed_prop);
$completed = RepeatRuleDateTime::withFallbackTzid($completed_prop, $fallback_tzid);
if ( !isset($dtstart) || (isset($dtstart) && $completed < $dtstart) ) $dtstart = $completed;
if ( !isset($dtend) || (isset($dtend) && $completed > $dtend) ) $dtend = $completed;
}
@ -1461,7 +1507,7 @@ function getComponentRange(vComponent $comp) {
* @param object $vResource A vComponent which is a VCALENDAR containing components needing expansion
* @return RepeatRuleDateRange Representing the range of time covered by the event.
*/
function getVCalendarRange( $vResource ) {
function getVCalendarRange( $vResource, string $fallback_tzid = null ) {
$components = $vResource->GetComponents();
$dtstart = null;
@ -1471,7 +1517,7 @@ function getVCalendarRange( $vResource ) {
$has_repeats = false;
foreach( $components AS $k => $comp ) {
if ( $comp->GetType() == 'VTIMEZONE' ) continue;
$range = getComponentRange($comp);
$range = getComponentRange($comp, $fallback_tzid);
$dtstart = $range->from;
if ( !isset($dtstart) ) continue;
$duration = $range->getDuration();
@ -1490,7 +1536,7 @@ function getVCalendarRange( $vResource ) {
$range_end = new RepeatRuleDateTime();
$range_end->modify('+150 years');
}
$instances += rrule_expand($dtstart, 'RRULE', $comp, $range_end);
$instances += rrule_expand($dtstart, 'RRULE', $comp, $range_end, null, false, $fallback_tzid);
$instances += rdate_expand($dtstart, 'RDATE', $comp, $range_end);
foreach ( rdate_expand($dtstart, 'EXDATE', $comp, $range_end) AS $k => $v ) {
unset($instances[$k]);

View File

@ -219,16 +219,23 @@ class WritableCollection extends DAVResource {
$calitem_params[':due'] = $first->GetPValue('DUE');
$calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
$calitem_params[':status'] = $first->GetPValue('STATUS');
$range = getVCalendarRange($vcal, $this->timezone_name());
$calitem_params[':first_instance_start'] = isset($range->from) ? $range->from->UTC() : null;
$calitem_params[':last_instance_end'] = isset($range->until) ? $range->until->UTC() : null;
if ( $create_resource ) {
$sql = <<<EOSQL
INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
dtstart, dtend, summary, location, class, transp,
description, rrule, tz_id, last_modified, url, priority,
created, due, percent_complete, status, collection_id )
created, due, percent_complete, status, collection_id,
first_instance_start, last_instance_end )
VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp,
:dtstart, $dtend, :summary, :location, :class, :transp,
:description, :rrule, :tzid, :modified, :url, :priority,
:created, :due, :percent_complete, :status, $collection_id )
:created, :due, :percent_complete, :status, $collection_id,
:first_instance_start, :last_instance_end)
EOSQL;
$sync_change = 201;
}
@ -237,7 +244,8 @@ EOSQL;
UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location, class=:class, transp=:transp,
description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
created=:created, due=:due, percent_complete=:percent_complete, status=:status
created=:created, due=:due, percent_complete=:percent_complete, status=:status,
first_instance_start=:first_instance_start, last_instance_end=:last_instance_end
WHERE user_no=:user_no AND dav_name=:dav_name
EOSQL;
$sync_change = 200;

View File

@ -83,6 +83,7 @@ $c->permission_scan_depth = 2;
$c->expand_pdo_parameters = true;
$c->home_calendar_name = 'calendar';
$c->home_addressbook_name = 'addresses';
$c->carddav_max_resource_size = 6550000;
$c->enable_row_linking = true;
$c->enable_scheduling = false;
$c->http_auth_mode = 'Basic';
@ -344,6 +345,7 @@ function getStatusMessage($status) {
* Construct a URL from the supplied dav_name. The URL will be urlencoded,
* except for any '/' characters in it.
* @param string $partial_path The part of the path after the script name
* @param boolean $force_script Whether to force-include caldav.php in the URL. Defaults to false.
*/
function ConstructURL( $partial_path, $force_script = false ) {
global $c;
@ -379,9 +381,9 @@ function ConstructURL( $partial_path, $force_script = false ) {
/**
* Deconstruct a dav_name from the supplied URL. The dav_name will be urldecoded.
*
* @param string $partial_path The part of the path after the script name
* @param string $url The full path including the script name
*/
function DeconstructURL( $url, $force_script = false ) {
function DeconstructURL( $url ) {
global $c;
$dav_name = rawurldecode($url);

View File

@ -231,6 +231,9 @@ function UpdateCollectionTimezones( $username, $new_timezone=null ) {
if ( empty($new_timezone) ) return;
$qry = new AwlQuery('UPDATE collection SET timezone=? WHERE dav_name LIKE ? AND is_calendar', '/'.$username.'/%', $new_timezone);
$qry->Exec();
require_once("instance_range.php");
update_instance_ranges($dav_resource->dav_name());
}
/**

View File

@ -133,8 +133,7 @@ function process_ace( $grantor, $by_principal, $by_collection, $ace ) {
if ( privilege_to_bits('all') != $privileges ) {
$request->PreconditionFailed(403, 'no-protected-ace-conflict', 'Owner must always have all permissions' );
}
continue; // and then we ignore it, since it's protected
break;
break; // and then we ignore it, since it's protected
case 'DAV::unauthenticated':
$request->PreconditionFailed(403, 'allowed-principal', 'May not set privileges for unauthenticated users' );
@ -175,7 +174,7 @@ function process_ace( $grantor, $by_principal, $by_collection, $ace ) {
case 'DAV::authenticated':
$principal_type = 'authenticated';
if ( bindec($grantor->GetProperty('default_privileges')) == $privileges ) continue; // There is no change, so skip it
if ( bindec($grantor->GetProperty('default_privileges')) == $privileges ) break; // There is no change, so skip it
$sqlparms = array( ':privileges' => $privileges );
if ( isset($by_collection) ) {
$sql = 'UPDATE collection SET default_privileges=:privileges::INT::BIT(24) WHERE collection_id=:by_collection';

View File

@ -82,7 +82,7 @@ if ( preg_match ( '{^(?:https?://|file:///)([^/]+)(:[0-9]\+)?/.+$}', $href, $mat
$qry = new AwlQuery( 'SELECT bind_id from dav_binding where dav_name = :dav_name', array( ':dav_name' => $destination_path ) );
if ( ! $qry->Exec('BIND',__LINE__,__FILE__) || $qry->rows() != 1 || !($row = $qry->Fetch()) )
$request->DoResponse(500,translate('Database Error'));
fetch_external ( $row->bind_id, '' );
fetch_external ( $row->bind_id, '', $c->external_ua_string );
$request->DoResponse(201);
}
else {

View File

@ -105,7 +105,9 @@ function get_collection_contents( $depth, $collection, $parent_path = null ) {
global $c, $session, $request, $reply, $property_list;
// for http header comparison
function compare_val_with_re($val, $re){ return preg_match($re, $val)===1 ? 0 : 1; }
if (! function_exists ('compare_val_with_re') ) {
function compare_val_with_re($val, $re){ return preg_match($re, $val)===1 ? 0 : 1; }
}
$bound_from = $collection->bound_from();
$bound_to = $collection->dav_name();

View File

@ -275,6 +275,8 @@ foreach( $setprops AS $k => $setting ) {
$qry->QDo('UPDATE collection SET timezone = :tzid WHERE dav_name = :dav_name',
array( ':tzid' => $tzid, ':dav_name' => $dav_resource->dav_name()) );
require_once("instance_range.php");
update_instance_ranges($dav_resource->dav_name());
}
else {
add_failure('set', $tag, 'HTTP/1.1 409 Conflict', translate("calendar-timezone property is only valid for a calendar."));
@ -389,6 +391,8 @@ foreach( $rmprops AS $k => $setting ) {
case 'urn:ietf:params:xml:ns:caldav:calendar-timezone':
if ( $dav_resource->IsCollection() && $dav_resource->IsCalendar() && ! $dav_resource->IsBinding() ) {
$qry->QDo('UPDATE collection SET timezone = NULL WHERE dav_name = :dav_name', array( ':dav_name' => $dav_resource->dav_name()) );
require_once("instance_range.php");
update_instance_ranges($dav_resource->dav_name());
}
else {
add_failure('rm', $tag, 'HTTP/1.1 403 Forbidden',

View File

@ -622,6 +622,7 @@ function do_scheduling_requests( vCalendar $resource, $create, $old_data = null
if ( !$qry->Exec('PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
dbg_error_log( 'PUT', "Could not find event in attendee's calendars" );
$attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
unset($row); // signal "not found" below
} else {
$row = $qry->Fetch();
$r = new DAVResource($row);
@ -639,13 +640,40 @@ function do_scheduling_requests( vCalendar $resource, $create, $old_data = null
$response = '5.2'; // No scheduling support for user
}
else {
if ($attendee_is_new || !isset($row)) {
$this_schedule_request = clone($schedule_request);
$this_resource = clone($resource);
} else {
dbg_error_log('PUT',"adjusting only some major properties in %s's instance of the event", $schedule_target->username());
$this_resource = new vCalendar($row->caldav_data);
$these_events = $this_resource->GetComponents("VEVENT");
$this_event = $these_events[0];
$events = $resource->GetComponents("VEVENT");
$event = $events[0];
// ??? CLASS, CREATED, LAST-MODIFIED, ATTENDEE, ORGANIZER, others???
$this_event->SetProperties( $event->GetProperties('DTSTAMP'), 'DTSTAMP' );
$this_event->SetProperties( $event->GetProperties('SEQUENCE'), 'SEQUENCE' );
$this_event->SetProperties( $event->GetProperties('DTSTART'), 'DTSTART' );
$this_event->SetProperties( $event->GetProperties('DTEND'), 'DTEND' );
$this_event->SetProperties( $event->GetProperties('DURATION'), 'DURATION' );
$this_event->SetProperties( $event->GetProperties('SUMMARY'), 'SUMMARY' );
$this_event->SetProperties( $event->GetProperties('LOCATION'), 'LOCATION' );
$this_event->SetProperties( $event->GetProperties('DESCRIPTION'), 'DESCRIPTION' );
$this_event->SetProperties( $event->GetProperties('GEO'), 'GEO' );
$this_event->SetProperties( $event->GetProperties('RESOURCES'), 'RESOURCES' );
$this_event->SetProperties( $event->GetProperties('STATUS'), 'STATUS' );
$this_event->SetProperties( $event->GetProperties('ATTENDEE'), 'ATTENDEE' );
$this_resource->SetComponents($these_events, "VEVENT");
$this_schedule_request = clone($this_resource);
$this_schedule_request->AddProperty('METHOD', 'REQUEST');
}
$attendee_inbox = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-inbox')));
if ( ! $attendee_inbox->HavePrivilegeTo('schedule-deliver-invite') ) {
$response = '3.8'; // No authority to deliver invitations to user.
}
else if ( $attendee_inbox->WriteCalendarMember($schedule_request, $attendee_is_new) !== false ) {
else if ( $attendee_inbox->WriteCalendarMember($this_schedule_request, $attendee_is_new) !== false ) {
$response = '1.2'; // Scheduling invitation delivered successfully
if ( $attendee_calendar->WriteCalendarMember($orig_resource, $attendee_is_new) === false ) {
if ( $attendee_calendar->WriteCalendarMember($this_resource, $attendee_is_new) === false ) {
dbg_error_log('ERROR','Could not write %s calendar member to %s', ($attendee_is_new?'new':'updated'),
$attendee_calendar->dav_name(), $attendee_calendar->dav_name(), $schedule_target->username());
trace_bug('Failed to write scheduling resource.');
@ -681,9 +709,52 @@ function do_scheduling_requests( vCalendar $resource, $create, $old_data = null
if ( !$create ) {
foreach( $removed_attendees AS $attendee ) {
$email = preg_replace( '/^mailto:/i', '', $attendee->Value() );
$schedule_target = new Principal('email',$email);
if ( $schedule_target->Exists() ) {
$attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
// Instead of always writing to schedule-default-calendar, we first try to
// find a calendar with an existing instance of the event.
$sql = 'SELECT caldav_data.dav_name, caldav_data.caldav_data, caldav_data.collection_id FROM caldav_data JOIN calendar_item USING(dav_id) ';
$sql .= 'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
$sql .= 'AND uid=? LIMIT 1';
$qry = new AwlQuery($sql,$schedule_target->user_no(), $uid);
if ( !$qry->Exec('PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
dbg_error_log( 'PUT', "Could not find event in attendee's calendars" );
$attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
} else {
$row = $qry->Fetch();
$r = new DAVResource($row);
$attendee_calendar = new WritableCollection(array('path' => $r->parent_path()));
if ($attendee_calendar->IsCalendar()) {
dbg_error_log( 'PUT', "found the event in attendee's calendar %s", $attendee_calendar->dav_name() );
} else {
dbg_error_log( 'PUT', 'could not find the event in any calendar, using schedule-default-calendar');
$attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
}
dbg_error_log('PUT',"marking %s's instance of the event to show that the user's invitation has been revoked", $schedule_target->username());
$this_resource = new vCalendar($row->caldav_data);
$these_events = $this_resource->GetComponents("VEVENT");
$this_event = $these_events[0];
$properties[] = new vProperty( "DESCRIPTION:Your invitation to this event has been revoked." );
$this_event->SetProperties( $properties, 'DESCRIPTION' );
$properties[] = new vProperty( "STATUS:CANCELLED" );
$this_event->SetProperties( $properties, 'STATUS' );
$this_event->SetProperties( null, 'ATTENDEE' );
$this_resource->SetComponents($these_events, "VEVENT");
$this_schedule_request = clone($this_resource);
$this_schedule_request->AddProperty('METHOD', 'REQUEST');
$attendee_inbox = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-inbox')));
if ( ! $attendee_inbox->HavePrivilegeTo('schedule-deliver-invite') ) {
$response = '3.8'; // No authority to deliver invitations to user.
}
else if ( $attendee_inbox->WriteCalendarMember($this_schedule_request, false) !== false ) {
$response = '1.2'; // Scheduling invitation delivered successfully
if ( $attendee_calendar->WriteCalendarMember($this_resource, false) === false ) {
dbg_error_log('ERROR','Could not write updated calendar member');
trace_bug('Failed to write scheduling resource.');
}
}
}
}
}
}
@ -1144,6 +1215,10 @@ EOSQL;
$calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
$calitem_params[':status'] = $first->GetPValue('STATUS');
// Intentionally not populating first_instance_start and last_instance_end
// here, As they may depend on the default tzid of the collection, which as
// I understand it hasn't yet been set
if ( $inserting ) {
$created = $first->GetPValue('CREATED');
if ( $created == '00001231T000000Z' ) $created = '20001231T000000Z';
@ -1325,14 +1400,6 @@ function write_resource( DAVResource $resource, $caldav_data, DAVResource $colle
$qry = new AwlQuery();
$qry->Begin();
$dav_params = array(
':etag' => $etag,
':dav_data' => $caldav_data,
':caldav_type' => $resource_type,
':session_user' => $author,
':weak_etag' => $weak_etag
);
$calitem_params = array(
':etag' => $etag
);
@ -1351,6 +1418,48 @@ function write_resource( DAVResource $resource, $caldav_data, DAVResource $colle
}
$dav_id = $row->dav_id;
$old_dav_data = $row->caldav_data;
// reset all attendees' partstat to needs-action in vcal and caldav_date here, if time of event has been modified.
if ($old_dav_data) {
$modified = false;
$oldvcal = new vCalendar( $old_dav_data );
$oldr = $oldvcal->GetComponents('VTIMEZONE',false);
$oldfirst = $oldr[0];
$dtstart = $first->GetPValue('DTSTART');
$olddtstart = $oldfirst->GetPValue('DTSTART');
if (strcmp($dtstart, $olddtstart)) $modified = true;
$dtend = $first->GetPValue('DTEND');
$olddtend = $oldfirst->GetPValue('DTEND');
if (strcmp($dtend, $olddtend)) $modified = true;
$duration = $first->GetPValue('DURATION');
$oldduration = $oldfirst->GetPValue('DURATION');
$organizer = $vcal->GetOrganizer();
if (strcmp($duration, $oldduration)) $modified = true;
if (($modified == true) && !($organizer === false || empty($organizer))) {
dbg_error_log( 'PUT', "event time attributes have been modified, reset all attendees' PARTSTAT to NEEDS-ACTION.");
$organizer_email = preg_replace( '/^mailto:/i', '', $organizer->Value() );
$attendees = $first->GetProperties('ATTENDEE');
foreach( $attendees AS $attendee ) {
$attendee_email = preg_replace( '/^mailto:/', '', $attendee->Value() );
if ( $attendee_email != $organizer_email ) {
$attendee->SetParameterValue("PARTSTAT", "NEEDS-ACTION");
$attendee->SetParameterValue("SCHEDULE-STATUS", "1.0");
}
}
$first->SetProperties($attendees,'ATTENDEE');
$caldav_data = $vcal->Render(null, true);
dbg_error_log( 'PUT', "event time attributes have been modified, reset all attendees' PARTSTAT completed.");
}
}
$dav_params = array(
':etag' => $etag,
':dav_data' => $caldav_data,
':caldav_type' => $resource_type,
':session_user' => $author,
':weak_etag' => $weak_etag
);
$dav_params[':dav_id'] = $dav_id;
$calitem_params[':dav_id'] = $dav_id;
@ -1491,7 +1600,11 @@ function write_resource( DAVResource $resource, $caldav_data, DAVResource $colle
$calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
$calitem_params[':status'] = $first->GetPValue('STATUS');
// force re-render the object (to get the same representation for all attendiees)
$range = getVCalendarRange($vcal, $resource->timezone_name());
$calitem_params[':first_instance_start'] = isset($range->from) ? $range->from->UTC() : null;
$calitem_params[':last_instance_end'] = isset($range->until) ? $range->until->UTC() : null;
// force re-render the object (to get the same representation for all attendees)
$vcal->Render(null, true);
if ( !$collection->IsSchedulingCollection() ) {
@ -1527,11 +1640,13 @@ function write_resource( DAVResource $resource, $caldav_data, DAVResource $colle
INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
dtstart, dtend, summary, location, class, transp,
description, rrule, tz_id, last_modified, url, priority,
created, due, percent_complete, status, collection_id )
created, due, percent_complete, status, collection_id,
first_instance_start, last_instance_end )
VALUES ( :user_no, :dav_name, :dav_id, :etag, :uid, :dtstamp,
:dtstart, $dtend, :summary, :location, :class, :transp,
:description, :rrule, :tzid, :modified, :url, :priority,
:created, :due, :percent_complete, :status, :collection_id )
:created, :due, :percent_complete, :status, $collection_id,
:first_instance_start, :last_instance_end)
EOSQL;
$sync_change = 201;
$calitem_params[':collection_id'] = $collection_id;
@ -1545,7 +1660,8 @@ UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location,
class=:class, transp=:transp, description=:description, rrule=:rrule,
tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
due=:due, percent_complete=:percent_complete, status=:status
due=:due, percent_complete=:percent_complete, status=:status,
first_instance_start=:first_instance_start, last_instance_end=:last_instance_end
WHERE dav_id=:dav_id
EOSQL;
$sync_change = 200;

View File

@ -183,7 +183,7 @@ function SqlFilterFragment( $filter, $components, $property = null, $parameter =
$subfilter = $v->GetContent();
if ( is_array( $subfilter ) ) {
$success = SqlFilterFragment( $subfilter, $components, $property, $parameter );
if ( $success === false ) continue; else {
if ( $success === false ) continue 2; else {
$sql .= $success['sql'];
$params = array_merge( $params, $success['params'] );
}
@ -220,12 +220,12 @@ function SqlFilterFragment( $filter, $components, $property = null, $parameter =
$need_post_filter = true;
unset($subproperty);
dbg_error_log("calquery", "Could not handle 'prop-filter' on %s in SQL", $propertyname );
continue;
continue 3;
}
if ( isset($subproperty) ) {
$subfilter = $v->GetContent();
$success = SqlFilterFragment( $subfilter, $components, $subproperty, $parameter );
if ( $success === false ) continue; else {
if ( $success === false ) continue 2; else {
$sql .= $success['sql'];
$params = array_merge( $params, $success['params'] );
}
@ -238,7 +238,7 @@ function SqlFilterFragment( $filter, $components, $property = null, $parameter =
$parameter = $v->GetAttribute("name");
$subfilter = $v->GetContent();
$success = SqlFilterFragment( $subfilter, $components, $property, $parameter );
if ( $success === false ) continue; else {
if ( $success === false ) continue 2; else {
$sql .= $success['sql'];
$params = array_merge( $params, $success['params'] );
}

View File

@ -144,7 +144,7 @@ EOSQL;
else if ( $object->sync_status == 201 && $first_status == 404 ) {
// ... Delete ... Create ... is indicated as a create, but don't forget we started with a delete
array_pop($responses);
$dav_resource = new DAVResource($object);
$dav_resource = new DAVResource($object, $collection);
$resultset = $dav_resource->GetPropStat($proplist,$reply);
array_unshift($resultset, new XMLElement( 'href', ConstructURL($object->dav_name)));
$responses[] = new XMLElement( 'response', $resultset );
@ -167,7 +167,7 @@ EOSQL;
$first_status = 404;
}
else {
$dav_resource = new DAVResource($object);
$dav_resource = new DAVResource($object, $collection);
$resultset = $dav_resource->GetPropStat($proplist,$reply);
array_unshift($resultset, new XMLElement( 'href', ConstructURL($object->dav_name)));
$first_status = $object->sync_status;

View File

@ -48,7 +48,7 @@ class CalDAVClient {
* @param string $pass The password for that user
* @param string $calendar The name of the calendar (not currently used)
*/
function CalDAVClient( $base_url, $user, $pass, $calendar = '' ) {
function __construct( $base_url, $user, $pass, $calendar = '' ) {
$this->user = $user;
$this->pass = $pass;
$this->calendar = $calendar;

View File

@ -49,7 +49,7 @@ function fetch_external ( $bind_id, $min_age = '1 hour', $ua_string )
$sql = 'SELECT collection.*, collection.dav_name AS path, dav_binding.external_url AS external_url FROM dav_binding LEFT JOIN collection ON (collection.collection_id=bound_source_id) WHERE bind_id = :bind_id';
$params = array( ':bind_id' => $bind_id );
if ( strlen ( $min_age ) > 2 ) {
$sql .= ' AND collection.modified + interval :interval < NOW()';
$sql .= ' AND ( collection.modified + interval :interval < NOW() OR collection.modified is NULL )';
$params[':interval'] = $min_age;
}
$sql .= ' ORDER BY modified DESC LIMIT 1';
@ -65,7 +65,7 @@ function fetch_external ( $bind_id, $min_age = '1 hour', $ua_string )
curl_setopt ( $curl, CURLOPT_TIMEVALUE, $local_ts->format("U") );
curl_setopt ( $curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE );
if ( isset( $ua_string ) )
curl_setopt($curl, CURLOPT_USERAGENT, $ua_string);
curl_setopt($curl, CURLOPT_USERAGENT, $ua_string);
dbg_error_log("external", "checking external resource for remote changes " . $row->external_url );
$ics = curl_exec ( $curl );
$info = curl_getinfo ( $curl );
@ -85,7 +85,6 @@ function fetch_external ( $bind_id, $min_age = '1 hour', $ua_string )
curl_setopt ( $curl, CURLOPT_NOBODY, false );
curl_setopt ( $curl, CURLOPT_HEADER, false );
$ics = curl_exec ( $curl );
curl_close ( $curl );
if ( is_string ( $ics ) && strlen ( $ics ) > 20 ) {
// BUGlet: should track server-time instead of local-time
$qry = new AwlQuery( 'UPDATE collection SET modified=NOW(), dav_etag=:etag WHERE collection_id = :cid',
@ -94,7 +93,14 @@ function fetch_external ( $bind_id, $min_age = '1 hour', $ua_string )
require_once ( 'caldav-PUT-functions.php');
import_collection ( $ics , $row->user_no, $row->path, 'External Fetch' , false ) ;
return true;
} else {
if ( curl_errno($curl) ) {
dbg_error_log('ERROR', 'external:Fetch of %s (%s) failed with curl error %s: %s', $row->collection_id, $row->external_url, curl_errno($curl), curl_error($curl));
} else {
dbg_error_log('ERROR', 'external:Not importing unusual ics data: "%s"', $ics);
}
}
curl_close ( $curl );
}
else {
dbg_error_log("external", "external resource up to date or not found id(%s)", $bind_id );
@ -111,13 +117,13 @@ function update_external ( $request )
dbg_error_log("external", "external resource cannot be fetched without curl, please install curl");
return ;
}
$sql = 'SELECT bind_id, external_url as url from dav_binding LEFT JOIN collection ON (collection.collection_id=bound_source_id) WHERE dav_binding.dav_name = :dav_name AND collection.modified + interval :interval < NOW()';
$sql = 'SELECT bind_id, external_url as url from dav_binding LEFT JOIN collection ON (collection.collection_id=bound_source_id) WHERE dav_binding.dav_name = :dav_name AND ( collection.modified + interval :interval < NOW() OR collection.modified is NULL )';
$qry = new AwlQuery( $sql, array ( ':dav_name' => $request->dav_name(), ':interval' => $c->external_refresh . ' minutes' ) );
dbg_error_log("external", "checking if external resource needs update");
if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $row = $qry->Fetch() ) {
if ( $row->bind_id != 0 ) {
dbg_error_log("external", "external resource needs updating, this might take a minute : %s", $row->url );
fetch_external ( $row->bind_id, $c->external_refresh . ' minutes', $c->external_ua_string );
fetch_external ( $row->bind_id, $c->external_refresh . ' minutes', @$c->external_ua_string );
}
}
}

View File

@ -20,7 +20,10 @@ function get_freebusy( $path_match, $range_start, $range_end, $bin_privs = null
}
$params = array( ':path_match' => $path_match, ':start' => $range_start->UTC(), ':end' => $range_end->UTC() );
$where = ' WHERE caldav_data.dav_name ~ :path_match ';
$where .= 'AND rrule_event_overlaps( dtstart, dtend, rrule, :start, :end) ';
$where .= "AND (";
$where .= " (calendar_item.first_instance_start <= :end AND (:start <= calendar_item.last_instance_end OR calendar_item.last_instance_end IS NULL)) ";
$where .= " OR (calendar_item.first_instance_start IS NULL AND rrule_event_overlaps( dtstart, dtend, rrule, :start, :end)) ";
$where .= ") ";
$where .= "AND caldav_data.caldav_type IN ( 'VEVENT', 'VTODO' ) ";
$where .= "AND (calendar_item.transp != 'TRANSPARENT' OR calendar_item.transp IS NULL) ";
$where .= "AND (calendar_item.status != 'CANCELLED' OR calendar_item.status IS NULL) ";
@ -34,7 +37,8 @@ function get_freebusy( $path_match, $range_start, $range_end, $bin_privs = null
$sql = 'SELECT caldav_data.caldav_data, calendar_item.rrule, calendar_item.transp, calendar_item.status, ';
$sql .= "to_char(calendar_item.dtstart at time zone 'GMT',".AWLDatabase::SqlUTCFormat.') AS start, ';
$sql .= "to_char(calendar_item.dtend at time zone 'GMT',".AWLDatabase::SqlUTCFormat.') AS finish, ';
$sql .= "calendar_item.class, calendar_item.dav_id ";
$sql .= "calendar_item.class, calendar_item.dav_id, ";
$sql .= "collection.timezone AS collection_tzid ";
$sql .= 'FROM caldav_data INNER JOIN calendar_item USING(dav_id,user_no,dav_name,collection_id) ';
$sql .= 'INNER JOIN collection USING(collection_id)';
$sql .= $where;
@ -53,6 +57,7 @@ function get_freebusy( $path_match, $range_start, $range_end, $bin_privs = null
$ics = new vComponent($calendar_object->caldav_data);
$expanded = expand_event_instances($ics, $range_start, $range_end);
$expansion = $expanded->GetComponents( array('VEVENT'=>true,'VTODO'=>true,'VJOURNAL'=>true) );
$collection_tzid = $calendar_object->collection_tzid;
dbg_error_log( "freebusy", "=================== $calendar_object->dav_id ======================== %s -> %s, %s %s", $calendar_object->start, $calendar_object->finish, $calendar_object->class, $extra );
$dtstart_type = 'DTSTART';
foreach( $expansion AS $k => $v ) {
@ -62,7 +67,10 @@ function get_freebusy( $path_match, $range_start, $range_end, $bin_privs = null
$dtstart_type = 'DUE';
$start_date = $v->GetProperty($dtstart_type);
}
$start_date = new RepeatRuleDateTime($start_date);
// Floating dtstarts (either VALUE=DATE or with no TZID) can default to the collection's tzid, if one is set
$start_date = RepeatRuleDateTime::withFallbackTzid( $start_date, $collection_tzid );
$duration = $v->GetProperty('DURATION');
$duration = ( !isset($duration) ? 'P1D' : $duration->Value());
$end_date = clone($start_date);

60
inc/instance_range.php Normal file
View File

@ -0,0 +1,60 @@
<?php
require_once("RRule.php");
require_once("vCalendar.php");
function update_instance_ranges(string $collection_dav_name) {
// This can take a while, since vCalendar parsing is very slow, and we're
// going to parse every event in the collection!
//
// Since we might be doing this during a caldav request which we'd rather not
// have fail, we increase the execution time limit to prevent timeouts
set_time_limit(120);
$qry = new AwlQuery();
$in_transaction = ($qry->TransactionState() == 1);
if ( ! $in_transaction ) $qry->Begin();
$qry->QDo(
"SELECT d.dav_id, c.collection_id, d.caldav_data, c.timezone, i.first_instance_start, i.last_instance_end
FROM caldav_data d
INNER JOIN collection c ON d.collection_id = c.collection_id
INNER JOIN calendar_item i ON d.collection_id = i.collection_id AND d.dav_id = i.dav_id
WHERE c.dav_name = :dav_name",
[":dav_name" => $collection_dav_name]
);
while( $row = $qry->Fetch() ) {
try {
$range = getVCalendarRange(new vCalendar($row->caldav_data), $row->timezone);
} catch (Exception $e) {
dbg_error_log('instance_range','Error parsing calendar item, skipping update');
continue;
}
$new_start = isset($range->from) ? $range->from->UTC() : null;
$new_end = isset($range->until) ? $range->until->UTC() : null;
if ($new_start != $row->first_instance_start || $new_end != $row->last_instance_end) {
$inner_qry = new AwlQuery(
"UPDATE calendar_item
SET first_instance_start = :first_instance_start,
last_instance_end = :last_instance_end
WHERE collection_id = :collection_id AND dav_id = :dav_id",
[
":dav_id" => $row->dav_id,
":collection_id" => $row->collection_id,
":first_instance_start" => $new_start,
":last_instance_end" => $new_end
]
);
$inner_qry->Exec('UpdateInstanceRange',__LINE__,__FILE__);
}
}
if ( ! $in_transaction ) $qry->Commit();
}
?>

View File

@ -218,7 +218,7 @@ function doItipOrganizerCancel( vCalendar $vcal ) {
global $request;
$attendees = $vcal->GetAttendees();
if ( count($attendees) == 0 && count($old_attendees) == 0 ) {
if ( count($attendees) == 0 ) {
dbg_error_log( 'schedule', 'Event has no attendees - no scheduling required.', count($attendees) );
return true;
}

View File

@ -91,7 +91,14 @@ if ( $can_write_collection && $editor->IsSubmit() ) {
}
else {
$c->messages[] = i18n("Updating Collection.");
// We need to know whether to update_instance_ranges, which is an expensive
// operation we should only do if the collection timezone has been updated
$tzqry = new AwlQuery( "SELECT timezone FROM collection WHERE collection_id=:id", [ ":id" => $id ] );
$tzqry->Exec('collection-edit',__LINE__,__FILE__);
$old_tz = $tzqry->Fetch()->timezone;
}
if ( !$editor->Write() ) {
$c->messages[] = i18n("Failed to write collection.");
if ( $id > 0 ) $editor->GetRecord();
@ -121,6 +128,13 @@ if ( $can_write_collection && $editor->IsSubmit() ) {
$c->messages[] = i18n('The file is not UTF-8 encoded, please check the error for more details.');
}
}
if ($editor->IsCreate() || $old_tz != $_POST['timezone']) {
dbg_error_log('collection-edit', 'Need to update instance ranges, this will take a while...');
require_once("instance_range.php");
update_instance_ranges($editor->Value('dav_name'));
}
// Uncache anything to do with the collection
$cache = getCacheInstance();
$cache->delete( 'collection-'.$editor->Value('dav_name'), null );
@ -190,7 +204,7 @@ $privilege_xlate = array(
* @param dbrow $row The row object we read from the database.
* @return string The formatted privileges.
*/
function collection_privilege_format_function( $value, $column, $row ) {
function collection_privilege_format_function( $value, $column = NULL, $row = NULL ) {
global $privilege_xlate;
$privs = bits_to_privilege($value, 'calendar');

View File

@ -454,7 +454,7 @@ EOTEMPLATE;
* @param dbrow $row The row object we read from the database.
* @return string The formatted privileges.
*/
function principal_privilege_format_function( $value, $column, $row ) {
function principal_privilege_format_function( $value, $column = NULL, $row = NULL ) {
global $privilege_xlate;
$privs = bits_to_privilege($value,'*');

View File

@ -890,6 +890,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr ""

View File

@ -890,6 +890,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr ""

View File

@ -889,6 +889,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr ""

View File

@ -900,6 +900,9 @@ msgstr "PUT-Anfrage an eine Collection ist nur mit text/calendar-Inhalt an eine
msgid "Passed"
msgstr "Erfolgreich"
msgid "Passed: %s"
msgstr "Erfolgreich: %s"
msgid "Password"
msgstr "Passwort"

View File

@ -890,6 +890,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr "Constraseña"

View File

@ -893,6 +893,9 @@ msgstr "El método PUT con contenido de tipo text/calendar sólo se permite en u
msgid "Passed"
msgstr "Aceptado"
msgid "Passed: %s"
msgstr "Aceptado: %s"
msgid "Password"
msgstr "Contraseña"

View File

@ -892,6 +892,9 @@ msgstr "PUT solo se permite para una colección si el contenido es text/calendar
msgid "Passed"
msgstr "Aceptado"
msgid "Passed: %s"
msgstr "Aceptado: %s"
msgid "Password"
msgstr "Clave de Acceso"

View File

@ -893,6 +893,9 @@ msgstr ""
msgid "Passed"
msgstr "Pasado"
msgid "Passed: %s"
msgstr "Pasado: %s"
msgid "Password"
msgstr "Contraseña"

View File

@ -889,6 +889,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr ""

View File

@ -890,6 +890,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr "Salasana"

View File

@ -896,6 +896,9 @@ msgstr "Mise à jour d'une collection agenda autorisée uniquement pour un conte
msgid "Passed"
msgstr "Passé"
msgid "Passed: %s"
msgstr "Passé: %s"
msgid "Password"
msgstr "Mot de passe"

View File

@ -890,6 +890,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr "Jelszó"

View File

@ -889,6 +889,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr ""

View File

@ -891,6 +891,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr "Password"

View File

@ -890,6 +890,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr "パスワード"

View File

@ -892,6 +892,9 @@ msgstr ""
msgid "Passed"
msgstr "정상"
msgid "Passed: %s"
msgstr "정상: %s"
msgid "Password"
msgstr "비밀번호"

View File

@ -894,6 +894,9 @@ msgstr "PUT på en samling er bare tillatt for tekst-/kalenderinnhold i en kalen
msgid "Passed"
msgstr "Godkjent"
msgid "Passed: %s"
msgstr "Godkjent: %s"
msgid "Password"
msgstr "Passord"

View File

@ -894,6 +894,9 @@ msgstr "PUT in een collectie is enkel toegestaan voor het type text/calendar teg
msgid "Passed"
msgstr "Geslaagd"
msgid "Passed: %s"
msgstr "Geslaagd: %s"
msgid "Password"
msgstr "Wachtwoord"

View File

@ -895,6 +895,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr "Hasło"

View File

@ -894,6 +894,9 @@ msgstr "Colocar em uma coleção só é permitido para texto / conteúdo do cale
msgid "Passed"
msgstr "Passou"
msgid "Passed: %s"
msgstr "Passou: %s"
msgid "Password"
msgstr "Senha"

View File

@ -893,6 +893,9 @@ msgstr "PUT de conteúdo text/calendar apenas é permitido numa colecção do ti
msgid "Passed"
msgstr "Passou"
msgid "Passed: %s"
msgstr "Passou: %s"
msgid "Password"
msgstr "Password"

View File

@ -892,6 +892,9 @@ msgstr ""
msgid "Passed"
msgstr "Успешно"
msgid "Passed: %s"
msgstr "Успешно: %s"
msgid "Password"
msgstr "Пароль"

View File

@ -894,6 +894,9 @@ msgstr "PUT žiadosť na zostavu je možné len na text/calendar obsah zostavy k
msgid "Passed"
msgstr "Prešlo"
msgid "Passed: %s"
msgstr "Prešlo: %s"
msgid "Password"
msgstr "Heslo"

View File

@ -890,6 +890,9 @@ msgstr ""
msgid "Passed"
msgstr ""
msgid "Passed: %s"
msgstr ""
msgid "Password"
msgstr "Lösenord"

View File

@ -0,0 +1,28 @@
Listen 127.0.1.1:80
<VirtualHost 127.0.1.1:80>
ServerName regression
ServerAlias myempty mycaldav
DocumentRoot /path/to/davical/htdocs
CustomLog ${APACHE_LOG_DIR}/regression-access.log combined
ErrorLog ${APACHE_LOG_DIR}/regression-error.log
Alias /davical /path/to/davical/htdocs
<Directory /path/to/davical/htdocs>
Require all granted
DirectoryIndex index.php index.html
php_value include_path /path/to/awl/inc:/path/to/davical/testing
</Directory>
RewriteEngine On
# Redirect /.well-known URLs
RewriteRule ^/\.well-known/(.*)$ /caldav.php/.well-known/$1 [NC,L]
# and other special URLs
RewriteRule ^/principals/users/(.*)$ /caldav.php/$1 [NC,L]
RewriteRule ^/principals/resources/(.*)$ /caldav.php/$1 [NC,L]
RewriteRule ^/calendars/__uids__/(.*)$ /caldav.php/$1 [NC,L]
RewriteRule ^/addressbooks/__uids__/(.*)$ /caldav.php/$1 [NC,L]
# let caldav.php handle "anything else"
RewriteCond %{REQUEST_URI} !^/$
RewriteCond %{REQUEST_URI} !\.(php|css|png|gif|js|jpg|ico)
RewriteRule ^(.*)$ /caldav.php$1 [NC,L]
</VirtualHost>

View File

@ -374,7 +374,7 @@ QUERY
SELECT something, or, other FROM table ...
ENDQUERY
REPLACE=/pattern/replacement/options
REPLACE=/pattern/replacement/
=================================================
URL The URL to request from.

View File

@ -54,6 +54,9 @@ while( <STDIN> ) {
/^Content-Length: 0/ && $no_content && do {
$_ = "";
};
/^Content-Type: / && $no_content && do {
$_ = "";
};
print;
}

View File

@ -0,0 +1,153 @@
<?php
set_include_path(get_include_path() . PATH_SEPARATOR . '/usr/share/awl/inc' . PATH_SEPARATOR . 'inc');
require_once('RRule.php');
require_once('vCalendar.php');
use PHPUnit\Framework\TestCase;
// 1PM-2PM Monday-Thursday (only for one week), NZ time
$base_cal = new vCalendar("BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20190117T001216Z
LAST-MODIFIED:20190117T001233Z
DTSTAMP:20190117T001233Z
UID:dae6404d-1ce0-42d0-af3b-0d303034197b
SUMMARY:New Event
RRULE:FREQ=DAILY;UNTIL=20190124T000000Z
DTSTART;TZID=Pacific/Auckland:20190121T130000
DTEND;TZID=Pacific/Auckland:20190121T140000
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR");
$tuesday_renamed_cal = new vCalendar("
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20190117T001216Z
LAST-MODIFIED:20190117T001805Z
DTSTAMP:20190117T001805Z
UID:d0d2df67-df7c-4b07-b729-221af3681c09
SUMMARY:New Event
RRULE:FREQ=DAILY;UNTIL=20190124T000000Z
DTSTART;TZID=Pacific/Auckland:20190121T130000
DTEND;TZID=Pacific/Auckland:20190121T140000
TRANSP:OPAQUE
X-MOZ-GENERATION:1
END:VEVENT
BEGIN:VEVENT
CREATED:20190117T001741Z
LAST-MODIFIED:20190117T001805Z
DTSTAMP:20190117T001805Z
UID:d0d2df67-df7c-4b07-b729-221af3681c09
SUMMARY:Tuesday has been renamed
RECURRENCE-ID;TZID=Pacific/Auckland:20190122T130000
DTSTART;TZID=Pacific/Auckland:20190122T130000
DTEND;TZID=Pacific/Auckland:20190122T140000
TRANSP:OPAQUE
X-MOZ-GENERATION:1
SEQUENCE:1
END:VEVENT
END:VCALENDAR
");
$tuesday_renamed_cal_order_swapped = new vCalendar("
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20190117T001741Z
LAST-MODIFIED:20190117T001805Z
DTSTAMP:20190117T001805Z
UID:d0d2df67-df7c-4b07-b729-221af3681c09
SUMMARY:Tuesday has been renamed
RECURRENCE-ID;TZID=Pacific/Auckland:20190122T130000
DTSTART;TZID=Pacific/Auckland:20190122T130000
DTEND;TZID=Pacific/Auckland:20190122T140000
TRANSP:OPAQUE
X-MOZ-GENERATION:1
SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
CREATED:20190117T001216Z
LAST-MODIFIED:20190117T001805Z
DTSTAMP:20190117T001805Z
UID:d0d2df67-df7c-4b07-b729-221af3681c09
SUMMARY:New Event
RRULE:FREQ=DAILY;UNTIL=20190124T000000Z
DTSTART;TZID=Pacific/Auckland:20190121T130000
DTEND;TZID=Pacific/Auckland:20190121T140000
TRANSP:OPAQUE
X-MOZ-GENERATION:1
END:VEVENT
END:VCALENDAR
");
/**
* A simplified model of get_freebusy, which works off of a passed-in vCalendar
* rather than making SQL queries
*/
function get_freebusyish(vCalendar $cal) {
$expansion = expand_event_instances($cal)->GetComponents(['VEVENT' => true]);
$result = array();
foreach ($expansion as $k => $instance) {
// The same logic used in freebusy-functions (apart from default timezone
// handling, which isn't really under test here)
$start_date = new RepeatRuleDateTime($instance->GetProperty('DTSTART'));
$duration = $instance->GetProperty('DURATION');
$duration = (!isset($duration) ? 'P1D' : $duration->Value());
$end_date = clone($start_date);
$end_date->modify($duration);
array_push($result, $start_date->UTC() .'/'. $end_date->UTC());
}
sort($result);
return $result;
}
final class ExpansionTest extends TestCase
{
const expected_freebusyish_for_base = [
'20190121T000000Z/20190121T010000Z',
'20190122T000000Z/20190122T010000Z',
'20190123T000000Z/20190123T010000Z',
'20190124T000000Z/20190124T010000Z',
];
public function testUnmodifiedCal() {
global $base_cal;
self::assertEquals(
self::expected_freebusyish_for_base,
get_freebusyish($base_cal)
);
}
public function testTueRenamed() {
global $tuesday_renamed_cal;
self::assertEquals(
self::expected_freebusyish_for_base,
get_freebusyish($tuesday_renamed_cal)
);
}
public function testTueRenamedSwapped() {
global $tuesday_renamed_cal_order_swapped;
self::assertEquals(
self::expected_freebusyish_for_base,
get_freebusyish($tuesday_renamed_cal_order_swapped)
);
}
}

View File

@ -0,0 +1,119 @@
<?php
set_include_path(get_include_path() . PATH_SEPARATOR . '/usr/share/awl/inc' . PATH_SEPARATOR . 'inc');
require_once('RRule.php');
require_once('vCalendar.php');
use PHPUnit\Framework\TestCase;
final class RangeTest extends TestCase
{
public function testGetVCalendarRange() {
$cal = new vCalendar("BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Asia/Baku
BEGIN:STANDARD
TZOFFSETFROM:+0400
TZOFFSETTO:+0400
TZNAME:+04
DTSTART:19700101T000000
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20181218T045608Z
LAST-MODIFIED:20181218T045951Z
DTSTAMP:20181218T045951Z
UID:8eeee169-420f-4645-9546-9ea8293a1c6d
SUMMARY:Blep
RRULE:FREQ=DAILY;UNTIL=20190102T050000Z;BYDAY=MO,TU,WE,TH,FR
DTSTART;TZID=Asia/Baku:20181226T090000
DTEND;TZID=Asia/Baku:20181226T112000
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR");
$range = getVCalendarRange($cal);
self::assertEquals("20181226T050000Z", (string) $range->from->UTC());
self::assertEquals("20190102T072000Z", (string) $range->until->UTC());
// TZ is specified in the event, so this should be unaffected by passing in a fallback timezone:
$range = getVCalendarRange($cal, "Asia/Baku");
self::assertEquals("20181226T050000Z", (string) $range->from->UTC());
self::assertEquals("20190102T072000Z", (string) $range->until->UTC());
}
public function testGetVCalendarRangeTwoDayAllDay() {
$cal = new vCalendar("BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20181218T052603Z
LAST-MODIFIED:20181218T052734Z
DTSTAMP:20181218T052734Z
UID:7e76efc0-b1ed-4b68-b0ed-9e34889c30bd
SUMMARY:New Event
RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=TU
DTSTART;VALUE=DATE:20181225
DTEND;VALUE=DATE:20181227
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR");
$range = getVCalendarRange($cal, "Asia/Baku");
self::assertEquals("20181224T200000Z", (string) $range->from->UTC());
self::assertEquals("20190109T200000Z", (string) $range->until->UTC());
}
public function testGetVCalendarRangeFloating() {
// When interpreted as being in Greece, this event crosses the daylight savings boundary!
// TODO deal with how that affects all-day events...
$cal = new vCalendar("BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20181218T052603Z
LAST-MODIFIED:20181218T052734Z
DTSTAMP:20181218T052734Z
UID:7e76efc0-b1ed-4b68-b0ed-9e34889c30bd
SUMMARY:New Event
RRULE:FREQ=MONTHLY;COUNT=4;INTERVAL=2;BYMONTHDAY=10
DTSTART:20180411T140000
DTEND:20180411T150000
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR");
$range = getVCalendarRange($cal, "Europe/Athens");
self::assertEquals("20180411T110000Z", (string) $range->from->UTC());
self::assertEquals("20181210T130000Z", (string) $range->until->UTC());
}
public function testGetVCalendarRangeAllDayAcrossDST() {
// When interpreted as being in Greece, this event crosses the daylight savings boundary!
$cal = new vCalendar("BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20181218T052603Z
LAST-MODIFIED:20181218T052734Z
DTSTAMP:20181218T052734Z
UID:7e76efc0-b1ed-4b68-b0ed-9e34889c30bd
SUMMARY:New Event
RRULE:FREQ=MONTHLY;COUNT=5;INTERVAL=2;BYMONTHDAY=10
DTSTART;VALUE=DATE:20180410
DTEND;VALUE=DATE:20180411
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR");
$range = getVCalendarRange($cal, "Europe/Athens");
self::assertEquals("20180409T210000Z", (string) $range->from->UTC());
// This is correctly at a different UTC time-of-day to the original, since DST has changed
self::assertEquals("20181210T220000Z", (string) $range->until->UTC());
}
}

View File

@ -0,0 +1,22 @@
<?php
$c->pg_connect[] = 'dbname=regression user=davical_app';
// use strict result ordering for regression testing only
$c->strict_result_ordering = true;
// PUT: 405 Method Not Allowed
$c->readonly_webdav_collections = false;
// fix ETag differences (CRLF) in 0218-Moz-REPORT and later
$c->hide_alarm = true;
// some freebusy queries will be 404 otherwise
$c->public_freebusy_url = true;
// helps with 1035-GET-mashup
$c->get_includes_subcollections = true;
// helps with 1036-REPORT-sync-initial-bound
$c->hide_TODO = false;
// for ischedule suite; also needs DKIM set up?
$c->enable_scheduling = true;
// enable for debugging, as needed
//$c->dbg['ALL'] = 1;
?>

View File

@ -0,0 +1,75 @@
setup_report() {
if [ ! -z "$IS_CI" ]; then
cat > "$REPORTFILE" <<EOHEADER
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
</testsuites>
EOHEADER
fi
}
report_test_success() {
if [ ! -z "$IS_CI" ]; then
SUITEPATH="/testsuites/testsuite[@name=\"$SUITE\"]"
xmlstarlet ed -L \
-s "$SUITEPATH" -t elem -n testcase \
-i "$SUITEPATH/testcase[not(@name)]" -t attr -n wip -v "1" \
-i "$SUITEPATH/testcase[@wip]" -t attr -n name -v "$TEST" \
-i "$SUITEPATH/testcase[@wip]" -t attr -n classname -v "$TEST" \
-i "$SUITEPATH/testcase[@wip]" -t attr -n timestamp -v "0" \
-d "$SUITEPATH/testcase[@wip]/@wip" \
"$REPORTFILE"
fi
}
report_test_failure() {
if [ ! -z "$IS_CI" ]; then
SUITEPATH="/testsuites/testsuite[@name=\"$SUITE\"]"
xmlstarlet ed -L \
-s "$SUITEPATH" -t elem -n testcase \
-i "$SUITEPATH/testcase[not(@name)]" -t attr -n wip -v "1" \
-i "$SUITEPATH/testcase[@wip]" -t attr -n name -v "$SUITE: $TEST" \
-i "$SUITEPATH/testcase[@wip]" -t attr -n classname -v "$TEST" \
-i "$SUITEPATH/testcase[@wip]" -t attr -n timestamp -v "0" \
-s "$SUITEPATH/testcase[@wip]" -t elem -n failure -v "xxxPERL_REPLACE_THIS_WITH_THE_ESCAPED_DIFFxxx" \
-i "$SUITEPATH/testcase[@wip]/failure" -t attr -n message -v "test failure" \
-d "$SUITEPATH/testcase[@wip]/@wip" \
"$REPORTFILE"
# I encountered a bug in xmlstarlet where small sections of large files are not properly escaped if inserted directly :/
perl -pi -e 'BEGIN {$diff = `xmlstarlet esc < "'"${REGRESSION}/diffs/${TEST}"'"`} s/xxxPERL_REPLACE_THIS_WITH_THE_ESCAPED_DIFFxxx/$diff/g' "$REPORTFILE"
fi
}
report_suite_setup() {
if [ ! -z "$IS_CI" ]; then
xmlstarlet ed -L \
-s '/testsuites' -t elem -n testsuite \
-i '/testsuites/testsuite[not(@name)]' -t attr -n name -v "$SUITE" \
-i '/testsuites/testsuite[not(@time)]' -t attr -n time -v "0" \
-i '/testsuites/testsuite[not(@errors)]' -t attr -n errors -v "0" \
-i '/testsuites/testsuite[not(@timestamp)]' -t attr -n timestamp -v "$(date -u +'%FT%TZ')" \
"$REPORTFILE"
fi
}
report_suite_counts() {
if [ ! -z "$IS_CI" ]; then
xmlstarlet ed -L \
-i "/testsuites/testsuite[@name=\"$SUITE\"]" -t attr -n tests -v "$(xmlstarlet sel -t -v "count(/testsuites/testsuite[@name=\"$SUITE\"]/testcase)" "$REPORTFILE")" \
-i "/testsuites/testsuite[@name=\"$SUITE\"]" -t attr -n failures -v "$(xmlstarlet sel -t -v "count(/testsuites/testsuite[@name=\"$SUITE\"]/testcase/failure)" "$REPORTFILE")" \
"$REPORTFILE"
fi
}
exit_based_on_reported_failures() {
if [ ! -z "$IS_CI" ]; then
FAILCOUNT="$(xmlstarlet sel -t -v "count(//failure)" "$REPORTFILE")"
if [ "$FAILCOUNT" -gt 0 ]; then
exit 2
else
exit 0
fi
fi
exit 0
}

View File

@ -12,12 +12,13 @@
DBNAME=regression
PGPOOL=inactive
HOSTNAME=mycaldav
REPORTFILE=report.xml
# We need to run the regression tests in the timezone they were written for.
export PGTZ=Pacific/Auckland
#export TZ=Pacific/Auckland
ALLSUITES="regression-suite binding carddav scheduling timezone"
ALLSUITES=${ALLSUITES:-"regression-suite binding carddav scheduling timezone"}
# who wants meld if they can have xxdiff? Go on, override it in regression.conf
MELD=meld
@ -49,6 +50,7 @@ ACCEPT_ALL=${2:-""}
# psql ${PSQLOPTS} -l
. ./regression_reporting.sh
check_result() {
TEST="$1"
@ -59,6 +61,7 @@ check_result() {
diff --text -u "${REGRESSION}/${TEST}.result" "${RESULTS}/${TEST}" >"${REGRESSION}/diffs/${TEST}"
if [ -s "${REGRESSION}/diffs/${TEST}" ] ; then
report_test_failure
if [ -z "$SKIPDIFF" ]; then
echo "======================================="
echo "Displaying diff for test ${TEST}"
@ -75,7 +78,7 @@ check_result() {
cp "${RESULTS}/${TEST}" "${REGRESSION}/${TEST}.result"
elif [ "${ACCEPT}" = "x" ]; then
echo "./dav_test --dsn '${DSN}' ${WEBHOST} ${ALTHOST} --suite '${SUITE}' --case '${TEST}' --debug"
exit
if [ -z "$IS_CI" ]; then exit 2; fi
elif [ "${ACCEPT}" = "v" ]; then
echo "Showing test $REGRESSION/${TEST}.test"
cat "$REGRESSION/${TEST}.test"
@ -107,6 +110,7 @@ check_result() {
return 1
fi
else
report_test_success
echo "Test ${TEST} passed OK!"
fi
return 0
@ -140,7 +144,16 @@ restore_database() {
dump_database() {
TEST="Dump-Database"
pg_dump -Fp $PSQLOPTS ${DBNAME} > "${REGRESSION}/initial.dbdump" 2>&1
pg_dump -Fp $PSQLOPTS ${DBNAME} \
| grep -v -E '(CREATE\ EXTENSION|COMMENT\ ON)' \
> "${REGRESSION}/initial.dbdump" 2>&1
# This is ugly, for the COPY into dav_binding to work on Pg >= 9.6 (possibly earlier)
# we need to ensure that the schema that collection is in is within our search path
# since the function real_path_exists is called on each insert into dav_binding and
# it doesn't specify the schema for 'collection', therefore the copy fails.
schema=$(grep "CREATE TABLE .*.collection" "${REGRESSION}/initial.dbdump" | sed -r 's/CREATE TABLE (.*?)\.collection \(/\1/')
sed -i "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', '$schema', false);/" "${REGRESSION}/initial.dbdump"
}
@ -152,7 +165,8 @@ initialise_regression() {
check_result "${TEST}"
TEST="Upgrade-Database"
../dba/update-davical-database ${DBAOPTS} --dbname=${DBNAME} --nopatch --appuser davical_app --owner davical_dba >"${RESULTS}/${TEST}" 2>&1
../dba/update-davical-database ${DBAOPTS} --dbname=${DBNAME} --nopatch --appuser davical_app --owner davical_dba >"${RESULTS}/${TEST}" \
| sed -r 's/is version [.0-9]+/is version XX/'2>&1
check_result "${TEST}"
if [ -f "${REGRESSION}/sample-data.sql" ]; then
@ -162,7 +176,8 @@ initialise_regression() {
fi
TEST="Really-Upgrade-Database"
../dba/update-davical-database ${DBAOPTS} --dbname=${DBNAME} --appuser davical_app --owner davical_dba >"${RESULTS}/${TEST}" 2>&1
../dba/update-davical-database ${DBAOPTS} --dbname=${DBNAME} --appuser davical_app --owner davical_dba \
| sed -r 's/is version [.0-9]+/is version XX/' >"${RESULTS}/${TEST}" 2>&1
check_result "${TEST}"
}
@ -173,6 +188,8 @@ run_regression_suite() {
mkdir -p "${RESULTS}"
mkdir -p "${REGRESSION}/diffs"
report_suite_setup
if [ -f "${REGRESSION}/initial.dbdump" ]; then
restore_database
else
@ -206,6 +223,8 @@ run_regression_suite() {
TCOUNT="$(( ${TCOUNT} + 1 ))"
done
report_suite_counts
}
@ -213,6 +232,8 @@ run_regression_suite() {
TSTART="`date +%s`"
TCOUNT=0
setup_report
if [ "${SUITE}" = "all" ]; then
for SUITE in ${ALLSUITES} ; do
echo "Running $SUITE"
@ -230,3 +251,5 @@ fi
TFINISH="`date +%s`"
echo "Regression test run took $(( ${TFINISH} - ${TSTART} )) seconds for ${TCOUNT} tests."
exit_based_on_reported_failures

View File

@ -12,7 +12,7 @@ $c->dbg = array();
require_once("RRule.php");
require_once('AwlQuery.php');
@header("Content-Type: text/plain");
@header("Content-Type: text/plain; charset=UTF-8");
echo <<<EOTXT
Testing the RRule v2 Library

View File

@ -6,7 +6,7 @@
<prop>
<displayname>User 4</displayname>
<resource-id>
<href>/caldav.php/.resources/1005</href>
<href>/caldav.php/.resources/XX</href>
</resource-id>
<parent-set>
<parent>
@ -24,7 +24,7 @@
<prop>
<displayname>User 2's Calendar, as uploaded by Admin</displayname>
<resource-id>
<href>/caldav.php/.resources/161</href>
<href>/caldav.php/.resources/XX</href>
</resource-id>
<parent-set>
<parent>
@ -46,7 +46,7 @@
<prop>
<displayname>mir-test</displayname>
<resource-id>
<href>/caldav.php/.resources/163</href>
<href>/caldav.php/.resources/XX</href>
</resource-id>
<parent-set>
<parent>
@ -64,7 +64,7 @@
<prop>
<displayname>user4 addresses</displayname>
<resource-id>
<href>/caldav.php/.resources/463</href>
<href>/caldav.php/.resources/XX</href>
</resource-id>
<parent-set>
<parent>
@ -82,7 +82,7 @@
<prop>
<displayname>New Zealand Public Holidays</displayname>
<resource-id>
<href>/caldav.php/.resources/1014</href>
<href>/caldav.php/.resources/XX</href>
</resource-id>
<parent-set>
<parent>
@ -100,7 +100,7 @@
<prop>
<displayname>US Holidays</displayname>
<resource-id>
<href>/caldav.php/.resources/1466</href>
<href>/caldav.php/.resources/XX</href>
</resource-id>
<parent-set>
<parent>
@ -118,7 +118,7 @@
<prop>
<displayname>User 4 Outbox</displayname>
<resource-id>
<href>/caldav.php/.resources/1600</href>
<href>/caldav.php/.resources/XX</href>
</resource-id>
<parent-set>
<parent>

View File

@ -22,3 +22,5 @@ BEGINDATA
ENDDATA
# <current-user-privilege-set/>
REPLACE=_resources/\d+_resources/XX_

View File

@ -2,7 +2,6 @@ HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Type: text/plain; charset="utf-8"
bind_id: >1607<

View File

@ -0,0 +1,282 @@
<?xml version="1.0" encoding="utf-8" ?>
<multistatus xmlns="DAV:" xmlns:C="http://calendarserver.org/ns/">
<response>
<href>/caldav.php/</href>
<propstat>
<prop>
<C:getctag/>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/home/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/addresses/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/created/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/.in/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/.out/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/6E20BB7C-EFD9-4F0F-9BDC-5335E04D47E0/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/VTODO-EFD9-4F0F-9BDC-5335E04D47E0/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/SOHO%20collection/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/collection/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user1/anotherone/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/user2/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/user1/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/home/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/addresses/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/nz_holidays/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/us_holidays/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/.out/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/base/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/calendar-proxy-read/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/user4/calendar-proxy-write/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/resource1/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/resource1/home/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/resource1/addresses/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/resource1/mozilla/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/resource2/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/resource2/home/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/resource2/addresses/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
<response>
<href>/caldav.php/resource2/mulberry/</href>
<propstat>
<prop>
<C:getctag>"XX"</C:getctag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>

View File

@ -0,0 +1,23 @@
#
# PROPFIND to find the collections user4 has access to
# - ctag returned
#
TYPE=PROPFIND
URL=http://regression.host/caldav.php
AUTH=user4:user4
HEADER=User-Agent: BIND Spec Tests
HEADER=Content-Type: text/xml; charset="UTF-8"
HEADER=Depth: 2
BEGINDATA
<?xml version="1.0" encoding="UTF-8"?>
<D:propfind xmlns:D="DAV:" xmlns:cs="http://calendarserver.org/ns/">
<D:prop>
<cs:getctag/>
</D:prop>
</D:propfind>
ENDDATA
REPLACE=_<C:getctag>"[0-9a-f]+"</C:getctag>_<C:getctag>"XX"</C:getctag>_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
#
# PROPFIND to find the collections user4 has access to, anchored to user4
# - ctag returned
#
TYPE=PROPFIND
URL=http://regression.host/caldav.php/user4
AUTH=user4:user4
HEADER=User-Agent: BIND Spec Tests
HEADER=Content-Type: text/xml; charset="UTF-8"
HEADER=Depth: 2
BEGINDATA
<?xml version="1.0" encoding="UTF-8"?>
<D:propfind xmlns:D="DAV:" xmlns:cs="http://calendarserver.org/ns/">
<D:prop>
<cs:getctag/>
</D:prop>
</D:propfind>
ENDDATA

View File

@ -1,4 +1,8 @@
psql:tests/binding/initial.dbdump:28: ERROR: must be owner of extension plpgsql
set_config
------------
public
(1 row)
setval
--------
1601

View File

@ -4,7 +4,6 @@ HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Type: text/plain; charset="utf-8"
caladruri: >NULL<

View File

@ -1,4 +1,8 @@
psql:tests/carddav/initial.dbdump:28: ERROR: must be owner of extension plpgsql
set_config
------------
public
(1 row)
setval
--------
1633

View File

@ -3,7 +3,6 @@ Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
ETag: "6cff373f55c3e8318ad1c0db93205d08"
Content-Type: text/plain; charset="utf-8"
A1 CalDAV DATA: >BEGIN:VCALENDAR

View File

@ -3,5 +3,4 @@ Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
ETag: "2c32a2f8aba853654eb17fe037a4db4d"
Content-Type: text/plain; charset="utf-8"

View File

@ -2,10 +2,13 @@ HTTP/1.1 412 Precondition Failed
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Length: 44
Content-Type: text/plain; charset="utf-8"
Content-Length: 204
Content-Type: text/xml; charset="utf-8"
Resource has changed on server - not deleted
<?xml version="1.0" encoding="utf-8" ?>
<error xmlns="DAV:">
<if-match/>Existing resource ETag of "6cff373f55c3e8318ad1c0db93205d08" does not match "bogus-etag-b000d7defa19ccb7cd21e546b54155ee"
</error>
dav_etag: >6cff373f55c3e8318ad1c0db93205d08<
dav_name: >/user1/home/F56B49B10FC923D20FE2DC92D6580340-0.ics<

View File

@ -2,6 +2,5 @@ HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Type: text/plain; charset="utf-8"

View File

@ -2,6 +2,5 @@ HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Type: text/plain; charset="utf-8"

View File

@ -2,7 +2,6 @@ HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Type: text/plain; charset="utf-8"
?column?: >Count of matching calendars (should be 0)<

View File

@ -3,7 +3,6 @@ Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
ETag: "efd0257efbc898d059c200d1391af060"
Content-Type: text/plain; charset="utf-8"
CalDAV Data: >BEGIN:VCALENDAR

View File

@ -3,7 +3,6 @@ Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
ETag: "6079c141bc189326bf2432a8426c4242"
Content-Type: text/plain; charset="utf-8"
CalDAV Data: >BEGIN:VCALENDAR

View File

@ -4,8 +4,8 @@ HTTP/1.1 207 Multi-Status
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
ETag: "d34169c5948ac4f074110acfe1f4aa10"
Content-Length: 28701
ETag: "ae8aac5229a8a5d9ee7c86100322162d"
Content-Length: 28703
Content-Type: text/xml; charset="utf-8"
<?xml version="1.0" encoding="utf-8" ?>
@ -310,7 +310,7 @@ Content-Type: text/xml; charset="utf-8"
</current-user-privilege-set>
<displayname>user1 addresses</displayname>
<C:getctag>"24c9e15e52afc47c225b757e7bee1f9d"</C:getctag>
<C2:max-resource-size>65500</C2:max-resource-size>
<C2:max-resource-size>6550000</C2:max-resource-size>
<owner>
<href>/caldav.php/user1/</href>
</owner>

View File

@ -2,6 +2,5 @@ HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Type: text/plain; charset="utf-8"

View File

@ -2,5 +2,4 @@ HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Type: text/plain; charset="utf-8"

View File

@ -2,5 +2,4 @@ HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Type: text/plain; charset="utf-8"

View File

@ -2,5 +2,4 @@ HTTP/1.1 204 No Content
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: extended-mkcol, bind, addressbook, calendar-auto-schedule, calendar-proxy
Content-Type: text/plain; charset="utf-8"

View File

@ -1,9 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<multistatus xmlns="DAV:" xmlns:A="http://apple.com/ns/ical/" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="urn:org:davical">
<response>
<href>/caldav.php/user1/home/</href>
<propstat>
<prop>
<A:calendar-color>#0252D4FF</A:calendar-color>
<A:calendar-order>1</A:calendar-order>
<C:schedule-default-calendar-URL>
<href>/caldav.php/user1/home/</href>
</C:schedule-default-calendar-URL>
<D:outer>
<D:middle>
<D:inner>inside</D:inner>
<D:inner>inside2</D:inner>
</D:middle>
</D:outer>
<getcontenttype>httpd/unix-directory</getcontenttype>
<resourcetype>
<collection/>

View File

@ -1,9 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<multistatus xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<multistatus xmlns="DAV:" xmlns:A="http://apple.com/ns/ical/" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="urn:org:davical">
<response>
<href>/caldav.php/user1/home/</href>
<propstat>
<prop>
<A:calendar-color>#0252D4FF</A:calendar-color>
<A:calendar-order>1</A:calendar-order>
<C:schedule-default-calendar-URL>
<href>/caldav.php/user1/home/</href>
</C:schedule-default-calendar-URL>
<D:outer>
<D:middle>
<D:inner>inside</D:inner>
<D:inner>inside2</D:inner>
</D:middle>
</D:outer>
<getcontenttype>httpd/unix-directory</getcontenttype>
<resourcetype>
<collection/>

View File

@ -16,15 +16,15 @@ DTEND:20070105T220000Z
FREEBUSY:20061031T210000Z/20061031T220000Z
FREEBUSY:20061101T210000Z/20061101T220000Z
FREEBUSY:20061101T233000Z/20061102T003000Z
FREEBUSY:20061102T183000Z/20061102T203000Z
FREEBUSY:20061103T030000Z/20061103T044500Z
FREEBUSY:20061103T063000Z/20061103T083000Z
FREEBUSY:20061108T210000Z/20061108T220000Z
FREEBUSY:20061115T210000Z/20061115T220000Z
FREEBUSY:20061117T030000Z/20061117T044500Z
FREEBUSY:20061122T210000Z/20061122T220000Z
FREEBUSY:20061129T210000Z/20061129T220000Z
FREEBUSY:20061201T030000Z/20061201T044500Z
FREEBUSY:20061202T183000Z/20061202T203000Z
FREEBUSY:20061203T063000Z/20061203T083000Z
FREEBUSY:20061206T210000Z/20061206T220000Z
FREEBUSY:20061213T210000Z/20061213T220000Z
FREEBUSY:20061215T030000Z/20061215T044500Z
@ -34,7 +34,7 @@ FREEBUSY:20061223T030000Z/20061223T050000Z
FREEBUSY;FBTYPE=BUSY-TENTATIVE:20061223T060000Z/20061223T080000Z
FREEBUSY:20061227T210000Z/20061227T220000Z
FREEBUSY:20061229T030000Z/20061229T044500Z
FREEBUSY:20070102T183000Z/20070102T203000Z
FREEBUSY:20070103T063000Z/20070103T083000Z
FREEBUSY:20070103T210000Z/20070103T220000Z
END:VFREEBUSY
END:VCALENDAR

Some files were not shown because too many files have changed in this diff Show More