MediaWiki r37149 - Code Review

Jump to: navigation, search
Repository:MediaWiki
Revision:r37148‎ | r37149 (on ViewVC)‎ | r37150 >
Date:22:52, 5 July 2008
Author:jablko
Status:old
Tags:
Comment:
Initial import from Free Geek repository: http://svn.freegeek.org/svn/mediawiki-webdav
Modified paths:

Diff [purge]

Index: trunk/extensions/WebDAV/deltav.php
===================================================================
--- trunk/extensions/WebDAV/deltav.php	(revision 0)
+++ trunk/extensions/WebDAV/deltav.php	(revision 37149)
@@ -0,0 +1,62 @@
+<?php
+
+# Initialise common code
+require_once( './includes/WebStart.php' );
+
+require_once( './WebDav.php' );
+
+$server = new WebDavServer();
+$server->handleRequest();
+
+/*
+# Initialize MediaWiki base class
+require_once( "includes/Wiki.php" );
+$mediaWiki = new MediaWiki();
+
+wfProfileIn( 'main-misc-setup' );
+OutputPage::setEncodings(); # Not really used yet
+
+# Query string fields
+$action = $wgRequest->getVal( 'action', 'view' );
+$title = $wgRequest->getVal( 'title' );
+
+$wgTitle = $mediaWiki->checkInitialQueries( $title,$action,$wgOut, $wgRequest, $wgContLang );
+if ($wgTitle == NULL) {
+	unset( $wgTitle );
+}
+
+#
+# Send Ajax requests to the Ajax dispatcher.
+#
+if ( $wgUseAjax && $action == 'ajax' ) {
+	require_once( $IP . '/includes/AjaxDispatcher.php' );
+
+	$dispatcher = new AjaxDispatcher();
+	$dispatcher->performAction();
+	$mediaWiki->restInPeace( $wgLoadBalancer );
+	exit;
+}
+
+
+wfProfileOut( 'main-misc-setup' );
+
+# Setting global variables in mediaWiki
+$mediaWiki->setVal( 'Server', $wgServer );
+$mediaWiki->setVal( 'DisableInternalSearch', $wgDisableInternalSearch );
+$mediaWiki->setVal( 'action', $action );
+$mediaWiki->setVal( 'SquidMaxage', $wgSquidMaxage );
+$mediaWiki->setVal( 'EnableDublinCoreRdf', $wgEnableDublinCoreRdf );
+$mediaWiki->setVal( 'EnableCreativeCommonsRdf', $wgEnableCreativeCommonsRdf );
+$mediaWiki->setVal( 'CommandLineMode', $wgCommandLineMode );
+$mediaWiki->setVal( 'UseExternalEditor', $wgUseExternalEditor );
+$mediaWiki->setVal( 'DisabledActions', $wgDisabledActions );
+
+$wgArticle = $mediaWiki->initialize ( $wgTitle, $wgOut, $wgUser, $wgRequest );
+$mediaWiki->finalCleanup ( $wgDeferredUpdateList, $wgLoadBalancer, $wgOut );
+
+# Not sure when $wgPostCommitUpdateList gets set, so I keep this separate from finalCleanup
+$mediaWiki->doUpdates( $wgPostCommitUpdateList );
+
+$mediaWiki->restInPeace( $wgLoadBalancer );
+*/
+?>

Property changes on: trunk/extensions/WebDAV/deltav.php
___________________________________________________________________
Added: svn:keywords
   + Author Id Revision
Added: svn:eol-style
   + native

Index: trunk/extensions/WebDAV/lib/HTTP/WebDAV/Server.php
===================================================================
--- trunk/extensions/WebDAV/lib/HTTP/WebDAV/Server.php	(revision 0)
+++ trunk/extensions/WebDAV/lib/HTTP/WebDAV/Server.php	(revision 37149)
@@ -0,0 +1,3018 @@
+<?php
+
+/*
+   +----------------------------------------------------------------------+
+   | Copyright (c) 2002-2007 Christian Stocker, Hartmut Holzgraefe        |
+   | All rights reserved                                                  |
+   |                                                                      |
+   | Redistribution and use in source and binary forms, with or without   |
+   | modification, are permitted provided that the following conditions   |
+   | are met:                                                             |
+   |                                                                      |
+   | 1. Redistributions of source code must retain the above copyright    |
+   |    notice, this list of conditions and the following disclaimer.     |
+   | 2. Redistributions in binary form must reproduce the above copyright |
+   |    notice, this list of conditions and the following disclaimer in   |
+   |    the documentation and/or other materials provided with the        |
+   |    distribution.                                                     |
+   | 3. The names of the authors may not be used to endorse or promote    |
+   |    products derived from this software without specific prior        |
+   |    written permission.                                               |
+   |                                                                      |
+   | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
+   | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
+   | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
+   | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE       |
+   | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,  |
+   | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
+   | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;     |
+   | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER     |
+   | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT   |
+   | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN    |
+   | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE      |
+   | POSSIBILITY OF SUCH DAMAGE.                                          |
+   +----------------------------------------------------------------------+
+*/
+
+define('HTTP_WEBDAV_SERVER_DATATYPE_NAMESPACE',
+    'urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882');
+
+/**
+ * WebDAV server base class, needs to be extended to do useful work
+ *
+ * @package HTTP_WebDAV_Server
+ * @author Hartmut Holzgraefe <hholzgra@php.net>
+ * @version 0.99.1dev
+ */
+class HTTP_WebDAV_Server
+{
+    // {{{ Member Variables
+
+    /**
+     * Realm for authentification popups
+     *
+     * @var string
+     */
+    var $authRealm;
+
+    /**
+     * Value of X-Dav-Powered-By response header
+     *
+     * @var string
+     */
+    var $poweredBy;
+
+    /**
+     * Request path components
+     *
+     * @var array
+     */
+    var $pathComponents;
+
+    /**
+     * Base URL components
+     *
+     * See PHP parse_url structure
+     *
+     * @var array
+     */
+    var $baseUrlComponents;
+
+    /**
+     * If request header components
+     *
+     * @var array
+     */
+    var $ifHeaderComponents;
+
+    /**
+     * Array of response headers which have already been sent
+     *
+     * @var array
+     */
+    var $responseHeaders;
+
+    /**
+     * Encoding of property values passed in
+     *
+     * @var string
+     */
+    var $_prop_encoding = 'utf-8';
+
+    // }}}
+
+    // {{{ init
+
+    /**
+     * Initialize
+     *
+     * @param void
+     * @return void
+     */
+    function init()
+    {
+        // realm for authentification popups
+        $this->authRealm = 'PHP WebDAV';
+
+        // value of X-Dav-Powered-By response header
+        $this->poweredBy = 'PHP class: ' . get_class($this);
+
+        // request path components
+        $this->pathComponents = array();
+        if (!empty($_SERVER['PATH_INFO'])) {
+            $this->pathComponents = preg_split('/\//', $_SERVER['PATH_INFO'], -1, PREG_SPLIT_NO_EMPTY);
+        }
+
+        // base URL components
+        $this->baseUrlComponents = array();
+        if (!empty($_SERVER['SCRIPT_NAME'])) {
+            $this->baseUrlComponents = $this->parseUrl($_SERVER['SCRIPT_NAME']);
+        }
+
+        if (!empty($_SERVER['REQUEST_URI'])) {
+            $requestUrlComponents = $this->parseUrl($_SERVER['REQUEST_URI']);
+
+            if (!empty($this->baseUrlComponents['pathComponents'])) {
+                $this->pathComponents = array_slice($requestUrlComponents['pathComponents'], count($this->baseUrlComponents['pathComponents']));
+            }
+
+            if (!empty($this->pathComponents)) {
+                $this->baseUrlComponents['pathComponents'] = array_slice($requestUrlComponents['pathComponents'], 0, -count($this->pathComponents));
+            }
+        }
+
+        if (!empty($_SERVER['HTTP_HOST'])) {
+            $this->baseUrlComponents['host'] = $_SERVER['HTTP_HOST'];
+        }
+
+        if (!empty($_SERVER['QUERY_STRING'])) {
+            $this->baseUrlComponents['query'] = $_SERVER['QUERY_STRING'];
+        }
+
+        // If request header components
+        $this->ifHeaderComponents = array();
+        if (!empty($_SERVER['HTTP_IF'])) {
+            $this->ifHeaderComponents = $this->_if_header_parser($_SERVER['HTTP_IF']);
+        }
+    }
+
+    // }}}
+
+    // {{{ handleRequest
+
+    /**
+     * Handle WebDAV request
+     *
+     * Dispatch WebDAV request to the apropriate method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function handleRequest()
+    {
+        // initialize
+        $this->init();
+
+        // identify ourselves
+        if (!empty($this->poweredBy)) {
+            $this->setResponseHeader('X-Dav-Powered-By: ' . $this->poweredBy);
+        }
+
+        // check authentication
+        if (!$this->check_auth_wrapper()) {
+
+            // RFC2518 says we must use Digest instead of Basic but Microsoft
+            // clients do not support Digest and we don't support NTLM or
+            // Kerberos so we are stuck with Basic here
+            $this->setResponseHeader('WWW-Authenticate: Basic realm="'
+                . $this->authRealm . '"');
+
+            // Windows seems to require this being the last response header
+            // sent (changed according to PECL bug #3138)
+            $this->setResponseStatus('401 Unauthorized');
+            return;
+        }
+
+        // check if request header components
+        if (!$this->check_if_helper($this->ifHeaderComponents)) {
+            $this->setResponseStatus('412 Precondition Failed');
+            return;
+        }
+
+        // detect requested method names
+        $method = strtolower($_SERVER['REQUEST_METHOD']);
+        $wrapper = $method . '_wrapper';
+
+        // get allowed methods
+        $allowedMethods = $this->getAllowedMethods();
+
+        // method not implemented
+        if (!in_array(strtoupper($method), $allowedMethods)) {
+            if ($method == 'lock') {
+                $this->setResponseStatus('412 Precondition Failed');
+                return;
+            }
+
+            // tell client what's allowed
+            $this->setResponseStatus('405 Method Not Allowed');
+            $this->setResponseHeader('Allow: ' . implode(',', $allowedMethods));
+            return;
+        }
+
+        return call_user_func(array($this, $wrapper));
+    }
+
+    // }}}
+
+    // {{{ abstract WebDAV methods
+
+    // {{{ PROPFIND
+
+    /**
+     * PROPFIND implementation
+     *
+     * @abstract
+     * @param array &$params
+     * @returns int HTTP-Statuscode
+     */
+
+    /* abstract
+       function propfind()
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // {{{ PROPPATCH
+
+    /**
+     * PROPPATCH implementation
+     *
+     * @abstract
+     * @param array &$params
+     * @returns int HTTP-Statuscode
+     */
+
+    /* abstract
+       function proppatch()
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // {{{ MKCOL
+
+    /**
+     * MKCOL implementation
+     *
+     * @abstract
+     * @param array &$params
+     * @returns int HTTP-Statuscode
+     */
+
+    /* abstract
+       function mkcol()
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // {{{ GET
+
+    /**
+     * GET implementation
+     *
+     * Overload this method to retrieve resources from your server
+     *
+     * @abstract
+     * @param array &$params array of input and output parameters
+     * <br><b>input</b><ul>
+     * <li> path -
+     * </ul>
+     * <br><b>output</b><ul>
+     * <li> size -
+     * </ul>
+     * @returns int HTTP-Statuscode
+     */
+
+    /* abstract
+       function get()
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // {{{ DELETE
+
+    /**
+     * DELETE implementation
+     *
+     * @abstract
+     * @param array &$params
+     * @returns int HTTP-Statuscode
+     */
+
+    /* abstract
+       function delete()
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // {{{ PUT
+
+    /**
+     * PUT implementation
+     *
+     * @abstract
+     * @param array &$params
+     * @returns int HTTP-Statuscode
+     */
+
+    /* abstract
+       function put()
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // {{{ COPY
+
+    /**
+     * COPY implementation
+     *
+     * @abstract
+     * @param array &$params
+     * @returns int HTTP-Statuscode
+     */
+
+    /* abstract
+       function copy()
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // {{{ MOVE
+
+    /**
+     * MOVE implementation
+     *
+     * @abstract
+     * @param array &$params
+     * @returns int HTTP-Statuscode
+     */
+
+    /* abstract
+       function move()
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // {{{ LOCK
+
+    /**
+     * LOCK implementation
+     *
+     * @abstract
+     * @param array &$params
+     * @returns int HTTP-Statuscode
+     */
+
+    /* abstract
+       function lock()
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // {{{ UNLOCK
+
+    /**
+     * UNLOCK implementation
+     *
+     * @abstract
+     * @param array &$params
+     * @returns int HTTP-Statuscode
+     */
+
+    /* abstract
+       function unlock()
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // }}}
+
+    // {{{ other abstract methods
+
+    // {{{ checkAuth
+
+    /**
+     * Check authentication
+     *
+     * Overload this method to retrieve and confirm authentication information
+     *
+     * @abstract
+     * @param string type Authentication type, e.g. "basic" or "digest"
+     * @param string username Transmitted username
+     * @param string password Transmitted password
+     * @returns bool Authentication status
+     */
+
+    /* abstract
+       function checkAuth($type, $username, $password)
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // {{{ getLocks
+
+    /**
+     * Get lock entries for a resource
+     *
+     * Overload this method to return shared and exclusive locks active for
+     * this resource
+     *
+     * @abstract
+     * @param string resource path to check
+     * @returns array of lock entries each consisting
+     *                of 'type' ('shared'/'exclusive'), 'token' and 'timeout'
+     */
+
+    /* abstract
+       function getLocks($path)
+       {
+           // dummy entry for PHPDoc
+       }
+    */
+
+    // }}}
+
+    // }}}
+
+    // {{{ WebDAV HTTP method wrappers
+
+    // {{{ options
+
+    /**
+     * OPTIONS method handler
+     *
+     * Responds with DAV and Allow response headers based on the methods
+     * actually implemented by this server instance
+     *
+     * @param options
+     * @return void
+     */
+    function options(array &$options)
+    {
+        // get allowed methods
+        $allowedMethods = $this->getAllowedMethods();
+
+        // DAV response header
+        $davHeader = array(1); // assume we are always DAV class 1 compliant
+        if (in_array('LOCK', $allowedMethods)
+                && in_array('UNLOCK', $allowedMethods)) {
+            $davHeader[] = 2; // DAV class 2 requires locking
+        }
+
+        // tell clients what we found
+        $this->setResponseHeader('Allow: ' . implode(',', $allowedMethods));
+        $this->setResponseHeader('DAV: ' . implode(',', $davHeader));
+
+        // Microsoft clients default to Frontpage protocol unless we tell them
+        // to use WebDAV
+        $this->setResponseHeader('MS-Author-Via: DAV');
+
+        return true;
+    }
+
+    // }}}
+
+    // {{{ options_wrapper
+
+    /**
+     * OPTIONS method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function options_wrapper()
+    {
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // load request body DOM document
+        $options['doc'] = new DOMDocument;
+        $this->loadRequestBody($options['doc']);
+
+        // analyze request body
+        $options['xpath'] = new DOMXPath($options['doc']);
+        $options['xpath']->registerNamespace('D', 'DAV:');
+
+        $status = $this->options($options);
+
+        $this->setResponseStatus($status);
+    }
+
+    // }}}
+
+    // {{{ propfind_request_helper
+
+    /**
+     * PROPFIND request helper - prepare data-structures from PROPFIND requests
+     *
+     * @param options
+     * @return void
+     */
+    function propfind_request_helper(array &$options)
+    {
+        // RFC2518 8.1: A client may submit a Depth header with a value of "0",
+        // "1", or "infinity" with a PROPFIND on a collection resource with
+        // internal member URIs.  DAV compliant servers MUST support the "0",
+        // "1" and "infinity" behaviors.  By default, the PROPFIND method
+        // without a Depth header MUST act as if a "Depth: infinity" header was
+        // included.
+        $options['depth'] = 'infinity';
+        if (isset($_SERVER['HTTP_DEPTH'])) {
+            $options['depth'] = $_SERVER['HTTP_DEPTH'];
+        }
+
+        // RFC3253 8.3: For certain methods (e.g. GET, PROPFIND), if the
+        // request-URL identifies a version-controlled resource, a label can be
+        // specified in a Label request header to cause the method to be
+        // applied to the version selected by that label from the version
+        // history of that version-controlled resource.
+        if (isset($_SERVER['HTTP_LABEL'])) {
+            $options['label'] = $_SERVER['HTTP_LABEL'];
+        }
+
+        $options['namespaces'] = array();
+
+        // Microsoft needs this special namespace for date and time values
+        $options['namespaces'][HTTP_WEBDAV_SERVER_DATATYPE_NAMESPACE] = 'T';
+
+        // RFC2518 8.1: A client may submit a propfind XML element in the body
+        // of the request method describing what information is being
+        // requested.  It is possible to request particular property values,
+        // all property values, or a list of the names of the resource's
+        // properties.  A client may choose not to submit a request body.  An
+        // empty PROPFIND request body MUST be treated as a request for the
+        // names and values of all properties.
+        if (empty($_SERVER['CONTENT_LENGTH'])) {
+            $options['props'] = 'allprops';
+            return true;
+        }
+
+        // load request body DOM document
+        $options['doc'] = new DOMDocument;
+        if (!$this->loadRequestBody($options['doc'])) {
+            $this->setResponseStatus('400 Bad Request');
+            return;
+        }
+
+        // analyze request body
+        $options['xpath'] = new DOMXPath($options['doc']);
+        $options['xpath']->registerNamespace('D', 'DAV:');
+
+        $options['props'] = array();
+        foreach ($options['xpath']->query('/D:propfind/D:prop/*') as $node) {
+            $options['props'][] = $this->mkprop(
+                $node->namespaceURI, $node->localName, null);
+
+            // namespace handling
+            if (empty($node->namespaceURI) || empty($node->prefix)) {
+                continue;
+            }
+
+            // http://bugs.php.net/bug.php?id=42082
+            //$options['namespaces'][$node->namespaceURI] = $node->prefix;
+        }
+
+        if (empty($options['props'])) {
+            $options['props'] = $options['xpath']->evaluate(
+                'local-name(/D:propfind/*)');
+        }
+
+        return true;
+    }
+
+    // }}}
+
+    // {{{ propfind_response_helper
+
+    /**
+     * PROPFIND response helper - format PROPFIND response
+     *
+     * @param options
+     * @param status
+     * @return void
+     */
+    function propfind_response_helper(array $options, $status)
+    {
+        // set response status
+        if (empty($status) || !is_array($status)) {
+            $this->setResponseStatus($status, false);
+            return;
+        }
+
+        $responses = array();
+
+        // now loop over all returned files
+        foreach ($status as $file) {
+            $response = array();
+
+            // copy to response
+            foreach (array('path', 'pathComponents', 'url', 'score') as $key) {
+                if (!empty($file[$key])) {
+                    $response[$key] = $file[$key];
+                }
+            }
+
+            $response['propstat'] = array();
+
+            // $options['props'] is guaranteed to be set.  handle empty array
+            // here.
+            if (is_array($options['props'])) {
+
+                // loop over all requested properties
+                foreach ($options['props'] as $reqprop) {
+                    $status = '200 OK';
+                    $prop = $this->getProp($reqprop, $file, $options);
+
+                    if (!empty($prop['status'])) {
+                        $status = $prop['status'];
+                    }
+
+                    $response['propstat'][$status][] = $prop;
+
+                    // namespace handling
+                    if (empty($prop['namespace'])
+                            || !empty($options['namespaces'][$prop['namespace']])) {
+                        continue;
+                    }
+
+                    // register namespace
+                    $options['namespaces'][$prop['namespace']] =
+                        'ns' . count($options['namespaces']);
+                }
+            } else if (!empty($file['props']) && is_array($file['props'])) {
+
+                // loop over all returned properties
+                foreach ($file['props'] as $prop) {
+                    $status = '200 OK';
+
+                    if (!empty($prop['status'])) {
+                        $status = $prop['status'];
+                    }
+
+                    if ($options['props'] == 'propname') {
+
+                        // only names of all existing properties were requested
+                        // so remove values
+                        unset($prop['value']);
+                    }
+
+                    $response['propstat'][$status][] = $prop;
+
+                    // namespace handling
+                    if (empty($prop['namespace'])
+                            || !empty($options['namespaces'][$prop['namespace']])) {
+                        continue;
+                    }
+
+                    // register namespace
+                    $options['namespaces'][$prop['namespace']] =
+                        'ns' . count($options['namespaces']);
+                }
+            }
+
+            if (!empty($file['status'])) {
+                $response['status'] = $file['status'];
+            }
+
+            $responses[] = $response;
+        }
+
+        // Assume label support is implemented.  RFC3253 8.3: A server MUST
+        // return an HTTP-1.1 Vary header containing Label in a successful
+        // response to a cacheable request (e.g., GET) that includes a Label
+        // header.
+        if (isset($options['label'])) {
+            $this->setResponseHeader('Vary: Label');
+        }
+
+        $this->multistatusResponseHelper($options, $responses);
+    }
+
+    // }}}
+
+    // {{{ propfind_wrapper
+
+    /**
+     * PROPFIND method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function propfind_wrapper()
+    {
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // prepare data-structure from PROPFIND request
+        if (!$this->propfind_request_helper($options)) {
+            return;
+        }
+
+        // call user handler
+        $status = $this->propfind($options);
+
+        // format PROPFIND response
+        $this->propfind_response_helper($options, $status);
+    }
+
+    // }}}
+
+    // {{{ proppatch_request_helper
+
+    /**
+     * PROPPATCH request helper - prepare data-structures from PROPPATCH requests
+     *
+     * @param options
+     * @return void
+     */
+    function proppatch_request_helper(array &$options)
+    {
+        $options['namespaces'] = array();
+
+        // Microsoft needs this special namespace for date and time values
+        $options['namespaces'][HTTP_WEBDAV_SERVER_DATATYPE_NAMESPACE] = 'T';
+
+        // load request body DOM document
+        $options['doc'] = new DOMDocument;
+        if (!$this->loadRequestBody($options['doc'])) {
+            $this->setResponseStatus('400 Bad Request');
+            return;
+        }
+
+        // analyze request body
+        $options['xpath'] = new DOMXPath($options['doc']);
+        $options['xpath']->registerNamespace('D', 'DAV:');
+
+        $options['props'] = array();
+        foreach ($options['xpath']->query('/D:propertyupdate/*') as $context) {
+            foreach ($options['xpath']->query('/D:prop/*', $node) as $node) {
+                $prop = $this->mkprop(
+                    $node->namespaceURI, $node->localName, null);
+                $prop[$context->localName] = $node->nodeValue;
+
+                $options['props'][] = $prop;
+
+                // namespace handling
+                if (empty($node->namespaceURI) || empty($node->prefix)) {
+                    continue;
+                }
+
+                // http://bugs.php.net/bug.php?id=42082
+                //$options['namespaces'][$node->namespaceURI] = $node->prefix;
+            }
+        }
+
+        return true;
+    }
+
+    // }}}
+
+    // {{{ proppatch_response_helper
+
+    /**
+     * PROPPATCH response helper - format PROPPATCH response
+     *
+     * @param options
+     * @param responsedescr
+     * @return void
+     */
+    function proppatch_response_helper(array $options, $status)
+    {
+        // set response status
+        if (empty($status) || !is_array($status)) {
+            $this->setResponseStatus($status, false);
+            return;
+        }
+
+        $file = $status;
+        $response = array();
+
+        // copy to response
+        foreach (array('path', 'pathComponents', 'url', 'responsedescription') as $key) {
+            if (!empty($file[$key])) {
+                $response[$key] = $file[$key];
+            }
+        }
+
+        $response['propstat'] = array();
+
+        // loop over all requested properties
+        foreach ($options['props'] as $reqprop) {
+            $status = '200 OK';
+            $prop = $this->getProp($reqprop, $file, $options);
+
+            if (!empty($prop['status'])) {
+                $status = $prop['status'];
+            }
+
+            $response['propstat'][$status][] = $prop;
+
+            // namespace handling
+            if (empty($prop['namespace'])
+                    || !empty($options['namespaces'][$prop['namespace']])) {
+                continue;
+            }
+
+            // register namespace
+            $options['namespaces'][$prop['namespace']] =
+                'ns' . count($options['namespaces']);
+        }
+
+        if (!empty($file['status'])) {
+            $response['status'] = $file['status'];
+        }
+
+        $this->multistatusResponseHelper($options, array($response));
+    }
+
+    // }}}
+
+    // {{{ proppatch_wrapper
+
+    /**
+     * PROPPATCH method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function proppatch_wrapper()
+    {
+        // check resource is not locked
+        if (!$this->check_locks_wrapper($this->pathComponents)) {
+            $this->setResponseStatus('423 Locked');
+            return;
+        }
+
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // perpare data-structure from PROPATCH request
+        if (!$this->proppatch_request_helper($options)) {
+            return;
+        }
+
+        // call user handler
+        $status = $this->proppatch($options);
+
+        // format PROPPATCH response
+        $this->proppatch_response_helper($options, $status);
+    }
+
+    // }}}
+
+    // {{{ mkcol_wrapper
+
+    /**
+     * MKCOL method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function mkcol_wrapper()
+    {
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        $status = $this->mkcol($options);
+
+        $this->setResponseStatus($status);
+    }
+
+    // }}}
+
+    // {{{ get_request_helper
+
+    /**
+     * GET request helper - prepare data-structures from GET requests
+     *
+     * @param options
+     * @return void
+     */
+    function get_request_helper(array &$options)
+    {
+        // RFC4918 8.4: Some of these new methods do not define bodies.
+        // Servers MUST examine all requests for a body, even when a body was
+        // not expected.  In cases where a request body is present but would be
+        // ignored by a server, the server MUST reject the request with 415
+        // (Unsupported Media Type).  This informs the client (which may have
+        // been attempting to use an extension) that the body could not be
+        // processed as the client intended.
+        if (!empty($_SERVER['CONTENT_LENGTH'])) {
+            $this->setResponseStatus('415 Unsupported Media Type');
+            return;
+        }
+
+        // RFC3253 8.3: For certain methods (e.g. GET, PROPFIND), if the
+        // request-URL identifies a version-controlled resource, a label can be
+        // specified in a Label request header to cause the method to be
+        // applied to the version selected by that label from the version
+        // history of that version-controlled resource.
+        if (isset($_SERVER['HTTP_LABEL'])) {
+            $options['label'] = $_SERVER['HTTP_LABEL'];
+        }
+
+        $this->_get_ranges($options);
+
+        return true;
+    }
+
+    /**
+     * Parse Range request header
+     *
+     * @param  array options array to store result in
+     * @return void
+     */
+    function _get_ranges(array &$options)
+    {
+        if (empty($_SERVER['HTTP_RANGE'])) {
+            return;
+        }
+
+        // we support only standard bytes range specification
+        if (!preg_match('/bytes\s*=\s*(.+)', $_SERVER['HTTP_RANGE'], $matches)) {
+            return;
+        }
+
+        $options['ranges'] = array();
+
+        // ranges are comma separated
+        foreach (explode(',', $matches[1]) as $range) {
+            // ranges are either from-to pairs or just end positions
+            list ($start, $end) = explode('-', $range);
+            $options['ranges'][] = ($start === '') ? array('last' => $end) : array('start' => $start, 'end' => $end);
+        }
+    }
+
+    // }}}
+
+    // {{{ get_response_helper
+
+    /**
+     * GET response helper - format GET response
+     *
+     * @param options
+     * @param status
+     * @return void
+     */
+    function get_response_helper(array $options, $status)
+    {
+        // set response headers before we start printing
+        $this->setResponseStatus($status, false);
+
+        if ($status !== true) {
+            return;
+        }
+
+        if (empty($options['mimetype'])) {
+            $options['mimetype'] = 'application/octet-stream';
+        }
+        $this->setResponseHeader('Content-Type: ' . $options['mimetype'], false);
+
+        if (!empty($options['mtime'])) {
+            $this->setResponseHeader('Last-Modified: '
+                . gmdate('D, d M Y H:i:s', $options['mtime']) . 'GMT', false);
+        }
+
+        // Assume label support is implemented.  RFC3253 8.3: A server MUST
+        // return an HTTP-1.1 Vary header containing Label in a successful
+        // response to a cacheable request (e.g., GET) that includes a Label
+        // header.
+        if (isset($options['label'])) {
+            $this->setResponseHeader('Vary: Label');
+        }
+
+        if (!empty($options['stream'])) {
+            // GET handler returned a stream
+
+            if (!empty($options['ranges'])
+                    && (fseek($options['stream'], 0, SEEK_SET) === 0)) {
+                // partial request and stream is seekable
+
+                if (count($options['ranges']) == 1) {
+                    $range = $options['ranges'][0];
+
+                    if (!empty($range['start'])) {
+                        fseek($options['stream'], $range['start'], SEEK_SET);
+                        if (feof($options['stream'])) {
+                            $this->setResponseStatus(
+                                '416 Requested Range Not Satisfiable', false);
+                            return;
+                        }
+
+                        if (!empty($range['end'])) {
+                            $size = $range['end'] - $range['start'] + 1;
+                            $this->setResponseStatus('206 Partial', false);
+                            $this->setResponseHeader(
+                                "Content-Length: $size", false);
+                            $this->setResponseHeader(
+                                "Content-Range: $range[start]-$range[end]/"
+                                . (!empty($options['size']) ? $options['size'] : '*'), false);
+                            while ($size && !feof($options['stream'])) {
+                                $buffer = fread($options['stream'], 4096);
+                                $size -= strlen($buffer);
+                                echo $buffer;
+                            }
+                        } else {
+                            $this->setResponseStatus('206 Partial', false);
+                            if (!empty($options['size'])) {
+                                $this->setResponseHeader("Content-Length: "
+                                    . ($options['size'] - $range['start']), false);
+                                $this->setResponseHeader(
+                                    "Content-Range: $range[start]-$range[end]/"
+                                    . (!empty($options['size']) ? $options['size'] : '*'), false);
+                            }
+                            fpassthru($options['stream']);
+                        }
+                    } else {
+                        $this->setResponseHeader("Content-Length: $range[last]", false);
+                        fseek($options['stream'], -$range['last'], SEEK_END);
+                        fpassthru($options['stream']);
+                    }
+                } else {
+                    $this->_multipart_byterange_header(); // init multipart
+                    foreach ($options['ranges'] as $range) {
+
+                        // TODO: What if size unknown?  500?
+                        if (!empty($range['start'])) {
+                            $from = $range['start'];
+                            $to = !empty($range['end']) ? $range['end'] : $options['size'] - 1;
+                        } else {
+                            $from = $options['size'] - $range['last'] - 1;
+                            $to = $options['size'] - 1;
+                        }
+                        $total = !empty($options['size']) ? $options['size'] : '*';
+                        $size = $to - $from + 1;
+                        $this->_multipart_byterange_header(
+                            $options['mimetype'], $from, $to, $total);
+
+                        fseek($options['stream'], $start, SEEK_SET);
+                        while ($size && !feof($options['stream'])) {
+                            $buffer = fread($options['stream'], 4096);
+                            $size -= strlen($buffer);
+                            echo $buffer;
+                        }
+                    }
+
+                    // end multipart
+                    $this->_multipart_byterange_header();
+                }
+            } else {
+                // normal request or stream isn't seekable, return full content
+                if (!empty($options['size'])) {
+                    $this->setResponseHeader(
+                        'Content-Length: ' . $options['size'], false);
+                }
+
+                fpassthru($options['stream']);
+            }
+        } else if (!empty($options['data']))  {
+            if (is_array($options['data'])) {
+                // reply to partial request
+            } else {
+                $this->setResponseHeader(
+                    'Content-Length: ' . strlen($options['data']), false);
+                echo $options['data'];
+            }
+        }
+    }
+
+    /**
+     * Generate separator headers for multipart response
+     *
+     * First and last call happen without parameters to generate the initial
+     * header and closing sequence, all calls inbetween require content
+     * mimetype, start and end byte position and optionaly the total byte
+     * length of the requested resource
+     *
+     * @param string mimetype
+     * @param int start byte position
+     * @param int end byte position
+     * @param int total resource byte size
+     */
+    function _multipart_byterange_header($mimetype = false, $from = false, $to = false, $total = false)
+    {
+        if ($mimetype === false) {
+            if (empty($this->multipart_separator)) {
+                // init
+                // a little naive, this sequence *might* be part of the content
+                // but it's really not likely and rather expensive to check
+                $this->multipart_separator = 'SEPARATOR_' . md5(microtime());
+
+                $this->setResponseHeader(
+                    'Content-Type: multipart/byteranges; boundary='
+                    . $this->multipart_separator, false);
+                return;
+            }
+
+            // end
+            // generate closing multipart sequence
+            echo "\n--{$this->multipart_separator}--";
+            return;
+        }
+
+        // generate separator and header for next part
+        echo "\n--{$this->multipart_separator}\n";
+        echo "Content-Type: $mimetype\n";
+        echo "Content-Range: $from-$to/"
+            . ($total === false ? "*" : $total) . "\n\n";
+    }
+
+    // }}}
+
+    // {{{ get_wrapper
+
+    /**
+     * GET method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function get_wrapper()
+    {
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // perpare data-structure from GET request
+        if (!$this->get_request_helper($options)) {
+            return;
+        }
+
+        // call user handler
+        $status = $this->get($options);
+
+        // format GET response
+        $this->get_response_helper($options, $status);
+    }
+
+    // }}}
+
+    // {{{ head_wrapper
+
+    /**
+     * HEAD method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function head_wrapper()
+    {
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // TODO: get_response_helper needn't do any output in case of HEAD
+        // responses.  Is the advantage to optimizing it worthwhile?
+        ob_start();
+
+        // call user handler
+        if (method_exists($this, 'head')) {
+            $status = $this->head($options);
+        } else {
+
+            // emulate HEAD using GET
+            $status = $this->get($options);
+        }
+
+        // format HEAD response
+        $this->get_response_helper($options, $status);
+        ob_end_clean();
+    }
+
+    // }}}
+
+    // {{{ put_request_helper
+
+    /**
+     * PUT request helper - prepare data-structures from PUT requests
+     *
+     * @param options
+     * @return void
+     */
+    function put_request_helper(array &$options)
+    {
+        // Content-Length may be zero
+        if (!isset($_SERVER['CONTENT_LENGTH'])) {
+            return;
+        }
+        $options['content_length'] = $_SERVER['CONTENT_LENGTH'];
+
+        // default content type if none given
+        $options['content_type'] = 'application/unknown';
+
+        // get the content-type
+        if (!empty($_SERVER['CONTENT_TYPE'])) {
+
+            // for now we do not support any sort of multipart requests
+            if (!strncmp($_SERVER['CONTENT_TYPE'], 'multipart/', 10)) {
+                $this->setResponseStatus('501 Not Implemented');
+                echo 'The service does not support mulipart PUT requests';
+                return;
+            }
+
+            $options['content_type'] = $_SERVER['CONTENT_TYPE'];
+        }
+
+        // RFC2616 2.6: The recipient of the entity MUST NOT ignore any
+        // Content-* (e.g. Content-Range) headers that it does not understand
+        // or implement and MUST return a 501 (Not Implemented) response in
+        // such cases.
+        foreach ($_SERVER as $key => $value) {
+            if (strncmp($key, 'HTTP_CONTENT', 11)) {
+                continue;
+            }
+
+            switch ($key) {
+            case 'HTTP_CONTENT_ENCODING': // RFC2616 14.11
+
+                // TODO: Support this if ext/zlib filters are available
+                $this->setResponseStatus('501 Not Implemented');
+                echo "The service does not support '$value' content encoding";
+                return;
+
+            case 'HTTP_CONTENT_LANGUAGE': // RFC2616 14.12
+
+                // we assume it is not critical if this one is ignored in the
+                // actual PUT implementation
+                $options['content_language'] = $value;
+                break;
+
+            case 'HTTP_CONTENT_LENGTH':
+
+                // defined on IIS and has the same value as CONTENT_LENGTH
+                break;
+
+            case 'HTTP_CONTENT_LOCATION': // RFC2616 14.14
+
+                // meaning of the Content-Location request header in PUT or
+                // POST requests is undefined; servers are free to ignore it in
+                // those cases
+                break;
+
+            case 'HTTP_CONTENT_RANGE': // RFC2616 14.16
+
+                // single byte range requests are supported
+                // the header format is also specified in RFC2616 14.16
+                // TODO: Ensure that implementations support this or send 501
+                // instead
+                if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $value, $matches)) {
+                    $this->setResponseStatus('400 Bad Request');
+                    echo 'The service does only support single byte ranges';
+                    return;
+                }
+
+                $range = array('start' => $matches[1], 'end' => $matches[2]);
+                if (is_numeric($matches[3])) {
+                    $range['total_length'] = $matches[3];
+                }
+                $option['ranges'][] = $range;
+
+                // TODO: Make sure the implementation supports partial PUT
+                // this has to be done in advance to avoid data being
+                // overwritten on implementations that do not support this...
+                break;
+
+            case 'HTTP_CONTENT_MD5': // RFC2616 14.15
+
+                // TODO: Maybe we can just pretend here?
+                $this->setResponseStatus('501 Not Implemented');
+                echo 'The service does not support content MD5 checksum verification';
+                return;
+
+            case 'HTTP_CONTENT_TYPE':
+
+                // defined on IIS and has the same value as CONTENT_TYPE
+                break;
+
+            default:
+
+                // any other unknown Content-* request headers
+                $this->setResponseStatus('501 Not Implemented');
+                echo 'The service does not support ' . $key;
+                return;
+            }
+        }
+
+        $options['stream'] = $this->openRequestBody();
+
+        return true;
+    }
+
+    // }}}
+
+    // {{{ put_response_helper
+
+    /**
+     * PUT response helper - format PUT response
+     *
+     * @param options
+     * @param status
+     * @return void
+     */
+    function put_response_helper(array $options, $status)
+    {
+        if (empty($status)) {
+            $status = '403 Forbidden';
+        } else if (is_resource($status)
+                && get_resource_type($status) == 'stream') {
+            $stream = $status;
+            $status = '201 Created';
+            if (isset($options['new']) && $options['new'] === false) {
+                $status = '204 No Content';
+            }
+
+            if (!empty($options['ranges'])) {
+
+                // TODO: Multipart support is missing (see also above)
+                if (0 == fseek($stream, $range[0]['start'], SEEK_SET)) {
+                    $length = $range[0]['end'] - $range[0]['start'] + 1;
+                    if (!fwrite($stream, fread($options['stream'], $length))) {
+                        $status = '403 Forbidden';
+                    }
+                } else {
+                    $status = '403 Forbidden';
+                }
+            } else {
+                while (!feof($options['stream'])) {
+                    $buf = fread($options['stream'], 4096);
+                    if (fwrite($stream, $buf) != 4096) {
+                        break;
+                    }
+                }
+            }
+
+            fclose($stream);
+        }
+
+        $this->setResponseStatus($status);
+    }
+
+    // }}}
+
+    // {{{ put_wrapper
+
+    /**
+     * PUT method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function put_wrapper()
+    {
+        // check resource is not locked
+        if (!$this->check_locks_wrapper($this->pathComponents)) {
+            $this->setResponseStatus('423 Locked');
+            return;
+        }
+
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // perpare data-structure from PUT request
+        if (!$this->put_request_helper($options)) {
+            return;
+        }
+
+        // call user handler
+        $status = $this->put($options);
+
+        // format PUT response
+        $this->put_response_helper($options, $status);
+    }
+
+    // }}}
+
+    // {{{ delete_wrapper
+
+    /**
+     * DELETE method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function delete_wrapper()
+    {
+        // RFC2518 9.2: Please note, however, that it is always an error to
+        // submit a value for the Depth header that is not allowed by the
+        // method's definition.  Thus submitting a "Depth: 1" on a COPY, even
+        // if the resource does not have internal members, will result in a 400
+        // (Bad Request).  The method should fail not because the resource
+        // doesn't have internal members, but because of the illegal value in
+        // the header.
+        if (isset($_SERVER['HTTP_DEPTH'])
+                && $_SERVER['HTTP_DEPTH'] != 'infinity') {
+            $this->setResponseStatus('400 Bad Request');
+            return;
+        }
+
+        // check resource is not locked
+        if (!$this->check_locks_wrapper($this->pathComponents)) {
+            $this->setResponseStatus('423 Locked');
+            return;
+        }
+
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // call user handler
+        $status = $this->delete($options);
+        if ($status === true) {
+            $status = '204 No Content';
+        }
+
+        $this->setResponseStatus($status);
+    }
+
+    // }}}
+
+    // {{{ copymove_request_helper
+
+    /**
+     * COPY/MOVE request helper - prepare data-structures from COPY/MOVE
+     * requests
+     *
+     * @param options
+     * @return void
+     */
+    function copymove_request_helper(array &$options)
+    {
+        // RFC4918 8.4: Some of these new methods do not define bodies.
+        // Servers MUST examine all requests for a body, even when a body was
+        // not expected.  In cases where a request body is present but would be
+        // ignored by a server, the server MUST reject the request with 415
+        // (Unsupported Media Type).  This informs the client (which may have
+        // been attempting to use an extension) that the body could not be
+        // processed as the client intended.
+        if (!empty($_SERVER['CONTENT_LENGTH'])) {
+            $this->setResponseStatus('415 Unsupported Media Type');
+            return;
+        }
+
+        // RFC2518 8.8.3: The COPY method on a collection without a Depth
+        // header MUST act as if a Depth header with value "infinity" was
+        // included.  A client may submit a Depth header on a COPY on a
+        // collection with a value of "0" or "infinity".  DAV compliant servers
+        // MUST support the "0" and "infinity" Depth header behaviors.
+        $options['depth'] = 'infinity';
+        if (isset($_SERVER['HTTP_DEPTH'])) {
+            $options['depth'] = $_SERVER['HTTP_DEPTH'];
+        }
+
+        // RFC2518 9.6, 8.8.4 and 8.9.3
+        $options['overwrite'] = true;
+        if (!empty($_SERVER['HTTP_OVERWRITE'])) {
+            $options['overwrite'] = $_SERVER['HTTP_OVERWRITE'] == 'T';
+        }
+
+        $options['destinationUrlComponents'] = $this->parseUrl($_SERVER['HTTP_DESTINATION']);
+
+        return true;
+    }
+
+    // }}}
+
+    // {{{ copy_wrapper
+
+    /**
+     * COPY method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function copy_wrapper()
+    {
+        // no need to check source is not locked
+
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // perpare data-structure from COPY request
+        if (!$this->copymove_request_helper($options)) {
+            return;
+        }
+
+        // does the destination resource belong on this server?
+        if ($this->isDescendentUrl($options['destinationUrlComponents'])) {
+            $options['destinationPathComponents'] = array_slice(
+                $options['destinationUrlComponents']['pathComponents'],
+                count($this->baseUrlComponents['pathComponents']));
+
+            // RFC4918 9.8.5: 403 (Forbidden) - The operation is forbidden.  A
+            // special case for COPY could be that the source and destination
+            // resources are the same resource.
+            if ($options['destinationPathComponents'] == $this->pathComponents) {
+                $this->setResponseStatus('403 Forbidden');
+                return;
+            }
+
+            // check destination is not locked
+            if (!$this->check_locks_wrapper($options['destinationPathComponents'])) {
+                $this->setResponseStatus('423 Locked');
+                return;
+            }
+        }
+
+        // call user handler
+        $status = $this->copy($options);
+        if ($status === true) {
+            $status = $options['new'] === false ? '204 No Content' :
+                '201 Created';
+        }
+
+        $this->setResponseStatus($status);
+    }
+
+    // }}}
+
+    // {{{ move_wrapper
+
+    /**
+     * MOVE method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function move_wrapper()
+    {
+        // check resource is not locked
+        if (!$this->check_locks_wrapper($this->pathComponents)) {
+            $this->setResponseStatus('423 Locked');
+            return;
+        }
+
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // perpare data-structure from MOVE request
+        if (!$this->copymove_request_helper($options)) {
+            return;
+        }
+
+        // does the destination resource belong on this server?
+        if ($this->isDescendentUrl($options['destinationUrlComponents'])) {
+            $options['destinationPathComponents'] = array_slice(
+                $options['destinationUrlComponents']['pathComponents'],
+                count($this->baseUrlComponents['pathComponents']));
+
+            // RFC2518 8.8.5: Check source and destination are not the same -
+            // data could be lost if overwrite is true
+            if ($options['destinationPathComponents'] == $this->pathComponents) {
+                $this->setResponseStatus('403 Forbidden');
+                return;
+            }
+
+            // check destination is not locked
+            if (!$this->check_locks_wrapper($options['destinationPathComponents'])) {
+                $this->setResponseStatus('423 Locked');
+                return;
+            }
+        }
+
+        // call user handler
+        $status = $this->move($options);
+        if ($status === true) {
+            $status = $options['new'] === false ? '204 No Content' :
+                '201 Created';
+        }
+
+        $this->setResponseStatus($status);
+    }
+
+    // }}}
+
+    // {{{ lock_request_helper
+
+    /**
+     * LOCK request helper - prepare data-structures from LOCK requests
+     *
+     * @param options
+     * @return void
+     */
+    function lock_request_helper(array &$options)
+    {
+        // a LOCK request with an If request header but without a body is used
+        // to refresh a lock.  Content-Lenght may be unset or zero.
+        if (empty($_SERVER['CONTENT_LENGTH']) && !empty($_SERVER['HTTP_IF'])) {
+
+            // FIXME: Refresh multiple locks?
+            $options['update'] = substr($_SERVER['HTTP_IF'], 2, -2);
+
+            return true;
+        }
+
+        // RFC2518 8.10.4: If no Depth header is submitted on a LOCK request
+        // then the request MUST act as if a "Depth: infinity" had been
+        // submitted.
+        $options['depth'] = 'infinity';
+        if (isset($_SERVER['HTTP_DEPTH'])) {
+
+            // RFC2518 8.10.4: The Depth header may be used with the LOCK
+            // method.  Values other than 0 or infinity MUST NOT be used with
+            // the Depth header on a LOCK method.  All resources that support
+            // the LOCK method MUST support the Depth header.
+            if ($_SERVER['HTTP_DEPTH'] != 0
+                    && $_SERVER['HTTP_DEPTH'] != 'infinity') {
+                $this->setResponseStatus('400 Bad Request');
+                return;
+            }
+
+            $options['depth'] = $_SERVER['HTTP_DEPTH'];
+        }
+
+        if (!empty($_SERVER['HTTP_TIMEOUT'])) {
+            $options['timeout'] = explode(',', $_SERVER['HTTP_TIMEOUT']);
+        }
+
+        $options['namespaces'] = array();
+
+        // load request body DOM document
+        $options['doc'] = new DOMDocument;
+        if (!$this->loadRequestBody($options['doc'])) {
+            $this->setResponseStatus('400 Bad Request');
+            return;
+        }
+
+        // analyze request body
+        $options['xpath'] = new DOMXPath($options['doc']);
+        $options['xpath']->registerNamespace('D', 'DAV:');
+
+        $options['scope'] = $options['xpath']->evaluate(
+            'local-name(/D:lockinfo/D:lockscope/*)');
+        $options['scope'] = $options['xpath']->evaluate(
+            'local-name(/D:lockinfo/D:locktype/*)');
+        $options['owner'] = $options['xpath']->evaluate(
+            'string(/D:lockinfo/D:owner/*)');
+
+        $options['token'] = $this->getLockToken();
+
+        return true;
+    }
+
+    // }}}
+
+    // {{{ lock_response_helper
+
+    /**
+     * LOCK response helper - format LOCK response
+     *
+     * @param options
+     * @param status
+     * @return void
+     */
+    function lock_response_helper(array $options, $status)
+    {
+        if (!empty($options['locks']) && is_array($options['locks'])) {
+            $this->setResponseStatus('409 Conflict');
+
+            $responses = array();
+            foreach ($options['locks'] as $lock) {
+                $response = array();
+
+                // copy to response
+                foreach (array('path', 'pathComponents', 'url') as $key) {
+                    if (!empty($lock[$key])) {
+                        $response[$key] = $lock[$key];
+                    }
+                }
+
+                $response['status'] = '423 Locked';
+
+                $responses[] = $response;
+            }
+
+            $this->multistatusResponseHelper($options, $responses);
+
+            return;
+        }
+
+        if (empty($status)) {
+            $status = '423 Locked';
+        }
+
+        // set response headers before we start printing
+        $this->setResponseStatus($status);
+
+        if ($status === true || $status{0} == 2) { // 2xx status is OK
+            $this->setResponseHeader('Content-Type: application/xml; charset="utf-8"');
+
+            // RFC2518 8.10.1: In order to indicate the lock token associated
+            // with a newly created lock, a Lock-Token response header MUST be
+            // included in the response for every successful LOCK request for a
+            // new lock.  Note that the Lock-Token header would not be returned
+            // in the response for a successful refresh LOCK request because a
+            // new lock was not created.
+            if (empty($options['update']) || !empty($options['token'])) {
+                $this->setResponseHeader('Lock-Token: <' . $options['token'] . '>');
+            }
+
+            $lock = array();
+            foreach (array('scope', 'type', 'depth', 'owner') as $key) {
+                $lock[$key] = $options[$key];
+            }
+
+            if (!empty($options['expires'])) {
+                $lock['expires'] = $options['expires'];
+            } else {
+                $lock['timeout'] = $options['timeout'];
+            }
+
+            if (!empty($options['update'])) {
+                $lock['token'] = $options['update'];
+            } else {
+                $lock['token'] = $options['token'];
+            }
+
+            echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
+            echo "<D:prop xmlns:D=\"DAV:\">\n";
+            echo "  <D:lockdiscovery>\n";
+            echo '    ' . $this->activelocksResponseHelper(array($lock))
+                . "\n";
+            echo "  </D:lockdiscovery>\n";
+            echo "</D:prop>\n";
+        }
+    }
+
+    // }}}
+
+    // {{{ lock_wrapper
+
+    /**
+     * LOCK method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function lock_wrapper()
+    {
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // perpare data-structure from LOCK request
+        if (!$this->lock_request_helper($options)) {
+            return;
+        }
+
+        // check resource is not locked
+        if (!empty($options['update'])
+                && !$this->check_locks_wrapper(
+                    $this->pathComponents, $options['scope'] == 'shared')) {
+            $this->setResponseStatus('423 Locked');
+            return;
+        }
+
+        $options['locks'] = $this->getDescendentsLocks($this->pathComponents);
+        if (empty($options['locks'])) {
+
+            // call user handler
+            $status = $this->lock($options);
+        }
+
+        // format LOCK response
+        $this->lock_response_helper($options, $status);
+    }
+
+    // }}}
+
+    // {{{ unlock_request_helper
+
+    /**
+     * UNLOCK request helper - prepare data-structures from UNLOCK requests
+     *
+     * @param options
+     * @return void
+     */
+    function unlock_request_helper(array &$options)
+    {
+        // RFC4918 8.4: Some of these new methods do not define bodies.
+        // Servers MUST examine all requests for a body, even when a body was
+        // not expected.  In cases where a request body is present but would be
+        // ignored by a server, the server MUST reject the request with 415
+        // (Unsupported Media Type).  This informs the client (which may have
+        // been attempting to use an extension) that the body could not be
+        // processed as the client intended.
+        if (!empty($_SERVER['CONTENT_LENGTH'])) {
+            $this->setResponseStatus('415 Unsupported Media Type');
+            return;
+        }
+
+        // RFC4918 9.11.1: 400 (Bad Request) - No lock token was provided.
+        if (empty($_SERVER['HTTP_LOCK_TOKEN'])) {
+            $this->setResponseStatus('400 Bad Request');
+            return;
+        }
+
+        // strip surrounding <>
+        $options['token'] = substr(trim($_SERVER['HTTP_LOCK_TOKEN']), 1, -1);
+
+        return true;
+    }
+
+    // }}}
+
+    // {{{ unlock_wrapper
+
+    /**
+     * UNLOCK method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function unlock_wrapper()
+    {
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // perpare data-structure from UNLOCK request
+        if (!$this->unlock_request_helper($options)) {
+            return;
+        }
+
+        // call user handler
+        $status = $this->unlock($options);
+
+        // RFC2518 8.11.1
+        if ($status === true) {
+            $status = '204 No Content';
+        }
+
+        $this->setResponseStatus($status);
+    }
+
+    // }}}
+
+    // {{{ report_request_helper
+
+    /**
+     * REPORT request helper - prepare data-structures from REPORT requests
+     *
+     * @param options
+     * @return void
+     */
+    function report_request_helper(array &$options)
+    {
+        // RFC3253 3.6: The request MAY include a Depth header.  If no Depth
+        // header is included, Depth:0 is assumed.
+        $options['depth'] = 'infinity';
+        if (!empty($_SERVER['HTTP_DEPTH'])) {
+            $options['depth'] = $_SERVER['HTTP_DEPTH'];
+        }
+
+        $options['namespaces'] = array();
+
+        // Microsoft needs this special namespace for date and time values
+        $options['namespaces'][HTTP_WEBDAV_SERVER_DATATYPE_NAMESPACE] = 'T';
+
+        // load request body DOM document
+        $options['doc'] = new DOMDocument;
+        if (!$this->loadRequestBody($options['doc'])) {
+            $this->setResponseStatus('400 Bad Request');
+            return;
+        }
+
+        // analyze request body
+        $options['xpath'] = new DOMXPath($options['doc']);
+        $options['xpath']->registerNamespace('D', 'DAV:');
+
+        $options['report'] = $options['xpath']->evaluate(
+            'local-name()');
+
+        return true;
+    }
+
+    // }}}
+
+    // {{{ report
+
+    /**
+     * REPORT method handler
+     *
+     * @param options
+     * @return void
+     */
+    function report(array &$options)
+    {
+        // detect requested method names
+        $methodComponents = preg_split('/-/', strtolower($options['report']), -1, PREG_SPLIT_NO_EMPTY);
+
+        $method = array_shift($methodComponents);
+        foreach ($methodComponents as $methodComponent) {
+            if ($methodComponent == 'report') {
+                continue;
+            }
+
+            $method .= ucfirst($methodComponent);
+        }
+        $method .= 'Report';
+
+        if (method_exists($this, $method)) {
+            return call_user_func(array($this, $method), $options);
+        }
+
+        $options['props'] = array();
+        foreach ($options['xpath']->query('/D:' . $options['report'] . '/D:prop/*') as $node) {
+            $options['props'][] = $this->mkprop(
+                $node->namespaceURI, $node->localName, null);
+
+            // namespace handling
+            if (empty($node->namespaceURI) || empty($node->prefix)) {
+                continue;
+            }
+
+            // http://bugs.php.net/bug.php?id=42082
+            //$options['namespaces'][$node->namespaceURI] = $node->prefix;
+        }
+
+        if (empty($options['props'])) {
+            $options['props'] = $options['xpath']->evaluate(
+                'local-name(/D:' . $options['report'] . '/*)');
+        }
+
+        // emulate REPORT using PROPFIND
+        return $this->propfind($options);
+    }
+
+    // }}}
+
+    // {{{ report_wrapper
+
+    /**
+     * REPORT method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function report_wrapper()
+    {
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // prepare data-structure from REPORT request
+        if (!$this->report_request_helper($options)) {
+            return;
+        }
+
+        // call user handler
+        $status = $this->report($options);
+
+        // format REPORT response
+        $this->propfind_response_helper($options, $status);
+    }
+
+    // }}}
+
+    // {{{ search_request_helper
+
+    /**
+     * SEARCH request helper - prepare data-structures from SEARCH requests
+     *
+     * @param options
+     * @return void
+     */
+    function search_request_helper(array &$options)
+    {
+        // RFC3253 3.6: The request MAY include a Depth header.  If no Depth
+        // header is included, Depth:0 is assumed.
+        $options['depth'] = 'infinity';
+        if (!empty($_SERVER['HTTP_DEPTH'])) {
+            $options['depth'] = $_SERVER['HTTP_DEPTH'];
+        }
+
+        $options['namespaces'] = array();
+
+        // Microsoft needs this special namespace for date and time values
+        $options['namespaces'][HTTP_WEBDAV_SERVER_DATATYPE_NAMESPACE] = 'T';
+
+        // load request body DOM document
+        $options['doc'] = new DOMDocument;
+        if (!$this->loadRequestBody($options['doc'])) {
+            $this->setResponseStatus('400 Bad Request');
+            return;
+        }
+
+        // analyze request body
+        $options['xpath'] = new DOMXPath($options['doc']);
+        $options['xpath']->registerNamespace('D', 'DAV:');
+
+        $options['props'] = array();
+        foreach ($options['xpath']->query(
+                '/D:searchrequest/D:basicsearch/D:select/D:prop/*') as $node) {
+            $options['props'][] = $this->mkprop(
+                $node->namespaceURI, $node->localName, null);
+
+            // namespace handling
+            if (empty($node->namespaceURI) || empty($node->prefix)) {
+                continue;
+            }
+
+            // http://bugs.php.net/bug.php?id=42082
+            //$options['namespaces'][$node->namespaceURI] = $node->prefix;
+        }
+
+        if (empty($options['props'])) {
+            $options['props'] = $options['xpath']->evaluate(
+                'local-name(/D:searchrequest/D:basicsearch/D:select/*)');
+        }
+
+        return true;
+    }
+
+    // {{{ search_wrapper
+
+    /**
+     * SEARCH method wrapper
+     *
+     * @param void
+     * @return void
+     */
+    function search_wrapper()
+    {
+        $options = array();
+        $options['pathComponents'] = $this->pathComponents;
+
+        // prepare data-structure from SEARCH request
+        if (!$this->search_request_helper($options)) {
+            return;
+        }
+
+        // call user handler
+        $status = $this->search($options);
+
+        // format SEARCH response
+        $this->propfind_response_helper($options, $status);
+    }
+
+    // }}}
+
+    function multistatusResponseHelper(array $options, array $responses)
+    {
+        // now we generate the response header...
+        $this->setResponseStatus('207 Multi-Status', false);
+        $this->setResponseHeader('Content-Type: application/xml; charset="utf-8"');
+
+        // ...and body
+        $options['namespaces']['DAV:'] = 'D';
+        asort($options['namespaces']);
+
+        $namespaces = '';
+        foreach ($options['namespaces'] as $namespace => $prefix) {
+            $namespaces .= ' xmlns:' . $prefix . '="' . $namespace . '"';
+        }
+
+        echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
+        echo "<D:multistatus$namespaces>\n";
+
+        foreach ($responses as $response) {
+            echo "  <D:response>\n";
+
+            // print href
+            if (empty($response['url'])) {
+                $response['url'] = $this->getUrl($response);
+            }
+            echo "    <D:href>$response[url]</D:href>\n";
+
+            // report all found properties and their values (if any)
+            // nothing to do if no properties were returend for a file
+            if (!empty($response['propstat'])
+                    && is_array($response['propstat'])) {
+
+                foreach ($response['propstat'] as $status => $props) {
+                    echo "    <D:propstat>\n";
+                    echo "      <D:prop>\n";
+
+                    foreach ($props as $prop) {
+                        if (!is_array($prop) || empty($prop['name'])) {
+                            continue;
+                        }
+
+                        // empty properties (cannot use empty for check as '0'
+                        // is a legal value here)
+                        if (empty($prop['value']) && (!isset($prop['value'])
+                                || $prop['value'] !== 0)) {
+                            if ($prop['namespace'] == 'DAV:') {
+                                echo "        <D:$prop[name]/>\n";
+                                continue;
+                            }
+
+                            if (!empty($prop['namespace'])) {
+                                echo '        <' . $options['namespaces'][$prop['namespace']] . ":$prop[name]/>\n";
+                                continue;
+                            }
+
+                            echo "        <$prop[name] xmlns=\"\"/>\n";
+                            continue;
+                        }
+
+                        // some WebDAV properties need special treatment
+                        if ($prop['namespace'] == 'DAV:') {
+
+                            switch ($prop['name']) {
+                            case 'creationdate':
+                                echo '        <D:creationdate ' . $options['namespaces'][HTTP_WEBDAV_SERVER_DATATYPE_NAMESPACE] . ':dt="dateTime.tz">' . gmdate('Y-m-d\TH:i:s\Z', $prop['value']) . "</D:creationdate>\n";
+                                break;
+
+                            case 'getlastmodified':
+                                echo '        <D:getlastmodified ' . $options['namespaces'][HTTP_WEBDAV_SERVER_DATATYPE_NAMESPACE] . ':dt="dateTime.rfc1123">' . gmdate('D, d M Y H:i:s', $prop['value']) . " UTC</D:getlastmodified>\n";
+                                break;
+
+                            case 'resourcetype':
+                                echo "        <D:resourcetype><D:$prop[value]/></D:resourcetype>\n";
+                                break;
+
+                            case 'supportedlock':
+
+                                if (!empty($prop['value']) && is_array($prop['value'])) {
+                                    $prop['value'] =
+                                        $this->lockentriesResponseHelper(
+                                            $prop['value']);
+                                }
+                                echo "        <D:supportedlock>\n";
+                                echo "          $prop[value]\n";
+                                echo "        </D:supportedlock>\n";
+                                break;
+
+                            case 'lockdiscovery':
+
+                                if (!empty($prop['value']) && is_array($prop['value'])) {
+                                    $prop['value'] =
+                                        $this->activelocksResponseHelper(
+                                            $prop['value']);
+                                }
+                                echo "        <D:lockdiscovery>\n";
+                                echo "          $prop[value]\n";
+                                echo "        </D:lockdiscovery>\n";
+                                break;
+
+case 'baseline-collection':
+echo "        <D:baseline-collection><D:href>$prop[value]</D:href></D:baseline-collection>\n";
+break;
+
+case 'checked-in':
+echo "        <D:checked-in><D:href>$prop[value]</D:href></D:checked-in>\n";
+break;
+
+case 'version-controlled-configuration':
+echo "        <D:version-controlled-configuration><D:href>$prop[value]</D:href></D:version-controlled-configuration>\n";
+break;
+
+                            default:
+                                echo "        <D:$prop[name]>" . $this->_prop_encode(htmlspecialchars($prop['value'])) . "</D:$prop[name]>\n";
+                            }
+
+                            continue;
+                        }
+
+                        if (!empty($prop['namespace'])) {
+                            echo '        <' . $options['namespaces'][$prop['namespace']] . ":$prop[name]>" . $this->_prop_encode(htmlspecialchars($prop['value'])) . '</' . $options['namespaces'][$prop['namespace']] . ":$prop[name]>\n";
+
+                            continue;
+                        }
+
+                        echo "        <$prop[name] xmlns=\"\">" . $this->_prop_encode(htmlspecialchars($prop['value'])) . "</$prop[name]>\n";
+                    }
+
+                    echo "      </D:prop>\n";
+                    echo "      <D:status>HTTP/1.1 $status</D:status>\n";
+                    echo "    </D:propstat>\n";
+                }
+            }
+
+            if (!empty($response['responsedescription'])) {
+                echo '    <D:responsedescription>' . $this->_prop_encode(htmlspecialchars($response['responsedescription'])) . "</D:responsedescription>\n";
+            }
+
+            if (!empty($response['status'])) {
+                echo "    <D:status>HTTP/1.1 $response[status]</D:status>\n";
+            }
+
+if (!empty($response['score'])) {
+    echo "    <D:score>$response[score]</D:score>\n";
+}
+
+            echo "  </D:response>\n";
+        }
+
+        echo "</D:multistatus>\n";
+    }
+
+    function activelocksResponseHelper(array $locks)
+    {
+        if (empty($locks) || !is_array($locks)) {
+            return '';
+        }
+
+        foreach ($locks as $key => $lock) {
+            if (empty($lock) || !is_array($lock)) {
+                continue;
+            }
+
+            // check for 'timeout' or 'expires'
+            $timeout = 'Infinite';
+            if (!empty($lock['expires'])) {
+                $timeout = 'Second-' . ($lock['expires'] - time());
+            } else if (!empty($lock['timeout'])) {
+
+                // more than a million is considered an absolute timestamp
+                // less is more likely a relative value
+                $timeout = "Second-$lock[timeout]";
+                if ($lock['timeout'] > 1000000) {
+                    $timeout = 'Second-' . ($lock['timeout'] - time());
+                }
+            }
+
+            // genreate response block
+            $locks[$key] = "<D:activelock>
+            <D:lockscope><D:$lock[scope]/></D:lockscope>
+            <D:locktype><D:$lock[type]/></D:locktype>
+            <D:depth>" . ($lock['depth'] == 'infinity' ? 'Infinity' : $lock['depth']) . "</D:depth>
+            <D:owner>$lock[owner]</D:owner>
+            <D:timeout>$timeout</D:timeout>
+            <D:locktoken><D:href>$lock[token]</D:href></D:locktoken>
+          </D:activelock>";
+        }
+
+        return implode('', $locks);
+    }
+
+    function lockentriesResponseHelper(array $locks)
+    {
+        if (empty($locks) || !is_array($locks)) {
+            return '';
+        }
+
+        foreach ($locks as $key => $lock) {
+            if (empty($lock) || !is_array($lock)) {
+                continue;
+            }
+
+            $locks[$key] = "<D:lockentry>
+            <D:lockscope><D:$lock[scope]/></D:lockscope>
+            <D:locktype><D:$lock[type]/></D:locktype>
+          </D:lockentry>";
+        }
+
+        return implode('', $locks);
+    }
+
+    function getUrl($urlComponents=null, $baseUrlComponents=null, array $options=array())
+    {
+        $urlComponents = $this->getUrlComponents($urlComponents, $baseUrlComponents, $options);
+
+        $url = '';
+        if (!empty($options['absoluteUrl'])
+                && !empty($urlComponents['scheme'])
+                && !empty($urlComponents['host'])) {
+            $url = $urlComponents['scheme'] . '://' . $urlComponents['host'];
+
+            // hide default port
+            if (!empty($urlComponents['port'])
+                    && $urlComponents['port'] != getservbyname($urlComponents['scheme'], 'tcp')) {
+                $url .= ':' . $urlComponents['port'];
+            }
+        }
+
+        $url .= '/' . implode('/', $urlComponents['pathComponents']);
+
+        return $url;
+    }
+
+    function getUrlComponents($urlComponents=null, $baseUrlComponents=null, array $options=array())
+    {
+        if (!isset($baseUrlComponents)) {
+            $baseUrlComponents = $this->baseUrlComponents;
+        }
+
+        foreach (array('scheme', 'host', 'port') as $component) {
+            if (empty($urlComponents[$component]) && !empty($baseUrlComponents[$component])) {
+                $urlComponents[$component] = $baseUrlComponents[$component];
+            }
+        }
+
+        if (empty($urlComponents['pathComponents'])) {
+            $urlComponents['pathComponents'] = array();
+        }
+
+        if (!empty($urlComponents['path'])) {
+            $urlComponents['pathComponents'] = preg_split('/\//', $urlComponents['path'], -1, PREG_SPLIT_NO_EMPTY);
+        }
+
+        if (!empty($baseUrlComponents['pathComponents'])) {
+            $urlComponents['pathComponents'] = array_merge($baseUrlComponents['pathComponents'], $urlComponents['pathComponents']);
+        }
+
+        return $urlComponents;
+    }
+
+    function isDescendentUrl($urlComponents, $baseUrlComponents=null)
+    {
+        if (!isset($baseUrlComponents)) {
+            $baseUrlComponents = $this->baseUrlComponents;
+        }
+
+        // set default port for descendent
+        if (!empty($urlComponents['scheme']) && empty($urlComponents['port'])) {
+            $urlComponents['port'] = getservbyname($urlComponents['scheme'], 'tcp');
+        }
+
+        // set default port for ancestor
+        if (!empty($baseUrlComponents['scheme']) && empty($baseUrlComponents['port'])) {
+            $baseUrlComponents['port'] = getservbyname($baseUrlComponents['scheme'], 'tcp');
+        }
+
+        // if descendent has scheme, host or port, check it matches ancestor
+        foreach (array('scheme', 'host', 'port') as $component) {
+            if (!empty($urlComponents[$component])
+                    && (empty($baseUrlComponents[$component])
+                        || $urlComponents[$component] != $baseUrlComponents[$component])) {
+                return false;
+            }
+        }
+
+        // if ancestor has path components and descendent has path components,
+        // check that descendent path components start with ancestor path
+        // components
+        if (!empty($baseUrlComponents['pathComponents'])
+                && (empty($urlComponents['pathComponents'])
+                    || array_slice($urlComponents['pathComponents'], 0, count($baseUrlComponents['pathComponents'])) != $baseUrlComponents['pathComponents'])) {
+            return false;
+        }
+
+        // if ancestor has query string and descendent has query string, check
+        // that descendent has all ancestor query components
+        if (!empty($baseUrlComponents['query'])) {
+            if (empty($urlComponents['query'])) {
+                return false;
+            }
+
+            $queryComponents = preg_split('/&/', $urlComponents['query'], -1, PREG_SPLIT_NO_EMPTY);
+            $baseQueryComponents = preg_split('/&/', $baseUrlComponents['query'], -1, PREG_SPLIT_NO_EMPTY);
+            foreach ($baseQueryComponents as $queryComponent) {
+                if (!in_array($queryComponent, $queryComponents)) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    function parseUrl($url)
+    {
+        $urlComponents = parse_url($url);
+
+        if (!empty($urlComponents['scheme']) && empty($urlComponents['port'])) {
+            $urlComponents['port'] = getservbyname($urlComponents['scheme'], 'tcp');
+        }
+
+        $urlComponents['pathComponents'] = array();
+        if (!empty($urlComponents['path'])) {
+            $urlComponents['pathComponents'] = preg_split('/\//', $urlComponents['path'], -1, PREG_SPLIT_NO_EMPTY);
+        }
+
+        return $urlComponents;
+    }
+
+    function getProp($reqprop, $file, $options)
+    {
+        // check if property exists in response
+        if (!empty($file['props'])) {
+            foreach ($file['props'] as $prop) {
+                if ($reqprop['name'] == $prop['name']
+                        && $reqprop['namespace'] == $prop['namespace']) {
+                    return $prop;
+                }
+            }
+        }
+
+        if ($reqprop['name'] == 'lockdiscovery'
+                && $reqprop['namespace'] == 'DAV:'
+                && method_exists($this, 'getLocks')) {
+            return $this->mkprop('DAV:', 'lockdiscovery',
+                $this->getLocks($file['pathComponents']));
+        }
+
+        // in case the requested property had a value, like calendar-data
+        unset($reqprop['value']);
+        $reqprop['status'] = '404 Not Found';
+
+        return $reqprop;
+    }
+
+    function getDescendentsLocks(array $pathComponents)
+    {
+        $options = array();
+        $options['pathComponents'] = $pathComponents;
+        $options['depth'] = 'infinity';
+        $options['props'] = array();
+        $options['props'][] = $this->mkprop('DAV:', 'lockdiscovery', null);
+
+        // call user handler
+        return $this->propfind($options);
+    }
+
+    // {{{ getAllowedMethods()
+
+    /**
+     * Get allowed methods
+     *
+     * @param void
+     * @return array of allowed methods
+     */
+    function getAllowedMethods()
+    {
+        $allowedMethods = array();
+
+        // all other methods need both a method_wrapper() and a method()
+        // implementation
+        // the base class defines only wrappers
+        foreach (get_class_methods($this) as $method) {
+
+            // strncmp breaks with negative len -
+            // http://bugs.php.net/bug.php?id=36944
+            //if (!strncmp('_wrapper', $method, -8)) {
+            if (strcmp(substr($method, -8), '_wrapper')) {
+                continue;
+            }
+
+            $method = strtolower(substr($method, 0, -8));
+            if (!method_exists($this, $method)) {
+                continue;
+            }
+
+            if (($method == 'lock' || $method == 'unlock')
+                    && !method_exists($this, 'getLocks')) {
+                continue;
+            }
+
+            $allowedMethods[] = strtoupper($method);
+
+            // emulate HEAD using GET
+            if ($method == 'get') {
+                $allowedMethods[] = 'HEAD';
+            }
+        }
+
+        return $allowedMethods;
+    }
+
+    // }}}
+
+    // {{{ mkprop
+
+    /**
+     * Helper for property element creation
+     *
+     * @param string XML namespace (optional)
+     * @param string property name
+     * @param string property value
+     * @return array property array
+     */
+    function mkprop()
+    {
+        $args = func_get_args();
+
+        $prop = array();
+        $prop['namespace'] = 'DAV:';
+        if (count($args) > 2) {
+            $prop['namespace'] = array_shift($args);
+        }
+
+        $prop['name'] = array_shift($args);
+        $prop['value'] = array_shift($args);
+        $prop['status'] = array_shift($args);
+
+        return $prop;
+    }
+
+    // }}}
+
+    // {{{ check_auth_wrapper
+
+    /**
+     * Check authentication if implemented
+     *
+     * @param void
+     * @return boolean true if authentication succeded or not necessary
+     */
+    function check_auth_wrapper()
+    {
+        if (method_exists($this, 'checkAuth')) {
+
+            // PEAR style method name
+            return $this->checkAuth(@$_SERVER['AUTH_TYPE'],
+                @$_SERVER['PHP_AUTH_USER'],
+                @$_SERVER['PHP_AUTH_PW']);
+        }
+
+        if (method_exists($this, 'check_auth')) {
+
+            // old (pre 1.0) method name
+            return $this->check_auth(@$_SERVER['AUTH_TYPE'],
+                @$_SERVER['PHP_AUTH_USER'],
+                @$_SERVER['PHP_AUTH_PW']);
+        }
+
+        // no method found -> no authentication required
+        return true;
+    }
+
+    // }}}
+
+    // {{{ UUID stuff
+
+    /**
+     * Get new Universally Unique Identifier
+     *
+     * @param void
+     * @return string new Universally Unique Identifier
+     */
+    function uuid_create()
+    {
+        // use uuid extension from PECL if available
+        if (function_exists('uuid_create')) {
+            return uuid_create();
+        }
+
+        // fallback
+        $uuid = md5(microtime() . getmypid()); // this should be random enough for now
+
+        // set variant and version fields for true random uuid
+        $uuid{12} = '4';
+        $n = 8 + (ord($uuid{16}) & 3);
+        $hex = '0123456789abcdef';
+        $uuid{16} = $hex{$n};
+
+        // return formated uuid
+        return substr($uuid,  0, 8)
+            . '-' . substr($uuid,  8, 4)
+            . '-' . substr($uuid, 12, 4)
+            . '-' . substr($uuid, 16, 4)
+            . '-' . substr($uuid, 20);
+    }
+
+    /**
+     * Get unique lock token
+     *
+     * @param void
+     * @return string unique lock token
+     */
+    function getLockToken()
+    {
+        // RFC4918 6.5: This specification encourages servers to create
+        // Universally Unique Identifiers (UUIDs) for lock tokens, and to use
+        // the URI form defined by "A Universally Unique Identifier (UUID) URN
+        // Namespace" ([RFC4122]).
+        return 'urn:uuid:' . $this->uuid_create();
+    }
+
+    // }}}
+
+    // {{{ If request header
+
+    /**
+     *
+     *
+     * @param string to parse
+     * @param int current parsing position
+     * @return array next token (type and value)
+     */
+    function _if_header_lexer($str, &$pos)
+    {
+        $len = strlen($str);
+
+        // skip whitespace
+        do {
+            if ($pos >= $len) {
+                return;
+            }
+        } while (ctype_space($chr = substr($str, $pos++, 1)));
+
+        // check character
+        switch ($chr) {
+
+            // State tokens enclosed in <...>
+            case '<':
+                $stateToken = substr($str, $pos, strpos($str, '>', $pos) - $pos);
+                $pos += strlen($stateToken) + 1;
+                return array('URI', $stateToken);
+
+            // Entity tags enclosed in [...]
+            case '[':
+                $type = 'ETAG_STRONG';
+                if (substr($str, $pos, 1) == 'W') {
+                    $type = 'ETAG_WEAK';
+                    $pos += 2;
+                }
+
+                $entityTag = substr($str, $pos, strpos($str, ']', $pos) - $pos);
+                $pos += strlen($entityTag) + 1;
+                return array($type, $entityTag);
+
+            // 'N' indicates negation
+            case 'N':
+                $pos += 2;
+                return array('NOT', 'Not');
+        }
+
+        // anything else is returned verbatim
+        return array('CHAR', $chr);
+    }
+
+    /**
+     * Parse If request header
+     *
+     * @param string to parse
+     * @return array If header components
+     */
+    function _if_header_parser($str)
+    {
+        $ifHeaderComponents = array();
+
+        // parsed URLs are path absolute
+        $baseUrlComponents = $this->getUrlComponents();
+        $baseUrlComponents['pathComponents'] = null;
+
+        $pos = 0;
+        $len = strlen($str);
+
+        while ($pos < $len) {
+
+            // get next token
+            $token = $this->_if_header_lexer($str, $pos);
+
+            $urlComponents = array();
+            if ($token[0] == 'URI') {
+                // normalize URL
+                $urlComponents = $this->parseUrl($token[1]);
+
+                // get next token
+                $token = $this->_if_header_lexer($str, $pos);
+            }
+            $url = $this->getUrl($urlComponents, $baseUrlComponents, array('absoluteUrl' => true));
+
+            // sanity check
+            if ($token[0] != 'CHAR' || $token[1] != '(') {
+                return;
+            }
+
+            $list = array();
+            while ($pos < $len) {
+
+                // get next token
+                $token = $this->_if_header_lexer($str, $pos);
+
+                if ($token[0] == 'CHAR' && $token[1] == ')') {
+                    break;
+                }
+
+                $not = '';
+                if ($token[0] == 'NOT') {
+                    $not = '!';
+
+                    // get next token
+                    $token = $this->_if_header_lexer($str, $pos);
+                }
+
+                switch ($token[0]) {
+                    case 'URI':
+                        $list[] = $not . '<' . $token[1] . '>';
+                        break;
+
+                    case 'ETAG_WEAK':
+                        $list[] = $not . '[W/"' . $token[1] . '"]';
+                        break;
+
+                    case 'ETAG_STRONG':
+                        $list[] = $not . '["' . $token[1] . '"]';
+                        break;
+                }
+            }
+
+            // RFC4918 10.4.3: A Condition that consists of a single entity-tag
+            // or state-token evaluates to true if the resource matches the
+            // described state (where the individual matching functions are
+            // defined below in Section 10.4.4).  Prefixing it with "Not"
+            // reverses the result of the evaluation (thus, the "Not" applies
+            // only to the subsequent entity-tag or state-token).
+            //
+            // Each List production describes a series of conditions.  The
+            // whole list evaluates to true if and only if each condition
+            // evaluates to true (that is, the list represents a logical
+            // conjunction of Conditions).
+            //
+            // Each No-tag-list and Tagged-list production may contain one or
+            // more Lists.  They evaluate to true if and only if any of the
+            // contained lists evaluates to true (that is, if there's more than
+            // one List, that List sequence represents a logical disjunction of
+            // the Lists).
+            //
+            // Finally, the whole If header evaluates to true if and only if at
+            // least one of the No-tag-list or Tagged-list productions
+            // evaluates to true.  If the header evaluates to false, the server
+            // MUST reject the request with a 412 (Precondition Failed) status.
+            // Otherwise, execution of the request can proceed as if the header
+            // wasn't present.
+            $ifHeaderComponents[$url][] = $list;
+        }
+
+        return $ifHeaderComponents;
+    }
+
+    /**
+     * Check If request header
+     *
+     * @param array If request header components
+     * @return boolean
+     */
+    function check_if_helper(array $ifHeaderComponents)
+    {
+        // RFC4918 10.4.1: The first purpose is to make a request conditional
+        // by supplying a series of state lists with conditions that match
+        // tokens and ETags to a specific resource.  If this header is
+        // evaluated and all state lists fail, then the request MUST fail with
+        // a 412 (Precondition Failed) status.  On the other hand, the request
+        // can succeed only if one of the described state lists succeeds.  The
+        // success criteria for state lists and matching functions are defined
+        // in Sections 10.4.3 and 10.4.4.
+        if (empty($ifHeaderComponents)) {
+            return true;
+        }
+
+        // any match is ok
+        foreach ($ifHeaderComponents as $url => $lists) {
+
+            // all must match
+            foreach ($list as $condition) {
+
+                // lock tokens may be free form (RFC2518 6.3)
+                // but if opaquelocktokens are used (RFC2518 6.4)
+                // we have to check the format (litmus tests this)
+                if (!strncmp($condition, '<opaquelocktoken:', strlen('<opaquelocktoken'))) {
+                    if (!preg_match('/^<opaquelocktoken:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}>$/', $condition)) {
+                        return;
+                    }
+                }
+
+                if (!$this->_check_uri_condition($url, $condition)) {
+                    continue 2;
+                }
+            }
+
+            return true;
+        }
+    }
+
+    /**
+     * Check a single URL condition from If request header
+     *
+     * @abstract
+     * @param string URL to check
+     * @param string condition to check for this URL
+     * @return boolean
+     */
+    function _check_uri_condition($url, $condition)
+    {
+        // not really implemented here, implementations must override
+        return true;
+    }
+
+    /**
+     * For each lock, check that the lock token was submitted in the If request
+     * header.  If requesting a shared lock, check only exclusive locks.
+     *
+     * @param array of locks
+     * @param array if request header components
+     * @param boolean check only exclusive locks
+     * @return boolean true if the request is allowed
+     */
+    function check_locks_helper(array $locks, array $ifHeaderComponents, $shared=false)
+    {
+        foreach ($locks as $lock) {
+            if ($shared && ($lock['scope'] == 'shared')) {
+                continue;
+            }
+
+            // RFC4918 10.4.4: A lock state token is considered to match if the
+            // resource is anywhere in the scope of the lock.
+            if ($lock['depth'] === 0) {
+                $lists = $ifHeaderComponents[$this->getUrl($lock, null, array('absoluteUrl' => true))];
+                foreach ($lists as $list) {
+                    if (in_array('<' . $lock['token'] . '>', $list)) {
+                        continue 2;
+                    }
+                }
+
+                return false;
+            }
+
+            foreach ($ifHeaderComponents as $url => $lists) {
+                if (!$this->isDescendentUrl($this->parseUrl($url), $this->getUrlComponents($lock))) {
+                    continue;
+                }
+
+                foreach ($lists as $list) {
+                    if (in_array('<' . $lock['token'] . '>', $list)) {
+                        continue 3;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * @param array path components to check
+     * @param boolean check only exclusive locks
+     */
+    function check_locks_wrapper(array $pathComponents, $shared=false)
+    {
+        if (!method_exists($this, 'getLocks')) {
+            return true;
+        }
+
+        return $this->check_locks_helper($this->getLocks($pathComponents),
+            $this->ifHeaderComponents, $shared);
+    }
+
+    // }}}
+
+    /**
+     * Open request body input stream
+     *
+     * Because it's not possible to write to php://input (unlike the potential
+     * to set request variables) and not possible until PHP 5.1 to register
+     * alternative stream wrappers with php:// URLs, this function enables
+     * sub-classes to override the request body.  Gallery uses this for unit
+     * testing.  This function also collects all instances of opening the
+     * request body in one place.
+     *
+     * @return resource request body input stream
+     */
+    function openRequestBody()
+    {
+        return fopen('php://input', 'rb');
+    }
+
+    /**
+     * Load request body DOM document
+     *
+     * @return object DOMDocument request body DOM document
+     */
+    function loadRequestBody(DOMDocument $doc)
+    {
+        // libxml2 correctly reports a notice on DAV: namespace:
+        // http://bugzilla.gnome.org/show_bug.cgi?id=457559
+        $errorReporting = ini_get('error_reporting');
+        ini_set('error_reporting', $errorReporting & ~E_NOTICE);
+
+        if (!$doc->load('php://input')) {
+            ini_set('error_reporting', $errorReporting);
+            return;
+        }
+
+        ini_set('error_reporting', $errorReporting);
+        return $doc;
+    }
+
+    /**
+     * Set HTTP response header
+     *
+     * This function enables sub-classes to override header-setting.  Gallery
+     * uses this to avoid replacing headers elsewhere in the application, and
+     * for testability.
+     *
+     * @param string status code and message
+     * @return void
+     */
+    function setResponseHeader($header, $replace=true)
+    {
+        $key = 'status';
+        if (strncasecmp($header, 'HTTP/', 5) !== 0) {
+            $key = strtolower(substr($header, 0, strpos($header, ':')));
+        }
+
+        if ($replace || empty($this->responseHeaders[$key])) {
+            header($header);
+            $this->responseHeaders[$key] = $header;
+        }
+    }
+
+    /**
+     * Set HTTP response status and mirror it in a private header
+     *
+     * @param string status code and message
+     * @return void
+     */
+    function setResponseStatus($status, $replace=true)
+    {
+        // failure
+        if (empty($status)) {
+            $status = '404 Not Found';
+        }
+
+        // success
+        if ($status === true) {
+            $status = '200 OK';
+        }
+
+        // generate HTTP status response
+        $this->setResponseHeader('HTTP/1.1 ' . $status, $replace);
+        $this->setResponseHeader('X-WebDAV-Status: ' . $status, $replace);
+    }
+
+    /**
+     * Private minimalistic version of PHP urlencode
+     *
+     * Only blanks and XML special chars must be encoded here.  Full urlencode
+     * encoding confuses some clients.
+     *
+     * @param string URL to encode
+     * @return string encoded URL
+     */
+    function _urlencode($url)
+    {
+        return strtr($url, array(' ' => '%20',
+                '&' => '%26',
+                '<' => '%3C',
+                '>' => '%3E'));
+    }
+
+    /**
+     * Private version of PHP urldecode
+     *
+     * Not really needed but added for completenes.
+     *
+     * @param string URL to decode
+     * @return string decoded URL
+     */
+    function _urldecode($path)
+    {
+        return urldecode($path);
+    }
+
+    /**
+     * UTF-8 encode property values if not already done so
+     *
+     * @param string text to encode
+     * @return string UTF-8 encoded text
+     */
+    function _prop_encode($text)
+    {
+        switch (strtolower($this->_prop_encoding)) {
+        case 'utf-8':
+            return $text;
+        case 'iso-8859-1':
+        case 'iso-8859-15':
+        case 'latin-1':
+        default:
+            return utf8_encode($text);
+        }
+    }
+}
+
+// Local variables:
+// tab-width: 4
+// c-basic-offset: 4
+// End:

Property changes on: trunk/extensions/WebDAV/lib/HTTP/WebDAV/Server.php
___________________________________________________________________
Added: svn:keywords
   + Author Id Revision
Added: svn:eol-style
   + native

Index: trunk/extensions/WebDAV/WebDav.php
===================================================================
--- trunk/extensions/WebDAV/WebDav.php	(revision 0)
+++ trunk/extensions/WebDAV/WebDav.php	(revision 37149)
@@ -0,0 +1,779 @@
+<?php
+
+define( 'MW_SEARCH_LIMIT', 50 );
+
+require_once( './lib/HTTP/WebDAV/Server.php' );
+
+class WebDavServer extends HTTP_WebDAV_Server {
+
+	function init() {
+		parent::init();
+
+		# Prepend script path component to path components
+		array_unshift( $this->pathComponents, array_pop( $this->baseUrlComponents['pathComponents'] ) );
+	}
+
+	function getAllowedMethods() {
+		return array( 'OPTIONS', 'PROPFIND', 'GET', 'HEAD', 'DELETE', 'PUT', 'REPORT', 'SEARCH' );
+	}
+
+	function options( &$serverOptions ) {
+		parent::options( &$serverOptions );
+
+		if ( $serverOptions['xpath']->evaluate( 'boolean(/D:options/D:activity-collection-set)' ) ) {
+			$this->setResponseHeader( 'Content-Type: text/xml; charset="utf-8"' );
+
+			echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
+			echo "<D:options-response xmlns:D=\"DAV:\">\n";
+			echo '  <D:activity-collection-set><D:href>' . $this->getUrl( array( 'path' => 'deltav.php/act' ) ) . "</D:href></D:activity-collection-set>\n";
+			echo "</D:options-response>\n";
+		}
+
+		return true;
+	}
+
+	function propfind( &$serverOptions ) {
+		if ( empty( $this->pathComponents ) ) {
+			return;
+		}
+		$pathComponent = array_shift( $this->pathComponents );
+		if ( $pathComponent != 'deltav.php' && $pathComponent != 'webdav.php' ) {
+			return;
+		}
+
+		if ( $pathComponent == 'deltav.php' ) {
+			if ( empty( $this->pathComponents ) ) {
+				return;
+			}
+			$pathComponent = array_shift( $this->pathComponents );
+			if ( $pathComponent != 'bc' && $pathComponent != 'bln' && $pathComponent != 'vcc' && $pathComponent != 'ver' ) {
+				return;
+			}
+
+			if ( $pathComponent == 'vcc' ) {
+				if ( empty( $this->pathComponents ) ) {
+					return;
+				}
+				$pathComponent = array_shift( $this->pathComponents );
+				if ( $pathComponent != 'default' ) {
+					return;
+				}
+				if ( !empty( $this->pathComponents ) ) {
+					return;
+				}
+
+				if ( isset( $serverOptions['label'] ) ) {
+					return $this->propfindBln( $serverOptions, $serverOptions['label'] );
+				}
+
+				return $this->propfindVcc( $serverOptions );
+			}
+
+			if ( empty( $this->pathComponents ) ) {
+				return;
+			}
+			$revisionId = array_shift( $this->pathComponents );
+
+			if ( $pathComponent == 'bc' ) {
+				return $this->propfindBc( $serverOptions, $revisionId, $this->pathComponents );
+			}
+
+			if ( !empty( $this->pathComponents ) ) {
+				return;
+			}
+
+			if ( $pathComponent == 'bln' ) {
+				return $this->propfindBln( $serverOptions, $revisionId );
+			}
+
+			return $this->propfindVer( $serverOptions, $revisionId );
+		}
+
+		if ( isset( $serverOptions['label'] ) ) {
+			# TODO: Verify revision belongs to this resource, or should we care?
+			return $this->propfindVer( $serverOptions, $serverOptions['label'] );
+		}
+
+		$serverOptions['namespaces']['http://subversion.tigris.org/xmlns/dav/'] = 'V';
+
+		$status = array();
+
+		# Handle root collection
+		if ( empty( $this->pathComponents ) ) {
+			global $wgSitename;
+
+			$response = array();
+			$response['path'] = 'webdav.php/';
+
+			# TODO: Use Main_Page revision?
+			$response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/ver' ) ) );
+
+			$response['props'][] = WebDavServer::mkprop( 'displayname', $wgSitename );
+			$response['props'][] = WebDavServer::mkprop( 'getcontentlength', 0 );
+			$response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'httpd/unix-directory' );
+			$response['props'][] = WebDavServer::mkprop( 'resourcetype', 'collection' );
+			$response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) );
+
+			$response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', null );
+
+			# TODO: Don't hardcode this
+			$response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'repository-uuid', '87a9137c-6795-46f8-83b9-2ee953e66e08' );
+
+			$status[] = $response;
+
+			# Don't descend if depth is zero
+			if ( empty( $serverOptions['depth'] ) ) {
+				return $status;
+			}
+		}
+
+		# TODO: Use $wgMemc
+		$dbr =& wfGetDB( DB_SLAVE );
+
+		# TODO: Think harder about pages' hierarchical structure.  The trouble is most filesystems don't support directories which themselves have file content, which is a problem for making pages descendents of other pages.
+		$where = array();
+		if ( !empty( $this->pathComponents ) ) {
+			$where[] = 'page_title = ' . $dbr->addQuotes( implode( '/', $this->pathComponents ) );
+		}
+
+		$whereClause = null;
+		if ( !empty( $where ) ) {
+			$whereClause = ' WHERE ' . implode( ' AND ', $where );
+		}
+		$results = $dbr->query( '
+			SELECT page_title, page_latest, page_len, page_touched
+			FROM page' . $whereClause );
+
+		while ( ( $result = $dbr->fetchRow( $results ) ) !== false ) {
+			# TODO: Should maybe not be using page_title as URL component, but it's currently what we do elsewhere
+			$title = Title::newFromUrl( $result[0] );
+
+			$response = array();
+			$response['path'] = 'webdav.php/' . $result[0];
+			$response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/ver/' . $result[1] ) ) );
+			$response['props'][] = WebDavServer::mkprop( 'displayname', $title->getText() );
+			$response['props'][] = WebDavServer::mkprop( 'getcontentlength', $result[2] );
+			$response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'text/x-wiki' );
+			$response['props'][] = WebDavServer::mkprop( 'getlastmodified', wfTimestamp( TS_UNIX, $result[3] ) );
+			$response['props'][] = WebDavServer::mkprop( 'resourcetype', null );
+			$response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) );
+
+			$response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', $result[0] );
+
+			$status[] = $response;
+		}
+
+		return $status;
+	}
+
+	function propfindBc( &$serverOptions, $revisionId, $pathComponents ) {
+		$serverOptions['namespaces']['http://subversion.tigris.org/xmlns/dav/'] = 'V';
+
+		$status = array();
+
+		# TODO: Verify $revisionId is valid
+		# Handle root collection
+		if ( empty( $pathComponents ) ) {
+			global $wgSitename;
+
+			$response = array();
+			$response['path'] = 'deltav.php/bc/' . $revisionId . '/';
+			# TODO: Use Main_Page revision?
+			$response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/ver' ) ) );
+
+			$response['props'][] = WebDavServer::mkprop( 'displayname', $wgSitename );
+			$response['props'][] = WebDavServer::mkprop( 'getcontentlength', 0 );
+			$response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'httpd/unix-directory' );
+			$response['props'][] = WebDavServer::mkprop( 'resourcetype', 'collection' );
+			$response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) );
+			$response['props'][] = WebDavServer::mkprop( 'version-name', null );
+
+			$response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', null );
+
+			# TODO: Don't hardcode this
+			$response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'repository-uuid', '87a9137c-6795-46f8-83b9-2ee953e66e08' );
+
+			$status[] = $response;
+
+			# Don't descend if depth is zero
+			if ( empty( $serverOptions['depth'] ) ) {
+				return $status;
+			}
+		}
+
+		# TODO: Use $wgMemc
+		$dbr =& wfGetDB( DB_SLAVE );
+
+		# TODO: Think harder about pages' hierarchical structure.  The trouble is most filesystems don't support directories which themselves have file content, which is a problem for making pages descendents of other pages.
+		$where = array();
+		$where[] = 'rev_page = page_id';
+		$where[] = 'rev_id <= ' . $dbr->addQuotes( $revisionId );
+		if ( !empty( $pathComponents ) ) {
+			$where[] = 'page_title = ' . $dbr->addQuotes( implode( '/', $pathComponents ) );
+		}
+
+		$whereClause = null;
+		if ( !empty( $where ) ) {
+			$whereClause = ' WHERE ' . implode( ' AND ', $where );
+		}
+		$results = $dbr->query( '
+			SELECT page_title, MAX(rev_id), page_len, page_touched
+			FROM page, revision' . $whereClause . '
+			GROUP BY page_id' );
+
+		while ( ( $result = $dbr->fetchRow( $results ) ) !== false ) {
+			# TODO: Should maybe not be using page_title as URL component, but it's currently what we do elsewhere
+			$title = Title::newFromUrl( $result[0] );
+
+			$response = array();
+			$response['path'] = 'deltav.php/bc/' . $revisionId . '/' . $result[0];
+			$response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/ver/' . $result[1] ) ) );
+			$response['props'][] = WebDavServer::mkprop( 'displayname', $title->getText() );
+			$response['props'][] = WebDavServer::mkprop( 'getcontentlength', $result[2] );
+			$response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'text/x-wiki' );
+			$response['props'][] = WebDavServer::mkprop( 'getlastmodified', wfTimestamp( TS_UNIX, $result[3] ) );
+			$response['props'][] = WebDavServer::mkprop( 'resourcetype', null );
+			$response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) );
+			$response['props'][] = WebDavServer::mkprop( 'version-name', $result[1] );
+
+			$response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', $result[0] );
+
+			# TODO: Don't hardcode this
+			$response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'repository-uuid', '87a9137c-6795-46f8-83b9-2ee953e66e08' );
+
+			$status[] = $response;
+		}
+
+		return $status;
+	}
+
+	function propfindBln( &$serverOptions, $revisionId ) {
+		$response = array();
+		$response['path'] = 'bln/' . $revisionId;
+		$response['props'][] = WebDavServer::mkprop( 'baseline-collection', $this->getUrl( array( 'path' => 'deltav.php/bc/' . $revisionId . '/' ) ) );
+		$response['props'][] = WebDavServer::mkprop( 'version-name', $revisionId );
+
+		return array( $response );
+	}
+
+	function propfindVcc( &$serverOptions ) {
+		# TODO: Use $wgMemc
+		$dbr =& wfGetDB( DB_SLAVE );
+
+		$results = $dbr->query( '
+			SELECT MAX(rev_id)
+			FROM revision' );
+
+		if ( ( $result = $dbr->fetchRow( $results ) ) === false ) {
+			return;
+		}
+
+		$response = array();
+		$response['path'] = 'deltav.php/vcc/default';
+		$response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/bln/' . $result[0] ) ) );
+
+		return array( $response );
+	}
+
+	function propfindVer( &$serverOptions, $revisionId ) {
+		$response = array();
+		$response['path'] = 'deltav.php/ver/' . $revisionId;
+		$response['props'][] = WebDavServer::mkprop( 'resourcetype', null );
+		#$response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) );
+
+		#$response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', null );
+
+		return array( $response );
+	}
+
+	function getRawPage() {
+		if ( empty( $this->pathComponents ) ) {
+			return;
+		}
+		$pathComponent = array_shift( $this->pathComponents );
+		if ( $pathComponent != 'deltav.php' && $pathComponent != 'webdav.php' ) {
+			return;
+		}
+
+		if ( $pathComponent == 'deltav.php' ) {
+			if ( empty( $this->pathComponents ) ) {
+				return;
+			}
+			$pathComponent = array_shift( $this->pathComponents );
+			if ( $pathComponent != 'bc' && $pathComponent != 'ver' ) {
+				return;
+			}
+
+			if ( empty( $this->pathComponents ) ) {
+				return;
+			}
+			$revisionId = array_shift( $this->pathComponents );
+
+			if ( $pathComponent == 'bc' ) {
+				$title = Title::newFromUrl( implode( '/', $this->pathComponents ) );
+				if (!isset( $title )) {
+					$title = Title::newMainPage();
+				}
+			} else {
+				if ( !empty( $this->pathComponents ) ) {
+					return;
+				}
+
+				$revision = Revision::newFromId( $revisionId );
+				$title = $revision->getTitle();
+			}
+		} else {
+			if ( isset( $serverOptions['label'] ) ) {
+				# TODO: Verify revision belongs to this resource, or should we care?
+				$revisionId = $serverOptions['label'];
+			}
+
+			$title = Title::newFromUrl( implode( '/', $this->pathComponents ) );
+			if (!isset( $title )) {
+				$title = Title::newMainPage();
+			}
+		}
+
+		$mediaWiki = new MediaWiki();
+		$article = $mediaWiki->articleFromTitle( $title );
+
+		$rawPage = new RawPage( $article );
+
+		if ( isset( $revisionId ) ) {
+			$rawPage->mOldId = $revisionId;
+		}
+		
+		return $rawPage;
+	}
+
+	# RawPage::view handles Content-Type, Cache-Control, etc. and we don't want get_response_helper to overwrite, but MediaWiki doesn't let us get response headers.  It could work if we kept setResponseHeader updated with headers_list on PHP 5.
+	function get_wrapper() {
+		$rawPage = $this->getRawPage();
+		if ( !isset( $rawPage ) ) {
+			$this->setResponseStatus( false, false );
+			return;
+		}
+
+		$rawPage->view();
+	}
+
+	function head_wrapper() {
+		$rawPage = $this->getRawPage();
+		if ( !isset( $rawPage ) ) {
+			$this->setResponseStatus( false, false );
+			return;
+		}
+
+		# TODO: Does MediaWiki handle HEAD requests specially?
+		ob_start();
+		$rawPage->view();
+		ob_end_clean();
+	}
+
+	function delete( $serverOptions ) {
+		global $wgUser;
+
+		if ( !$wgUser->isAllowed( 'delete' ) ) {
+			$this->setResponseStatus( '401 Unauthorized' );
+			return;
+		}
+
+		if ( wfReadOnly() ) {
+			$this->setResponseStatus( '403 Forbidden' );
+			return;
+		}
+
+		if ( empty( $this->pathComponents ) ) {
+			return;
+		}
+		$pathComponent = array_shift( $this->pathComponents );
+		if ( $pathComponent != 'webdav.php' ) {
+			return;
+		}
+
+		$title = Title::newFromUrl( implode( '/', $this->pathComponents ) );
+		if (!isset( $title )) {
+			$title = Title::newMainPage();
+		}
+
+		$mediaWiki = new MediaWiki();
+		$article = $mediaWiki->articleFromTitle( $title );
+
+		# Must check if article exists to avoid 500 Internal Server Error
+
+		# No way to get reason for deletion.  Can't use null: MySQL returned error "<tt>1048: Column 'log_comment' cannot be null (localhost)</tt>".
+		$article->doDelete( null );
+	}
+
+	function put( $serverOptions ) {
+		global $wgUser;
+
+		if ( !$wgUser->isAllowed( 'edit' ) ) {
+			$this->setResponseStatus( '401 Unauthorized' );
+			return;
+		}
+
+		if ( wfReadOnly() ) {
+			$this->setResponseStatus( '403 Forbidden' );
+			return;
+		}
+
+		if ( empty( $this->pathComponents ) ) {
+			return;
+		}
+		$pathComponent = array_shift( $this->pathComponents );
+		if ( $pathComponent != 'webdav.php' ) {
+			return;
+		}
+
+		$title = Title::newFromUrl( implode( '/', $this->pathComponents ) );
+		if (!isset( $title )) {
+			$title = Title::newMainPage();
+		}
+
+		if ( !$title->exists() && !$title->userCan( 'create' ) ) {
+			$this->setResponseStatus( '401 Unauthorized' );
+			return;
+		}
+
+		$mediaWiki = new MediaWiki();
+		$article = $mediaWiki->articleFromTitle( $title );
+
+		if ( ( $handle = $this->openRequestBody() ) === false ) {
+			return;
+		}
+
+		$text = null;
+		while ( !feof( $handle ) ) {
+			if ( ( $buffer = fread( $handle, 4096 ) ) === false ) {
+				return;
+			}
+
+			$text .= $buffer;
+		}
+
+		$article->doEdit( $text, null );
+
+		return true;
+	}
+
+	function versionTreeReport( &$serverOptions ) {
+		if ( empty( $this->pathComponents ) ) {
+			return;
+		}
+		$pathComponent = array_shift( $this->pathComponents );
+		if ( $pathComponent != 'deltav.php' && $pathComponent != 'webdav.php' ) {
+			return;
+		}
+
+		$serverOptions['props'] = array();
+		foreach ( $serverOptions['xpath']->query( '/D:version-tree/D:prop/*' ) as $node) {
+			$serverOptions['props'][] = $this->mkprop( $node->namespaceURI, $node->localName, null );
+
+			# Namespace handling
+			if ( empty( $node->namespaceURI ) || empty( $node->prefix ) ) {
+			    continue;
+			}
+
+			# http://bugs.php.net/bug.php?id=42082
+			#$serverOptions['namespaces'][$node->namespaceURI] = $node->prefix;
+		}
+
+		if (empty($serverOptions['props'])) {
+			$serverOptions['props'] = $serverOptions['xpath']->evaluate( 'local-name(/D:version-tree/*)' );
+		}
+
+		$status = array();
+
+		# Handle root collection
+		if ( empty( $this->pathComponents ) ) {
+			$response = array();
+			$response['props'][] = WebDavServer::mkprop( 'getcontentlength', 0 );
+			$response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'httpd/unix-directory' );
+			$response['props'][] = WebDavServer::mkprop( 'resourcetype', 'collection' );
+
+			$status[] = $response;
+
+			# Don't descend if depth is zero
+			if ( empty( $serverOptions['depth'] ) ) {
+				return $status;
+			}
+		}
+
+		# TODO: Use $wgMemc
+		$dbr =& wfGetDB( DB_SLAVE );
+
+		# TODO: Think harder about pages' hierarchical structure.  The trouble is most filesystems don't support directories which themselves have file content, which is a problem for making pages descendents of other pages.
+		$where = array();
+		$where[] = 'rev_page = page_id';
+		if ( !empty( $this->pathComponents ) ) {
+			$where[] = 'page_title = ' . $dbr->addQuotes( implode( '/', $this->pathComponents ) );
+		}
+
+		$whereClause = null;
+		if ( !empty( $where ) ) {
+			$whereClause = ' WHERE ' . implode( ' AND ', $where );
+		}
+		$results = $dbr->query( '
+			SELECT page_title, rev_id, rev_comment, rev_user_text, rev_len, rev_timestamp, rev_parent_id
+			FROM page, revision' . $whereClause );
+
+		$successors = array();
+		while ( ( $result = $dbr->fetchRow( $results ) ) !== false ) {
+			$response = array();
+			$response['path'] = 'deltav.php/ver/' . $result[1];
+			$response['props'][] = WebDavServer::mkprop( 'comment', $result[2] );
+			$response['props'][] = WebDavServer::mkprop( 'creator-displayname', $result[3] );
+			$response['props'][] = WebDavServer::mkprop( 'getcontentlength', $result[4] );
+			$response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'text/x-wiki' );
+			$response['props'][] = WebDavServer::mkprop( 'getlastmodified', wfTimestamp( TS_UNIX, $result[5] ) );
+			$response['props'][] = WebDavServer::mkprop( 'predecessor-set', array( $result[6] ) );
+			$response['props'][] = WebDavServer::mkprop( 'resourcetype', null );
+			$response['props'][] = WebDavServer::mkprop( 'successor-set', array() );
+			$response['props'][] = WebDavServer::mkprop( 'version-name', $result[1] );
+
+			$status[$result[1]] = $response;
+
+			# Build successor-set
+			$successors[$result[6]][] = $result[1];
+		}
+
+		return $status;
+	}
+
+	function updateReport( &$serverOptions ) {
+		if ( empty( $this->pathComponents ) ) {
+			return;
+		}
+		$pathComponent = array_shift( $this->pathComponents );
+		if ( $pathComponent != 'deltav.php' ) {
+			return;
+		}
+
+		if ( empty( $this->pathComponents ) ) {
+			return;
+		}
+		$pathComponent = array_shift( $this->pathComponents );
+		if ( $pathComponent != 'vcc' ) {
+			return;
+		}
+
+		if ( empty( $this->pathComponents ) ) {
+			return;
+		}
+		$pathComponent = array_shift( $this->pathComponents );
+		if ( $pathComponent != 'default' ) {
+			return;
+		}
+		if ( !empty( $this->pathComponents ) ) {
+			return;
+		}
+
+		# TODO: Can we ignore this?
+		if ( isset( $serverOptions['label'] ) ) {
+			return;
+		}
+
+		$serverOptions['xpath']->registerNamespace( 'S', 'svn:' );
+
+		# TODO: Error checking?
+		$targetRevision = $serverOptions['xpath']->evaluate( 'string(/S:update-report/S:target-revision)' );
+
+		# src-path is a misnomer, it's a URL
+		$srcPath = $serverOptions['xpath']->evaluate( 'string(/S:update-report/S:src-path)' );
+		$srcComponents = $this->parseUrl( $srcPath );
+		$srcComponents['pathComponents'] = array_slice( $srcComponents['pathComponents'], count( $this->baseUrlComponents['pathComponents'] ) + 1 );
+
+		# TODO: Use $wgMemc
+		$dbr =& wfGetDB( DB_SLAVE );
+
+		$entryConditions = array();
+		foreach ( $serverOptions['xpath']->query( '/S:update-report/S:entry' ) as $node ) {
+			$entryConditions[$node->textContent] = null;
+			if ( !$node->hasAttribute( 'start-empty' ) ) {
+
+				# TODO: Error checking?
+				$entryConditions[$node->textContent] = 'new.rev_id > ' . $dbr->addQuotes( $node->getAttribute( 'rev' ) );
+			}
+		}
+
+		function cmp( $a, $b ) {
+			return strlen( $a ) - strlen( $b );
+		}
+		uksort( $entryConditions, 'cmp' );
+
+		$entryCondition = null;
+		foreach ( $entryConditions as $path => $revisionCondition ) {
+			if ( !empty( $path ) ) {
+				$pathCondition = '(page_title = ' . $dbr->addQuotes( $path ) . ' OR page_title LIKE \'' . $dbr->escapeLike( $path ) . '/%\')';
+
+				if ( !empty( $revisionCondition ) ) {
+					$revisionCondition = ' AND ' . $revisionCondition;
+				}
+				$revisionCondition = $pathCondition . $revisionCondition;
+
+				if ( !empty( $entryCondition ) ) {
+					$entryCondition = ' AND ' . $entryCondition;
+				}
+				$entryCondition = 'NOT ' . $pathCondition . $entryCondition;
+			}
+
+			if ( !empty( $revisionCondition ) ) {
+				if ( !empty( $entryCondition ) ) {
+					$revisionCondition = '(' . $revisionCondition;
+					$entryCondition = ' OR ' . $entryCondition . ')';
+				}
+				$entryCondition = $revisionCondition . $entryCondition;
+			}
+		}
+		if ( !empty( $entryCondition ) ) {
+			$entryCondition = ' AND ' . $entryCondition;
+		}
+
+		$where = array();
+		if ( !empty( $targetRevision ) ) {
+			$where[] = 'old.rev_id <= ' . $dbr->addQuotes( $targetRevision );
+		}
+		if ( !empty( $srcComponents['pathComponents'] ) ) {
+			$where[] = 'page_title = ' . $dbr->addQuotes( implode( '/', $srcComponents['pathComponents'] ) );
+		}
+
+		if ( empty( $targetRevision ) ) {
+			$results = $dbr->query( '
+				SELECT MAX(rev_id)
+				FROM revision' );
+
+			if ( ( $result = $dbr->fetchRow( $results ) ) === false ) {
+				return;
+			}
+
+			$targetRevision = $result[0];
+		}
+
+		$this->setResponseHeader( 'Content-Type: text/xml; charset="utf-8"', false );
+
+		echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
+		echo "<S:update-report xmlns:D=\"DAV:\" xmlns:S=\"svn:\" xmlns:V=\"http://subversion.tigris.org/xmlns/dav\" send-all=\"true\">\n";
+
+		# TODO: Get the revision from the report request
+		echo "  <S:target-revision rev=\"$targetRevision\"/>\n";
+
+		# TODO: Use Main_Page revision?
+		echo "  <S:open-directory rev=\"$targetRevision\">\n";
+		echo '    <D:checked-in><D:href>' . $this->getUrl( array( 'path' => 'deltav.php/ver' ) ) . "</D:href></D:checked-in>\n";
+
+		$whereClause = null;
+		if ( !empty( $where ) ) {
+			$whereClause = ' WHERE ' . implode( ' AND ', $where );
+		}
+
+		# SUM(new.rev_id IS NULL) is the number of revisions which didn't match the entry condition
+		# TODO: Invert entry condition to make getting the base revision cleaner
+		$results = $dbr->query( '
+			SELECT page_title, SUM(new.rev_id IS NULL), MAX(CASE WHEN new.rev_id IS NULL THEN old.rev_id ELSE NULL END), MAX(new.rev_id)
+			FROM page
+			JOIN revision AS old
+			ON page_id = old.rev_page
+			LEFT JOIN revision AS new
+			ON old.rev_id = new.rev_id' . $entryCondition . $whereClause . '
+			GROUP BY page_id
+			HAVING COUNT(new.rev_id)' );
+
+		while ( ( $result = $dbr->fetchRow( $results ) ) !== false ) {
+			$addOrOpen = 'add';
+			$baseRev = null;
+
+			$newText = Revision::newFromId( $result[3] )->revText();
+			$oldText = null;
+
+			if ( $result[1] > 0 ) {
+				$addOrOpen = 'open';
+				$baseRev = ' rev="' . $result[2] . '"';
+
+				$oldText = Revision::newFromId( $result[2] )->revText();
+			}
+
+			# TODO: Use only last path component
+			echo "    <S:$addOrOpen-file name=\"$result[0]\"$baseRev>\n";
+
+			echo '      <D:checked-in><D:href>' . $this->getUrl( array( 'path' => 'deltav.php/ver/' . $result[3] ) ) . "</D:href></D:checked-in>\n";
+			echo '      <S:txdelta>' . base64_encode( $this->getSvnDiff( $oldText, $newText ) ) . "\n</S:txdelta>\n";
+			echo '      <S:prop><V:md5-checksum>' . md5( $newText ) . "</V:md5-checksum></S:prop>\n";
+			echo "    </S:$addOrOpen-file>\n";
+		}
+
+		echo "  </S:open-directory>\n";
+		echo "</S:update-report>\n";
+
+		return true;
+	}
+
+	function getSvnDiff( $oldText, $newText ) {
+		$instructions = chr( 0x80 | strlen( $newText ) );
+		if ( strlen( $newText ) > 0x37 ) {
+			$instructions = "\x80" . $this->encodeInt( strlen( $newText ) );
+		}
+
+		return "SVN\x00\x00"
+			. $this->encodeInt( strlen( $oldText ) )
+			. $this->encodeInt( strlen( $newText ) )
+			. $this->encodeInt( strlen( $instructions ) )
+			. $this->encodeInt( strlen( $newText ) )
+			. $instructions
+			. $newText;
+	}
+
+	function encodeInt( $int ) {
+		# Least seven bits
+		$bytes = chr( $int & 0x7f );
+
+		# Shift by seven bits until nothing remains
+		while ( 0 < $int >>= 7 ) {
+			# Prepend seven bits with the eighth bit, the continuation bit, set, to the string of bytes
+			$bytes = chr( $int & 0x7f | 0x80 ) . $bytes;
+		}
+
+		return $bytes;
+	}
+
+	function search( &$serverOptions ) {
+		$serverOptions['namespaces']['http://subversion.tigris.org/xmlns/dav/'] = 'V';
+
+		$status = array();
+
+		$search = SearchEngine::create();
+
+		# TODO: Use (int)$wgUser->getOption( 'searchlimit' );
+		$search->setLimitOffset( MW_SEARCH_LIMIT );
+
+		$results = $search->searchText( $serverOptions['xpath']->evaluate( 'string(/D:searchrequest/D:basicsearch/D:where/D:contains)' ) );
+
+		while ( ( $result = $results->next() ) !== false ) {
+			$title = $result->getTitle();
+			$revision = Revision::newFromTitle( $title );
+
+			$response = array();
+			$response['path'] = 'webdav.php/' . $title->getPrefixedUrl();
+			$response['props'][] = WebDavServer::mkprop( 'checked-in', $this->getUrl( array( 'path' => 'deltav.php/ver/' . $revision->getId() ) ) );
+			$response['props'][] = WebDavServer::mkprop( 'displayname', $title->getText() );
+			$response['props'][] = WebDavServer::mkprop( 'getcontentlength', $revision->getSize() );
+			$response['props'][] = WebDavServer::mkprop( 'getcontenttype', 'text/x-wiki' );
+			$response['props'][] = WebDavServer::mkprop( 'getlastmodified', wfTimestamp( TS_UNIX, $revision->mTimestamp ) );
+			$response['props'][] = WebDavServer::mkprop( 'resourcetype', null );
+			$response['props'][] = WebDavServer::mkprop( 'version-controlled-configuration', $this->getUrl( array( 'path' => 'deltav.php/vcc/default' ) ) );
+
+			$response['props'][] = WebDavServer::mkprop( 'http://subversion.tigris.org/xmlns/dav/', 'baseline-relative-path', $title->getFullUrl() );
+			$response['score'] = $result->getScore();
+
+			$status[] = $response;
+		}
+
+		# TODO: Check if we exceed our limit
+		#$response = array();
+		#$response['status'] = '507 Insufficient Storage';
+
+		#$status[] = $response;
+
+		return $status;
+	}
+}

Property changes on: trunk/extensions/WebDAV/WebDav.php
___________________________________________________________________
Added: svn:keywords
   + Author Id Revision
Added: svn:eol-style
   + native

Index: trunk/extensions/WebDAV/webdav.php
===================================================================
--- trunk/extensions/WebDAV/webdav.php	(revision 0)
+++ trunk/extensions/WebDAV/webdav.php	(revision 37149)
@@ -0,0 +1,62 @@
+<?php
+
+# Initialise common code
+require_once( './includes/WebStart.php' );
+
+require_once( './WebDav.php' );
+
+$server = new WebDavServer;
+$server->handleRequest();
+
+/*
+# Initialize MediaWiki base class
+require_once( "includes/Wiki.php" );
+$mediaWiki = new MediaWiki();
+
+wfProfileIn( 'main-misc-setup' );
+OutputPage::setEncodings(); # Not really used yet
+
+# Query string fields
+$action = $wgRequest->getVal( 'action', 'view' );
+$title = $wgRequest->getVal( 'title' );
+
+$wgTitle = $mediaWiki->checkInitialQueries( $title,$action,$wgOut, $wgRequest, $wgContLang );
+if ($wgTitle == NULL) {
+	unset( $wgTitle );
+}
+
+#
+# Send Ajax requests to the Ajax dispatcher.
+#
+if ( $wgUseAjax && $action == 'ajax' ) {
+	require_once( $IP . '/includes/AjaxDispatcher.php' );
+
+	$dispatcher = new AjaxDispatcher();
+	$dispatcher->performAction();
+	$mediaWiki->restInPeace( $wgLoadBalancer );
+	exit;
+}
+
+
+wfProfileOut( 'main-misc-setup' );
+
+# Setting global variables in mediaWiki
+$mediaWiki->setVal( 'Server', $wgServer );
+$mediaWiki->setVal( 'DisableInternalSearch', $wgDisableInternalSearch );
+$mediaWiki->setVal( 'action', $action );
+$mediaWiki->setVal( 'SquidMaxage', $wgSquidMaxage );
+$mediaWiki->setVal( 'EnableDublinCoreRdf', $wgEnableDublinCoreRdf );
+$mediaWiki->setVal( 'EnableCreativeCommonsRdf', $wgEnableCreativeCommonsRdf );
+$mediaWiki->setVal( 'CommandLineMode', $wgCommandLineMode );
+$mediaWiki->setVal( 'UseExternalEditor', $wgUseExternalEditor );
+$mediaWiki->setVal( 'DisabledActions', $wgDisabledActions );
+
+$wgArticle = $mediaWiki->initialize ( $wgTitle, $wgOut, $wgUser, $wgRequest );
+$mediaWiki->finalCleanup ( $wgDeferredUpdateList, $wgLoadBalancer, $wgOut );
+
+# Not sure when $wgPostCommitUpdateList gets set, so I keep this separate from finalCleanup
+$mediaWiki->doUpdates( $wgPostCommitUpdateList );
+
+$mediaWiki->restInPeace( $wgLoadBalancer );
+*/
+?>

Property changes on: trunk/extensions/WebDAV/webdav.php
___________________________________________________________________
Added: svn:keywords
   + Author Id Revision
Added: svn:eol-style
   + native

Status & tagging log

  • 15:28, 12 September 2011 Meno25 (Talk | contribs) changed the status of r37149 [removed: ok added: old]
Personal tools
Namespaces
Variants
Views
Actions
Site
Support
Download
Development
Communication
Toolbox