initial support for remote url BINDing

This commit is contained in:
Rob Ostensen 2011-06-27 18:39:40 -05:00 committed by Andrew McMillan
parent 40b4bdfd7f
commit 6331c50003
6 changed files with 205 additions and 42 deletions

View File

@ -188,6 +188,16 @@ $c->collections_always_exist = false;
$c->schedule_private_key = 'PRIVATE-KEY-BASE-64-DATA';
*/
/*
* External subscription (BIND) minimum refresh interval
* Required if you want to enable remote binding ( webcal subscriptions )
* Default: none
*/
/*
$c->external_refresh = 60;
*/
/***************************************************************************

View File

@ -97,6 +97,11 @@ class DAVResource
*/
private $_is_binding;
/**
* @var True if this resource is a binding to an external resource
*/
private $_is_external;
/**
* @var True if this resource is an addressbook collection
*/
@ -158,6 +163,7 @@ class DAVResource
$this->_is_principal = false;
$this->_is_calendar = false;
$this->_is_binding = false;
$this->_is_external = false;
$this->_is_addressbook = false;
$this->_is_proxy_request = false;
if ( isset($parameters) && is_object($parameters) ) {
@ -400,7 +406,8 @@ EOSQL;
SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), p.principal_id,
p.type_id AS principal_type_id, p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges,
time_zone.tz_spec, dav_binding.access_ticket_id, dav_binding.parent_container AS bind_parent_container,
dav_binding.dav_displayname, owner.dav_name AS bind_owner_url, dav_binding.dav_name AS bound_to
dav_binding.dav_displayname, owner.dav_name AS bind_owner_url, dav_binding.dav_name AS bound_to,
dav_binding.external_url AS external_url, dav_binding.type AS external_type, dav_binding.bind_id AS bind_id
FROM dav_binding
LEFT JOIN collection ON (collection.collection_id=bound_source_id)
LEFT JOIN principal p USING (user_no)
@ -431,7 +438,16 @@ EOSQL;
$this->collection->type = 'schedule-'. $matches[3]. 'box';
else
$this->collection->type = 'collection';
if ( strlen($row->external_url) > 8 ) {
$this->collection->bound_from = $row->external_url;
$this->_is_external = true;
if ( $row->external_type == 'calendar' )
$this->collection->type = 'calendar';
else if ( $row->external_type == 'addressbook' )
$this->collection->type = 'addressbook';
else
$this->collection->type = 'collection';
}
$this->_is_binding = true;
$this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
if ( isset($row->access_ticket_id) ) {
@ -1047,6 +1063,14 @@ EOQRY;
}
/**
* Checks whether this resource is a bind to an external resource
*/
function IsExternal() {
return $this->_is_external;
}
/**
* Checks whether this resource actually exists, in the virtual sense, within the hierarchy
*/

View File

@ -47,45 +47,88 @@ if ( $destination->Exists() ) {
$request->PreconditionFailed(403,'DAV::can-overwrite',translate('A resource already exists at the destination.'));
}
$source = new DAVResource( $href );
if ( !$source->Exists() ) {
$request->PreconditionFailed(403,'DAV::bind-source-exists',translate('The BIND Request MUST identify an existing resource.'));
}
if ( preg_match ( '{^https?://[A-Za-z][^/]*/.+$}', $href ) ) {
require_once('external-fetch.php');
$qry = new AwlQuery( );
$qry->QDo('SELECT collection_id FROM collection WHERE dav_name = :dav_name ', array( ':dav_name' => '/.external/'. md5($href) ));
if ( $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
$dav_id = $row->dav_id;
}
else {
create_external ( '/.external/'. md5($href) ,true,false );
$qry->QDo('SELECT collection_id FROM collection WHERE dav_name = :dav_name ', array( ':dav_name' => '/.external/'. md5($href) ));
if ( $qry->rows() != 1 || !($row = $qry->Fetch()) )
$request->DoResponse(500,translate('Database Error1'));
$dav_id = $row->collection_id;
}
if ( $source->IsPrincipal() || !$source->IsCollection() ) {
$request->PreconditionFailed(403,'DAV::binding-allowed',translate('DAViCal only allows BIND requests for collections at present.'));
}
/*
bind_id INT8 DEFAULT nextval('dav_id_seq') PRIMARY KEY,
bound_source_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE,
access_ticket_id TEXT REFERENCES access_ticket(ticket_id) ON UPDATE CASCADE ON DELETE SET NULL,
parent_container TEXT NOT NULL,
dav_name TEXT UNIQUE NOT NULL,
dav_displayname TEXT
*/
$sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname )
VALUES( :target_id, :ticket_id, :session_principal, :parent_container, :dav_name, :displayname )';
$params = array(
':target_id' => $source->GetProperty('collection_id'),
':ticket_id' => (isset($request->ticket) ? $request->ticket->id() : null),
':parent_container' => $parent->dav_name(),
':session_principal' => $session->principal_id,
':dav_name' => $destination_path,
':displayname' => $source->GetProperty('displayname')
);
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('BIND',__LINE__,__FILE__) ) {
header('Location: '. ConstructURL($destination_path) );
// Uncache anything to do with the target
$cache = getCacheInstance();
$cache_ns = 'collection-'.$destination_path;
$cache->delete( $cache_ns, null );
$request->DoResponse(201);
}
$sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname, external_url, type )
VALUES( :target_id, :ticket_id, :session_principal, :parent_container, :dav_name, :displayname, :external_url, :external_type )';
$params = array(
':target_id' => $dav_id,
':ticket_id' => null,
':parent_container' => $parent->dav_name(),
':session_principal' => $session->principal_id,
':dav_name' => $destination_path,
':displayname' => $segment,
':external_url' => $href,
':external_type' => 'calendar'
);
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('BIND',__LINE__,__FILE__) ) {
$qry = new AwlQuery( 'SELECT bind_id from dav_binding where dav_name = :dav_name', array( ':dav_name' => $destination_path ) );
if ( ! $qry->Exec('BIND',__LINE__,__FILE__) || $qry->rows() != 1 || !($row = $qry->Fetch()) )
$request->DoResponse(500,translate('Database Error1'));
fetch_external ( $row->bind_id, '' );
$request->DoResponse(201);
}
else {
$request->DoResponse(500,translate('Database Error2'));
}
}
else {
$request->DoResponse(500,translate('Database Error'));
}
$source = new DAVResource( $href );
if ( !$source->Exists() ) {
$request->PreconditionFailed(403,'DAV::bind-source-exists',translate('The BIND Request MUST identify an existing resource.'));
}
if ( $source->IsPrincipal() || !$source->IsCollection() ) {
$request->PreconditionFailed(403,'DAV::binding-allowed',translate('DAViCal only allows BIND requests for collections at present.'));
}
/*
bind_id INT8 DEFAULT nextval('dav_id_seq') PRIMARY KEY,
bound_source_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE,
access_ticket_id TEXT REFERENCES access_ticket(ticket_id) ON UPDATE CASCADE ON DELETE SET NULL,
parent_container TEXT NOT NULL,
dav_name TEXT UNIQUE NOT NULL,
dav_displayname TEXT,
external_url TEXT,
type TEXT
*/
$sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname )
VALUES( :target_id, :ticket_id, :session_principal, :parent_container, :dav_name, :displayname )';
$params = array(
':target_id' => $source->GetProperty('collection_id'),
':ticket_id' => (isset($request->ticket) ? $request->ticket->id() : null),
':parent_container' => $parent->dav_name(),
':session_principal' => $session->principal_id,
':dav_name' => $destination_path,
':displayname' => $source->GetProperty('displayname')
);
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('BIND',__LINE__,__FILE__) ) {
header('Location: '. ConstructURL($destination_path) );
// Uncache anything to do with the target
$cache = getCacheInstance();
$cache_ns = 'collection-'.$destination_path;
$cache->delete( $cache_ns, null );
$request->DoResponse(201);
}
else {
$request->DoResponse(500,translate('Database Error'));
}
}

View File

@ -15,6 +15,9 @@ require_once("DAVResource.php");
$dav_resource = new DAVResource($request->path);
$dav_resource->NeedPrivilege( array('urn:ietf:params:xml:ns:caldav:read-free-busy','DAV::read') );
if ( $dav_resource->IsExternal() ) {
update_external ( $dav_resource );
}
if ( ! $dav_resource->Exists() ) {
$request->DoResponse( 404, translate("Resource Not Found.") );

View File

@ -61,9 +61,15 @@ switch( $xmltree->GetTag() ) {
include("caldav-REPORT-pps-set.php");
exit; // Not that it should return anyway.
case 'DAV::sync-collection':
require_once("external-fetch.php");
if ( $target->IsExternal() )
update_external ( $target );
include("caldav-REPORT-sync-collection.php");
exit; // Not that it should return anyway.
case 'DAV::expand-property':
require_once("external-fetch.php");
if ( $target->IsExternal() )
update_external ( $target );
include("caldav-REPORT-expand-property.php");
exit; // Not that it should return anyway.
case 'DAV::principal-match':
@ -313,6 +319,10 @@ function component_to_xml( $properties, $item ) {
return $response;
}
require_once("external-fetch.php");
if ( $target->IsExternal() )
update_external ( $target );
if ( $xmltree->GetTag() == "urn:ietf:params:xml:ns:caldav:calendar-query" ) {
$calquery = $xmltree->GetPath("/urn:ietf:params:xml:ns:caldav:calendar-query/*");
include("caldav-REPORT-calquery.php");

73
inc/external-fetch.php Normal file
View File

@ -0,0 +1,73 @@
<?PHP
/**
* Functions for managing external BIND resources
*
*
* @package davical
* @subpackage external-bind
* @author Rob Ostensen <rob@boxacle.net>
* @copyright Rob Ostensen
* @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
function create_external ( $path,$is_calendar,$is_addressbook )
{
global $request;
$resourcetypes = '<DAV::collection/>';
if ($is_calendar) $resourcetypes .= '<urn:ietf:params:xml:ns:caldav:calendar/>';
$qry = new AwlQuery();
if ( ! $qry->QDo( 'INSERT INTO collection ( user_no, parent_container, dav_name, dav_etag, dav_displayname,
is_calendar, is_addressbook, resourcetypes, created, modified )
VALUES( :user_no, :parent_container, :dav_name, :dav_etag, :dav_displayname,
:is_calendar, :is_addressbook, :resourcetypes, current_timestamp, current_timestamp )',
array(
':user_no' => $request->user_no,
':parent_container' => '/.external/',
':dav_name' => $path,
':dav_etag' => md5($request->user_no. $path),
':dav_displayname' => $path,
':is_calendar' => ($is_calendar ? 't' : 'f'),
':is_addressbook' => ($is_addressbook ? 't' : 'f'),
':resourcetypes' => $resourcetypes
) ) ) {
$request->DoResponse( 500, translate('Error writing calendar details to database.') );
}
}
function fetch_external ( $bind_id, $min_age )
{
$sql = 'SELECT collection.*, collection.dav_name as path, dav_binding.external_url as external_url FROM dav_binding LEFT JOIN collection ON (collection.collection_id=bound_source_id) WHERE bind_id = :bind_id';
$params = array( ':bind_id' => $bind_id );
if ( strlen ( $min_age ) > 2 ) {
$sql .= ' and collection.modified + interval :interval > NOW()';
$params[':interval'] = $min_age;
}
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $row = $qry->Fetch() ) {
$curl = curl_init ( $row->external_url );
curl_setopt ( $curl, CURLOPT_RETURNTRANSFER, true );
$ics = curl_exec ( $curl );
curl_close ( $curl );
if ( is_string ( $ics ) && strlen ( $ics ) > 20 ) {
$qry = new AwlQuery( 'UPDATE collection SET modified=NOW() where collection_id = :cid', array ( ':cid' => $row->collection_id ) );
$qry->Exec('DAVResource');
require_once ( 'caldav-PUT-functions.php');
import_collection ( $ics , $row->user_no, $row->path, 'External Fetch' , false ) ;
return true;
}
}
return false;
}
function update_external ( $request )
{
global $c;
if ( $c->external_refresh < 1 )
return ;
$sql = 'SELECT bind_id from dav_binding LEFT JOIN collection ON (collection.collection_id=bound_source_id) collection where dav_name = :dav_name and collection.modified + interval :interval < NOW()';
$qry = new AwlQuery( $sql, array ( ':dav_name' => $request->dav_name, ':interval' => $c->external_refresh + ' minutes' ) );
if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $row = $qry->Fetch() ) {
if ( $row->bind_id != 0 )
fetch_external ( $row->bind_id );
}
}