* @copyright Catalyst .Net Ltd, Morphoss Ltd * @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once("XMLDocument.php"); require_once("CalDAVPrincipal.php"); include("DAVTicket.php"); include_once("drivers_ldap.php"); define('DEPTH_INFINITY', 9999); /** * A class for collecting things to do with this request. * * @package davical */ class CalDAVRequest { var $options; /** * The raw data sent along with the request */ var $raw_post; /** * The HTTP request method: PROPFIND, LOCK, REPORT, OPTIONS, etc... */ var $method; /** * The depth parameter from the request headers, coerced into a valid integer: 0, 1 * or DEPTH_INFINITY which is defined above. The default is set per various RFCs. */ var $depth; /** * The 'principal' (user/resource/...) which this request seeks to access * @var CalDAVPrincipal */ var $principal; /** * The 'current_user_principal_xml' the DAV:current-user-principal answer. An * XMLElement object with an or fragment. */ var $current_user_principal_xml; /** * The user agent making the request. */ var $user_agent; /** * The ID of the collection containing this path, or of this path if it is a collection */ var $collection_id; /** * The path corresponding to the collection_id */ var $collection_path; /** * The type of collection being requested: * calendar, schedule-inbox, schedule-outbox */ var $collection_type; /** * The type of collection being requested: * calendar, schedule-inbox, schedule-outbox */ protected $exists; /** * The decimal privileges allowed by this user to the identified resource. */ protected $privileges; /** * A static structure of supported privileges. */ var $supported_privileges; /** * A DAVTicket object, if there is a ?ticket=id or Ticket: id with this request */ public $ticket; /** * Create a new CalDAVRequest object. */ function __construct( $options = array() ) { global $session, $c, $debugging; $this->supported_privileges = array( 'all' => array( 'read' => translate('Read the content of a resource or collection'), 'write' => array( 'bind' => translate('Create a resource or collection'), 'unbind' => translate('Delete a resource or collection'), 'write-content' => translate('Write content'), 'write-properties' => translate('Write properties') ), 'urn:ietf:params:xml:ns:caldav:read-free-busy' => translate('Read the free/busy information for a calendar collection'), 'read-acl' => translate('Read ACLs for a resource or collection'), 'read-current-user-privilege-set' => translate('Read the details of the current user\'s access control to this resource.'), 'write-acl' => translate('Write ACLs for a resource or collection'), 'unlock' => translate('Remove a lock'), 'urn:ietf:params:xml:ns:caldav:schedule-deliver' => array( 'urn:ietf:params:xml:ns:caldav:schedule-deliver-invite'=> translate('Deliver scheduling invitations from an organiser to this scheduling inbox'), 'urn:ietf:params:xml:ns:caldav:schedule-deliver-reply' => translate('Deliver scheduling replies from an attendee to this scheduling inbox'), 'urn:ietf:params:xml:ns:caldav:schedule-query-freebusy' => translate('Allow free/busy enquiries targeted at the owner of this scheduling inbox') ), 'urn:ietf:params:xml:ns:caldav:schedule-send' => array( 'urn:ietf:params:xml:ns:caldav:schedule-send-invite' => translate('Send scheduling invitations as an organiser from the owner of this scheduling outbox.'), 'urn:ietf:params:xml:ns:caldav:schedule-send-reply' => translate('Send scheduling replies as an attendee from the owner of this scheduling outbox.'), 'urn:ietf:params:xml:ns:caldav:schedule-send-freebusy' => translate('Send free/busy enquiries') ) ) ); $this->options = $options; if ( !isset($this->options['allow_by_email']) ) $this->options['allow_by_email'] = false; $this->principal = (object) array( 'username' => $session->username, 'user_no' => $session->user_no ); $this->raw_post = file_get_contents ( 'php://input'); if ( (isset($c->dbg['ALL']) && $c->dbg['ALL']) || (isset($c->dbg['request']) && $c->dbg['request']) ) { /** Log the request headers */ $lines = apache_request_headers(); dbg_error_log( "LOG ", "***************** Request Header ****************" ); dbg_error_log( "LOG ", "%s %s", $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'] ); foreach( $lines AS $k => $v ) { dbg_error_log( "LOG headers", "-->%s: %s", $k, $v ); } dbg_error_log( "LOG ", "******************** Request ********************" ); // Log the request in all it's gory detail. $lines = preg_split( '#[\r\n]+#', $this->raw_post); foreach( $lines AS $v ) { dbg_error_log( "LOG request", "-->%s", $v ); } } if ( isset($debugging) && isset($_GET['method']) ) { $_SERVER['REQUEST_METHOD'] = $_GET['method']; } else if ( $_SERVER['REQUEST_METHOD'] == 'POST' && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']) ){ $_SERVER['REQUEST_METHOD'] = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; } $this->method = $_SERVER['REQUEST_METHOD']; if ( isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > 7 ) { $this->content_type = (isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : null); if ( preg_match( '{^(\S+/\S+)\s*(;.*)?$}', $this->content_type, $matches ) ) { $this->content_type = $matches[1]; } if ( $this->method == 'PROPFIND' || $this->method == 'REPORT' ) { if ( !preg_match( '{^(text|application)/xml$}', $this->content_type ) ) { dbg_error_log( "LOG request", 'Request is "%s" but client set content-type to "%s". Assuming they meant XML!', $request->method, $this->content_type ); $this->content_type = 'text/xml'; } } } $this->user_agent = ((isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : "Probably Mulberry")); /** * A variety of requests may set the "Depth" header to control recursion */ if ( isset($_SERVER['HTTP_DEPTH']) ) { $this->depth = $_SERVER['HTTP_DEPTH']; } else { /** * Per rfc2518, section 9.2, 'Depth' might not always be present, and if it * is not present then a reasonable request-type-dependent default should be * chosen. */ switch( $this->method ) { case 'PROPFIND': case 'DELETE': case 'MOVE': case 'COPY': case 'LOCK': $this->depth = 'infinity'; break; case 'REPORT': default: $this->depth = 0; } } if ( $this->depth == 'infinity' ) $this->depth = DEPTH_INFINITY; $this->depth = intval($this->depth); /** * MOVE/COPY use a "Destination" header and (optionally) an "Overwrite" one. */ if ( isset($_SERVER['HTTP_DESTINATION']) ) $this->destination = $_SERVER['HTTP_DESTINATION']; $this->overwrite = ( isset($_SERVER['HTTP_OVERWRITE']) && ($_SERVER['HTTP_OVERWRITE'] == 'F') ? false : true ); // RFC4918, 9.8.4 says default True. /** * LOCK things use an "If" header to hold the lock in some cases, and "Lock-token" in others */ if ( isset($_SERVER['HTTP_IF']) ) $this->if_clause = $_SERVER['HTTP_IF']; if ( isset($_SERVER['HTTP_LOCK_TOKEN']) && preg_match( '#[<]opaquelocktoken:(.*)[>]#', $_SERVER['HTTP_LOCK_TOKEN'], $matches ) ) { $this->lock_token = $matches[1]; } /** * Check for an access ticket. */ if ( isset($_GET['ticket']) ) { $this->ticket = new DAVTicket($_GET['ticket']); } else if ( isset($_SERVER['HTTP_TICKET']) ) { $this->ticket = new DAVTicket($_SERVER['HTTP_TICKET']); } /** * LOCK things use a "Timeout" header to set a series of reducing alternative values */ if ( isset($_SERVER['HTTP_TIMEOUT']) ) { $timeouts = explode( ',', $_SERVER['HTTP_TIMEOUT'] ); foreach( $timeouts AS $k => $v ) { if ( strtolower($v) == 'infinite' ) { $this->timeout = (isset($c->maximum_lock_timeout) ? $c->maximum_lock_timeout : 86400 * 100); break; } elseif ( strtolower(substr($v,0,7)) == 'second-' ) { $this->timeout = min( intval(substr($v,7)), (isset($c->maximum_lock_timeout) ? $c->maximum_lock_timeout : 86400 * 100) ); break; } } if ( ! isset($this->timeout) || $this->timeout == 0 ) $this->timeout = (isset($c->default_lock_timeout) ? $c->default_lock_timeout : 900); } /** * Our path is /