Kinda working. Nearly packaged.

This commit is contained in:
Andrew McMillan 2006-09-11 22:30:00 +12:00
parent 792b1a5083
commit dc4526c3d7
23 changed files with 810 additions and 204 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
caldav.session
rscds.session

1
README Normal file
View File

@ -0,0 +1 @@
Really Simple CalDAV Store by Andrew McMillan.

9
TODO Normal file
View File

@ -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

View File

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

View File

@ -1,5 +1,8 @@
<?php
$c->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") ) {

View File

@ -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;

14
debian/README.debian vendored Normal file
View File

@ -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 <andrew@catalyst.net.nz>, Tue. 2 May 2006 07:11:22 +1200

5
debian/changelog vendored Normal file
View File

@ -0,0 +1,5 @@
rscds (0.1.0) unstable; urgency=low
* Initial Debian packaging
-- Andrew McMillan <debian@mcmillan.net.nz> Tue, 2 May 2006 07:43:59 +1200

15
debian/control vendored Normal file
View File

@ -0,0 +1,15 @@
Source: rscds
Section: catalyst
Priority: extra
Maintainer: Andrew McMillan <andrew@catalyst.net.nz>
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).

3
debian/copyright vendored Normal file
View File

@ -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.

100
debian/rscds-phpdoc.ini vendored Normal file
View File

@ -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 <cellog@users.sourceforge.net>
;;
;; 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 <installdir>/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

55
debian/rules vendored Executable file
View File

@ -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

36
htdocs/caldav.php Normal file
View File

@ -0,0 +1,36 @@
<?php
require_once("always.php");
$raw_headers = apache_request_headers();
$raw_post = file_get_contents ( 'php://input');
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;
case 'GET':
include_once("caldav-GET.php");
break;
case 'DELETE':
include_once("caldav-DELETE.php");
break;
default:
dbg_error_log( "Unhandled request method >>%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)) );
}
?>

View File

@ -1,47 +0,0 @@
<?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)));
}
?>

194
inc/BasicAuthSession.php Normal file
View File

@ -0,0 +1,194 @@
<?php
/**
* A Class for handling BasicAuthSession data
*
* @package rscds
* @subpackage BasicAuthSession
* @author Andrew McMillan <andrew@catalyst.net.nz>
* @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();
?>

View File

@ -1,21 +1,78 @@
<?php
/**
* @package rscds
* @author Andrew McMillan <andrew@catalyst.net.nz>
* @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");
?>

27
inc/caldav-DELETE.php Normal file
View File

@ -0,0 +1,27 @@
<?php
dbg_error_log("DELETE method handler");
// The DELETE method is not sent with any wrapping XML so we simply delete it
$get_path = $_SERVER['PATH_INFO'];
$etag_none_match = str_replace('"','',$_SERVER["HTTP_IF_NONE_MATCH"]);
$qry = new PgQuery( "SELECT * 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") && $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);
}
?>

27
inc/caldav-GET.php Normal file
View File

@ -0,0 +1,27 @@
<?php
dbg_error_log("GET method handler");
// The GET method is not sent with any wrapping XML so we simply fetch it
$get_path = $_SERVER['PATH_INFO'];
$etag_none_match = str_replace('"','',$_SERVER["HTTP_IF_NONE_MATCH"]);
$qry = new PgQuery( "SELECT * FROM ics_event_data WHERE user_no = ? AND ics_event_name = ? ;", $session->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");
}
?>

View File

@ -1,5 +1,5 @@
<?php
error_log("caldav: DBG: OPTIONS method handler");
dbg_error_log("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");

View File

@ -1,105 +1,38 @@
<?php
error_log("caldav: DBG: PUT method handler");
dbg_error_log("PUT method handler");
$parser = xml_parser_create_ns('UTF-8');
xml_parser_set_option ( $parser, XML_OPTION_SKIP_WHITE, 1 );
// The PUT method is not sent with any wrapping XML so we simply store it
// after constructing an eTag and getting a name for it...
function xml_start_callback( $parser, $el_name, $el_attrs ) {
error_log( "DBG: Parsing $el_name" );
dbg_log_array( "$el_name::attrs", $el_attrs, true );
$fh = fopen('/tmp/PUT.txt','w');
fwrite($fh,$raw_post);
fclose($fh);
$etag = md5($raw_post);
$put_path = $_SERVER['PATH_INFO'];
$etag_none_match = str_replace('"','',$_SERVER["HTTP_IF_NONE_MATCH"]);
$etag_match = str_replace('"','',$_SERVER["HTTP_IF_MATCH"]);
dbg_log_array( 'HEADERS', $raw_headers );
dbg_log_array( '_SERVER', $_SERVER, true );
if ( $etag_match == '*' || $etag_match == '' ) {
$qry = new PgQuery( "INSERT INTO ics_event_data ( user_no, ics_event_name, ics_event_etag, ics_raw_data ) VALUES( ?, ?, ?, ?)", $session->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 <<<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;
dbg_error_log( "PUT: User: %d, ETag: %s, Path: %s", $session->user_no, $etag, $put_path);
?>

View File

@ -1,20 +1,17 @@
<?php
error_log("caldav: DBG: REPORT method handler");
include_once("iCalendar.php");
error_reporting(E_ALL);
dbg_error_log("REPORT 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_error_log( "REPORT: 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" );
dbg_error_log( "REPORT: Finished Parsing $el_name" );
}
xml_set_element_handler ( $parser, 'xml_start_callback', 'xml_end_callback' );
@ -51,11 +48,11 @@ foreach( $rpt_request AS $k => $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 <<<EOXML
$response_tpl = <<<RESPONSETPL
<D:response>
<D:href>http://%s:%d%s%s</D:href>
<D:propstat>
<D:prop>
<D:getetag>"%s"</D:getetag>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
RESPONSETPL;
echo <<<REPORTHDR
<?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();
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 <<<EOXML
END:VCALENDAR
</C:calendar-data>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
EOXML;

136
inc/vEvent.php Normal file
View File

@ -0,0 +1,136 @@
<?php
/**
* A Class for handling vEvent data
*
* @package rscds
* @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 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");
}
}
}
?>

View File

@ -1,6 +1,6 @@
<!DOCTYPE webproject>
<webproject>
<project type="Local" name="caldav" encoding="utf8" >
<project type="Local" name="rscds" encoding="utf8" >
<upload/>
<author>Andrew McMillan</author>
<email>andrew@catalyst.net.nz</email>
@ -8,20 +8,26 @@
<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" />
<item url="dba/caldav.sql" uploadstatus="1" />
<item url="dba/" uploadstatus="1" />
<item url="dba/create-database.sh" uploadstatus="1" />
<item url="dba/sample-data.sql" uploadstatus="1" />
<item url="inc/caldav-GET.php" uploadstatus="1" />
<item url="inc/caldav-DELETE.php" uploadstatus="1" />
<item url="htdocs/caldav.php" uploadstatus="1" />
<item url="inc/BasicAuthSession.php" uploadstatus="1" />
<item url="inc/session-util.php" uploadstatus="1" />
<item url="inc/vEvent.php" uploadstatus="1" />
<item url="debian/" />
<item url="debian/rules" />
</project>
</webproject>