Logo Search packages:      
Sourcecode: davical version File versions  Download package

CalDAVRequest::CalDAVRequest ( options = array() )

Create a new CalDAVRequest object.

A variety of requests may set the "Depth" header to control recursion

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.

MOVE/COPY use a "Destination" header and (optionally) an "Overwrite" one.

LOCK things use an "If" header to hold the lock in some cases, and "Lock-token" in others

LOCK things use a "Timeout" header to set a series of reducing alternative values

Our path is /<script name>="">/<user name>="">/<user controlled>=""> if it ends in a trailing '/' then it is referring to a DAV 'collection' but otherwise it is referring to a DAV data item.

Permissions are controlled as follows: 1. if there is no <user name>=""> component, the request has read privileges 2. if the requester is an admin, the request has read/write priviliges 3. if there is a <user name>=""> component which matches the logged on user then the request has read/write privileges 4. otherwise we query the defined relationships between users and use the minimum privileges returned from that analysis.

RFC2518, 5.2: URL pointing to a collection SHOULD end in '/', and if it does not then we SHOULD return a Content-location header with the correction...

We therefore look for a collection which matches one of the following URLs:

  • The exact request.
  • If the exact request, doesn't end in '/', then the request URL with a '/' appended
  • The request URL truncated to the last '/' The collection URL for this request is therefore the longest row in the result, so we can "... ORDER BY LENGTH(dav_name) DESC LIMIT 1"

Extract the user whom we are accessing

Evaluate our permissions for accessing the target

If the content we are receiving is XML then we parse it here. RFC2518 says we should reasonably expect to see either text/xml or application/xml

Look out for If-None-Match or If-Match headers

Definition at line 82 of file CalDAVRequest.php.

References DoResponse(), and setPermissions().

                                               {
    global $session, $c, $debugging;

    $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($debugging) && isset($_GET['method']) ) {
      $_SERVER['REQUEST_METHOD'] = $_GET['method'];
    }
    $this->method = $_SERVER['REQUEST_METHOD'];

    $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'] : 'T' ); // RFC2518, 9.6 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];
    }

    /**
    * LOCK things use a "Timeout" header to set a series of reducing alternative values
    */
    if ( isset($_SERVER['HTTP_TIMEOUT']) ) {
      $timeouts = split( ',', $_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 /<script name>/<user name>/<user controlled> if it ends in
    * a trailing '/' then it is referring to a DAV 'collection' but otherwise
    * it is referring to a DAV data item.
    *
    * Permissions are controlled as follows:
    *  1. if there is no <user name> component, the request has read privileges
    *  2. if the requester is an admin, the request has read/write priviliges
    *  3. if there is a <user name> component which matches the logged on user
    *     then the request has read/write privileges
    *  4. otherwise we query the defined relationships between users and use
    *     the minimum privileges returned from that analysis.
    */
    $this->path = (isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : "");
    if ( $this->path == null || $this->path == '' ) $this->path = '/';
    // dbg_error_log( "caldav", "Sanitising path '%s'", $this->path );
    $bad_chars_regex = '/[\\^\\[\\(\\\\]/';
    if ( preg_match( $bad_chars_regex, $this->path ) ) {
      $this->DoResponse( 400, translate("The calendar path contains illegal characters.") );
    }

    $this->user_no = $session->user_no;
    $this->username = $session->username;
    if ( $session->user_no > 0 ) {
      $this->current_user_principal_url = new XMLElement('href', ConstructURL('/'.$session->username.'/') );
    }
    else {
      $this->current_user_principal_url = new XMLElement('unauthenticated' );
    }

    /**
    * RFC2518, 5.2: URL pointing to a collection SHOULD end in '/', and if it does not then
    * we SHOULD return a Content-location header with the correction...
    *
    * We therefore look for a collection which matches one of the following URLs:
    *  - The exact request.
    *  - If the exact request, doesn't end in '/', then the request URL with a '/' appended
    *  - The request URL truncated to the last '/'
    * The collection URL for this request is therefore the longest row in the result, so we
    * can "... ORDER BY LENGTH(dav_name) DESC LIMIT 1"
    */
    $sql = "SELECT * FROM collection WHERE dav_name = ".qpg($this->path);
    if ( !preg_match( '#/$#', $this->path ) ) {
      $sql .= " OR dav_name = ".qpg(preg_replace( '#[^/]*$#', '', $this->path));
      $sql .= " OR dav_name = ".qpg($this->path."/");
    }
    $sql .= "ORDER BY LENGTH(dav_name) DESC LIMIT 1";
    $qry = new PgQuery( $sql );
    if ( $qry->Exec('caldav') && $qry->rows == 1 && ($row = $qry->Fetch()) ) {
      if ( $row->dav_name == $this->path."/" ) {
        $this->path = $row->dav_name;
        dbg_error_log( "caldav", "Path is actually a collection - sending Content-Location header." );
        header( "Content-Location: $this->path" );
      }

      $this->collection_id = $row->collection_id;
      $this->collection_path = $row->dav_name;
      $this->collection = $row;
    }
    else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->path, $matches ) ) {
      // The request is for a scheduling inbox or outbox (or something inside one) and we should auto-create it
      $displayname = $session->fullname . ($matches[3] == 'in' ? ' Inbox' : ' Outbox');
      $sql = <<<EOSQL
INSERT INTO collection ( user_no, parent_container, dav_name, dav_displayname, is_calendar, created, modified )
    VALUES( ?, ?, ?, ?, FALSE, current_timestamp, current_timestamp )
EOSQL;
      $qry = new PgQuery( $sql, $session->user_no, $matches[2] , $matches[1], $displayname );
      $qry->Exec('caldav');
      dbg_error_log( "caldav", "Created new collection as '$displayname'." );

      $qry = new PgQuery( "SELECT * FROM collection WHERE user_no = ? AND dav_name = ?;", $session->user_no, $matches[1] );
      if ( $qry->Exec('caldav') && $qry->rows == 1 && ($row = $qry->Fetch()) ) {
        $this->collection_id = $row->collection_id;
        $this->collection_path = $matches[1];
        $this->collection = $row;
      }
    }
    else if ( preg_match( '#/(\S+@\S+[.]\S+)/?$#', $this->path) ) {
      $this->collection_id = -1;
      $this->collection_type = 'email';
      $this->collection_path = $this->path;
    }
    else if ( preg_match( '#^(/[^/]+)/?$#', $this->path, $matches) ) {
      $this->collection_id = -1;
      $this->collection_path = $matches[1].'/';  // Enforce trailling '/'
      $this->collection_type = 'principal';
      $this->_is_principal = true;
      if ( $this->collection_path == $this->path."/" ) {
        $this->path .= '/';
        dbg_error_log( "caldav", "Path is actually a collection - sending Content-Location header." );
        header( "Content-Location: $this->path" );
      }
    }
    else if ( $this->path == '/' ) {
      $this->collection_id = -1;
      $this->collection_path = '/';
      $this->collection_type = 'root';
    }

    if ( $this->collection_path == $this->path ) $this->_is_collection = true;
    dbg_error_log( "caldav", " Collection '%s' is %d, type %s", $this->collection_path, $this->collection_id, $this->collection_type );

    /**
    * Extract the user whom we are accessing
    */
    $this->principal = new CalDAVPrincipal( array( "path" => $this->path, "options" => $this->options ) );
    if ( isset($this->principal->user_no) ) $this->user_no  = $this->principal->user_no;
    if ( isset($this->principal->username)) $this->username = $this->principal->username;
    if ( isset($this->principal->by_email)) $this->by_email = true;

    /**
    * Evaluate our permissions for accessing the target
    */
    $this->setPermissions();

    /**
    * If the content we are receiving is XML then we parse it here.  RFC2518 says we
    * should reasonably expect to see either text/xml or application/xml
    */
    if ( isset($_SERVER['CONTENT_TYPE']) && preg_match( '#(application|text)/xml#', $_SERVER['CONTENT_TYPE'] ) ) {
      $xml_parser = xml_parser_create_ns('UTF-8');
      $this->xml_tags = array();
      xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
      xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
      xml_parse_into_struct( $xml_parser, $this->raw_post, $this->xml_tags );
      xml_parser_free($xml_parser);
      dbg_error_log( "caldav", " Parsed incoming XML request body." );
    }

    /**
    * Look out for If-None-Match or If-Match headers
    */
    if ( isset($_SERVER["HTTP_IF_NONE_MATCH"]) ) {
      $this->etag_none_match = str_replace('"','',$_SERVER["HTTP_IF_NONE_MATCH"]);
      if ( $this->etag_none_match == '' ) unset($this->etag_none_match);
    }
    if ( isset($_SERVER["HTTP_IF_MATCH"]) ) {
      $this->etag_if_match = str_replace('"','',$_SERVER["HTTP_IF_MATCH"]);
      if ( $this->etag_if_match == '' ) unset($this->etag_if_match);
    }
  }

Here is the call graph for this function:


Generated by  Doxygen 1.6.0   Back to index