Example #1
0
 /**
  * Method used to get the user's roles
  */
 function GetRoles()
 {
     $this->roles = array();
     $sql = 'SELECT role_name FROM roles JOIN role_member ON roles.role_no=role_member.role_no WHERE user_no = ' . $this->user_no;
     $qry = new AwlQuery($sql);
     if ($qry->Exec('DAViCalSession') && $qry->rows() > 0) {
         while ($role = $qry->Fetch()) {
             $this->roles[$role->role_name] = 1;
         }
     }
 }
Example #2
0
function delete_collection($id)
{
    $params = array(':collection_id' => $id);
    $qry = new AwlQuery('SELECT child.collection_id AS child_id FROM collection child JOIN collection parent ON (parent.dav_name = child.parent_container) WHERE parent.collection_id = :collection_id', $params);
    if ($qry->Exec('DELETE', __LINE__, __FILE__) && $qry->rows() > 0) {
        while ($row = $qry->Fetch()) {
            delete_collection($row->child_id);
        }
    }
    if ($qry->QDo("SELECT write_sync_change(collection_id, 404, caldav_data.dav_name) FROM caldav_data WHERE collection_id = :collection_id", $params) && $qry->QDo("DELETE FROM property WHERE dav_name LIKE (SELECT dav_name FROM collection WHERE collection_id = :collection_id) || '%'", $params) && $qry->QDo("DELETE FROM locks WHERE dav_name LIKE (SELECT dav_name FROM collection WHERE collection_id = :collection_id) || '%'", $params) && $qry->QDo("DELETE FROM caldav_data WHERE collection_id = :collection_id", $params) && $qry->QDo("DELETE FROM collection WHERE collection_id = :collection_id", $params)) {
        @dbg_error_log("DELETE", "DELETE (collection): User: %d, ETag: %s, Path: %s", $session->user_no, $request->etag_if_match, $request->path);
        return true;
    }
    return false;
}
Example #3
0
 static function getInstance($name)
 {
     $qry = new AwlQuery('SELECT * FROM timezones WHERE tzid = ? ORDER BY active DESC', $name);
     if ($qry->Exec('VTimezone', __LINE__, __FILE__) && $qry->rows() > 0 && ($row = $qry->Fetch())) {
         $vtz = new vComponent($row->vtimezone);
         if ($vtz->GetType() == 'VTIMEZONE') {
             return $vtz;
         }
         $tmp = $vtz->GetComponents('VTIMEZONE');
         if (count($tmp) < 1 || $tmp[0]->GetType() != 'VTIMEZONE') {
             return null;
         }
         $vtz = $tmp[0];
         return $vtz;
     }
     return null;
 }
Example #4
0
 function SQLTest()
 {
     $result = '';
     $sql = "SELECT event_instances::timestamp AS event_date FROM event_instances(:dtstart,:rrule) LIMIT " . $this->result_limit;
     $start = microtime(true);
     $qry = new AwlQuery($sql, array(':dtstart' => $this->dtstart, ':rrule' => $this->recur));
     // printf( "%s\n", $qry->querystring);
     if ($qry->Exec("test") && $qry->rows() > 0) {
         $i = 0;
         while ($row = $qry->Fetch()) {
             if ($i++ % 4 == 0) {
                 $result .= "\n";
             }
             $result .= "   " . $row->event_date;
         }
     }
     $this->SQL_time = microtime(true) - $start;
     return $result;
 }
Example #5
0
 /**
  * Split this out so we do it as infrequently as possible, given the cost.
  */
 function FetchProxyGroups()
 {
     global $c;
     $this->read_proxy_group = array();
     $this->write_proxy_group = array();
     $this->write_proxy_for = array();
     $this->read_proxy_for = array();
     if (!isset($c->disable_caldav_proxy) || $c->disable_caldav_proxy === false) {
         $write_priv = privilege_to_bits(array('write'));
         // whom are we a proxy for? who is a proxy for us?
         // (as per Caldav Proxy section 5.1 Paragraph 7 and 5)
         $sql = 'SELECT principal_id, username, pprivs(:request_principal::int8,principal_id,:scan_depth::int) FROM principal JOIN usr USING(user_no) WHERE principal_id IN (SELECT * from p_has_proxy_access_to(:request_principal,:scan_depth))';
         $params = array(':request_principal' => $this->principal_id, ':scan_depth' => $c->permission_scan_depth);
         $qry = new AwlQuery($sql, $params);
         if ($qry->Exec('CalDAVPrincipal') && $qry->rows() > 0) {
             while ($relationship = $qry->Fetch()) {
                 if ((bindec($relationship->pprivs) & $write_priv) != 0) {
                     $this->write_proxy_for[] = ConstructURL('/' . $relationship->username . '/', true);
                     $this->group_membership[] = ConstructURL('/' . $relationship->username . '/calendar-proxy-write/', true);
                 } else {
                     $this->read_proxy_for[] = ConstructURL('/' . $relationship->username . '/', true);
                     $this->group_membership[] = ConstructURL('/' . $relationship->username . '/calendar-proxy-read/', true);
                 }
             }
         }
         $sql = 'SELECT principal_id, username, pprivs(:request_principal::int8,principal_id,:scan_depth::int) FROM principal JOIN usr USING(user_no) WHERE principal_id IN (SELECT * from grants_proxy_access_from_p(:request_principal,:scan_depth))';
         $qry = new AwlQuery($sql, $params);
         // reuse $params assigned for earlier query
         if ($qry->Exec('CalDAVPrincipal') && $qry->rows() > 0) {
             while ($relationship = $qry->Fetch()) {
                 if (bindec($relationship->pprivs) & $write_priv) {
                     $this->write_proxy_group[] = ConstructURL('/' . $relationship->username . '/', true);
                 } else {
                     $this->read_proxy_group[] = ConstructURL('/' . $relationship->username . '/', true);
                 }
             }
         }
         //      @dbg_error_log( 'principal', 'Read-proxy-for:    %s', implode(',',$this->read_proxy_for) );
         //      @dbg_error_log( 'principal', 'Write-proxy-for:   %s', implode(',',$this->write_proxy_for) );
         //      @dbg_error_log( 'principal', 'Read-proxy-group:  %s', implode(',',$this->read_proxy_group) );
         //      @dbg_error_log( 'principal', 'Write-proxy-group: %s', implode(',',$this->write_proxy_group) );
     }
 }
$proplist = array();
foreach ($props as $k => $v) {
    $proplist[] = $v->GetTag();
}
function display_status($status_code)
{
    return sprintf('HTTP/1.1 %03d %s', intval($status_code), getStatusMessage($status_code));
}
$collection = new DAVResource($request->path);
if (!$collection->Exists()) {
    $request->DoResponse(404);
}
$params = array(':collection_id' => $collection->GetProperty('collection_id'), ':sync_token' => $sync_token);
$sql = "SELECT new_sync_token( :sync_token, :collection_id)";
$qry = new AwlQuery($sql, $params);
if (!$qry->Exec("REPORT", __LINE__, __FILE__) || $qry->rows() <= 0) {
    $request->DoResponse(500, translate("Database error"));
}
$row = $qry->Fetch();
if (!isset($row->new_sync_token)) {
    /** If we got a null back then they gave us a sync token we know not of, so provide a full sync */
    $sync_token = 0;
    $params[':sync_token'] = $sync_token;
    if (!$qry->QDo($sql, $params) || $qry->rows() <= 0) {
        $request->DoResponse(500, translate("Database error"));
    }
    $row = $qry->Fetch();
}
$new_token = $row->new_sync_token;
if ($sync_token == $new_token) {
    // No change, so we just re-send the old token.
Example #7
0
/**
* Authenticate against a different PostgreSQL database which contains a usr table in
* the AWL format.
*
* Use this as in the following example config snippet:
*
* require_once('auth-functions.php');
*  $c->authenticate_hook = array(
*      'call'   => 'AuthExternalAwl',
*      'config' => array(
*           // A PgSQL database connection string for the database containing user records
*          'connection[]' => 'dbname=wrms host=otherhost port=5433 user=general',
*           // Which columns should be fetched from the database
*          'columns'    => "user_no, active, email_ok, joined, last_update AS updated, last_used, username, password, fullname, email",
*           // a WHERE clause to limit the records returned.
*          'where'    => "active AND org_code=7"
*      )
*  );
*
*/
function AuthExternalAWL($username, $password)
{
    global $c;
    $persistent = isset($c->authenticate_hook['config']['use_persistent']) && $c->authenticate_hook['config']['use_persistent'];
    if (isset($c->authenticate_hook['config']['columns'])) {
        $cols = $c->authenticate_hook['config']['columns'];
    } else {
        $cols = '*';
    }
    if (isset($c->authenticate_hook['config']['where'])) {
        $andwhere = ' AND ' . $c->authenticate_hook['config']['where'];
    } else {
        $andwhere = '';
    }
    $qry = new AwlQuery('SELECT ' . $cols . ' FROM usr WHERE lower(username) = :username ' . $andwhere, array(':username' => strtolower($username)));
    $authconn = $qry->SetConnection($c->authenticate_hook['config']['connection'], $persistent ? array(PDO::ATTR_PERSISTENT => true) : null);
    if (!$authconn) {
        echo <<<EOERRMSG
  <html><head><title>Database Connection Failure</title></head><body>
  <h1>Database Error</h1>
  <h3>Could not connect to PostgreSQL database</h3>
  </body>
  </html>
EOERRMSG;
        exit(1);
    }
    if ($qry->Exec('Login', __LINE__, __FILE__) && $qry->rows() == 1) {
        $usr = $qry->Fetch();
        if (session_validate_password($password, $usr->password)) {
            UpdateUserFromExternal($usr);
            /**
             * We disallow login by inactive users _after_ we have updated the local copy
             */
            if (isset($usr->active) && $usr->active == 'f') {
                return false;
            }
            $qry = new AwlQuery('SELECT * FROM dav_principal WHERE username = :username', array(':username' => $usr->username));
            if ($qry->Exec() && $qry->rows() == 1) {
                $principal = $qry->Fetch();
                return $principal;
            }
            return $usr;
            // Somewhat optimistically
        }
    }
    return false;
}
Example #8
0
    /**
     * Create a new CalDAVRequest object.
     */
    function __construct($options = array())
    {
        global $session, $c, $debugging;
        $this->options = $options;
        if (!isset($this->options['allow_by_email'])) {
            $this->options['allow_by_email'] = false;
        }
        if (isset($_SERVER['HTTP_PREFER'])) {
            $this->prefer = explode(',', $_SERVER['HTTP_PREFER']);
        } else {
            if (isset($_SERVER['HTTP_BRIEF']) && strtoupper($_SERVER['HTTP_BRIEF']) == 'T') {
                $this->prefer = array('return-minimal');
            } else {
                $this->prefer = array();
            }
        }
        /**
         * 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.
         */
        if (isset($_SERVER['PATH_INFO'])) {
            $this->path = $_SERVER['PATH_INFO'];
        } else {
            $this->path = '/';
            if (isset($_SERVER['REQUEST_URI'])) {
                if (preg_match('{^(.*?\\.php)(.*)$}', $_SERVER['REQUEST_URI'], $matches)) {
                    $this->path = $matches[2];
                    if (substr($this->path, 0, 1) != '/') {
                        $this->path = '/' . $this->path;
                    }
                } else {
                    if ($_SERVER['REQUEST_URI'] != '/') {
                        dbg_error_log('LOG', 'Server is not supplying PATH_INFO and REQUEST_URI does not include a PHP program.  Wildly guessing "/"!!!');
                    }
                }
            }
        }
        $this->path = rawurldecode($this->path);
        /** Allow a request for .../calendar.ics to translate into the calendar URL */
        if (preg_match('#^(/[^/]+/[^/]+).ics$#', $this->path, $matches)) {
            $this->path = $matches[1] . '/';
        }
        if (isset($c->replace_path) && isset($c->replace_path['from']) && isset($c->replace_path['to'])) {
            $this->path = preg_replace($c->replace_path['from'], $c->replace_path['to'], $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."));
        }
        if (strstr($this->path, '//')) {
            $this->path = preg_replace('#//+#', '/', $this->path);
        }
        if (!isset($c->raw_post)) {
            $c->raw_post = file_get_contents('php://input');
        }
        if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) {
            $encoding = $_SERVER['HTTP_CONTENT_ENCODING'];
            @dbg_error_log('caldav', 'Content-Encoding: %s', $encoding);
            $encoding = preg_replace('{[^a-z0-9-]}i', '', $encoding);
            if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['caldav']))) {
                $fh = fopen('/var/log/davical/encoded_data.debug' . $encoding, 'w');
                if ($fh) {
                    fwrite($fh, $c->raw_post);
                    fclose($fh);
                }
            }
            switch ($encoding) {
                case 'gzip':
                    $this->raw_post = @gzdecode($c->raw_post);
                    break;
                case 'deflate':
                    $this->raw_post = @gzinflate($c->raw_post);
                    break;
                case 'compress':
                    $this->raw_post = @gzuncompress($c->raw_post);
                    break;
                default:
            }
            if (empty($this->raw_post) && !empty($c->raw_post)) {
                $this->PreconditionFailed(415, 'content-encoding', sprintf('Unable to decode "%s" content encoding.', $_SERVER['HTTP_CONTENT_ENCODING']));
            }
            $c->raw_post = $this->raw_post;
        } else {
            $this->raw_post = $c->raw_post;
        }
        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'];
        $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 (strlen($c->raw_post) > 0) {
            if ($this->method == 'PROPFIND' || $this->method == 'REPORT' || $this->method == 'PROPPATCH' || $this->method == 'BIND' || $this->method == 'MKTICKET' || $this->method == 'ACL') {
                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!', $this->method, $this->content_type);
                    $this->content_type = 'text/xml';
                }
            } else {
                if ($this->method == 'PUT' || $this->method == 'POST') {
                    $this->CoerceContentType();
                }
            }
        } else {
            $this->content_type = 'text/plain';
        }
        $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 'DELETE':
                case 'MOVE':
                case 'COPY':
                case 'LOCK':
                    $this->depth = 'infinity';
                    break;
                case 'REPORT':
                    $this->depth = 0;
                    break;
                case 'PROPFIND':
                default:
                    $this->depth = 0;
            }
        }
        if (!is_int($this->depth) && "infinity" == $this->depth) {
            $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'];
            if (preg_match('{^(https?)://([a-z.-]+)(:[0-9]+)?(/.*)$}', $this->destination, $matches)) {
                $this->destination = $matches[4];
            }
        }
        $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;
            }
        }
        $this->principal = new Principal('path', $this->path);
        /**
         * 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 = :exact_name";
        $params = array(':exact_name' => $this->path);
        if (!preg_match('#/$#', $this->path)) {
            $sql .= " OR dav_name = :truncated_name OR dav_name = :trailing_slash_name";
            $params[':truncated_name'] = preg_replace('#[^/]*$#', '', $this->path);
            $params[':trailing_slash_name'] = $this->path . "/";
        }
        $sql .= " ORDER BY LENGTH(dav_name) DESC LIMIT 1";
        $qry = new AwlQuery($sql, $params);
        if ($qry->Exec('caldav', __LINE__, __FILE__) && $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: " . ConstructURL($this->path));
            }
            $this->collection_id = $row->collection_id;
            $this->collection_path = $row->dav_name;
            $this->collection_type = $row->is_calendar == 't' ? 'calendar' : 'collection';
            $this->collection = $row;
            if (preg_match('#^((/[^/]+/)\\.(in|out)/)[^/]*$#', $this->path, $matches)) {
                $this->collection_type = 'schedule-' . $matches[3] . 'box';
            }
            $this->collection->type = $this->collection_type;
        } else {
            if (preg_match('{^( ( / ([^/]+) / ) \\.(in|out)/ ) [^/]*$}x', $this->path, $matches)) {
                // The request is for a scheduling inbox or outbox (or something inside one) and we should auto-create it
                $params = array(':username' => $matches[3], ':parent_container' => $matches[2], ':dav_name' => $matches[1]);
                $params[':boxname'] = $matches[4] == 'in' ? ' Inbox' : ' Outbox';
                $this->collection_type = 'schedule-' . $matches[4] . 'box';
                $params[':resourcetypes'] = sprintf('<DAV::collection/><urn:ietf:params:xml:ns:caldav:%s/>', $this->collection_type);
                $sql = <<<EOSQL
INSERT INTO collection ( user_no, parent_container, dav_name, dav_displayname, is_calendar, created, modified, dav_etag, resourcetypes )
    VALUES( (SELECT user_no FROM usr WHERE username = text(:username)),
            :parent_container, :dav_name,
            (SELECT fullname FROM usr WHERE username = text(:username)) || :boxname,
             FALSE, current_timestamp, current_timestamp, '1', :resourcetypes )
EOSQL;
                $qry = new AwlQuery($sql, $params);
                $qry->Exec('caldav', __LINE__, __FILE__);
                dbg_error_log('caldav', 'Created new collection as "%s".', trim($params[':boxname']));
                // Uncache anything to do with the collection
                $cache = getCacheInstance();
                $cache->delete('collection-' . $params[':dav_name'], null);
                $cache->delete('principal-' . $params[':parent_container'], null);
                $qry = new AwlQuery("SELECT * FROM collection WHERE dav_name = :dav_name", array(':dav_name' => $matches[1]));
                if ($qry->Exec('caldav', __LINE__, __FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch())) {
                    $this->collection_id = $row->collection_id;
                    $this->collection_path = $matches[1];
                    $this->collection = $row;
                    $this->collection->type = $this->collection_type;
                }
            } else {
                if (preg_match('#^((/[^/]+/)calendar-proxy-(read|write))/?[^/]*$#', $this->path, $matches)) {
                    $this->collection_type = 'proxy';
                    $this->_is_proxy_request = true;
                    $this->proxy_type = $matches[3];
                    $this->collection_path = $matches[1] . '/';
                    // Enforce trailling '/'
                    if ($this->collection_path == $this->path . "/") {
                        $this->path .= '/';
                        dbg_error_log("caldav", "Path is actually a (proxy) collection - sending Content-Location header.");
                        header("Content-Location: " . ConstructURL($this->path));
                    }
                } else {
                    if ($this->options['allow_by_email'] && preg_match('#^/(\\S+@\\S+[.]\\S+)/?$#', $this->path)) {
                        /** @todo we should deprecate this now that Evolution 2.27 can do scheduling extensions */
                        $this->collection_id = -1;
                        $this->collection_type = 'email';
                        $this->collection_path = $this->path;
                        $this->_is_principal = true;
                    } else {
                        if (preg_match('#^(/[^/?]+)/?$#', $this->path, $matches) || preg_match('#^(/principals/[^/]+/[^/]+)/?$#', $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: " . ConstructURL($this->path));
                            }
                            if (preg_match('#^(/principals/[^/]+/[^/]+)/?$#', $this->path, $matches)) {
                                // Force a depth of 0 on these, which are at the wrong URL.
                                $this->depth = 0;
                            }
                        } 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 DAVPrincipal(array("path" => $this->path, "options" => $this->options));
        $this->user_no = $this->principal->user_no();
        $this->username = $this->principal->username();
        $this->by_email = $this->principal->byEmail();
        $this->principal_id = $this->principal->principal_id();
        if ($this->collection_type == 'principal' || $this->collection_type == 'email' || $this->collection_type == 'proxy') {
            $this->collection = $this->principal->AsCollection();
            if ($this->collection_type == 'proxy') {
                $this->collection = $this->principal->AsCollection();
                $this->collection->is_proxy = 't';
                $this->collection->type = 'proxy';
                $this->collection->proxy_type = $this->proxy_type;
                $this->collection->dav_displayname = sprintf('Proxy %s for %s', $this->proxy_type, $this->principal->username());
            }
        } elseif ($this->collection_type == 'root') {
            $this->collection = (object) array('collection_id' => 0, 'dav_name' => '/', 'dav_etag' => md5($c->system_name), 'is_calendar' => 'f', 'is_addressbook' => 'f', 'is_principal' => 'f', 'user_no' => 0, 'dav_displayname' => $c->system_name, 'type' => 'root', 'created' => date('Ymd\\THis'));
        }
        /**
         * Evaluate our permissions for accessing the target
         */
        $this->setPermissions();
        $this->supported_methods = array('OPTIONS' => '', 'PROPFIND' => '', 'REPORT' => '', 'DELETE' => '', 'LOCK' => '', 'UNLOCK' => '', 'MOVE' => '', 'ACL' => '');
        if ($this->IsCollection()) {
            switch ($this->collection_type) {
                case 'root':
                case 'email':
                    // We just override the list completely here.
                    $this->supported_methods = array('OPTIONS' => '', 'PROPFIND' => '', 'REPORT' => '');
                    break;
                case 'schedule-inbox':
                case 'schedule-outbox':
                    $this->supported_methods = array_merge($this->supported_methods, array('POST' => '', 'GET' => '', 'PUT' => '', 'HEAD' => '', 'PROPPATCH' => ''));
                    break;
                case 'calendar':
                    $this->supported_methods['GET'] = '';
                    $this->supported_methods['PUT'] = '';
                    $this->supported_methods['HEAD'] = '';
                    break;
                case 'collection':
                case 'principal':
                    $this->supported_methods['GET'] = '';
                    $this->supported_methods['PUT'] = '';
                    $this->supported_methods['HEAD'] = '';
                    $this->supported_methods['MKCOL'] = '';
                    $this->supported_methods['MKCALENDAR'] = '';
                    $this->supported_methods['PROPPATCH'] = '';
                    $this->supported_methods['BIND'] = '';
                    break;
            }
        } else {
            $this->supported_methods = array_merge($this->supported_methods, array('GET' => '', 'HEAD' => '', 'PUT' => ''));
        }
        $this->supported_reports = array('DAV::principal-property-search' => '', 'DAV::expand-property' => '', 'DAV::sync-collection' => '');
        if (isset($this->collection) && $this->collection->is_calendar) {
            $this->supported_reports = array_merge($this->supported_reports, array('urn:ietf:params:xml:ns:caldav:calendar-query' => '', 'urn:ietf:params:xml:ns:caldav:calendar-multiget' => '', 'urn:ietf:params:xml:ns:caldav:free-busy-query' => ''));
        }
        if (isset($this->collection) && $this->collection->is_addressbook) {
            $this->supported_reports = array_merge($this->supported_reports, array('urn:ietf:params:xml:ns:carddav:addressbook-query' => '', 'urn:ietf:params:xml:ns:carddav:addressbook-multiget' => ''));
        }
        /**
         * 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($this->content_type) && preg_match('#(application|text)/xml#', $this->content_type)) {
            if (!isset($this->raw_post) || $this->raw_post == '') {
                $this->XMLResponse(400, new XMLElement('error', new XMLElement('missing-xml'), array('xmlns' => 'DAV:')));
            }
            $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);
            $rc = xml_parse_into_struct($xml_parser, $this->raw_post, $this->xml_tags);
            if ($rc == false) {
                dbg_error_log('ERROR', 'XML parsing error: %s at line %d, column %d', xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser));
                $this->XMLResponse(400, new XMLElement('error', new XMLElement('invalid-xml'), array('xmlns' => 'DAV:')));
            }
            xml_parser_free($xml_parser);
            if (count($this->xml_tags)) {
                dbg_error_log("caldav", " Parsed incoming XML request body.");
            } else {
                $this->xml_tags = null;
                dbg_error_log("ERROR", "Incoming request sent content-type XML with no XML request body.");
            }
        }
        /**
         * Look out for If-None-Match or If-Match headers
         */
        if (isset($_SERVER["HTTP_IF_NONE_MATCH"])) {
            $this->etag_none_match = $_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 = $_SERVER["HTTP_IF_MATCH"];
            if ($this->etag_if_match == '') {
                unset($this->etag_if_match);
            }
        }
    }
Example #9
0
 /**
  * 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)
 {
     if (!isset($u->principal_id)) {
         // If they don't have a principal_id set then we should re-read from our local database
         $qry = new AwlQuery('SELECT * FROM dav_principal WHERE username = :username', array(':username' => $u->username));
         if ($qry->Exec() && $qry->rows() == 1) {
             $u = $qry->Fetch();
         }
     }
     // Assign each field in the selected record to the object
     foreach ($u as $k => $v) {
         $this->{$k} = $v;
     }
     $this->GetRoles();
     $this->logged_in = true;
     if (function_exists("awl_set_locale") && isset($this->locale) && $this->locale != "") {
         awl_set_locale($this->locale);
     }
 }
                           LEFT JOIN calendar_item USING (collection_id,dav_id)
                           LEFT JOIN addressbook_resource USING (dav_id)
                           WHERE collection.collection_id = :collection_id {$hide_older} {$hide_todo}
         AND sync_time >= (SELECT modification_time FROM sync_tokens WHERE sync_token = :sync_token)
EOSQL;
        if (isset($c->strict_result_ordering) && $c->strict_result_ordering) {
            $sql .= " ORDER BY collection.collection_id, lower(sync_changes.dav_name), sync_changes.sync_time";
        } else {
            $sql .= " ORDER BY collection.collection_id, sync_changes.dav_name, sync_changes.sync_time";
        }
    }
    $qry = new AwlQuery($sql, $params);
    $last_dav_name = '';
    $first_status = 0;
    if ($qry->Exec("REPORT", __LINE__, __FILE__)) {
        if ($qry->rows() > 50) {
            // If there are more than 50 rows to send we should not send full data in response ...
            $c->sync_resource_data_ok = false;
        }
        while ($object = $qry->Fetch()) {
            if ($request_via_binding) {
                $object->dav_name = str_replace($bound_from, $collection_path, $object->dav_name);
            }
            if ($object->dav_name == $last_dav_name) {
                /** The complex case: this is the second or subsequent for this dav_id */
                if ($object->sync_status == 404) {
                    array_pop($responses);
                    $resultset = array(new XMLElement('href', ConstructURL($object->dav_name)), new XMLElement('status', display_status($object->sync_status)));
                    $responses[] = new XMLElement('response', $resultset);
                    $first_status = 404;
                } else {
Example #11
0
function handle_freebusy_request($ic)
{
    global $c, $session, $request, $ical;
    $request->NeedPrivilege('CALDAV:schedule-send-freebusy');
    $reply = new XMLDocument(array("DAV:" => "", "urn:ietf:params:xml:ns:caldav" => "C"));
    $responses = array();
    $fbq_start = $ic->GetPValue('DTSTART');
    $fbq_end = $ic->GetPValue('DTEND');
    if (!(isset($fbq_start) || isset($fbq_end))) {
        $request->DoResponse(400, 'All valid freebusy requests MUST contain a DTSTART and a DTEND');
    }
    $range_start = new RepeatRuleDateTime($fbq_start);
    $range_end = new RepeatRuleDateTime($fbq_end);
    $attendees = $ic->GetProperties('ATTENDEE');
    if (preg_match('# iCal/\\d#', $_SERVER['HTTP_USER_AGENT'])) {
        dbg_error_log("POST", "Non-compliant iCal request.  Using X-WR-ATTENDEE property");
        $wr_attendees = $ic->GetProperties('X-WR-ATTENDEE');
        foreach ($wr_attendees as $k => $v) {
            $attendees[] = $v;
        }
    }
    dbg_error_log("POST", "Responding with free/busy for %d attendees", count($attendees));
    foreach ($attendees as $k => $attendee) {
        $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value());
        dbg_error_log("POST", "Calculating free/busy for %s", $attendee_email);
        /** @todo Refactor this so we only do one query here and loop through the results */
        $params = array(':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth, ':email' => $attendee_email);
        $qry = new AwlQuery('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params);
        if (!$qry->Exec('POST', __LINE__, __FILE__)) {
            $request->DoResponse(501, 'Database error');
        }
        if ($qry->rows() > 1) {
            // Unlikely, but if we get more than one result we'll do an exact match instead.
            if (!$qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE usr.email = :email', $params)) {
                $request->DoResponse(501, 'Database error');
            }
            if ($qry->rows() == 0) {
                /** Sigh... Go back to the original case-insensitive match */
                $qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params);
            }
        }
        $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav');
        $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value()));
        if ($qry->rows() == 0) {
            $remote = new iSchedule();
            $answer = $remote->sendRequest($attendee->Value(), 'VFREEBUSY/REQUEST', $ical->Render());
            if ($answer === false) {
                $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User");
                $reply->CalDAVElement($response, "calendar-data");
                $responses[] = $response;
                continue;
            }
            foreach ($answer as $a) {
                if ($a === false) {
                    $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User");
                    $reply->CalDAVElement($response, "calendar-data");
                } elseif (substr($a, 0, 1) >= 1) {
                    $reply->CalDAVElement($response, "request-status", $a);
                    $reply->CalDAVElement($response, "calendar-data");
                } else {
                    $reply->CalDAVElement($response, "request-status", "2.0;Success");
                    $reply->CalDAVElement($response, "calendar-data", $a);
                }
                $responses[] = $response;
            }
            continue;
        }
        if (!($attendee_usr = $qry->Fetch())) {
            $request->DoResponse(501, 'Database error');
        }
        if ((privilege_to_bits('schedule-query-freebusy') & bindec($attendee_usr->p)) == 0) {
            $reply->CalDAVElement($response, "request-status", "3.8;No authority");
            $reply->CalDAVElement($response, "calendar-data");
            $responses[] = $response;
            continue;
        }
        $attendee_path_match = '^/' . $attendee_usr->username . '/';
        $fb = get_freebusy($attendee_path_match, $range_start, $range_end, bindec($attendee_usr->p));
        $fb->AddProperty('UID', $ic->GetPValue('UID'));
        $fb->SetProperties($ic->GetProperties('ORGANIZER'), 'ORGANIZER');
        $fb->AddProperty($attendee);
        $vcal = new vCalendar(array('METHOD' => 'REPLY'));
        $vcal->AddComponent($fb);
        $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav');
        $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value()));
        $reply->CalDAVElement($response, "request-status", "2.0;Success");
        // Cargo-cult setting
        $reply->CalDAVElement($response, "calendar-data", $vcal->Render());
        $responses[] = $response;
    }
    $response = $reply->NewXMLElement("schedule-response", $responses, $reply->GetXmlNsArray(), 'urn:ietf:params:xml:ns:caldav');
    $request->XMLResponse(200, $response);
}
Example #12
0
 /**
  * Get the addressbook_home_set, as lazily as possible
  */
 function addressbook_home_set()
 {
     if (!isset($this->addressbook_home_set)) {
         $this->addressbook_home_set = array();
         $qry = new AwlQuery('SELECT DISTINCT parent_container FROM collection WHERE is_addressbook AND dav_name ~ :dav_name_start', array(':dav_name_start' => '^' . $this->dav_name));
         if ($qry->Exec('principal', __LINE__, __FILE__)) {
             if ($qry->rows() > 0) {
                 while ($addressbook = $qry->Fetch()) {
                     $this->addressbook_home_set[] = ConstructURL($addressbook->parent_container, true);
                 }
             } else {
                 $this->addressbook_home_set[] = $this->url;
             }
         }
     }
     return $this->addressbook_home_set;
 }
Example #13
0
     break;
 case 'urn:ietf:params:xml:ns:caldav:calendar-timezone':
     if ($dav_resource->IsCollection() && $dav_resource->IsCalendar() && !$dav_resource->IsBinding()) {
         $tzcomponent = $setting->GetPath('urn:ietf:params:xml:ns:caldav:calendar-timezone');
         $tzstring = $tzcomponent[0]->GetContent();
         $calendar = new vCalendar($tzstring);
         $timezones = $calendar->GetComponents('VTIMEZONE');
         if (count($timezones) == 0) {
             break;
         }
         $tz = $timezones[0];
         // Backward compatibility
         $tzid = $tz->GetPValue('TZID');
         $params = array(':tzid' => $tzid);
         $qry = new AwlQuery('SELECT 1 FROM timezones WHERE tzid = :tzid', $params);
         if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 0) {
             $params[':olson_name'] = $calendar->GetOlsonName($tz);
             $params[':vtimezone'] = isset($tz) ? $tz->Render() : null;
             $qry->QDo('INSERT INTO timezones (tzid, olson_name, active, vtimezone) VALUES(:tzid,:olson_name,false,:vtimezone)', $params);
         }
         $qry->QDo('UPDATE collection SET timezone = :tzid WHERE dav_name = :dav_name', array(':tzid' => $tzid, ':dav_name' => $dav_resource->dav_name()));
     } else {
         add_failure('set', $tag, 'HTTP/1.1 409 Conflict', translate("calendar-timezone property is only valid for a calendar."));
     }
     break;
     /**
      * The following properties are read-only, so they will cause the request to fail
      */
 /**
  * The following properties are read-only, so they will cause the request to fail
  */
Example #14
0
    /**
     * Return general server-related properties for this URL
     */
    function ResourceProperty($tag, $prop, &$reply, &$denied)
    {
        global $c, $session, $request;
        //    dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name );
        if ($reply === null) {
            $reply = $GLOBALS['reply'];
        }
        switch ($tag) {
            case 'DAV::allprop':
                $property_list = $this->DAV_AllProperties();
                $discarded = array();
                foreach ($property_list as $k => $v) {
                    $this->ResourceProperty($v, $prop, $reply, $discarded);
                }
                break;
            case 'DAV::href':
                $prop->NewElement('href', ConstructURL($this->dav_name));
                break;
            case 'DAV::resource-id':
                if ($this->resource_id > 0) {
                    $reply->DAVElement($prop, 'resource-id', $reply->href(ConstructURL('/.resources/' . $this->resource_id)));
                } else {
                    return false;
                }
                break;
            case 'DAV::parent-set':
                $sql = <<<EOQRY
SELECT b.parent_container FROM dav_binding b JOIN collection c ON (b.bound_source_id=c.collection_id)
 WHERE regexp_replace( b.dav_name, '^.*/', c.dav_name ) = :bound_from
EOQRY;
                $qry = new AwlQuery($sql, array(':bound_from' => $this->bound_from()));
                $parents = array();
                if ($qry->Exec('DAVResource', __LINE__, __FILE__) && $qry->rows() > 0) {
                    while ($row = $qry->Fetch()) {
                        $parents[$row->parent_container] = true;
                    }
                }
                $parents[preg_replace('{(?<=/)[^/]+/?$}', '', $this->bound_from())] = true;
                $parents[preg_replace('{(?<=/)[^/]+/?$}', '', $this->dav_name())] = true;
                $parent_set = $reply->DAVElement($prop, 'parent-set');
                foreach ($parents as $parent => $v) {
                    if (preg_match('{^(.*)?/([^/]+)/?$}', $parent, $matches)) {
                        $reply->DAVElement($parent_set, 'parent', array(new XMLElement('href', ConstructURL($matches[1])), new XMLElement('segment', $matches[2])));
                    } else {
                        if ($parent == '/') {
                            $reply->DAVElement($parent_set, 'parent', array(new XMLElement('href', '/'), new XMLElement('segment', ConstructURL('/') == '/caldav.php/' ? 'caldav.php' : '')));
                        }
                    }
                }
                break;
            case 'DAV::getcontenttype':
                if (!isset($this->contenttype) && !$this->_is_collection && !isset($this->resource)) {
                    $this->FetchResource();
                }
                $prop->NewElement('getcontenttype', $this->contenttype);
                break;
            case 'DAV::resourcetype':
                $resourcetypes = $prop->NewElement('resourcetype');
                if ($this->_is_collection) {
                    $type_list = $this->GetProperty('resourcetype');
                    if (!is_array($type_list)) {
                        return true;
                    }
                    //        dbg_error_log( 'DAVResource', ':ResourceProperty: "%s" are "%s".', $tag, implode(', ',$type_list) );
                    foreach ($type_list as $k => $v) {
                        if ($v == '') {
                            continue;
                        }
                        $reply->NSElement($resourcetypes, $v);
                    }
                    if ($this->_is_binding) {
                        $reply->NSElement($resourcetypes, 'http://xmlns.davical.org/davical:webdav-binding');
                    }
                }
                break;
            case 'DAV::getlastmodified':
                /** getlastmodified is HTTP Date format: i.e. the Last-Modified header in response to a GET */
                $reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified')));
                break;
            case 'DAV::creationdate':
                /** creationdate is ISO8601 format */
                $reply->NSElement($prop, $tag, DateToISODate($this->GetProperty('created'), true));
                break;
            case 'DAV::getcontentlength':
                if ($this->_is_collection) {
                    return false;
                }
                if (!isset($this->resource)) {
                    $this->FetchResource();
                }
                if (isset($this->resource)) {
                    $reply->NSElement($prop, $tag, strlen($this->resource->caldav_data));
                }
                break;
            case 'DAV::getcontentlanguage':
                $locale = isset($c->current_locale) ? $c->current_locale : '';
                if (isset($this->locale) && $this->locale != '') {
                    $locale = $this->locale;
                }
                $reply->NSElement($prop, $tag, $locale);
                break;
            case 'DAV::acl-restrictions':
                $reply->NSElement($prop, $tag, array(new XMLElement('grant-only'), new XMLElement('no-invert')));
                break;
            case 'DAV::inherited-acl-set':
                $inherited_acls = array();
                if (!$this->_is_collection) {
                    $inherited_acls[] = $reply->href(ConstructURL($this->collection->dav_name));
                }
                $reply->NSElement($prop, $tag, $inherited_acls);
                break;
            case 'DAV::owner':
                // The principal-URL of the owner
                if ($this->IsExternal()) {
                    $reply->DAVElement($prop, 'owner', $reply->href(ConstructURL($this->collection->bound_from)));
                } else {
                    $reply->DAVElement($prop, 'owner', $reply->href(ConstructURL(DeconstructURL($this->principal_url()))));
                }
                break;
            case 'DAV::add-member':
                if (!$this->_is_collection) {
                    return false;
                }
                if (isset($c->post_add_member) && $c->post_add_member === false) {
                    return false;
                }
                $reply->DAVElement($prop, 'add-member', $reply->href(ConstructURL(DeconstructURL($this->url())) . '?add-member'));
                break;
                // Empty tag responses.
            // Empty tag responses.
            case 'DAV::group':
            case 'DAV::alternate-URI-set':
                $reply->NSElement($prop, $tag);
                break;
            case 'DAV::getetag':
                if ($this->_is_collection) {
                    return false;
                }
                $reply->NSElement($prop, $tag, $this->unique_tag());
                break;
            case 'http://calendarserver.org/ns/:getctag':
                if (!$this->_is_collection) {
                    return false;
                }
                $reply->NSElement($prop, $tag, $this->unique_tag());
                break;
            case 'DAV::sync-token':
                if (!$this->_is_collection) {
                    return false;
                }
                $sync_token = $this->sync_token();
                if (empty($sync_token)) {
                    return false;
                }
                $reply->NSElement($prop, $tag, $sync_token);
                break;
            case 'http://calendarserver.org/ns/:calendar-proxy-read-for':
                $proxy_type = 'read';
            case 'http://calendarserver.org/ns/:calendar-proxy-write-for':
                if (isset($c->disable_caldav_proxy) && $c->disable_caldav_proxy) {
                    return false;
                }
                if (!isset($proxy_type)) {
                    $proxy_type = 'write';
                }
                // ProxyFor is an already constructed URL
                $reply->CalendarserverElement($prop, 'calendar-proxy-' . $proxy_type . '-for', $reply->href($this->principal->ProxyFor($proxy_type)));
                break;
            case 'DAV::current-user-privilege-set':
                if ($this->HavePrivilegeTo('DAV::read-current-user-privilege-set')) {
                    $reply->NSElement($prop, $tag, $this->BuildPrivileges());
                } else {
                    $denied[] = $tag;
                }
                break;
            case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data':
                if (!$this->IsCalendar() && !$this->IsSchedulingCollection()) {
                    return false;
                }
                $reply->NSElement($prop, $tag, 'text/calendar');
                break;
            case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set':
                if (!$this->_is_collection) {
                    return false;
                }
                if ($this->IsCalendar()) {
                    if (!isset($this->dead_properties)) {
                        $this->FetchDeadProperties();
                    }
                    if (isset($this->dead_properties[$tag])) {
                        $set_of_components = $this->dead_properties[$tag];
                        foreach ($set_of_components as $k => $v) {
                            if (preg_match('{(VEVENT|VTODO|VJOURNAL|VTIMEZONE|VFREEBUSY|VPOLL|VAVAILABILITY)}', $v, $matches)) {
                                $set_of_components[$k] = $matches[1];
                            } else {
                                unset($set_of_components[$k]);
                            }
                        }
                    } else {
                        if (isset($c->default_calendar_components) && is_array($c->default_calendar_components)) {
                            $set_of_components = $c->default_calendar_components;
                        } else {
                            $set_of_components = array('VEVENT', 'VTODO', 'VJOURNAL');
                        }
                    }
                } else {
                    if ($this->IsSchedulingCollection()) {
                        $set_of_components = array('VEVENT', 'VTODO', 'VFREEBUSY');
                    } else {
                        return false;
                    }
                }
                $components = array();
                foreach ($set_of_components as $v) {
                    $components[] = $reply->NewXMLElement('comp', '', array('name' => $v), 'urn:ietf:params:xml:ns:caldav');
                }
                $reply->CalDAVElement($prop, 'supported-calendar-component-set', $components);
                break;
            case 'DAV::supported-method-set':
                $prop->NewElement('supported-method-set', $this->BuildSupportedMethods());
                break;
            case 'DAV::supported-report-set':
                $prop->NewElement('supported-report-set', $this->BuildSupportedReports($reply));
                break;
            case 'DAV::supportedlock':
                $prop->NewElement('supportedlock', new XMLElement('lockentry', array(new XMLElement('lockscope', new XMLElement('exclusive')), new XMLElement('locktype', new XMLElement('write')))));
                break;
            case 'DAV::supported-privilege-set':
                $prop->NewElement('supported-privilege-set', $request->BuildSupportedPrivileges($reply));
                break;
            case 'DAV::principal-collection-set':
                $prop->NewElement('principal-collection-set', $reply->href(ConstructURL('/')));
                break;
            case 'DAV::current-user-principal':
                $prop->NewElement('current-user-principal', $reply->href(ConstructURL(DeconstructURL($request->principal->url()))));
                break;
            case 'SOME-DENIED-PROPERTY':
                /** indicating the style for future expansion */
                $denied[] = $reply->Tag($tag);
                break;
            case 'urn:ietf:params:xml:ns:caldav:calendar-timezone':
                if (!$this->_is_collection) {
                    return false;
                }
                if (!isset($this->collection->vtimezone) || $this->collection->vtimezone == '') {
                    return false;
                }
                $cal = new iCalComponent();
                $cal->VCalendar();
                $cal->AddComponent(new iCalComponent($this->collection->vtimezone));
                $reply->NSElement($prop, $tag, $cal->Render());
                break;
            case 'urn:ietf:params:xml:ns:carddav:address-data':
            case 'urn:ietf:params:xml:ns:caldav:calendar-data':
                if ($this->_is_collection) {
                    return false;
                }
                if (!isset($c->sync_resource_data_ok) || $c->sync_resource_data_ok == false) {
                    return false;
                }
                if (!isset($this->resource)) {
                    $this->FetchResource();
                }
                $reply->NSElement($prop, $tag, $this->resource->caldav_data);
                break;
            case 'urn:ietf:params:xml:ns:carddav:max-resource-size':
                if (!$this->_is_collection || !$this->_is_addressbook) {
                    return false;
                }
                $reply->NSElement($prop, $tag, 65500);
                break;
            case 'urn:ietf:params:xml:ns:carddav:supported-address-data':
                if (!$this->_is_collection || !$this->_is_addressbook) {
                    return false;
                }
                $address_data = $reply->NewXMLElement('address-data', false, array('content-type' => 'text/vcard', 'version' => '3.0'), 'urn:ietf:params:xml:ns:carddav');
                $reply->NSElement($prop, $tag, $address_data);
                break;
            case 'DAV::acl':
                if ($this->HavePrivilegeTo('DAV::read-acl')) {
                    $reply->NSElement($prop, $tag, $this->GetACL($reply));
                } else {
                    $denied[] = $tag;
                }
                break;
            case 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery':
            case 'DAV::ticketdiscovery':
                $reply->NSElement($prop, 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery', $this->BuildTicketinfo($reply));
                break;
            default:
                $property_value = $this->GetProperty(preg_replace('{^(DAV:|urn:ietf:params:xml:ns:ca(rd|l)dav):}', '', $tag));
                if (isset($property_value)) {
                    $reply->NSElement($prop, $tag, $property_value);
                } else {
                    if (!isset($this->dead_properties)) {
                        $this->FetchDeadProperties();
                    }
                    if (isset($this->dead_properties[$tag])) {
                        $reply->NSElement($prop, $tag, $this->dead_properties[$tag]);
                    } else {
                        //            dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name );
                        return false;
                    }
                }
        }
        return true;
    }
Example #15
0
 /**
  * To read the record from the database.
  * If we don't have any keys then the record will be blank.
  * @return boolean Whether we actually read a record.
  */
 function Read()
 {
     $i_read_the_record = false;
     $values = (object) array();
     $this->EditMode = true;
     $where = $this->_BuildWhereClause(true);
     if ("" != $where) {
         // $fieldlist = $this->_BuildFieldList();
         $fieldlist = "*";
         //    $join = $this->_BuildJoinClause(true);
         $sql = "SELECT {$fieldlist} FROM {$this->Table} {$where}";
         $qry = new AwlQuery($sql);
         if ($qry->Exec("DBRecord", __LINE__, __FILE__) && $qry->rows() > 0) {
             $i_read_the_record = true;
             $values = $qry->Fetch();
             $this->EditMode = false;
             // Default to not editing if we read the record.
             dbg_error_log("DBRecord", ":Read: Read %s record from table.", $this->Table, $this->WriteType);
         }
     }
     $this->Values =& $values;
     $this->WriteType = $i_read_the_record ? "update" : "insert";
     dbg_error_log("DBRecord", ":Read: Record %s write type is %s.", $this->Table, $this->WriteType);
     return $i_read_the_record;
 }
Example #16
0
/**
* Authentication has already happened.  We know the username, we just need
* to do the authorisation / access control.  The password is ignored.
*
* @package   awl
*/
function auth_external($username, $password)
{
    global $c;
    $qry = new AwlQuery("SELECT * FROM usr WHERE active AND lower(username) = ? ", strtolower($username));
    if ($qry->Exec('Login', __LINE, __FILE__) && $qry->rows() == 1) {
        $usr = $qry->Fetch();
        return $usr;
    }
    return false;
}
}
$sql = 'SELECT calendar_item.*, addressbook_resource.*, caldav_data.* FROM caldav_data
                  LEFT JOIN calendar_item USING(dav_id, user_no, dav_name, collection_id)
                  LEFT JOIN addressbook_resource USING(dav_id)
                  LEFT JOIN collection USING(collection_id)';
/**
 * @todo: Add stanzas for missing rows, so we don't just return a blank multistatus but
 * actually return <response> stanzas with a 404 for each absent href.  We could do
 * this relatively easily with an array_flip($params) and remove each matching dav_name
 * as we process it.
 */
if (isset($c->strict_result_ordering) && $c->strict_result_ordering) {
    $where .= " ORDER BY caldav_data.dav_id";
}
$qry = new AwlQuery($sql . $where, $params);
if ($qry->Exec('REPORT', __LINE__, __FILE__) && $qry->rows() > 0) {
    while ($dav_object = $qry->Fetch()) {
        if ($bound_from != $collection->dav_name()) {
            $dav_object->dav_name = str_replace($bound_from, $collection->dav_name(), $dav_object->dav_name);
        }
        //if ( $need_expansion ) {
        $vResource = new vComponent($dav_object->caldav_data);
        $expanded = expand_event_instances($vResource, $expand_range_start, $expand_range_end);
        //      $event = $expanded->GetComponents("VEVENT")[0];
        //
        //      $attendeeName = "ATTENDEE";
        //
        //      $event->ClearProperties($attendeeName);
        //
        //      $attendeeQry = new AwlQuery("SELECT params, attendee FROM calendar_attendee WHERE dav_id = :dav_id", array(':dav_id' => $dav_object->dav_id));
        //      $attendeeQry->Execute();
function get_freebusy($path_match, $range_start, $range_end, $bin_privs = null)
{
    global $request, $c;
    $debugging = false;
    //    if ( $debugging ) {
    //        printf( "Path: %s\n", $path_match );
    //        print_r( $range_start );
    //        print_r( $range_end );
    //    }
    if (!isset($bin_privs)) {
        $bin_privs = $request->Privileges();
    }
    if (!isset($range_start) || !isset($range_end)) {
        $request->DoResponse(400, 'All valid freebusy requests MUST contain a time-range filter');
    }
    $params = array(':path_match' => $path_match, ':start' => $range_start->UTC(), ':end' => $range_end->UTC());
    $where = ' WHERE caldav_data.dav_name ~ :path_match ';
    $where .= 'AND rrule_event_overlaps( dtstart, dtend, rrule, :start, :end) ';
    $where .= "AND caldav_data.caldav_type IN ( 'VEVENT', 'VTODO' ) ";
    $where .= "AND (calendar_item.transp != 'TRANSPARENT' OR calendar_item.transp IS NULL) ";
    $where .= "AND (calendar_item.status != 'CANCELLED' OR calendar_item.status IS NULL) ";
    $where .= "AND collection.is_calendar AND collection.schedule_transp = 'opaque' ";
    if ($bin_privs != privilege_to_bits('all')) {
        $where .= "AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) ";
    }
    $fbtimes = array();
    $sql = 'SELECT caldav_data.caldav_data, calendar_item.rrule, calendar_item.transp, calendar_item.status, ';
    $sql .= "to_char(calendar_item.dtstart at time zone 'GMT'," . AWLDatabase::SqlUTCFormat . ') AS start, ';
    $sql .= "to_char(calendar_item.dtend at time zone 'GMT'," . AWLDatabase::SqlUTCFormat . ') AS finish, ';
    $sql .= "calendar_item.class, calendar_item.dav_id ";
    $sql .= 'FROM caldav_data INNER JOIN calendar_item USING(dav_id,user_no,dav_name,collection_id) ';
    $sql .= 'INNER JOIN collection USING(collection_id)';
    $sql .= $where;
    if (isset($c->strict_result_ordering) && $c->strict_result_ordering) {
        $sql .= ' ORDER BY dav_id';
    }
    $qry = new AwlQuery($sql, $params);
    if ($qry->Exec("REPORT", __LINE__, __FILE__) && $qry->rows() > 0) {
        while ($calendar_object = $qry->Fetch()) {
            $extra = '';
            if ($calendar_object->status == 'TENTATIVE') {
                $extra = ';BUSY-TENTATIVE';
            } else {
                if (isset($c->_workaround_client_freebusy_bug) && $c->_workaround_client_freebusy_bug) {
                    $extra = ';BUSY';
                }
            }
            //      if ( $debugging ) {
            //        $extra = ';'.$calendar_object->dav_id;
            //      }
            //      dbg_error_log( "REPORT", " FreeBusy: Not transparent, tentative or cancelled: %s, %s, %s", $calendar_object->start, $calendar_object->finish, $calendar_object->class );
            $ics = new vComponent($calendar_object->caldav_data);
            $expanded = expand_event_instances($ics, $range_start, $range_end);
            $expansion = $expanded->GetComponents(array('VEVENT' => true, 'VTODO' => true, 'VJOURNAL' => true));
            //      if ( $debugging ) echo "===================   $calendar_object->dav_id   ========================\n";
            $dtstart_type = 'DTSTART';
            foreach ($expansion as $k => $v) {
                //        if ( $debugging ) print $k."\n".$v->Render();
                $start_date = $v->GetProperty($dtstart_type);
                if (!isset($start_date) && $v->GetType() != 'VTODO') {
                    $dtstart_type = 'DUE';
                    $start_date = $v->GetProperty($dtstart_type);
                }
                $start_date = new RepeatRuleDateTime($start_date);
                $duration = $v->GetProperty('DURATION');
                $duration = !isset($duration) ? 'P1D' : $duration->Value();
                $end_date = clone $start_date;
                $end_date->modify($duration);
                if ($end_date == $start_date || $end_date < $range_start || $start_date > $range_end) {
                    //            if ( $debugging )
                    //              echo "-----------------------------------------------------\n";
                    continue;
                }
                //        if ( $debugging )
                //            echo "+++++++++++++++++++++++++++++++++++++++++++++++++++++\n";
                $thisfb = $start_date->UTC() . '/' . $end_date->UTC() . $extra;
                array_push($fbtimes, $thisfb);
            }
        }
    }
    $freebusy = new vComponent();
    $freebusy->setType('VFREEBUSY');
    $freebusy->AddProperty('DTSTAMP', date('Ymd\\THis\\Z'));
    $freebusy->AddProperty('DTSTART', $range_start->UTC());
    $freebusy->AddProperty('DTEND', $range_end->UTC());
    sort($fbtimes);
    foreach ($fbtimes as $k => $v) {
        $text = explode(';', $v, 2);
        $freebusy->AddProperty('FREEBUSY', $text[0], isset($text[1]) ? array('FBTYPE' => $text[1]) : null);
    }
    return $freebusy;
}
/**
* Actually write the resource to the database.  All checking of whether this is reasonable
* should be done before this is called.
* 
* @param DAVResource $resource The resource being written
* @param string $caldav_data The actual data to be written
* @param DAVResource $collection The collection containing the resource being written
* @param int $author The user_no who wants to put this resource on the server
* @param string $etag An etag unique for this event
* @param string $put_action_type INSERT or UPDATE depending on what we are to do
* @param boolean $caldav_context True, if we are responding via CalDAV, false for other ways of calling this
* @param string Either 'INSERT' or 'UPDATE': the type of action we are doing
* @param boolean $log_action Whether to log the fact that we are writing this into an action log (if configured)
* @param string $weak_etag An etag that is NOT modified on ATTENDEE changes for this event
* 
* @return boolean True for success, false for failure.
*/
function write_resource(DAVResource $resource, $caldav_data, DAVResource $collection, $author, &$etag, $put_action_type, $caldav_context, $log_action = true, $weak_etag = null)
{
    global $tz_regex, $session;
    $path = $resource->bound_from();
    $user_no = $collection->user_no();
    $vcal = new vCalendar($caldav_data);
    $resources = $vcal->GetComponents('VTIMEZONE', false);
    // Not matching VTIMEZONE
    if (!isset($resources[0])) {
        $resource_type = 'Unknown';
        /** @todo Handle writing non-calendar resources, like address book entries or random file data */
        rollback_on_error($caldav_context, $user_no, $path, translate('No calendar content'), 412);
        return false;
    } else {
        $first = $resources[0];
        if (!$first instanceof vComponent) {
            print $vcal->Render();
            fatal('This is not a vComponent!');
        }
        $resource_type = $first->GetType();
    }
    $collection_id = $collection->collection_id();
    $qry = new AwlQuery();
    $qry->Begin();
    $dav_params = array(':etag' => $etag, ':dav_data' => $caldav_data, ':caldav_type' => $resource_type, ':session_user' => $author, ':weak_etag' => $weak_etag);
    $calitem_params = array(':etag' => $etag);
    if ($put_action_type == 'INSERT') {
        $qry->QDo('SELECT nextval(\'dav_id_seq\') AS dav_id, null AS caldav_data');
    } else {
        $qry->QDo('SELECT dav_id, caldav_data FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path));
    }
    if ($qry->rows() != 1 || !($row = $qry->Fetch())) {
        // No dav_id?  => We're toast!
        trace_bug('No dav_id for "%s" on %s!!!', $path, $create_resource ? 'create' : 'update');
        rollback_on_error($caldav_context, $user_no, $path);
        return false;
    }
    $dav_id = $row->dav_id;
    $old_dav_data = $row->caldav_data;
    $dav_params[':dav_id'] = $dav_id;
    $calitem_params[':dav_id'] = $dav_id;
    $due = null;
    if ($first->GetType() == 'VTODO') {
        $due = $first->GetPValue('DUE');
    }
    $calitem_params[':due'] = $due;
    $dtstart = $first->GetPValue('DTSTART');
    if (empty($dtstart)) {
        $dtstart = $due;
    }
    $calitem_params[':dtstart'] = $dtstart;
    $dtend = $first->GetPValue('DTEND');
    if (isset($dtend) && $dtend != '') {
        dbg_error_log('PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION'));
        $calitem_params[':dtend'] = $dtend;
        $dtend = ':dtend';
    } else {
        // In this case we'll construct the SQL directly as a calculation relative to :dtstart
        $dtend = 'NULL';
        if ($first->GetPValue('DURATION') != '' and $dtstart != '') {
            $duration = trim(preg_replace('#[PT]#', ' ', $first->GetPValue('DURATION')));
            if ($duration == '') {
                $duration = '0 seconds';
            }
            $dtend = '(:dtstart::timestamp with time zone + :duration::interval)';
            $calitem_params[':duration'] = $duration;
        } elseif ($first->GetType() == 'VEVENT') {
            /**
             * From RFC2445 4.6.1:
             * For cases where a "VEVENT" calendar component specifies a "DTSTART"
             * property with a DATE data type but no "DTEND" property, the events
             * non-inclusive end is the end of the calendar date specified by the
             * "DTSTART" property. For cases where a "VEVENT" calendar component specifies
             * a "DTSTART" property with a DATE-TIME data type but no "DTEND" property,
             * the event ends on the same calendar date and time of day specified by the
             * "DTSTART" property.
             *
             * So we're looking for 'VALUE=DATE', to identify the duration, effectively.
             *
             */
            $dtstart_prop = $first->GetProperty('DTSTART');
            $value_type = $dtstart_prop->GetParameterValue('VALUE');
            dbg_error_log('PUT', 'DTSTART without DTEND. DTSTART value type is %s', $value_type);
            if (isset($value_type) && $value_type == 'DATE') {
                $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
            } else {
                $dtend = ':dtstart';
            }
        }
    }
    $dtstamp = $first->GetPValue('DTSTAMP');
    if (!isset($dtstamp) || $dtstamp == '') {
        // Strictly, we're dealing with an out of spec component here, but we'll try and survive
        $dtstamp = gmdate('Ymd\\THis\\Z');
    }
    $calitem_params[':dtstamp'] = $dtstamp;
    $last_modified = $first->GetPValue('LAST-MODIFIED');
    if (!isset($last_modified) || $last_modified == '') {
        $last_modified = $dtstamp;
    }
    $dav_params[':modified'] = $last_modified;
    $calitem_params[':modified'] = $last_modified;
    $created = $first->GetPValue('CREATED');
    if ($created == '00001231T000000Z') {
        $created = '20001231T000000Z';
    }
    $class = $first->GetPValue('CLASS');
    /* Check and see if we should over ride the class. */
    /** @todo is there some way we can move this out of this function? Or at least get rid of the need for the SQL query here. */
    if (public_events_only($user_no, $path)) {
        $class = 'PUBLIC';
    }
    /*
     * It seems that some calendar clients don't set a class...
     * RFC2445, 4.8.1.3:
     * Default is PUBLIC
     */
    if (!isset($class) || $class == '') {
        $class = 'PUBLIC';
    }
    $calitem_params[':class'] = $class;
    /** Calculate what timezone to set, first, if possible */
    $last_olson = 'Turkmenikikamukau';
    // I really hope this location doesn't exist!
    $tzid = GetTZID($first);
    if (!empty($tzid)) {
        $timezones = $vcal->GetComponents('VTIMEZONE');
        foreach ($timezones as $k => $tz) {
            if ($tz->GetPValue('TZID') != $tzid) {
                /**
                 * We'll skip any tz definitions that are for a TZID other than the DTSTART/DUE on the first VEVENT/VTODO 
                 */
                dbg_error_log('ERROR', ' Event uses TZID[%s], skipping included TZID[%s]!', $tz->GetPValue('TZID'), $tzid);
                continue;
            }
            $olson = olson_from_tzstring($tzid);
            if (empty($olson)) {
                $olson = $tz->GetPValue('X-LIC-LOCATION');
                if (!empty($olson)) {
                    $olson = olson_from_tzstring($olson);
                }
            }
        }
        dbg_error_log('PUT', ' Using TZID[%s] and location of [%s]', $tzid, isset($olson) ? $olson : '');
        if (!empty($olson) && $olson != $last_olson && preg_match($tz_regex, $olson)) {
            dbg_error_log('PUT', ' Setting timezone to %s', $olson);
            if ($olson != '') {
                $qry->QDo('SET TIMEZONE TO \'' . $olson . "'");
            }
            $last_olson = $olson;
        }
        $params = array(':tzid' => $tzid);
        $qry = new AwlQuery('SELECT 1 FROM timezones WHERE tzid = :tzid', $params);
        if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 0) {
            $params[':olson_name'] = $olson;
            $params[':vtimezone'] = isset($tz) ? $tz->Render() : null;
            $qry->QDo('INSERT INTO timezones (tzid, olson_name, active, vtimezone) VALUES(:tzid,:olson_name,false,:vtimezone)', $params);
        }
        if (!isset($olson) || $olson == '') {
            $olson = $tzid;
        }
    }
    $qry->QDo('SELECT new_sync_token(0,' . $collection_id . ')');
    $calitem_params[':tzid'] = $tzid;
    $calitem_params[':uid'] = $first->GetPValue('UID');
    $calitem_params[':summary'] = $first->GetPValue('SUMMARY');
    $calitem_params[':location'] = $first->GetPValue('LOCATION');
    $calitem_params[':transp'] = $first->GetPValue('TRANSP');
    $calitem_params[':description'] = $first->GetPValue('DESCRIPTION');
    $calitem_params[':rrule'] = $first->GetPValue('RRULE');
    $calitem_params[':url'] = $first->GetPValue('URL');
    $calitem_params[':priority'] = $first->GetPValue('PRIORITY');
    $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
    $calitem_params[':status'] = $first->GetPValue('STATUS');
    if (!$collection->IsSchedulingCollection()) {
        if (do_scheduling_requests($vcal, $put_action_type == 'INSERT', $old_dav_data, true)) {
            $dav_params[':dav_data'] = $vcal->Render(null, true);
            $etag = null;
        }
    }
    if (!isset($dav_params[':modified'])) {
        $dav_params[':modified'] = 'now';
    }
    if ($put_action_type == 'INSERT') {
        $sql = 'INSERT INTO caldav_data ( dav_id, user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id, weak_etag )
            VALUES( :dav_id, :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, :created, :modified, :collection_id, :weak_etag )';
        $dav_params[':collection_id'] = $collection_id;
        $dav_params[':user_no'] = $user_no;
        $dav_params[':dav_name'] = $path;
        $dav_params[':created'] = isset($created) && $created != '' ? $created : $dtstamp;
    } else {
        $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
            modified=:modified, weak_etag=:weak_etag WHERE dav_id=:dav_id';
    }
    $qry = new AwlQuery($sql, $dav_params);
    if (!$qry->Exec('PUT', __LINE__, __FILE__)) {
        fatal('Insert into calendar_item failed...');
        rollback_on_error($caldav_context, $user_no, $path);
        return false;
    }
    if ($put_action_type == 'INSERT') {
        $sql = <<<EOSQL
INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
                dtstart, dtend, summary, location, class, transp,
                description, rrule, tz_id, last_modified, url, priority,
                created, due, percent_complete, status, collection_id )
   VALUES ( :user_no, :dav_name, :dav_id, :etag, :uid, :dtstamp,
                :dtstart, {$dtend}, :summary, :location, :class, :transp,
                :description, :rrule, :tzid, :modified, :url, :priority,
                :created, :due, :percent_complete, :status, :collection_id )
EOSQL;
        $sync_change = 201;
        $calitem_params[':collection_id'] = $collection_id;
        $calitem_params[':user_no'] = $user_no;
        $calitem_params[':dav_name'] = $path;
        $calitem_params[':created'] = $dav_params[':created'];
    } else {
        $sql = <<<EOSQL
UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
                dtstart=:dtstart, dtend={$dtend}, summary=:summary, location=:location,
                class=:class, transp=:transp, description=:description, rrule=:rrule,
                tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
                due=:due, percent_complete=:percent_complete, status=:status
       WHERE dav_id=:dav_id
EOSQL;
        $sync_change = 200;
    }
    write_alarms($dav_id, $first);
    write_attendees($dav_id, $vcal);
    if ($log_action && function_exists('log_caldav_action')) {
        log_caldav_action($put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path);
    } else {
        if ($log_action) {
            dbg_error_log('PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.', $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path);
        }
    }
    $qry = new AwlQuery($sql, $calitem_params);
    if (!$qry->Exec('PUT', __LINE__, __FILE__)) {
        rollback_on_error($caldav_context, $user_no, $path);
        return false;
    }
    $qry->QDo("SELECT write_sync_change( {$collection_id}, {$sync_change}, :dav_name)", array(':dav_name' => $path));
    $qry->Commit();
    if (function_exists('post_commit_action')) {
        post_commit_action($put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path);
    }
    // Uncache anything to do with the collection
    $cache = getCacheInstance();
    $cache_ns = 'collection-' . preg_replace('{/[^/]*$}', '/', $path);
    $cache->delete($cache_ns, null);
    dbg_error_log('PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path);
    return true;
    // Success!
}
Example #20
0
function ticket_row_editor()
{
    global $c, $id, $editor, $can_write_principal, $privilege_names;
    $ticketrow = new Editor("Tickets", "access_ticket");
    $ticketrow->SetSubmitName('ticketrow');
    if ($can_write_principal && $ticketrow->IsSubmit()) {
        $username = $editor->Value('username');
        $ugly_path = $_POST['target'];
        if ($ugly_path == '/' . $username || $ugly_path == '/' . $username . '/') {
            $target_collection = $id;
        } else {
            $username_len = strlen($username) + 2;
            $sql = "SELECT collection_id FROM collection WHERE dav_name = :exact_name";
            $sql .= " AND substring(dav_name FROM 1 FOR {$username_len}) = '/{$username}/'";
            $params = array(':exact_name' => $ugly_path);
            if (!preg_match('#/$#', $ugly_path)) {
                $sql .= " OR dav_name = :truncated_name OR dav_name = :trailing_slash_name";
                $params[':truncated_name'] = preg_replace('#[^/]*$#', '', $ugly_path);
                $params[':trailing_slash_name'] = $ugly_path . "/";
            }
            $sql .= " ORDER BY LENGTH(dav_name) DESC LIMIT 1";
            $qry = new AwlQuery($sql, $params);
            if ($qry->Exec() && $qry->rows() > 0) {
                $row = $qry->Fetch();
                $target_collection = $row->collection_id;
            } else {
                $c->messages[] = translate('Can only add tickets for existing collection paths which you own');
                return $ticketrow;
            }
        }
        $_POST['dav_owner_id'] = $id;
        $_POST['target_collection_id'] = $target_collection;
        $ticket_id = check_by_regex($_POST['ticket_id'], '/[A-Za-z0-9]+/');
        $ticketrow->SetWhere('dav_owner_id=' . $id . ' AND ticket_id=' . AwlQuery::quote($ticket_id));
        if (isset($_POST['ticket_privileges'])) {
            $privilege_bitpos = array_flip($privilege_names);
            $priv_names = array_keys($_POST['ticket_privileges']);
            $privs_dec = privilege_to_bits($priv_names);
            $_POST['privileges'] = sprintf('%024s', decbin($privs_dec));
            $ticketrow->Assign('privileges', $privs_dec);
        }
        $c->messages[] = translate('Creating new ticket granting privileges to this Principal');
        $ticketrow->Write();
    }
    return $ticketrow;
}
    dbg_log_array('cardquery', 'qry_filters', $qry_filters, true);
    $components = array();
    $filter_fragment = SqlFilterCardDAV($qry_filters, $components);
    if ($filter_fragment !== false) {
        $where .= ' ' . $filter_fragment['sql'];
        $params = $filter_fragment['params'];
    }
} else {
    dbg_error_log('cardquery', 'No query filters');
}
$sql = 'SELECT * FROM caldav_data INNER JOIN addressbook_resource USING(dav_id)' . $where;
if (isset($c->strict_result_ordering) && $c->strict_result_ordering) {
    $sql .= " ORDER BY dav_id";
}
$qry = new AwlQuery($sql, $params);
if ($qry->Exec("cardquery", __LINE__, __FILE__) && $qry->rows() > 0) {
    while ($address_object = $qry->Fetch()) {
        if (!$need_post_filter || apply_filter($qry_filters, $address_object)) {
            if ($bound_from != $target_collection->dav_name()) {
                $address_object->dav_name = str_replace($bound_from, $target_collection->dav_name(), $address_object->dav_name);
            }
            if (count($address_data_properties) > 0) {
                $vcard = new VCard($address_object->caldav_data);
                $vcard->MaskProperties($address_data_properties);
                $address_object->caldav_data = $vcard->Render();
            }
            $responses[] = component_to_xml($properties, $address_object);
        }
    }
}
$multistatus = new XMLElement("multistatus", $responses, $reply->GetXmlNsArray());
Example #22
0
/**
* A slightly simpler version of write_resource which will make more sense for calling from
* an external program.  This makes assumptions that the collection and user do exist
* and bypasses all checks for whether it is reasonable to write this here.
* @param string $path The path to the resource being written
* @param string $caldav_data The actual resource to be written
* @param string $put_action_type INSERT or UPDATE depending on what we are to do
* @return boolean True for success, false for failure.
*/
function simple_write_resource($path, $caldav_data, $put_action_type, $write_action_log = false)
{
    $etag = md5($caldav_data);
    $ic = new iCalComponent($caldav_data);
    /**
     * We pull the user_no & collection_id out of the collection table, based on the resource path
     */
    $collection_path = preg_replace('#/[^/]*$#', '/', $path);
    $qry = new AwlQuery('SELECT user_no, collection_id FROM collection WHERE dav_name = :dav_name ', array(':dav_name' => $collection_path));
    if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 1) {
        $collection = $qry->Fetch();
        $user_no = $collection->user_no;
        return write_resource($user_no, $path, $caldav_data, $collection->collection_id, $user_no, $etag, $ic, $put_action_type, false, $write_action_log);
    }
    return false;
}
Example #23
0
 /**
  * Do what must be done with time zones from on file.  Attempt to turn
  * them into something that PostgreSQL can understand...
  *
  * @DEPRECATED: This class will be removed soon.
  * @todo Remove this function.
  */
 function DealWithTimeZones()
 {
     global $c;
     deprecated('iCalendar::DealWithTimeZones');
     $tzid = $this->Get('TZID');
     if (isset($c->save_time_zone_defs) && $c->save_time_zone_defs) {
         $qry = new AwlQuery("SELECT tz_locn FROM time_zone WHERE tz_id = ?;", $tzid);
         if ($qry->Exec('iCalendar') && $qry->rows() == 1) {
             $row = $qry->Fetch();
             $this->tz_locn = $row->tz_locn;
         }
         dbg_error_log('iCalendar', " TZCrap2: TZID '%s', DB Rows=%d, Location '%s'", $tzid, $qry->rows(), $this->tz_locn);
     }
     if ((!isset($this->tz_locn) || $this->tz_locn == '') && $tzid != '') {
         /**
          * In case there was no X-LIC-LOCATION defined, let's hope there is something in the TZID
          * that we can use.  We are looking for a string like "Pacific/Auckland" if possible.
          */
         $tzname = preg_replace('#^(.*[^a-z])?([a-z]+/[a-z]+)$#i', '$1', $tzid);
         /**
         * Unfortunately this kind of thing will never work well :-(
         *
         if ( strstr( $tzname, ' ' ) ) {
           $words = preg_split('/\s/', $tzname );
           $tzabbr = '';
           foreach( $words AS $i => $word ) {
             $tzabbr .= substr( $word, 0, 1);
           }
           $this->tz_locn = $tzabbr;
         }
         */
         if (preg_match('#\\S+/\\S+#', $tzname)) {
             $this->tz_locn = $tzname;
         }
         dbg_error_log('iCalendar', " TZCrap3: TZID '%s', Location '%s', Perhaps: %s", $tzid, $this->tz_locn, $tzname);
     }
     if ($tzid != '' && isset($c->save_time_zone_defs) && $c->save_time_zone_defs && $qry->rows() != 1 && isset($this->vtimezone) && $this->vtimezone != "") {
         $qry2 = new AwlQuery("INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES( ?, ?, ? );", $tzid, $this->tz_locn, $this->vtimezone);
         $qry2->Exec('iCalendar');
     }
     if ((!isset($this->tz_locn) || $this->tz_locn == "") && isset($c->local_tzid)) {
         $this->tz_locn = $c->local_tzid;
     }
 }
Example #24
0
function write_zone_aliases($tzid, $aliases)
{
    global $added_aliases;
    foreach ($aliases as $alias_node) {
        $alias = $alias_node->GetContent();
        $params = array(':tzid' => $tzid, ':alias' => $alias);
        $qry = new AwlQuery('SELECT * FROM tz_aliases JOIN timezones USING(our_tzno) WHERE tzid=:tzid AND tzalias=:alias', $params);
        if ($qry->Exec('tz/update', __LINE__, __FILE__) && $qry->rows() < 1) {
            $qry->QDo('INSERT INTO tz_aliases(our_tzno,tzalias) SELECT our_tzno, :alias FROM timezones WHERE tzid = :tzid', $params);
            $added_aliases++;
        }
    }
}
Example #25
0
    /**
     * Writes the data to a member in the collection and returns the segment_name of the resource in our internal namespace. 
     * @param $data iCalendar The resource to be written.
     * @param $create_resource boolean True if this is a new resource.
     * @param $segment_name The name of the resource within the collection.
     */
    function WriteCalendarMember($data, $create_resource, $segment_name = null)
    {
        if (!$this->IsSchedulingCollection() && !$this->IsCalendar()) {
            return false;
        }
        // function write_resource( $user_no, $path, $caldav_data, $collection_id, $author, $etag, $ic, $put_action_type, $caldav_context, $log_action=true, $weak_etag=null ) {
        global $tz_regex;
        $resources = $ic->GetComponents('VTIMEZONE', false);
        // Not matching VTIMEZONE
        if (!isset($resources[0])) {
            $resource_type = 'Unknown';
            /** @TODO: Handle writing non-calendar resources, like address book entries or random file data */
            rollback_on_error($caldav_context, $user_no, $path, translate('No calendar content'), 412);
            return false;
        } else {
            $first = $resources[0];
            $resource_type = $first->GetType();
        }
        $qry = new AwlQuery();
        $qry->Begin();
        $params = array(':dav_name' => $path, ':user_no' => $user_no, ':etag' => $etag, ':dav_data' => $caldav_data, ':caldav_type' => $resource_type, ':session_user' => $author, ':weak_etag' => $weak_etag);
        if ($put_action_type == 'INSERT') {
            create_scheduling_requests($vcal);
            $sql = 'INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id, weak_etag )
            VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id, :weak_etag )';
            $params[':collection_id'] = $collection_id;
        } else {
            update_scheduling_requests($vcal);
            $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
            modified=current_timestamp, weak_etag=:weak_etag WHERE user_no=:user_no AND dav_name=:dav_name';
        }
        if (!$qry->QDo($sql, $params)) {
            rollback_on_error($caldav_context, $user_no, $path);
            return false;
        }
        $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path));
        if ($qry->rows() == 1 && ($row = $qry->Fetch())) {
            $dav_id = $row->dav_id;
        }
        $calitem_params = array(':dav_name' => $path, ':user_no' => $user_no, ':etag' => $etag);
        $dtstart = $first->GetPValue('DTSTART');
        $calitem_params[':dtstart'] = $dtstart;
        if ((!isset($dtstart) || $dtstart == '') && $first->GetPValue('DUE') != '') {
            $dtstart = $first->GetPValue('DUE');
        }
        $dtend = $first->GetPValue('DTEND');
        if (isset($dtend) && $dtend != '') {
            dbg_error_log('PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION'));
            $calitem_params[':dtend'] = $dtend;
            $dtend = ':dtend';
        } else {
            $dtend = 'NULL';
            if ($first->GetPValue('DURATION') != '' and $dtstart != '') {
                $duration = preg_replace('#[PT]#', ' ', $first->GetPValue('DURATION'));
                $dtend = '(:dtstart::timestamp with time zone + :duration::interval)';
                $calitem_params[':duration'] = $duration;
            } elseif ($first->GetType() == 'VEVENT') {
                /**
                 * From RFC2445 4.6.1:
                 * For cases where a "VEVENT" calendar component specifies a "DTSTART"
                 * property with a DATE data type but no "DTEND" property, the events
                 * non-inclusive end is the end of the calendar date specified by the
                 * "DTSTART" property. For cases where a "VEVENT" calendar component specifies
                 * a "DTSTART" property with a DATE-TIME data type but no "DTEND" property,
                 * the event ends on the same calendar date and time of day specified by the
                 * "DTSTART" property.
                 *
                 * So we're looking for 'VALUE=DATE', to identify the duration, effectively.
                 *
                 */
                $value_type = $first->GetPParamValue('DTSTART', 'VALUE');
                dbg_error_log('PUT', 'DTSTART without DTEND. DTSTART value type is %s', $value_type);
                if (isset($value_type) && $value_type == 'DATE') {
                    $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
                } else {
                    $dtend = ':dtstart';
                }
            }
        }
        $last_modified = $first->GetPValue('LAST-MODIFIED');
        if (!isset($last_modified) || $last_modified == '') {
            $last_modified = gmdate('Ymd\\THis\\Z');
        }
        $calitem_params[':modified'] = $last_modified;
        $dtstamp = $first->GetPValue('DTSTAMP');
        if (!isset($dtstamp) || $dtstamp == '') {
            $dtstamp = $last_modified;
        }
        $calitem_params[':dtstamp'] = $dtstamp;
        $class = $first->GetPValue('CLASS');
        /* Check and see if we should over ride the class. */
        /** @TODO: is there some way we can move this out of this function? Or at least get rid of the need for the SQL query here. */
        if (public_events_only($user_no, $path)) {
            $class = 'PUBLIC';
        }
        /*
         * It seems that some calendar clients don't set a class...
         * RFC2445, 4.8.1.3:
         * Default is PUBLIC
         */
        if (!isset($class) || $class == '') {
            $class = 'PUBLIC';
        }
        $calitem_params[':class'] = $class;
        /** Calculate what timezone to set, first, if possible */
        $last_tz_locn = 'Turkmenikikamukau';
        // I really hope this location doesn't exist!
        $tzid = $first->GetPParamValue('DTSTART', 'TZID');
        if (!isset($tzid) || $tzid == '') {
            $tzid = $first->GetPParamValue('DUE', 'TZID');
        }
        $timezones = $ic->GetComponents('VTIMEZONE');
        foreach ($timezones as $k => $tz) {
            if ($tz->GetPValue('TZID') != $tzid) {
                /**
                 * We'll pretend they didn't forget to give us a TZID and that they
                 * really hope the server is running in the timezone they supplied... but be noisy about it.
                 */
                dbg_error_log('ERROR', ' Event includes TZID[%s] but uses TZID[%s]!', $tz->GetPValue('TZID'), $tzid);
                $tzid = $tz->GetPValue('TZID');
            }
            // This is the one
            $tz_locn = $tz->GetPValue('X-LIC-LOCATION');
            if (!isset($tz_locn)) {
                if (preg_match('#([^/]+/[^/]+)$#', $tzid, $matches)) {
                    $tz_locn = $matches[1];
                } else {
                    if (isset($tzid) && $tzid != '') {
                        dbg_error_log('ERROR', ' Couldn\'t guess Olsen TZ from TZID[%s].  This may end in tears...', $tzid);
                    }
                }
            } else {
                if (!preg_match($tz_regex, $tz_locn)) {
                    if (preg_match('#([^/]+/[^/]+)$#', $tzid, $matches)) {
                        $tz_locn = $matches[1];
                    }
                }
            }
            dbg_error_log('PUT', ' Using TZID[%s] and location of [%s]', $tzid, isset($tz_locn) ? $tz_locn : '');
            if (isset($tz_locn) && $tz_locn != $last_tz_locn && preg_match($tz_regex, $tz_locn)) {
                dbg_error_log('PUT', ' Setting timezone to %s', $tz_locn);
                if ($tz_locn != '') {
                    $qry->QDo('SET TIMEZONE TO \'' . $tz_locn . "'");
                }
                $last_tz_locn = $tz_locn;
            }
            $params = array(':tzid' => $tzid);
            $qry = new AwlQuery('SELECT tz_locn FROM time_zone WHERE tz_id = :tzid', $params);
            if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 0) {
                $params[':tzlocn'] = $tz_locn;
                $params[':tzspec'] = isset($tz) ? $tz->Render() : null;
                $qry->QDo('INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES(:tzid,:tzlocn,:tzspec)', $params);
            }
            if (!isset($tz_locn) || $tz_locn == '') {
                $tz_locn = $tzid;
            }
        }
        $created = $first->GetPValue('CREATED');
        if ($created == '00001231T000000Z') {
            $created = '20001231T000000Z';
        }
        $calitem_params[':created'] = $created;
        $calitem_params[':tzid'] = $tzid;
        $calitem_params[':uid'] = $first->GetPValue('UID');
        $calitem_params[':summary'] = $first->GetPValue('SUMMARY');
        $calitem_params[':location'] = $first->GetPValue('LOCATION');
        $calitem_params[':transp'] = $first->GetPValue('TRANSP');
        $calitem_params[':description'] = $first->GetPValue('DESCRIPTION');
        $calitem_params[':rrule'] = $first->GetPValue('RRULE');
        $calitem_params[':url'] = $first->GetPValue('URL');
        $calitem_params[':priority'] = $first->GetPValue('PRIORITY');
        $calitem_params[':due'] = $first->GetPValue('DUE');
        $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
        $calitem_params[':status'] = $first->GetPValue('STATUS');
        if ($put_action_type == 'INSERT') {
            $sql = <<<EOSQL
INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
                dtstart, dtend, summary, location, class, transp,
                description, rrule, tz_id, last_modified, url, priority,
                created, due, percent_complete, status, collection_id )
   VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp,
                :dtstart, {$dtend}, :summary, :location, :class, :transp,
                :description, :rrule, :tzid, :modified, :url, :priority,
                :created, :due, :percent_complete, :status, {$collection_id} )
EOSQL;
            $sync_change = 201;
        } else {
            $sql = <<<EOSQL
UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
                dtstart=:dtstart, dtend={$dtend}, summary=:summary, location=:location, class=:class, transp=:transp,
                description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
                created=:created, due=:due, percent_complete=:percent_complete, status=:status
       WHERE user_no=:user_no AND dav_name=:dav_name
EOSQL;
            $sync_change = 200;
        }
        write_alarms($dav_id, $first);
        write_attendees($dav_id, $first);
        if ($log_action && function_exists('log_caldav_action')) {
            log_caldav_action($put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path);
        } else {
            if ($log_action) {
                dbg_error_log('PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.', $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path);
            }
        }
        $qry = new AwlQuery($sql, $calitem_params);
        if (!$qry->Exec('PUT', __LINE__, __FILE__)) {
            rollback_on_error($caldav_context, $user_no, $path);
            return false;
        }
        $qry->QDo("SELECT write_sync_change( {$collection_id}, {$sync_change}, :dav_name)", array(':dav_name' => $path));
        $qry->Commit();
        dbg_error_log('PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path);
        return $segment_name;
    }
Example #26
0
        $status[] = new XMLElement('propstat', array(new XMLElement('prop', $props), new XMLElement('status', 'HTTP/1.1 424 Failed Dependency')));
        if ($request_type == 'extended-mkcol') {
            $request->DoResponse($failure_code, $reply->Render('mkcol-response', array_merge($status, $failure), 'text/xml; charset="utf-8"'));
        } else {
            array_unshift($failure, $reply->href(ConstructURL($request->path)));
            $failure[] = new XMLElement('responsedescription', translate('Some properties were not able to be set.'));
            $request->DoResponse(207, $reply->Render('multistatus', new XMLElement('response', $failure)), 'text/xml; charset="utf-8"');
        }
    }
}
$sql = 'SELECT * FROM collection WHERE dav_name = :dav_name';
$qry = new AwlQuery($sql, array(':dav_name' => $request->path));
if (!$qry->Exec('MKCOL', __LINE__, __FILE__)) {
    $request->DoResponse(500, translate('Error querying database.'));
}
if ($qry->rows() != 0) {
    $request->DoResponse(405, translate('A collection already exists at that location.'));
}
$qry = new AwlQuery();
$qry->Begin();
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' => $parent_container, ':dav_name' => $request->path, ':dav_etag' => md5($request->user_no . $request->path), ':dav_displayname' => $displayname, ':is_calendar' => $is_calendar ? 't' : 'f', ':is_addressbook' => $is_addressbook ? 't' : 'f', ':resourcetypes' => $resourcetypes))) {
    $request->DoResponse(500, translate('Error writing calendar details to database.'));
}
foreach ($dav_properties as $k => $v) {
    if (!$qry->QDo('SELECT set_dav_property( :dav_name, :user_no::integer, :tag::text, :value::text )', array(':dav_name' => $request->path, ':user_no' => $request->user_no, ':tag' => $k, ':value' => $v))) {
        $request->DoResponse(500, translate('Error writing calendar properties to database.'));
    }
}
Example #27
0
/**
* Get XML response for items in the collection
* If '/' is requested, a list of visible users is given, otherwise
* a list of calendars for the user which are parented by this path.
*/
function get_collection_contents($depth, $collection, $parent_path = null)
{
    global $c, $session, $request, $reply, $property_list;
    $bound_from = $collection->bound_from();
    $bound_to = $collection->dav_name();
    if (!isset($parent_path)) {
        $parent_path = $collection->dav_name();
    }
    dbg_error_log('PROPFIND', 'Getting collection contents: Depth %d, Path: %s, Bound from: %s, Bound to: %s', $depth, $collection->dav_name(), $bound_from, $bound_to);
    $date_format = AwlDatabase::HttpDateFormat;
    $responses = array();
    if (!$collection->IsCalendar() && !$collection->IsAddressbook()) {
        /**
         * Calendar/Addressbook collections may not contain collections, so we are only looking in the other ones
         */
        $params = array(':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth);
        if ($bound_from == '/') {
            $sql = "SELECT usr.*, '/' || username || '/' AS dav_name, md5(username || updated::text) AS dav_etag, ";
            $sql .= "to_char(joined at time zone 'GMT',{$date_format}) AS created, ";
            $sql .= "to_char(updated at time zone 'GMT',{$date_format}) AS modified, ";
            $sql .= 'FALSE AS is_calendar, TRUE AS is_principal, FALSE AS is_addressbook, \'principal\' AS type, ';
            $sql .= 'principal_id AS collection_id, ';
            $sql .= 'principal.* ';
            $sql .= 'FROM usr JOIN principal USING (user_no) ';
            $sql .= "WHERE (pprivs(:session_principal::int8,principal.principal_id,:scan_depth::int) & 1::BIT(24))::INT4::BOOLEAN ";
            $sql .= 'ORDER BY usr.user_no';
        } else {
            $qry = new AwlQuery('SELECT * FROM dav_binding WHERE dav_binding.parent_container = :this_dav_name ORDER BY bind_id', array(':this_dav_name' => $bound_from));
            if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) {
                while ($binding = $qry->Fetch()) {
                    $resource = new DAVResource($binding->dav_name);
                    if ($resource->IsExternal()) {
                        require_once "external-fetch.php";
                        update_external($resource);
                    }
                    if ($resource->HavePrivilegeTo('DAV::read', false)) {
                        $resource->set_bind_location(str_replace($bound_from, $bound_to, $binding->dav_name));
                        $responses[] = $resource->RenderAsXML($property_list, $reply);
                        if ($depth > 0) {
                            $responses = array_merge($responses, get_collection_contents($depth - 1, $resource, $binding->dav_name));
                        }
                    }
                }
            }
            $sql = 'SELECT principal.*, collection.*, \'collection\' AS type ';
            $sql .= 'FROM collection LEFT JOIN principal USING (user_no) ';
            $sql .= 'WHERE parent_container = :this_dav_name ';
            $sql .= ' ORDER BY collection_id';
            $params[':this_dav_name'] = $bound_from;
            unset($params[':session_principal']);
            unset($params[':scan_depth']);
        }
        $qry = new AwlQuery($sql, $params);
        if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) {
            while ($subcollection = $qry->Fetch()) {
                $resource = new DAVResource($subcollection);
                if (!$resource->HavePrivilegeTo('DAV::read')) {
                    continue;
                }
                $resource->set_bind_location(str_replace($bound_from, $bound_to, $subcollection->dav_name));
                $responses[] = $resource->RenderAsXML($property_list, $reply);
                if ($depth > 0) {
                    $responses = array_merge($responses, get_collection_contents($depth - 1, $resource, str_replace($resource->parent_path(), $parent_path, $resource->dav_name())));
                }
            }
        }
        if ((!isset($c->disable_caldav_proxy) || $c->disable_caldav_proxy == false) && $collection->IsPrincipal()) {
            // Caldav Proxy: 5.1 par. 2: Add child resources calendar-proxy-(read|write)
            dbg_error_log('PROPFIND', 'Adding calendar-proxy-read and write. Path: %s', $bound_from);
            $response = add_proxy_response('read', $bound_from);
            if (isset($response)) {
                $responses[] = $response;
            }
            $response = add_proxy_response('write', $bound_from);
            if (isset($response)) {
                $responses[] = $response;
            }
        }
    }
    /**
     * freebusy permission is not allowed to see the items in a collection.  Must have at least read permission.
     */
    if ($collection->HavePrivilegeTo('DAV::read', false)) {
        dbg_error_log('PROPFIND', 'Getting collection items: Depth %d, Path: %s', $depth, $bound_from);
        $privacy_clause = ' ';
        $todo_clause = ' ';
        $time_limit_clause = ' ';
        if ($collection->IsCalendar()) {
            if (!$collection->HavePrivilegeTo('all', false)) {
                $privacy_clause = " AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) ";
            }
            if (isset($c->hide_TODO) && ($c->hide_TODO === true || is_string($c->hide_TODO) && preg_match($c->hide_TODO, $_SERVER['HTTP_USER_AGENT'])) && !$collection->HavePrivilegeTo('all')) {
                $todo_clause = " AND caldav_data.caldav_type NOT IN ('VTODO') ";
            }
            if (isset($c->hide_older_than) && intval($c->hide_older_than > 0)) {
                $time_limit_clause = " AND (CASE WHEN caldav_data.caldav_type<>'VEVENT' OR calendar_item.dtstart IS NULL THEN true ELSE calendar_item.dtstart > (now() - interval '" . intval($c->hide_older_than) . " days') END) ";
            }
        }
        $sql = 'SELECT collection.*, principal.*, calendar_item.*, caldav_data.*, ';
        $sql .= "to_char(coalesce(calendar_item.created, caldav_data.created) at time zone 'GMT',{$date_format}) AS created, ";
        $sql .= "to_char(coalesce(calendar_item.last_modified, caldav_data.modified) at time zone 'GMT',{$date_format}) AS modified, ";
        $sql .= 'summary AS dav_displayname ';
        $sql .= 'FROM caldav_data LEFT JOIN calendar_item USING( dav_id, user_no, dav_name, collection_id) ';
        $sql .= 'LEFT JOIN collection USING(collection_id,user_no) LEFT JOIN principal USING(user_no) ';
        $sql .= 'WHERE collection.dav_name = :collection_dav_name ' . $time_limit_clause . ' ' . $todo_clause . ' ' . $privacy_clause;
        if (isset($c->strict_result_ordering) && $c->strict_result_ordering) {
            $sql .= " ORDER BY caldav_data.dav_id";
        }
        $qry = new AwlQuery($sql, array(':collection_dav_name' => $bound_from));
        if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) {
            while ($item = $qry->Fetch()) {
                if ($bound_from != $bound_to) {
                    $item->bound_from = $item->dav_name;
                    $item->dav_name = str_replace($bound_from, $bound_to, $item->dav_name);
                }
                $resource = new DAVResource($item);
                $responses[] = $resource->RenderAsXML($property_list, $reply, $parent_path);
            }
        }
    }
    return $responses;
}
Example #28
0
        $dav_id = $row->collection_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 Error'));
        }
        $dav_id = $row->collection_id;
    }
    $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 Error'));
        }
        fetch_external($row->bind_id, '');
        $request->DoResponse(201);
    } else {
        $request->DoResponse(500, translate('Database Error'));
    }
} else {
    $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.'));
    }
                break;
            default:
                /**
                 * @todo We should handle a lot more properties here.  principal-URL seems a likely one to be used.
                 * @todo We should catch the unsupported properties in the query and fire back an error indicating so.
                 */
                dbg_error_log("principal", "Unhandled tag '%s' to match '%s'\n", $v1->GetNSTag(), $match);
        }
    }
    if ($subwhere != "") {
        $where .= sprintf("%s(%s)", $where == "" ? "" : $clause_joiner, $subwhere);
    }
}
if ($where != "") {
    $where = "WHERE {$where}";
}
$sql = "SELECT * FROM dav_principal {$where} ORDER BY principal_id LIMIT 100";
$qry = new AwlQuery($sql, $params);
$get_props = $xmltree->GetPath('/DAV::principal-property-search/DAV::prop/*');
$properties = array();
foreach ($get_props as $k1 => $v1) {
    $properties[] = $v1->GetNSTag();
}
if ($qry->Exec("REPORT", __LINE__, __FILE__) && $qry->rows() > 0) {
    while ($row = $qry->Fetch()) {
        $principal = new DAVResource($row);
        $responses[] = $principal->RenderAsXML($properties, $reply);
    }
}
$multistatus = new XMLElement("multistatus", $responses, $reply->GetXmlNsArray());
$request->XMLResponse(207, $multistatus);
Example #30
0
 /**
  * Render the user's administrative roles
  *
  * @return string The string of html to be output
  */
 function RenderRoles($ef, $title = null)
 {
     global $session;
     $html = "";
     if ($title == null) {
         $title = i18n("User Roles");
     }
     $html = $title == "" ? "" : $ef->BreakLine(translate($title));
     $html .= '<tr><th class="prompt">' . translate("User Roles") . '</th><td class="entry">';
     if ($ef->EditMode) {
         $sql = "SELECT role_name FROM roles ";
         if (!$session->AllowedTo('Admin')) {
             $sql .= "NATURAL JOIN role_member WHERE user_no={$session->user_no} ";
         }
         $sql .= "ORDER BY roles.role_no";
         $ef->record->roles = array();
         // Select the records
         $q = new AwlQuery($sql);
         if ($q && $q->Exec("User") && $q->rows()) {
             $i = 0;
             while ($row = $q->Fetch()) {
                 @dbg_error_log("User", ":RenderRoles: Is a member of '%s': %s", $row->role_name, $this->roles[$row->role_name]);
                 $ef->record->roles[$row->role_name] = isset($this->roles[$row->role_name]) ? $this->roles[$row->role_name] : 'f';
                 $html .= $ef->DataEntryField("", "checkbox", "roles[{$row->role_name}]", array("title" => translate("Does the user have the right to perform this role?"), "_label" => translate($row->role_name)));
             }
         }
     } else {
         $i = 0;
         foreach ($this->roles as $k => $v) {
             if ($i++ > 0) {
                 $html .= ", ";
             }
             $html .= $k;
         }
     }
     $html .= '</td></tr>' . "\n";
     return $html;
 }