diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2effd388 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +caldav.session +rscds.session diff --git a/README b/README new file mode 100644 index 00000000..556d882b --- /dev/null +++ b/README @@ -0,0 +1 @@ +Really Simple CalDAV Store by Andrew McMillan. diff --git a/TODO b/TODO new file mode 100644 index 00000000..614eab3d --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ +Critical + - accept the free/busy information as a PUT + +Important + - Parse the normal PUT requests so we know when time is available + - Understand the repeat frequencies so ditto... + +Nice + - Some form of admin functionality diff --git a/caldav.session b/caldav.session deleted file mode 100644 index 46c2be27..00000000 --- a/caldav.session +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/config/config.php b/config/config.php index e713d200..712cc30e 100644 --- a/config/config.php +++ b/config/config.php @@ -1,5 +1,8 @@ domainname = "mycaldav.andrew"; +// $c->domainname = "mycaldav.andrew"; +// $c->sysabbr = 'rscds'; +// $c->admin_email = 'andrew@catalyst.net.nz'; +// $c->system_name = "Really Simple CalDAV Store"; if ( ! $dbconn = pg_Connect("port=5433 dbname=caldav user=general") ) { if ( ! $dbconn = pg_Connect("port=5432 dbname=caldav user=general") ) { diff --git a/dba/caldav.sql b/dba/caldav.sql index 5ee0efc9..da300e73 100644 --- a/dba/caldav.sql +++ b/dba/caldav.sql @@ -9,6 +9,44 @@ CREATE TABLE ics_event_data ( user_no INT references usr(user_no), ics_event_name TEXT, ics_event_etag TEXT, - ics_raw_data TEXT + ics_raw_data TEXT, + + PRIMARY KEY ( user_no, ics_event_name, ics_event_etag ) ); +GRANT SELECT,INSERT,UPDATE,DELETE ON ics_event_data TO general; + + +CREATE TABLE time_zones ( + tzid TEXT PRIMARY KEY, + location TEXT, + tz_spec TEXT +); +GRANT SELECT,INSERT ON time_zones TO general; + + +CREATE TABLE ical_events ( + user_no INT references usr(user_no), + ics_event_name TEXT, + ics_event_etag TEXT, + + -- Extracted iCalendar event data + uid TEXT, + dtstamp TEXT, + dtstart TIMESTAMP, + dtend TIMESTAMP, + summary TEXT, + location TEXT, + class TEXT, + transp TEXT, + description TEXT, + rrule TEXT, + tzid TEXT REFERENCES time_zones( tzid ), + + -- Cascade updates / deletes from the ics_event_data table + CONSTRAINT ics_event_exists FOREIGN KEY ( user_no, ics_event_name, ics_event_etag ) + REFERENCES ics_event_data ( user_no, ics_event_name, ics_event_etag ) + MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON ical_events TO general; diff --git a/debian/README.debian b/debian/README.debian new file mode 100644 index 00000000..52796f43 --- /dev/null +++ b/debian/README.debian @@ -0,0 +1,14 @@ +RSCDS for Debian +---------------- + +This is a Really Simple CalDAV Store which I wrote because I +was getting sick of the length of time it was taking to make +worthwhile CalDAV server-side implementations that worked OK +with Evolution. + +Then, when I finally did find a CalDAV store that worked, I +found that it was quite bloated because it wanted to do vast +amounts of irrelevant stuff. Well, irrelevant for me in any +case. + +Andrew McMillan , Tue. 2 May 2006 07:11:22 +1200 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..b9325895 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +rscds (0.1.0) unstable; urgency=low + + * Initial Debian packaging + + -- Andrew McMillan Tue, 2 May 2006 07:43:59 +1200 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..5c43459d --- /dev/null +++ b/debian/control @@ -0,0 +1,15 @@ +Source: rscds +Section: catalyst +Priority: extra +Maintainer: Andrew McMillan +Standards-Version: 3.5.9 +Build-Depends: debhelper + +Package: rscds +Architecture: all +Depends: debconf (>= 1.0.32), php4 (>= 4:4.1), php4-pgsql(>= 3:4.1.0), postgresql-client (>= 7.2.1) | postgresql-client-8.0 | postgresql-client-8.1, libawl-php (>=0.1-1) +Description: Really Simple CalDAV Server + The Really Simple CalDAV Server is designed to trivially store + CalDAV calendars, such as those from Evolution, in a central + location, providing shared calendars, free/busy publication + and basic administration (grouping, delegating, etc). diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..aa5e060d --- /dev/null +++ b/debian/copyright @@ -0,0 +1,3 @@ +Andrew's Web Libraries are copyright 2006 Catalyst IT Ltd and +are licensed under the Gnu Public License version 2, or later. + diff --git a/debian/rscds-phpdoc.ini b/debian/rscds-phpdoc.ini new file mode 100644 index 00000000..0587fa7d --- /dev/null +++ b/debian/rscds-phpdoc.ini @@ -0,0 +1,100 @@ +;; phpDocumentor parse configuration file +;; +;; This file is designed to cut down on repetitive typing on the command-line or web interface +;; You can copy this file to create a number of configuration files that can be used with the +;; command-line switch -c, as in phpdoc -c default.ini or phpdoc -c myini.ini. The web +;; interface will automatically generate a list of .ini files that can be used. +;; +;; default.ini is used to generate the online manual at http://www.phpdoc.org/docs +;; +;; ALL .ini files must be in the user subdirectory of phpDocumentor with an extension of .ini +;; +;; Copyright 2002, Greg Beaver +;; +;; WARNING: do not change the name of any command-line parameters, phpDocumentor will ignore them + +[Parse Data] +;; title of all the documentation +;; legal values: any string +title = RSCDS + +;; parse files that start with a . like .bash_profile +;; legal values: true, false +hidden = false + +;; show elements marked @access private in documentation by setting this to on +;; legal values: on, off +parseprivate = off + +;; parse with javadoc-like description (first sentence is always the short description) +;; legal values: on, off +javadocdesc = off + +;; add any custom @tags separated by commas here +;; legal values: any legal tagname separated by commas. +;customtags = mytag1,mytag2 + +;; This is only used by the XML:DocBook/peardoc2 converter +defaultcategoryname = Documentation + +;; what is the main package? +;; legal values: alphanumeric string plus - and _ +defaultpackagename = RSCDS + +;; output any parsing information? set to on for cron jobs +;; legal values: on +;quiet = on + +;; parse a PEAR-style repository. Do not turn this on if your project does +;; not have a parent directory named "pear" +;; legal values: on/off +;pear = on + +;; where should the documentation be written? +;; legal values: a legal path +target = /home/andrew/projects/rscds/debian/html + +;; Which files should be parsed out as special documentation files, such as README, +;; INSTALL and CHANGELOG? This overrides the default files found in +;; phpDocumentor.ini (this file is not a user .ini file, but the global file) +readmeinstallchangelog = README, INSTALL, CHANGELOG, NEWS, FAQ, LICENSE + +;; limit output to the specified packages, even if others are parsed +;; legal values: package names separated by commas +;packageoutput = package1,package2 + +;; comma-separated list of files to parse +;; legal values: paths separated by commas +;filename = /path/to/file1,/path/to/file2,fileincurrentdirectory + +;; comma-separated list of directories to parse +;; legal values: directory paths separated by commas +;directory = /path1,/path2,.,..,subdirectory +;directory = /home/jeichorn/cvs/pear +directory = /home/andrew/projects/rscds/inc,/home/andrew/projects/rscds/html + +;; template base directory (the equivalent directory of /phpDocumentor) +;templatebase = /path/to/my/templates + +;; directory to find any example files in through @example and {@example} tags +;examplesdir = /path/to/my/templates +examplesdir = /home/andrew/projects/rscds/examples + +;; comma-separated list of files, directories or wildcards ? and * (any wildcard) to ignore +;; legal values: any wildcard strings separated by commas +;ignore = /path/to/ignore*,*list.php,myfile.php,subdirectory/ +ignore = api/,CVS + +;; comma-separated list of Converters to use in outputformat:Convertername:templatedirectory format +;; legal values: HTML:frames:default,HTML:frames:l0l33t,HTML:frames:phpdoc.de,HTML:frames:phphtmllib, +;; HTML:frames:earthli, +;; HTML:frames:DOM/default,HTML:frames:DOM/l0l33t,HTML:frames:DOM/phpdoc.de, +;; HTML:frames:DOM/phphtmllib,HTML:frames:DOM/earthli +;; HTML:Smarty:default,HTML:Smarty:PHP,HTML:Smarty:HandS +;; PDF:default:default,CHM:default:default,XML:DocBook/peardoc2:default +output=HTML:frames:earthli + +;; turn this option on if you want highlighted source code for every file +;; legal values: on/off +sourcecode = on + diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..af456227 --- /dev/null +++ b/debian/rules @@ -0,0 +1,55 @@ +#!/usr/bin/make -f +# Made with the aid of debmake, by Christoph Lameter, +# based on the sample debian/rules file for GNU hello by Ian Jackson. + +package=rscds +dt=debian/$(package) + +build: inc htdocs + $(checkdir) + cd ~andrew/projects/phpdoc/phpDocumentor-1.2.3 && ./phpdoc -c ~andrew/projects/$(package)/debian/$(package)-phpdoc.ini + touch build + +clean: + $(checkdir) + rm -f build + rm -f `find . -name "*~"` + -rm -rf $(dt) debian/files* core debian/substvars debian/html + +binary-indep: checkroot build + $(checkdir) + -rm -rf $(dt) + dh_clean -k + # dh_installdebconf + install -d $(dt) $(dt)/DEBIAN $(dt)/etc/$(package) \ + $(dt)/usr/share/$(package) $(dt)/usr/share/doc/$(package) \ + $(dt)/var/lib/$(package) + cp -a htdocs $(dt)/usr/share/$(package) + cp -a dba $(dt)/usr/share/$(package) + cp -a inc $(dt)/usr/share/$(package) + # cp -a admin $(dt)/usr/share/$(package) + dh_installdocs + dh_installchangelogs + dh_fixperms + install -m 1777 -d $(dt)/var/lib/$(package)/attachments + dh_installdeb + dpkg-gencontrol -P$(dt) + dpkg --build $(dt) .. + +binary-arch: checkroot build + $(checkdir) +# There are no architecture-dependent files to be uploaded +# generated by this package. If there were any they would be +# made here. + +define checkdir + test -f debian/rules +endef + +binary: binary-indep binary-arch + +checkroot: + $(checkdir) + test root = "`whoami`" + +.PHONY: binary binary-arch binary-indep clean checkroot diff --git a/htdocs/caldav.php b/htdocs/caldav.php new file mode 100644 index 00000000..ddcc1801 --- /dev/null +++ b/htdocs/caldav.php @@ -0,0 +1,36 @@ +>%s<<", $_SERVER['REQUEST_METHOD'] ); + dbg_log_array( 'HEADERS', $raw_headers ); + dbg_log_array( '_SERVER', $_SERVER, true ); + dbg_error_log( "RAW: %s", str_replace("\n", "",str_replace("\r", "", $raw_post)) ); +} + + +?> \ No newline at end of file diff --git a/htdocs/index.php b/htdocs/index.php deleted file mode 100644 index 1c77f8cb..00000000 --- a/htdocs/index.php +++ /dev/null @@ -1,47 +0,0 @@ - $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))); -} - - -?> \ No newline at end of file diff --git a/inc/BasicAuthSession.php b/inc/BasicAuthSession.php new file mode 100644 index 00000000..b7a4ea78 --- /dev/null +++ b/inc/BasicAuthSession.php @@ -0,0 +1,194 @@ + +* @copyright Catalyst IT Ltd +* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 +*/ + +require_once("session-util.php"); + +/** +* A Class for handling a session using HTTP Basic Authentication +* +* @package rscds +*/ +class BasicAuthSession { + /**#@+ + * @access private + */ + + /** + * User ID number + * @var user_no int + */ + var $user_no; + + /** + * User e-mail + * @var email string + */ + var $email; + + /** + * User full name + * @var fullname string + */ + var $fullname; + + /** + * Group rights + * @var groups array + */ + var $groups; + /**#@-*/ + + /** + * The constructor, which pretty much drives it all + */ + function BasicAuthSession() { + global $c; + if ( isset($_SERVER['PHP_AUTH_USER']) ) { + if ( $u = $this->CheckPassword( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) ) { + $this->AssignSessionDetails($u); + } + else { + unset($_SERVER['PHP_AUTH_USER']); + } + } + + if (!isset($_SERVER['PHP_AUTH_USER'])) { + header( sprintf( 'WWW-Authenticate: Basic realm="%s"', $c->system_name) ); + header('HTTP/1.0 401 Unauthorized'); + echo 'Please log in for access to this system.'; + dbg_error_log( "Login: User is not authorised" ); + exit; + } + } + + /** + * Utility function to log stuff with printf expansion. + * + * This function could be expanded to log something identifying the session, but + * somewhat strangely this has not yet been done. + * + * @param string $whatever A log string + * @param mixed $whatever... Further parameters to be replaced into the log string a la printf + */ + function Log( $whatever ) + { + global $c; + + $argc = func_num_args(); + $format = func_get_arg(0); + if ( $argc == 1 || ($argc == 2 && func_get_arg(1) == "0" ) ) { + error_log( "$c->sysabbr: $format" ); + } + else { + $args = array(); + for( $i=1; $i < $argc; $i++ ) { + $args[] = func_get_arg($i); + } + error_log( "$c->sysabbr: " . vsprintf($format,$args) ); + } + } + + /** + * Utility function to log debug stuff with printf expansion, and the ability to + * enable it selectively. + * + * The enabling is done by setting a variable "$debuggroups[$group] = 1" + * + * @param string $group The name of an arbitrary debug group. + * @param string $whatever A log string + * @param mixed $whatever... Further parameters to be replaced into the log string a la printf + */ + function Dbg( $whatever ) + { + global $debuggroups, $c; + + $argc = func_num_args(); + $dgroup = func_get_arg(0); + + if ( ! (isset($debuggroups[$dgroup]) && $debuggroups[$dgroup]) ) return; + + $format = func_get_arg(1); + if ( $argc == 2 || ($argc == 3 && func_get_arg(2) == "0" ) ) { + error_log( "$c->sysabbr: DBG: $dgroup: $format" ); + } + else { + $args = array(); + for( $i=2; $i < $argc; $i++ ) { + $args[] = func_get_arg($i); + } + error_log( "$c->sysabbr: DBG: $dgroup: " . vsprintf($format,$args) ); + } + } + + + /** + * CheckPassword does all of the password checking and + * returns a user record object, or false if it all ends in tears. + */ + function CheckPassword( $username, $password ) { + $qry = new PgQuery( "SELECT * FROM usr WHERE lower(username) = ? ", $username ); + if ( $qry->Exec('BAS::CheckPassword',__LINE,__FILE__) && $qry->rows == 1 ) { + $usr = $qry->Fetch(); + dbg_error_log( "Login: Name:%s, Pass:%s, File:%s", $username, $password, $usr->password ); + if ( session_validate_password( $password, $usr->password ) ) { + return $usr; + } + } + return false; + } + + /** + * Checks whether a user is allowed to do something. + * + * The check is performed to see if the user has that role. + * + * @param string $whatever The role we want to know if the user has. + * @return boolean Whether or not the user has the specified role. + */ + function AllowedTo ( $whatever ) { + return ( $this->logged_in && isset($this->roles[$whatever]) && $this->roles[$whatever] ); + } + + + /** + * Internal function used to get the user's roles from the database. + */ + function GetRoles () { + $this->roles = array(); + $qry = new PgQuery( 'SELECT role_name FROM role_member m join roles r ON r.role_no = m.role_no WHERE user_no = ? ', $this->user_no ); + if ( $qry->Exec('BAS::GetRoles') && $qry->rows > 0 ) { + while( $role = $qry->Fetch() ) { + $this->roles[$role->role_name] = true; + } + } + } + + + /** + * Internal function used to assign the session details to a user's new session. + * @param object $u The user+session object we (probably) read from the database. + */ + function AssignSessionDetails( $u ) { + // Assign each field in the selected record to the object + foreach( $u AS $k => $v ) { + $this->{$k} = $v; + } + + $this->GetRoles(); + $this->logged_in = true; + } + + +} + +$session = new BasicAuthSession(); + +?> \ No newline at end of file diff --git a/inc/always.php b/inc/always.php index 9efb0f21..f7085e7f 100644 --- a/inc/always.php +++ b/inc/always.php @@ -1,21 +1,78 @@ +* @copyright Catalyst IT Ltd +* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 +*/ -$c->sysabbr = 'caldav'; +// Ensure the configuration starts out as an empty object. +unset($c); + +// Default some of the configurable values +$c->sysabbr = 'rscds'; $c->admin_email = 'andrew@catalyst.net.nz'; -$c->system_name = "Andrew's CalDAV Server"; +$c->system_name = "Really Simple CalDAV Store"; +$c->domain_name = $_SERVER['SERVER_NAME']; -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"); +// Kind of private configuration values +$c->total_query_time = 0; + +if ( $debugging && isset($_GET['method']) ) { + $_SERVER['REQUEST_METHOD'] = $_GET['method']; +} + +/** +* Writes a debug message into the error log using printf syntax +* @package rscds +*/ +function dbg_error_log() { + global $c; + $argc = func_num_args(); + $args = func_get_args(); + if ( 2 <= $argc ) { + $format = array_shift($args); + } + else { + $format = "%s"; + } + error_log( $c->sysabbr.": DBG: ". vsprintf( $format, $args ) ); +} + + +dbg_error_log( "==========> method =%s= =%s:%d= =%s= =%s=", + $_SERVER['REQUEST_METHOD'], $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $_SERVER['PATH_INFO']); +if ( file_exists("/etc/rscds/".$_SERVER['SERVER_NAME']."-conf.php") ) { + include_once("/etc/rscds/".$_SERVER['SERVER_NAME']."-conf.php"); } else if ( file_exists("../config/config.php") ) { include_once("../config/config.php"); } else { - include_once("caldav_configuration_missing.php"); + include_once("rscds_configuration_missing.php"); exit; } -include_once("iCalendar.php"); +// In case this was forced in the configuration file +$_SERVER['SERVER_NAME'] = $c->domain_name; + +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) { + dbg_error_log( "%s: >>%s<< = >>%s<<", $name, $key, $value); + if ( $recursive && (gettype($value) == 'array' || gettype($value) == 'object') ) { + dbg_log_array( "$name"."[$key]", $value, $recursive ); + } + } +} + +include_once("PgQuery.php"); +include_once("BasicAuthSession.php"); +// include_once("iCalendar.php"); ?> \ No newline at end of file diff --git a/inc/caldav-DELETE.php b/inc/caldav-DELETE.php new file mode 100644 index 00000000..6e993e0b --- /dev/null +++ b/inc/caldav-DELETE.php @@ -0,0 +1,27 @@ +user_no, $get_path, $etag_none_match ); +if ( $qry->Exec("caldav-DELETE") && $qry->rows == 1 ) { + $qry = new PgQuery( "DELETE FROM ics_event_data WHERE user_no = ? AND ics_event_name = ? AND ics_event_etag = ?;", $session->user_no, $get_path, $etag_none_match ); + if ( $qry->Exec("caldav-DELETE") ) { + header("HTTP/1.1 200 OK"); + dbg_error_log( "DELETE: User: %d, ETag: %s, Path: %s", $session->user_no, $etag_none_match, $get_path); + } + else { + header("HTTP/1.1 500 Infernal Server Error"); + dbg_error_log( "DELETE failed: User: %d, ETag: %s, Path: %s, SQL: %s", $session->user_no, $etag_none_match, $get_path, $qry->querystring); + } +} +else { + header("HTTP/1.1 404 Not Found"); + dbg_error_log( "DELETE row not found: User: %d, ETag: %s, Path: %s", $qry->rows, $session->user_no, $etag_none_match, $get_path); +} + +?> \ No newline at end of file diff --git a/inc/caldav-GET.php b/inc/caldav-GET.php new file mode 100644 index 00000000..58e914ea --- /dev/null +++ b/inc/caldav-GET.php @@ -0,0 +1,27 @@ +user_no, $get_path); +if ( $qry->Exec("caldav-GET") && $qry->rows == 1 ) { + $event = $qry->Fetch(); + + header("HTTP/1.1 200 OK"); + header("ETag: $event->ics_event_etag"); + header("Content-Type: text/calendar"); + + print $event->ics_raw_data; + + dbg_error_log( "GET: User: %d, ETag: %s, Path: %s", $session->user_no, $event->ics_event_etag, $get_path); + +} +else { + header("HTTP/1.1 500 Infernal Server Error"); +} + +?> \ No newline at end of file diff --git a/inc/caldav-OPTIONS.php b/inc/caldav-OPTIONS.php index e6933b4f..e9a15a13 100644 --- a/inc/caldav-OPTIONS.php +++ b/inc/caldav-OPTIONS.php @@ -1,5 +1,5 @@ user_no, $put_path, $etag, $raw_post); + $qry->Exec("caldav-PUT"); + + header("HTTP/1.1 201 Created"); + header("ETag: $etag"); +} +else { + $qry = new PgQuery( "UPDATE ics_event_data SET ics_raw_data=?, ics_event_etag=? WHERE user_no=? AND ics_event_name=? AND ics_event_etag=?", + $raw_post, $etag, $session->user_no, $put_path, $etag_match ); + $qry->Exec("caldav-PUT"); + + header("HTTP/1.1 201 Replaced"); + header("ETag: $etag"); } -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 << - - - http://mycaldav/?andrewmcmillan.ics - - - "fffff-abcd1" - 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 << - - HTTP/1.1 200 OK - - - -EOXML; +dbg_error_log( "PUT: User: %d, ETag: %s, Path: %s", $session->user_no, $etag, $put_path); ?> \ No newline at end of file diff --git a/inc/caldav-REPORT.php b/inc/caldav-REPORT.php index 630a644c..62b135da 100644 --- a/inc/caldav-REPORT.php +++ b/inc/caldav-REPORT.php @@ -1,20 +1,17 @@ $v ) { unset($report_properties); } else { - error_log( "DBG: Unexpected DAV::PROP type of ".$v['type'] ); + dbg_error_log( "REPORT: Unexpected DAV::PROP type of ".$v['type'] ); } } else { - error_log( "DBG: Unexpected DAV::PROP type of ".$v['type']." when no active report type."); + dbg_error_log( "REPORT: Unexpected DAV::PROP type of ".$v['type']." when no active report type."); } break; @@ -68,40 +65,47 @@ foreach( $rpt_request AS $k => $v ) { break; default: - error_log("caldav: DBG: Unhandled tag >>".$v['tag']."<<"); + dbg_error_log("REPORT: Unhandled tag >>".$v['tag']."<<"); } } -dbg_log_array( 'RPT', $rpt_request, true ); +// dbg_log_array( 'RPT', $rpt_request, true ); -dbg_log_array( 'REPORT', $report, true ); +// dbg_log_array( 'REPORT', $report, true ); -header("Content-type: text/xml"); +header("HTTP/1.1 207 Multi-Status"); +header("Content-type: text/xml;charset=UTF-8"); -echo << + http://%s:%d%s%s + + + "%s" + + HTTP/1.1 200 OK + + + +RESPONSETPL; + + +echo << - - http://mycaldav/?andrewmcmillan.ics - - - "fffff-abcd1" - 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(); +REPORTHDR; + + $qry = new PgQuery( "SELECT * FROM ics_event_data;" ); + if ( $qry->Exec() && $qry->rows > 0 ) { + while( $event = $qry->Fetch() ) { + printf( $response_tpl, $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $event->ics_event_name, $event->ics_event_etag ); + dbg_error_log("REPORT: ETag >>%s<< >>http://%s:%s%s%s<<", $event->ics_event_etag, + $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], $_SERVER['SCRIPT_NAME'], $event->ics_event_name); + } + } echo << - - HTTP/1.1 200 OK - - EOXML; diff --git a/inc/vEvent.php b/inc/vEvent.php new file mode 100644 index 00000000..5045d254 --- /dev/null +++ b/inc/vEvent.php @@ -0,0 +1,136 @@ + +* @copyright Catalyst IT Ltd +* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 +*/ + + +/** +* A Class for handling Events on a calendar +* +* @package rscds +*/ +class vEvent { + /**#@+ + * @access private + */ + + /** + * List of participants in this event + * @var participants array + */ + var $participants = array(); + + /** + * An array of arbitrary properties + * @var props array + */ + var $properties; + + /**#@-*/ + + /** + * The constructor takes an array of args. If there is an element called 'vevent' + * then that will be parsed into the vEvent object. Otherwise the array elements + * are converted into properties of the vEvent object directly. + */ + function vEvent( $args ) { + global $c; + + // Probably a good idea to always have values for these things... + $this->properties['tzid'] = $c->local_tzid; + $this->properties['modified'] = iCalendar::EpochTS(time()); + $this->properties['sequence'] = 1; + $this->properties['uid'] = sprintf( "%s@%s", time() * 1000 + rand(0,1000), $c->domain_name); + $this->properties['guid'] = sprintf( "%s@%s", time() * 1000 + rand(0,1000), $c->domain_name); + $this->properties['duration'] = "PT1H"; + $this->properties['status'] = "TENTATIVE"; + + if ( !isset($args) || !is_array($args) ) return; + + if ( isset($args['vevent']) ) { + $this->BuildFromText($args['vevent']); + $this->DealWithTimeZones(); + return; + } + + foreach( $args AS $k => $v ) { + $this->properties[strtoupper($k)] = $v; + } + } + + /** + * Build the vEvent object from a text string which is a single VEVENT + * + * @var vevent string + */ + function BuildFromText( $vevent ) { + $vevent = preg_replace('/[\r\n]+ /', ' ', $vevent ); + $lines = preg_split('/[\r\n]+/', $vevent ); + $properties = array(); + + $vtimezone = ""; + $state = 0; + foreach( $properties AS $k => $v ) { + + switch( $state ) { + case 0: + if ( $v == 'BEGIN:VEVENT' ) $state = $v; + else if ( $v == 'BEGIN:VTIMEZONE' ) $state = $v; + break; + + case 'BEGIN:VEVENT': + if ( $v == 'END:VEVENT' ) $state = 0; + break; + + case 'BEGIN:VTIMEZONE': + if ( $v == 'END:VTIMEZONE' ) { + $state = 0; + $vtimezone .= $v; + } + break; + } + + if ( $state == 'BEGIN:VEVENT' && $state != $v ) { + list( $parameter, $value ) = preg_split('/:/', $v ); + if ( preg_match('/^DT[A-Z]+;TZID=/', $parameter) ) { + list( $parameter, $tzid ) = preg_split('/;/', $parameter ); + $properties['TZID'] = $tzid; + } + $properties[$parameter] = $value; + } + if ( $state == 'BEGIN:VTIMEZONE' ) { + $vtimezone .= $v . "\n"; + } + } + + if ( $vtimezone != "" ) { + $properties['VTIMEZONE'] = $vtimezone; + } + + $this->properties = &$properties; + } + + + /** + * Do what must be done with time zones from on file. Attempt to turn + * them into something that PostgreSQL can understand... + */ + function DealWithTimeZones() { + $qry = new PgQuery( "SELECT pgtz FROM time_zones WHERE tzid = ?;", $this->properties['TZID'] ); + if ( $qry->Exec('vEvent') && $qry->rows == 1 ) { + } + else { + $qry2 = new PgQuery( "INSERT INTO time_zones (tzid, location, tz_spec) VALUES( ?, ?, ?);", $this->properties['TZID'], $location, $this->properties['VTIMEZONE'] ); + $qry2->Exec("vEvent"); + } + } + +} + +?> \ No newline at end of file diff --git a/caldav.webprj b/rscds.webprj similarity index 53% rename from caldav.webprj rename to rscds.webprj index fb072394..8054829f 100644 --- a/caldav.webprj +++ b/rscds.webprj @@ -1,6 +1,6 @@ - + Andrew McMillan andrew@catalyst.net.nz @@ -8,20 +8,26 @@ templates/ toolbars/ - - - - - - + + + + + + + + + + + +