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/
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+