Initial commit

This commit is contained in:
Andrew McMillan 2006-09-04 23:11:31 +12:00
commit 792b1a5083
25 changed files with 50258 additions and 0 deletions

12
caldav.session Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE webprojectsession>
<webprojectsession>
<session usePreviewPrefix="0" previewPrefix="" >
<itemcursorpositions/>
<treestatus>
<openfolder url="config" />
<openfolder url="dba" />
<openfolder url="htdocs" />
<openfolder url="inc" />
</treestatus>
</session>
</webprojectsession>

27
caldav.webprj Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE webproject>
<webproject>
<project type="Local" name="caldav" encoding="utf8" >
<upload/>
<author>Andrew McMillan</author>
<email>andrew@catalyst.net.nz</email>
<defaultDTD>-//W3C//DTD XHTML 1.0 Transitional//EN</defaultDTD>
<item url="" uploadstatus="1" />
<templates>templates/</templates>
<toolbars>toolbars/</toolbars>
<item url="htdocs/index.php" uploadstatus="1" />
<item url="htdocs/" uploadstatus="1" />
<annotations/>
<item url="inc/caldav-OPTIONS.php" uploadstatus="1" />
<item url="inc/" uploadstatus="1" />
<item url="inc/caldav-REPORT.php" uploadstatus="1" />
<item url="inc/iCalendar.php" uploadstatus="1" />
<item url="inc/always.php" uploadstatus="1" />
<item url="config/config.php" uploadstatus="1" />
<item url="config/" uploadstatus="1" />
<item url="inc/caldav-PUT.php" uploadstatus="1" />
<item url="dba/caldav.sql" />
<item url="dba/" />
<item url="dba/create-database.sh" />
<item url="dba/sample-data.sql" />
</project>
</webproject>

15
config/config.php Normal file
View File

@ -0,0 +1,15 @@
<?php
$c->domainname = "mycaldav.andrew";
if ( ! $dbconn = pg_Connect("port=5433 dbname=caldav user=general") ) {
if ( ! $dbconn = pg_Connect("port=5432 dbname=caldav user=general") ) {
echo "<html><head><title>Database Error</title></head><body>
<h1>Database Error</h1>
<h3>Could not connect to PGPool or to Postgres</h3>
</body>
</html>";
exit;
}
}
?>

14
dba/caldav.sql Normal file
View File

@ -0,0 +1,14 @@
-- My CalDAV server - Database Schema
--
-- Use the usr, group and schema management stufffrom libawl-php
\i /usr/share/awl/dba/awl-tables.sql
\i /usr/share/awl/dba/schema-management.sql
CREATE TABLE ics_event_data (
user_no INT references usr(user_no),
ics_event_name TEXT,
ics_event_etag TEXT,
ics_raw_data TEXT
);

12
dba/create-database.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/sh
#
# Build the CalDAV database
#
DBNAME="${1:-caldav}"
createdb -E UTF8 "${DBNAME}"
psql -f caldav.sql "${DBNAME}"
psql -f sample-data.sql "${DBNAME}"

6
dba/sample-data.sql Normal file
View File

@ -0,0 +1,6 @@
-- Some sample data to prime the database...
INSERT INTO usr ( user_no, active, email_ok, updated, username, password, fullname, email )
VALUES( 1, TRUE, current_date, current_date, 'andrew', '**x', 'Andrew McMillan', 'andrew@catalyst.net.nz' );
SELECT setval('usr_user_no_seq', 1);

7342
docs/IETF-CAP-RFC4324.txt Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

8342
docs/rfc2445.html Normal file

File diff suppressed because it is too large Load Diff

8291
docs/rfc2445.txt Normal file

File diff suppressed because it is too large Load Diff

5109
docs/rfc2518.html Normal file

File diff suppressed because it is too large Load Diff

9901
docs/rfc2616.html Normal file

File diff suppressed because it is too large Load Diff

47
htdocs/index.php Normal file
View File

@ -0,0 +1,47 @@
<?php
require_once("always.php");
if ( !function_exists('apache_request_headers') ) {
function apache_request_headers() {
return getallheaders();
}
}
function dbg_log_array( $name, $arr, $recursive = false ) {
foreach ($arr as $key => $value) {
error_log( "caldav: DBG: $name: >>$key<< = >>$value<<");
if ( $recursive && (gettype($value) == 'array' || gettype($value) == 'object') ) {
dbg_log_array( "$name"."[$key]", $value, $recursive );
}
}
}
$raw_headers = apache_request_headers();
$raw_post = file_get_contents ( 'php://input');
if ( $debugging && isset($_GET['method']) ) {
$_SERVER['REQUEST_METHOD'] = $_GET['method'];
}
switch ( $_SERVER['REQUEST_METHOD'] ) {
case 'OPTIONS':
include_once("caldav-OPTIONS.php");
break;
case 'REPORT':
include_once("caldav-REPORT.php");
break;
case 'PUT':
include_once("caldav-PUT.php");
break;
default:
error_log("Unhandled request method >>".$_SERVER['REQUEST_METHOD']."<<");
dbg_log_array( 'HEADERS', $raw_headers );
dbg_log_array( '_SERVER', $_SERVER, true );
error_log( "caldav: DBG: RAW: ".str_replace("\n", "", str_replace("\r", "", $raw_post)));
}
?>

21
inc/always.php Normal file
View File

@ -0,0 +1,21 @@
<?php
$c->sysabbr = 'caldav';
$c->admin_email = 'andrew@catalyst.net.nz';
$c->system_name = "Andrew's CalDAV Server";
error_log( $c->sysabbr.": DBG: ======================================= Start $PHP_SELF for $HTTP_HOST on $_SERVER[SERVER_NAME]" );
if ( file_exists("/etc/caldav/".$_SERVER['SERVER_NAME']."-conf.php") ) {
include_once("/etc/caldav/".$_SERVER['SERVER_NAME']."-conf.php");
}
else if ( file_exists("../config/config.php") ) {
include_once("../config/config.php");
}
else {
include_once("caldav_configuration_missing.php");
exit;
}
include_once("iCalendar.php");
?>

6
inc/caldav-OPTIONS.php Normal file
View File

@ -0,0 +1,6 @@
<?php
error_log("caldav: DBG: OPTIONS method handler");
header( "Content-type: text/plain");
header( "Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK, REPORT, ACL");
header( "DAV: 1, 2, 3, access-control, calendar-access");
?>

105
inc/caldav-PUT.php Normal file
View File

@ -0,0 +1,105 @@
<?php
error_log("caldav: DBG: PUT method handler");
$parser = xml_parser_create_ns('UTF-8');
xml_parser_set_option ( $parser, XML_OPTION_SKIP_WHITE, 1 );
function xml_start_callback( $parser, $el_name, $el_attrs ) {
error_log( "DBG: Parsing $el_name" );
dbg_log_array( "$el_name::attrs", $el_attrs, true );
}
function xml_end_callback( $parser, $el_name ) {
error_log( "DBG: Finished Parsing $el_name" );
}
xml_set_element_handler ( $parser, 'xml_start_callback', 'xml_end_callback' );
$put_request = array();
xml_parse_into_struct( $parser, $raw_post, $put_request );
xml_parser_free($parser);
$putnum = -1;
$put = array();
foreach( $put_request AS $k => $v ) {
switch ( $v['tag'] ) {
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY':
if ( $v['type'] == "open" ) {
$putnum++;
$put_type = substr($v['tag'],30);
$put[$putnum]['type'] = $put_type;
}
else {
unset($put_type);
}
break;
case 'DAV::PROP':
if ( isset($put_type) ) {
if ( $v['type'] == "open" ) {
$put_properties = array();
}
else if ( $v['type'] == "close" ) {
$put[$putnum]['properties'] = $put_properties;
unset($put_properties);
}
else {
error_log( "DBG: Unexpected DAV::PROP type of ".$v['type'] );
}
}
else {
error_log( "DBG: Unexpected DAV::PROP type of ".$v['type']." when no active put type.");
}
break;
case 'DAV::GETETAG':
if ( isset($put_properties) ) {
if ( $v['type'] == "complete" ) {
$put_properties['GETETAG'] = 1;
}
}
break;
default:
error_log("caldav: DBG: Unhandled tag >>".$v['tag']."<<");
}
}
dbg_log_array( 'RPT', $put_request, true );
dbg_log_array( 'REPORT', $put, true );
header("Content-type: text/xml");
echo <<<EOXML
<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:response>
<D:href>http://mycaldav/?andrewmcmillan.ics</D:href>
<D:propstat>
<D:prop>
<D:getetag>"fffff-abcd1"</D:getetag>
<C:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Andrew's CalDAV Server//PHP//0.0.1
EOXML;
echo iCalendar::vTimeZone("Pacific/Auckland");
$event = new vEvent( '20060517T150000Z', 'PT2H', 'andrew@catalyst.net.nz', '', 'A Test Event for Andrew' );
echo $event->ToString();
echo <<<EOXML
END:VCALENDAR
</C:calendar-data>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
EOXML;
?>

108
inc/caldav-REPORT.php Normal file
View File

@ -0,0 +1,108 @@
<?php
error_log("caldav: DBG: REPORT method handler");
include_once("iCalendar.php");
error_reporting(E_ALL);
$parser = xml_parser_create_ns('UTF-8');
xml_parser_set_option ( $parser, XML_OPTION_SKIP_WHITE, 1 );
function xml_start_callback( $parser, $el_name, $el_attrs ) {
error_log( "DBG: Parsing $el_name" );
dbg_log_array( "$el_name::attrs", $el_attrs, true );
}
function xml_end_callback( $parser, $el_name ) {
error_log( "DBG: Finished Parsing $el_name" );
}
xml_set_element_handler ( $parser, 'xml_start_callback', 'xml_end_callback' );
$rpt_request = array();
xml_parse_into_struct( $parser, $raw_post, $rpt_request );
xml_parser_free($parser);
$reportnum = -1;
$report = array();
foreach( $rpt_request AS $k => $v ) {
switch ( $v['tag'] ) {
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY':
if ( $v['type'] == "open" ) {
$reportnum++;
$report_type = substr($v['tag'],30);
$report[$reportnum]['type'] = $report_type;
}
else {
unset($report_type);
}
break;
case 'DAV::PROP':
if ( isset($report_type) ) {
if ( $v['type'] == "open" ) {
$report_properties = array();
}
else if ( $v['type'] == "close" ) {
$report[$reportnum]['properties'] = $report_properties;
unset($report_properties);
}
else {
error_log( "DBG: Unexpected DAV::PROP type of ".$v['type'] );
}
}
else {
error_log( "DBG: Unexpected DAV::PROP type of ".$v['type']." when no active report type.");
}
break;
case 'DAV::GETETAG':
if ( isset($report_properties) ) {
if ( $v['type'] == "complete" ) {
$report_properties['GETETAG'] = 1;
}
}
break;
default:
error_log("caldav: DBG: Unhandled tag >>".$v['tag']."<<");
}
}
dbg_log_array( 'RPT', $rpt_request, true );
dbg_log_array( 'REPORT', $report, true );
header("Content-type: text/xml");
echo <<<EOXML
<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:response>
<D:href>http://mycaldav/?andrewmcmillan.ics</D:href>
<D:propstat>
<D:prop>
<D:getetag>"fffff-abcd1"</D:getetag>
<C:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Andrew's CalDAV Server//PHP//0.0.1
EOXML;
echo iCalendar::vTimeZone("Pacific/Auckland");
$event = new vEvent( '20060517T150000Z', 'PT2H', 'andrew@catalyst.net.nz', '', 'A Test Event for Andrew' );
echo $event->ToString();
echo <<<EOXML
END:VCALENDAR
</C:calendar-data>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
EOXML;
?>

231
inc/iCalendar.php Normal file
View File

@ -0,0 +1,231 @@
<?php
/**
* A Class for handling iCalendar data
*
* @package caldav
* @subpackage iCalendar
* @author Andrew McMillan <andrew@catalyst.net.nz>
* @copyright Catalyst IT Ltd
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
*/
/**
* A Class for handling participants to events
*
* @package caldav
*/
class Participant {
/**#@+
* @access private
*/
/**
* Participant e-mail
* @var email string
*/
var $email;
/**
* Status of participant in relation to the event
* @var status string
*/
var $status;
/**
* Role of participant in relation to event
* @var email string
*/
var $role;
/**#@-*/
function Participant( $email, $status="NEEDS-ACTION", $role="ATTENDEE" ) {
$this->email = $email;
$this->status = $status;
$this->role = $role;
}
function ToString() {
$rv = sprintf( "ATTENDEE;PARTSTAT=%s%s:%s\n", $this->status, ($this-role == "ATTENDEE" ? "" : "ROLE=$this->role"), $this->email );
return $rv;
}
}
/**
* A Class for handling Evends on a calendar
*
* @package caldav
*/
class vEvent {
/**#@+
* @access private
*/
/**
* List of participants in this event
* @var participants array
*/
var $participants = array();
/**
* The start time for the event
* @var start datetime
*/
var $start;
/**
* The duration of the event
* @var duration interval
*/
var $duration;
/**
* The organizer of othe event
* @var organizer string
*/
var $organizer;
/**
* The status of the event
* @var status string
*/
var $status;
/**
* A summary description of the event
* @var summary string
*/
var $summary;
/**
* A last modified timestamp
* @var modified int
*/
var $modified;
/**
* A sequence for different revisions of the event
* @var sequence integer
*/
var $sequence;
/**
* A unique ID for the event
* @var uid string
*/
var $uid;
/**
* A GUID for the event
* @var guid string
*/
var $guid;
/**#@-*/
function vEvent( $start, $duration="PT1H", $organizer="", $status="TENTATIVE", $summary="" ) {
global $c;
$this->participants = array();
$this->start = $start;
$this->duration = $duration;
$this->organizer = $organizer;
$this->status = $status;
$this->summary = $summary;
$this->modified = iCalendar::EpochTS(time());
$this->sequence = 1;
$this->uid = sprintf( "%s@%s", time() * 1000 + rand(0,1000), $c->domainname);
$this->guid = sprintf( "%s@%s", time() * 1000 + rand(0,1000), $c->domainname);
}
function AddParticipant( $email, $status, $role ) {
$this->participants[] = new Participant($email,$status,$role);
}
/*
BEGIN:VEVENT
ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:cyrus@example.com
ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com
DTSTAMP:20060206T001220Z
DTSTART;TZID=US/Eastern:20060104T100000
DURATION:PT1H
LAST-MODIFIED:20060206T001330Z
ORGANIZER:mailto:cyrus@example.com
SEQUENCE:1
STATUS:TENTATIVE
SUMMARY:Event #3
UID:DC6C50A017428C5216A2F1CD@example.com
X-ABC-GUID:E1CX5Dr-0007ym-Hz@example.com
END:VEVENT
*/
function ToString() {
$participants = "";
foreach( $this->participants AS $k => $p ) {
$participants .= $p->ToString();
}
$fmt = <<<EOFMT
BEGIN:VEVENT
%sDTSTAMP:%s
DTSTART;TXID=%s:%s".
DURATION:%s
LAST-MODIFIED:%s
ORGANIZER:%s
SEQUENCE:%d
STATUS:%s
SUMMARY:%s
UID:%s
X-ABC-GUID:%s
END:VEVENT
EOFMT;
$string = sprintf( $fmt, $participants, iCalendar::EpochTS(time()), "Pacific/Auckland", $this->start, $this->duration, $this->modified,
$this->organizer, $this->sequence, $this->status, $this->summary, $this->uid, $this->guid );
return $string;
}
}
/**
* A Class for handling iCalendar data
*
* @package caldav
*/
class iCalendar {
function iCalendar() {
}
function EpochTS($epoch) {
$ts = date('Ymd\THis\Z', $epoch );
return $ts;
}
function vTimeZone( $tzname ) {
switch ( $tzname ) {
case 'Pacific/Auckland':
default:
$tzstring = <<<EOTZ
BEGIN:VTIMEZONE
LAST-MODIFIED:20040110T032845Z
TZID:Pacific/Auckland
BEGIN:STANDARD
DTSTART:20000404T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
TZNAME:NZST
TZOFFSETFROM:+1300
TZOFFSETTO:+1200
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20001026T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:EST
TZOFFSETFROM:+1200
TZOFFSETTO:+1300
END:DAYLIGHT
END:VTIMEZONE
EOTZ;
}
return $tzstring;
}
}
?>

View File

@ -0,0 +1,4 @@
DELETE /?andrewmcmillan.ics/20060509T195919Z.ics HTTP/1.1
Host: mycaldav
User-Agent: Evolution/1.6.1

View File

@ -0,0 +1,7 @@
HTTP/1.1 200 OK
Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE
Allow: PROPFIND, PROPPATCH, LOCK, UNLOCK, REPORT, ACL
DAV: 1, 2, 3, access-control, calendar-access
Date: Fri, 11 May 2006 09:32:12 GMT
Content-Length: 0

View File

@ -0,0 +1,87 @@
OPTIONS /?andrewmcmillan.ics HTTP/1.1
Host: mycaldav
User-Agent: Evolution/1.6.1
HTTP/1.1 200 OK
Date: Tue, 09 May 2006 00:40:08 GMT
Server: Apache/2.0.55 (Debian) DAV/2 PHP/4.4.2-1+b1
X-Powered-By: PHP/4.4.2-1+b1
Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK, REPORT, ACL
DAV: 1, 2, 3, access-control, calendar-access
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
0
OPTIONS /?andrewmcmillan.ics HTTP/1.1
Host: mycaldav
User-Agent: Evolution/1.6.1
HTTP/1.1 200 OK
Date: Tue, 09 May 2006 00:40:08 GMT
Server: Apache/2.0.55 (Debian) DAV/2 PHP/4.4.2-1+b1
X-Powered-By: PHP/4.4.2-1+b1
Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK, REPORT, ACL
DAV: 1, 2, 3, access-control, calendar-access
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
0
REPORT /?andrewmcmillan.ics HTTP/1.1
Host: mycaldav
Content-Length: 301
Depth: 1
User-Agent: Evolution/1.6.1
Content-Type: text/xml
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:">
<D:prop>
<D:getetag/>
</D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:is-defined/>
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>

View File

@ -0,0 +1,77 @@
PUT /?andrewmcmillan.ics/20060509T200223Z.ics HTTP/1.1
Host: mycaldav
Content-Length: 1049
If-None-Match: *
User-Agent: Evolution/1.6.1
Content-Type: text/calendar
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VEVENT
UID:20060509T200218Z-7351-1000-1-9@ubu
DTSTAMP:20060509T200218Z
DTSTART;TZID=/softwarestudio.org/Olson_20011030_5/Pacific/Auckland:
20060510T100000
DTEND;TZID=/softwarestudio.org/Olson_20011030_5/Pacific/Auckland:
20060510T110000
SUMMARY:Alistair
X-EVOLUTION-CALDAV-HREF:http:
//mycaldav/?andrewmcmillan.ics/20060509T200223Z.ics
BEGIN:VALARM
X-EVOLUTION-ALARM-UID:20060509T200218Z-10149-1000-1-48@ubu
ACTION:DISPLAY
TRIGGER;VALUE=DURATION;RELATED=START:-PT15M
DESCRIPTION:Alistair
END:VALARM
END:VEVENT
BEGIN:VTIMEZONE
TZID:/softwarestudio.org/Olson_20011030_5/Pacific/Auckland
X-LIC-LOCATION:Pacific/Auckland
BEGIN:STANDARD
TZOFFSETFROM:+1300
TZOFFSETTO:+1200
TZNAME:NZST
DTSTART:19700315T030000
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=3SU;BYMONTH=3
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+1200
TZOFFSETTO:+1300
TZNAME:NZDT
DTSTART:19701004T020000
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=10
END:DAYLIGHT
END:VTIMEZONE
END:VCALENDAR
HTTP/1.1 200 OK
Date: Tue, 09 May 2006 20:02:23 GMT
Server: Apache/2.0.55 (Debian) DAV/2 PHP/4.4.2-1+b1
X-Powered-By: PHP/4.4.2-1+b1
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
0
REPORT /?andrewmcmillan.ics HTTP/1.1
Host: mycaldav
Content-Length: 301
Depth: 1
User-Agent: Evolution/1.6.1
Content-Type: text/xml
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:">
<D:prop>
<D:getetag/>
</D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:is-defined/>
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>