mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-09-19 05:59:02 +00:00
initial support for remote url BINDing
This commit is contained in:
parent
40b4bdfd7f
commit
6331c50003
@ -188,6 +188,16 @@ $c->collections_always_exist = false;
|
|||||||
$c->schedule_private_key = 'PRIVATE-KEY-BASE-64-DATA';
|
$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;
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
|
|||||||
@ -97,6 +97,11 @@ class DAVResource
|
|||||||
*/
|
*/
|
||||||
private $_is_binding;
|
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
|
* @var True if this resource is an addressbook collection
|
||||||
*/
|
*/
|
||||||
@ -158,6 +163,7 @@ class DAVResource
|
|||||||
$this->_is_principal = false;
|
$this->_is_principal = false;
|
||||||
$this->_is_calendar = false;
|
$this->_is_calendar = false;
|
||||||
$this->_is_binding = false;
|
$this->_is_binding = false;
|
||||||
|
$this->_is_external = false;
|
||||||
$this->_is_addressbook = false;
|
$this->_is_addressbook = false;
|
||||||
$this->_is_proxy_request = false;
|
$this->_is_proxy_request = false;
|
||||||
if ( isset($parameters) && is_object($parameters) ) {
|
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,
|
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,
|
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,
|
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
|
FROM dav_binding
|
||||||
LEFT JOIN collection ON (collection.collection_id=bound_source_id)
|
LEFT JOIN collection ON (collection.collection_id=bound_source_id)
|
||||||
LEFT JOIN principal p USING (user_no)
|
LEFT JOIN principal p USING (user_no)
|
||||||
@ -431,7 +438,16 @@ EOSQL;
|
|||||||
$this->collection->type = 'schedule-'. $matches[3]. 'box';
|
$this->collection->type = 'schedule-'. $matches[3]. 'box';
|
||||||
else
|
else
|
||||||
$this->collection->type = 'collection';
|
$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->_is_binding = true;
|
||||||
$this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
|
$this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
|
||||||
if ( isset($row->access_ticket_id) ) {
|
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
|
* Checks whether this resource actually exists, in the virtual sense, within the hierarchy
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -47,45 +47,88 @@ if ( $destination->Exists() ) {
|
|||||||
$request->PreconditionFailed(403,'DAV::can-overwrite',translate('A resource already exists at the destination.'));
|
$request->PreconditionFailed(403,'DAV::can-overwrite',translate('A resource already exists at the destination.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$source = new DAVResource( $href );
|
if ( preg_match ( '{^https?://[A-Za-z][^/]*/.+$}', $href ) ) {
|
||||||
if ( !$source->Exists() ) {
|
require_once('external-fetch.php');
|
||||||
$request->PreconditionFailed(403,'DAV::bind-source-exists',translate('The BIND Request MUST identify an existing resource.'));
|
$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() ) {
|
$sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname, external_url, type )
|
||||||
$request->PreconditionFailed(403,'DAV::binding-allowed',translate('DAViCal only allows BIND requests for collections at present.'));
|
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,
|
||||||
bind_id INT8 DEFAULT nextval('dav_id_seq') PRIMARY KEY,
|
':parent_container' => $parent->dav_name(),
|
||||||
bound_source_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE,
|
':session_principal' => $session->principal_id,
|
||||||
access_ticket_id TEXT REFERENCES access_ticket(ticket_id) ON UPDATE CASCADE ON DELETE SET NULL,
|
':dav_name' => $destination_path,
|
||||||
parent_container TEXT NOT NULL,
|
':displayname' => $segment,
|
||||||
dav_name TEXT UNIQUE NOT NULL,
|
':external_url' => $href,
|
||||||
dav_displayname TEXT
|
':external_type' => 'calendar'
|
||||||
*/
|
);
|
||||||
|
$qry = new AwlQuery( $sql, $params );
|
||||||
$sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname )
|
if ( $qry->Exec('BIND',__LINE__,__FILE__) ) {
|
||||||
VALUES( :target_id, :ticket_id, :session_principal, :parent_container, :dav_name, :displayname )';
|
$qry = new AwlQuery( 'SELECT bind_id from dav_binding where dav_name = :dav_name', array( ':dav_name' => $destination_path ) );
|
||||||
$params = array(
|
if ( ! $qry->Exec('BIND',__LINE__,__FILE__) || $qry->rows() != 1 || !($row = $qry->Fetch()) )
|
||||||
':target_id' => $source->GetProperty('collection_id'),
|
$request->DoResponse(500,translate('Database Error1'));
|
||||||
':ticket_id' => (isset($request->ticket) ? $request->ticket->id() : null),
|
fetch_external ( $row->bind_id, '' );
|
||||||
':parent_container' => $parent->dav_name(),
|
$request->DoResponse(201);
|
||||||
':session_principal' => $session->principal_id,
|
}
|
||||||
':dav_name' => $destination_path,
|
else {
|
||||||
':displayname' => $source->GetProperty('displayname')
|
$request->DoResponse(500,translate('Database Error2'));
|
||||||
);
|
}
|
||||||
$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 {
|
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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -15,6 +15,9 @@ require_once("DAVResource.php");
|
|||||||
|
|
||||||
$dav_resource = new DAVResource($request->path);
|
$dav_resource = new DAVResource($request->path);
|
||||||
$dav_resource->NeedPrivilege( array('urn:ietf:params:xml:ns:caldav:read-free-busy','DAV::read') );
|
$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() ) {
|
if ( ! $dav_resource->Exists() ) {
|
||||||
$request->DoResponse( 404, translate("Resource Not Found.") );
|
$request->DoResponse( 404, translate("Resource Not Found.") );
|
||||||
|
|||||||
@ -61,9 +61,15 @@ switch( $xmltree->GetTag() ) {
|
|||||||
include("caldav-REPORT-pps-set.php");
|
include("caldav-REPORT-pps-set.php");
|
||||||
exit; // Not that it should return anyway.
|
exit; // Not that it should return anyway.
|
||||||
case 'DAV::sync-collection':
|
case 'DAV::sync-collection':
|
||||||
|
require_once("external-fetch.php");
|
||||||
|
if ( $target->IsExternal() )
|
||||||
|
update_external ( $target );
|
||||||
include("caldav-REPORT-sync-collection.php");
|
include("caldav-REPORT-sync-collection.php");
|
||||||
exit; // Not that it should return anyway.
|
exit; // Not that it should return anyway.
|
||||||
case 'DAV::expand-property':
|
case 'DAV::expand-property':
|
||||||
|
require_once("external-fetch.php");
|
||||||
|
if ( $target->IsExternal() )
|
||||||
|
update_external ( $target );
|
||||||
include("caldav-REPORT-expand-property.php");
|
include("caldav-REPORT-expand-property.php");
|
||||||
exit; // Not that it should return anyway.
|
exit; // Not that it should return anyway.
|
||||||
case 'DAV::principal-match':
|
case 'DAV::principal-match':
|
||||||
@ -313,6 +319,10 @@ function component_to_xml( $properties, $item ) {
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require_once("external-fetch.php");
|
||||||
|
if ( $target->IsExternal() )
|
||||||
|
update_external ( $target );
|
||||||
|
|
||||||
if ( $xmltree->GetTag() == "urn:ietf:params:xml:ns:caldav:calendar-query" ) {
|
if ( $xmltree->GetTag() == "urn:ietf:params:xml:ns:caldav:calendar-query" ) {
|
||||||
$calquery = $xmltree->GetPath("/urn:ietf:params:xml:ns:caldav:calendar-query/*");
|
$calquery = $xmltree->GetPath("/urn:ietf:params:xml:ns:caldav:calendar-query/*");
|
||||||
include("caldav-REPORT-calquery.php");
|
include("caldav-REPORT-calquery.php");
|
||||||
|
|||||||
73
inc/external-fetch.php
Normal file
73
inc/external-fetch.php
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user