This repository has been archived on 2024-12-02. You can view files and clone it, but cannot push or open issues or pull requests.
AbetoArmarios_Web/Source/gallery2/modules/webdav/classes/WebDavHelper.class

1996 lines
58 KiB
Plaintext
Raw Permalink Normal View History

<?php
/*
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2007 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
GalleryCoreApi::requireOnce('modules/webdav/lib/HTTP/WebDAV/Server.php');
/* WebDAV status codes */
define('WEBDAV_STATUS_NO_XML_PARSER', 0x00000002);
define('WEBDAV_STATUS_METHOD_NOT_HANDLED', 0x00000004);
define('WEBDAV_STATUS_HTTPAUTH_MODULE_DISABLED', 0x00000008);
define('WEBDAV_STATUS_REWRITE_MODULE_DISABLED', 0x00000010);
define('WEBDAV_STATUS_CONNECT_RULE_DISABLED', 0x00000020);
define('WEBDAV_STATUS_MISSING_DAV_HEADERS', 0x00000040);
define('WEBDAV_STATUS_ALTERNATIVE_URL_HEADERS', 0x00000080);
define('WEBDAV_STATUS_BAD_REWRITE_PARSER', 0x00000100);
define('WEBDAV_STATUS_OPTIONS_RULE_DISABLED', 0x00000200);
define('WEBDAV_STATUS_HTTPAUTH_AUTH_PLUGINS_DISABLED', 0x00000400);
define('WEBDAV_STATUS_ERROR_UNKNOWN', 0x80000000);
/* Gallery property namespace - RFC2518 18 */
define('WEBDAV_GALLERY_NAMESPACE', 'http://gallery2.org/dav/props/');
/**
* WebDAV helper class.
* @package WebDav
* @subpackage Classes
* @author Jack Bates <ms419@freezone.co.uk>
* @version $Revision: 16508 $
* @static
*/
class WebDavHelper {
/**
* Check this module's configuration.
* @return array object GalleryStatus a status code
* int WebDAV status code
*/
function checkConfiguration() {
global $gallery;
$phpVm = $gallery->getPhpVm();
$urlGenerator =& $gallery->getUrlGenerator();
$code = 0x00000000;
list ($ret, $moduleStatus) = GalleryCoreApi::fetchPluginList('module');
if ($ret) {
return array($ret, null);
}
/*
* URL rewrite module must be enabled. Check it regardless of missing DAV headers because
* it also implies the connect rule is disabled. Check it before checking for missing DAV
* headers causes because it is a missing DAV headers cause.
*/
if (empty($moduleStatus['rewrite']['active'])) {
$code |= WEBDAV_STATUS_REWRITE_MODULE_DISABLED;
} else {
list ($ret, $rewriteApi) = GalleryCoreApi::newFactoryInstance('RewriteApi');
if ($ret) {
return array($ret, null);
}
if (!isset($rewriteApi)) {
return array(GalleryCoreApi::error(ERROR_CONFIGURATION_REQUIRED), null);
}
list ($ret, $isCompatible) = $rewriteApi->isCompatibleWithApi(array(1, 1));
if ($ret) {
return array($ret, null);
}
if (!$isCompatible) {
return array(GalleryCoreApi::error(ERROR_CONFIGURATION_REQUIRED), null);
}
list ($ret, $activeRules) = $rewriteApi->fetchActiveRulesForModule('webdav');
if ($ret) {
return array($ret, null);
}
}
/*
* Check for missing DAV headers causes first so we can show the error unknown warning if no
* causes are found.
*/
if (!WebDavHelper::checkDavHeaders($urlGenerator->generateUrl(
array('controller' => 'webdav.WebDav'),
array('forceFullUrl' => true,
'htmlEntities' => false)))) {
/* Already checked one cause: URL rewrite module disabled. Check other causes. */
if (!WebDavHelper::checkDavHeaders($urlGenerator->generateUrl(
array('href' => 'modules/webdav/data/options/'),
array('forceFullUrl' => true,
'htmlEntities' => false)))) {
$code |= WEBDAV_STATUS_ALTERNATIVE_URL_HEADERS;
}
if (!empty($moduleStatus['rewrite']['active'])) {
if ($rewriteApi->getParserType() != 'preGallery') {
$code |= WEBDAV_STATUS_BAD_REWRITE_PARSER;
} else {
if (!in_array('options', $activeRules)) {
$code |= WEBDAV_STATUS_OPTIONS_RULE_DISABLED;
}
}
}
/* No causes found for missing DAV headers! */
if (!$code) {
$code |= WEBDAV_STATUS_ERROR_UNKNOWN;
}
$code |= WEBDAV_STATUS_MISSING_DAV_HEADERS;
}
/*
* Must use short URL because most WebDAV clients don't support query strings. Check it
* after checking for missing DAV headers causes so we can show the error unknown warning if
* no causes are found.
*/
if (!empty($moduleStatus['rewrite']['active'])) {
if (!in_array('connect', $activeRules)) {
$code |= WEBDAV_STATUS_CONNECT_RULE_DISABLED;
}
}
/*
* HTTP auth module must be enabled to authenticate with WebDAV. Check it after checking
* for missing DAV headers causes so we can show the error unknown warning if no causes are
* found.
*/
if (empty($moduleStatus['httpauth']['active'])) {
$code |= WEBDAV_STATUS_HTTPAUTH_MODULE_DISABLED;
} else {
/* Ensure HTTP auth is enabled */
list ($ret, $httpAuthInterface) =
GalleryCoreApi::newFactoryInstance('HttpAuthInterface_1_0');
if ($ret) {
return array($ret, null);
}
if (isset($httpAuthInterface)) {
list ($ret, $httpAuthPluginEnabled, $serverAuthPluginEnabled) =
$httpAuthInterface->getConfiguration();
if ($ret) {
return array($ret, null);
}
if (!$httpAuthPluginEnabled && !$serverAuthPluginEnabled) {
$code |= WEBDAV_STATUS_HTTPAUTH_AUTH_PLUGINS_DISABLED;
}
}
}
/*
* Check that Gallery handles WebDAV request methods. Check it after checking for missing
* DAV headers causes so we can show the error unknown warning if no causes are found.
*/
foreach (array('PROPFIND', 'PROPPATCH', 'MKCOL', 'DELETE', 'PUT', 'MOVE', 'LOCK', 'UNLOCK')
as $requestMethod) {
if (!WebDavHelper::checkRequestMethod($requestMethod)) {
if ($gallery->getDebug()) {
$gallery->debug('Error in WebDavHelper::checkConfiguration:'
. ' this server doesn\'t pass ' . $requestMethod . ' requests to Gallery.');
}
$code |= WEBDAV_STATUS_METHOD_NOT_HANDLED;
}
}
/*
* The WebDAV library requires a PHP XML parser. Check it after checking for missing DAV
* headers causes so we can show the error unknown warning if no causes are found.
*/
if (!$phpVm->extension_loaded('xml')) {
$code |= WEBDAV_STATUS_NO_XML_PARSER;
}
return array(null, $code);
}
/**
* Check that Gallery handles WebDAV request methods.
* @param string $requestMethod
* @return boolean true if Gallery handles the request method
*/
function checkRequestMethod($requestMethod) {
global $gallery;
$urlGenerator =& $gallery->getUrlGenerator();
list ($status, $headers, $body) = GalleryCoreApi::requestWebPage($urlGenerator->generateUrl(
array('view' => 'webdav.WebDavWorks'),
array('forceFullUrl' => true,
'htmlEntities' => false)), $requestMethod, array('Content-length' => 0));
if (!preg_match('/^HTTP\/[0-9]\.[0-9] 200/', $status)) {
return false;
}
if (trim($body) != 'PASS_WEBDAV') {
return false;
}
return true;
}
/**
* Check that OPTIONS responses includes the DAV headers.
* @param string $url
* @return boolean true if OPTIONS responses include the DAV headers
*/
function checkDavHeaders($url) {
list ($status, $headers, $body) = GalleryCoreApi::requestWebPage($url, 'OPTIONS');
if (!preg_match('/^HTTP\/[0-9]\.[0-9] 200/', $status)) {
return false;
}
if (empty($headers['Allow']) || $headers['Allow'] != 'OPTIONS,PROPFIND,PROPPATCH,MKCOL,GET'
. ',HEAD,DELETE,PUT,MOVE,LOCK,UNLOCK') {
return false;
}
if (empty($headers['DAV']) || $headers['DAV'] != '1,2') {
return false;
}
if (empty($headers['MS-Author-Via']) || $headers['MS-Author-Via'] != 'DAV') {
return false;
}
return true;
}
/**
* Returns a browser-specifc mount link for the given item.
* @param int $itemId
* @return array('href' => string the davmount URL,
* 'script' (optional) => string JavaScript to be used as onclick,
* 'attrs' => array() string additional link tag attributes)
*/
function getMountLink($itemId) {
global $gallery;
$urlGenerator =& $gallery->getUrlGenerator();
$userAgent = GalleryUtilities::getServerVar('HTTP_USER_AGENT');
$url = $urlGenerator->generateUrl(array('controller' => 'webdav.WebDav',
'itemId' => $itemId),
array('forceFullUrl' => true,
'forceSessionId' => false,
'useAuthToken' => false));
$link['attrs'] = 'style="behavior: url(#default#anchorClick)" folder="'. $url . '"';
if (strpos($userAgent, 'MSIE') !== false) {
/*
* Mount with JavaScript only if using MSIE. By default, dropdowns link to davmount
* resources.
*/
$url = $urlGenerator->generateUrl(array('controller' => 'webdav.WebDav',
'itemId' => $itemId),
array('forceFullUrl' => true,
'htmlEntities' => false,
'forceSessionId' => false,
'useAuthToken' => false));
$link['script'] =
"this.style.behavior = 'url(#default#httpFolder)'; this.navigate('$url')";
}
if (strpos($userAgent, 'Konqueror') !== false) {
/* Konqueror supports webdav:// URLs */
$urlParams = array('controller' => 'webdav.WebDav', 'itemId' => $itemId);
$urlOptions = array('protocol' => 'webdav', 'forceSessionId' => false,
'useAuthToken' => false);
} else {
$urlParams = array('view' => 'webdav.DownloadDavMount', 'itemId' => $itemId);
$urlOptions = array();
}
$link['href'] = $urlGenerator->generateUrl($urlParams, $urlOptions);
return $link;
}
/**
* Returns the id of item that corresponds to the parent of the given path.
* The item at the given path doesn't have to exist, but its parent is expected to exist.
*
* @param string $path, e.g. /foo/bar
* @return array object GalleryStatus a status code,
* int the id of the parent item
*/
function getParentItemIdByPath($path) {
$parentPath = dirname($path);
/* dirname('foo') is '.' and \ for dirname ('/foo') on Windows */
if (in_array($parentPath, array('.', '\\'))) {
list ($ret, $parentId) =
GalleryCoreApi::getPluginParameter('module', 'core', 'id.rootAlbum');
if ($ret) {
return array($ret, null);
}
} else {
list ($ret, $parentId) = GalleryCoreApi::fetchItemIdByPath($parentPath);
if ($ret) {
return array($ret, null);
}
}
return array(null, (int)$parentId);
}
/**
* Take two entities of possibly different classes and make the second entity as close a copy of
* the first entity as possible. Copy the id but not the entity type because the entity type
* must always match the class name.
* @param object GalleryEntity $sourceEntity entity to copy from
* @param object GalleryEntity $mirrorEntity entity to copy to
* @return array object GalleryStatus a status code
* object GalleryEntity the mirror entity
*/
function mirrorEntity($sourceEntity, $mirrorEntity) {
$className = $mirrorClassName = $mirrorEntity->getClassName();
list ($ret, $entityInfo) = GalleryCoreApi::describeEntity($className);
if ($ret) {
return array($ret, null);
}
list ($ret, $memberAccessInfo) =
GalleryCoreApi::getExternalAccessMemberList($className);
if ($ret) {
return array($ret, null);
}
/* We need to override id and pathComponent */
$override = array('id', 'pathComponent');
/*
* Walk down the mirror entity's class hierarchy copying class members from the source
* entity if they are defined
*/
while (!empty($className)) {
foreach ($entityInfo[$className]['members'] as $memberName => $memberInfo) {
if (isset($sourceEntity->$memberName)
&& (!empty($memberAccessInfo[$memberName]['write'])
|| in_array($memberName, $override))) {
$mirrorEntity->$memberName = $sourceEntity->$memberName;
}
}
$className = $entityInfo[$className]['parent'];
}
/*
* Reset the entity type to the mirror entity's class name because the entity type must
* always match the class name
*/
$mirrorEntity->entityType = $mirrorClassName;
return array(null, $mirrorEntity);
}
/**
* Get singleton WebDAV server library instance.
*
* If it didn't need path and baseUrl, we could eliminate and call library methods staticly.
*
* @return object WebDavServer instance
*/
function &getWebDavServer() {
static $webDavServer;
if (!isset($webDavServer)) {
global $gallery;
$urlGenerator =& $gallery->getUrlGenerator();
$webDavServer = new WebDavServer();
/*
* Needed by HTTP_WebDAV_Server::copymove_request_helper and
* HTTP_WebDAV_Server::_check_if_header_conditions
*/
$path = GalleryUtilities::getRequestVariables('path');
$path = trim($path, '/');
$webDavServer->path = $path;
$webDavServer->baseUrl = parse_url($urlGenerator->generateUrl(
array('controller' => 'webdav.WebDav'),
array('forceFullUrl' => true,
'htmlEntities' => false,
'forceSessionId' => false,
'useAuthToken' => false)));
}
return $webDavServer;
}
/**
* Get active WebDAV locks at specified path.
* @param string $path
* @param boolean $getDescendentsLocks (optional) also get locks at any descendant path
* @return array object GalleryStatus a status code
* array active WebDAV locks (scope, type, depth, owner, expires, token, path)
*/
function getLocks($path, $getDescendentsLocks=false) {
global $gallery;
/* We haven't done any database calls yet, so GallerySqlFragment isn't defined */
GalleryCoreApi::requireOnce('modules/core/classes/GalleryStorage.class');
/* Remove stale locks */
$ret = GalleryCoreApi::removeMapEntry('WebDavLockMap',
array('expires' => new GallerySqlFragment('< ?', time())));
if ($ret) {
return array($ret, null);
}
$data = array();
$query = '
SELECT
[WebDavLockMap::depth],
[WebDavLockMap::owner],
[WebDavLockMap::expires],
[WebDavLockMap::token],
[WebDavLockMap::path]
FROM
[WebDavLockMap]
WHERE';
/*
* Hacks to get ancestors' and descendants' locks will disappear with MPTT -
* http://codex.gallery2.org/index.php/Gallery2:Modified_Preorder_Tree_Traversal
*/
if ($getDescendentsLocks) {
$data[] = "$path%";
$query .= '
[WebDavLockMap::path] LIKE ?';
} else {
$data[] = $path;
$query .= '
[WebDavLockMap::path] = ?';
}
$pathComponents = explode('/', $path);
$count = 0;
/* Get ancestors' locks */
while (array_pop($pathComponents) !== null) {
$data[] = implode('/', $pathComponents);
$count++;
}
if ($count) {
$query .= '
OR
([WebDavLockMap::path] IN (' . GalleryUtilities::makeMarkers($count)
. ') AND [WebDavLockMap::depth] = \'infinity\')';
}
list ($ret, $results) = $gallery->search($query, $data);
if ($ret) {
return array($ret, null);
}
$locks = array();
while (($result = $results->nextResult()) !== false) {
$locks[] = array('scope' => 'exclusive',
'type' => 'write',
'depth' => $result[0],
'owner' => $result[1],
'expires' => (int)$result[2],
'token' => $result[3],
'path' => $result[4]);
}
return array(null, $locks);
}
/**
* Get active locks at specified path or any descendant path.
* @see WebDavHelper::getLocks
*/
function getDescendentsLocks($path) {
return WebDavHelper::getLocks($path, true);
}
/**
* Check if there are no active locks at the specified path, or the request matches the token of
* the active lock.
* @param string $path
* @return boolean no active locks or the request matches the active lock
*/
function checkLocks($path) {
$webDavServer =& WebDavHelper::getWebDavServer();
list ($ret, $locks) = WebDavHelper::getLocks($path);
if ($ret) {
return $ret;
}
if (!empty($locks) && !$webDavServer->check_locks_helper($locks, $path)) {
WebDavServer::setResponseStatus('423 Locked');
return GalleryCoreApi::error(ERROR_LOCK_IN_USE);
}
}
/**
* OPTIONS handler.
* @see HTTP_WebDAV_Server::options
*/
function options() {
/* TODO: COPY not implemented */
GalleryUtilities::setResponseHeader(
'Allow: OPTIONS,PROPFIND,PROPPATCH,MKCOL,GET,HEAD,DELETE,PUT,MOVE,LOCK,UNLOCK');
GalleryUtilities::setResponseHeader('DAV: 1,2');
GalleryUtilities::setResponseHeader('Content-Length: 0');
GalleryUtilities::setResponseHeader('MS-Author-Via: DAV');
}
/**
* PROPFIND request helper.
*
* Wrapper around HTTP_WebDAV_Server::propfind_request_helper which prepares data-structures
* from PROPFIND requests.
*
* @return array object GalleryStatus a status code
* array WebDAV library options
* int maximum depth of descendant paths
* @see HTTP_WebDAV_Server::propfind_request_helper
*/
function propfindRequestHelper() {
$webDavServer =& WebDavHelper::getWebDavServer();
if (!$webDavServer->propfind_request_helper($webDavOptions)) {
/* WebDAV library found error in the request */
return array(GalleryCoreApi::error(ERROR_UNKNOWN), null, null);
}
return array(null, $webDavOptions, $webDavOptions['depth']);
}
/**
* PROPFIND response helper.
*
* Wrapper around HTTP_WebDAV_Server::propfind_response_helper which formats PROPFIND responses.
*
* @param array $webDavOptions WebDAV library options
* @param array $files files for WebDAV response (path, props)
* @param array $namespaces namespaces for WebDAV response (URI => prefix)
* @see HTTP_WebDAV_Server::propfind_response_helper
*/
function propfindResponseHelper($webDavOptions, $files, $namespaces) {
$webDavServer =& WebDavHelper::getWebDavServer();
$webDavOptions['namespaces'] = $namespaces;
$webDavServer->propfind_response_helper($webDavOptions, $files);
}
/**
* PROPFIND handler.
* @return object GalleryStatus status code
*/
function propfind() {
/* Prepare data-structure from PROPFIND request */
list ($ret, $webDavOptions, $depth) = WebDavHelper::propfindRequestHelper();
if ($ret) {
return $ret;
}
$path = GalleryUtilities::getRequestVariables('path');
$path = trim($path, '/');
if (empty($path)) {
list ($ret, $itemId) = GalleryCoreApi::getDefaultAlbumId();
if ($ret) {
return $ret;
}
} else {
list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
if ($ret) {
return $ret;
}
}
list ($ret, $item) = GalleryCoreApi::loadEntitiesById($itemId);
if ($ret) {
return $ret;
}
$files = array();
$ret = WebDavHelper::_propfindFiles($item, $path, $depth, $files);
if ($ret) {
return $ret;
}
$namespaces = array(WEBDAV_GALLERY_NAMESPACE => 'G');
/* Format PROPFIND response */
$ret = WebDavHelper::propfindResponseHelper($webDavOptions, $files, $namespaces);
if ($ret) {
return $ret;
}
}
/**
* PROPFIND recursive function.
*
* Builds file arrays (path, props) from items until depth is exhausted.
*
* Could be done iteratively, but waiting for MPTT for the ultimate solution -
* http://codex.gallery2.org/index.php/Gallery2:Modified_Preorder_Tree_Traversal
*
* @param object GalleryItem $item
* @param string $path
* @param int $depth maximum depth of descendant paths
* @param array $files files for WebDAV response (path, props)
* @return object GalleryStatus a status code
* @access private
*/
function _propfindFiles($item, $path, $depth, &$files) {
/* Verify that the provided object implements the required methods */
foreach (array('creationTimestamp', 'title', 'modificationTimestamp',
'pathComponent') as $memberName) {
if (!method_exists($item, 'get' . $memberName)) {
return GalleryCoreApi::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
"Item object does not implement a getter for '$memberName'");
}
}
$file = array('path' => $path, 'props' => array());
/* Build standard DAV: properties */
$file['props'][] = WebDavServer::mkprop('creationdate', $item->getCreationTimestamp());
$displayName = $item->getTitle();
if (empty($displayName)) {
$displayName = $item->getPathComponent();
}
$file['props'][] = WebDavServer::mkprop('displayname', $displayName);
$file['props'][] =
WebDavServer::mkprop('getlastmodified', $item->getModificationTimestamp());
/*
* Support exclusive write locks.
*
* Any DAV compliant resource that supports the LOCK method MUST support the supportedlock
* property.
*/
$file['props'][] = WebDavServer::mkprop(
'supportedlock',
array(array('scope' => 'exclusive', 'type' => 'write')));
/*
* WebDavHelper::getLocks is potentially expensive. Could optimize this if we knew
* $webDavOptions['props'] didn't contain 'lockdiscovery' or 'allprop'.
*/
list ($ret, $locks) = WebDavHelper::getLocks($path);
if ($ret) {
return $ret;
}
$file['props'][] = WebDavServer::mkprop('lockdiscovery', $locks);
if (GalleryUtilities::isA($item, 'GalleryAlbumItem')) {
if (!empty($path)) {
$file['path'] = "$path/";
}
$file['props'][] = WebDavServer::mkprop('getcontentlength', 0);
$file['props'][] = WebDavServer::mkprop('getcontenttype', 'httpd/unix-directory');
$file['props'][] = WebDavServer::mkprop('resourcetype', 'collection');
} else {
$size = 0;
if (method_exists($item, 'getSize')) {
$size = $item->getSize();
}
$mimeType = 'application/unknown';
if (method_exists($item, 'getMimeType')) {
$mimeType = $item->getMimeType();
}
$file['props'][] = WebDavServer::mkprop('getcontentlength', $size);
$file['props'][] = WebDavServer::mkprop('getcontenttype', $mimeType);
$file['props'][] = WebDavServer::mkprop('resourcetype', null);
}
/* Build Gallery properties */
if (method_exists($item, 'getClassName')) {
list ($ret, $memberInfo) =
GalleryCoreApi::getExternalAccessMemberList($item->getClassName());
if ($ret) {
return $ret;
}
/* Keep track of the properties that we add to prevent repetition */
$defaultMembers = array('pathComponent', 'creationTimestamp', 'title',
'modificationTimestamp', 'mimeType', 'size');
foreach ($memberInfo as $memberName => $accessInfo) {
$getter = 'get' . $memberName;
/* Only show properties that are not intended for internal use only */
if ($accessInfo['read'] && !in_array($memberName, $defaultMembers)
&& method_exists($item, $getter)) {
$value = $item->$getter();
/* Ignore array valued properties */
if (!is_array($value) && !is_object($value)) {
$file['props'][] = WebDavServer::mkprop(WEBDAV_GALLERY_NAMESPACE,
$memberName, $value);
}
}
}
}
$files[] = $file;
if ($depth <= 0) {
return null;
}
list ($ret, $childIds) = GalleryCoreApi::fetchChildItemIds($item);
if ($ret) {
return $ret;
}
if (empty($childIds)) {
return null;
}
list ($ret, $childItems) = GalleryCoreApi::loadEntitiesById($childIds);
if ($ret) {
return $ret;
}
foreach ($childItems as $childItem) {
/* Could we simply use something like $childItem->fetchLogicalPath? */
$childPath = $childItem->getPathComponent();
if (!empty($path)) {
$childPath = "$path/" . $childPath;
}
$ret = WebDavHelper::_propfindFiles($childItem, $childPath, $depth - 1, $files);
if ($ret) {
return $ret;
}
}
return null;
}
/**
* PROPPATCH request helper.
*
* Wrapper around HTTP_WebDAV_Server::proppatch_request_helper which prepares data-structures
* from PROPPATCH requests.
*
* @return array object GalleryStatus a status code
* array WebDAV library options
* array properties to set (ns => namespace, name => name, val => value)
* @see HTTP_WebDAV_Server::proppatch_request_helper
*/
function proppatchRequestHelper() {
$webDavServer =& WebDavHelper::getWebDavServer();
if (!$webDavServer->proppatch_request_helper($webDavOptions)) {
/* WebDAV library found error in the request */
return array(GalleryCoreApi::error(ERROR_UNKNOWN), null, null);
}
return array(null, $webDavOptions, $webDavOptions['props']);
}
/**
* PROPPATCH response helper.
*
* Wrapper around HTTP_WebDAV_Server::proppatch_response_helper which formats PROPPATCH
* responses.
*
* @param array $webDavOptions WebDAV library options
* @param string $path
* @param array $props properties set (ns => namespace,
name => name,
val => value,
status => status)
* @param array $namespace namespaces for WebDAV response (URI => prefix)
* @see HTTP_WebDAV_Server::proppatch_response_helper
*/
function proppatchResponseHelper($webDavOptions, $path, $props, $namespaces) {
$webDavServer =& WebDavHelper::getWebDavServer();
$webDavOptions['path'] = $path;
$webDavOptions['props'] = $props;
$webDavOptions['namespaces'] = $namespaces;
$webDavServer->proppatch_response_helper($webDavOptions);
}
/**
* PROPPATCH handler.
* @return object GalleryStatus a status code
*/
function proppatch() {
$path = GalleryUtilities::getRequestVariables('path');
$path = trim($path, '/');
/* Check resource is not locked */
$ret = WebDavHelper::checkLocks($path);
if ($ret) {
return $ret;
}
if (empty($path)) {
list ($ret, $itemId) = GalleryCoreApi::getDefaultAlbumId();
if ($ret) {
return $ret;
}
} else {
list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
if ($ret) {
return $ret;
}
}
list ($ret, $item) = GalleryCoreApi::loadEntitiesById($itemId);
if ($ret) {
return $ret;
}
/* Prepare data-structure from PROPPATCH request */
list ($ret, $webDavOptions, $props) = WebDavHelper::proppatchRequestHelper();
if ($ret) {
return $ret;
}
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($itemId);
if ($ret) {
return $ret;
}
$ret = WebDavHelper::_setItemProps($item, $props);
if ($ret) {
return $ret;
}
if ($item->isModified()) {
$ret = $item->save();
if ($ret) {
GalleryCoreApi::releaseLocks($lockId);
return $ret;
}
}
$ret = GalleryCoreApi::releaseLocks($lockId);
if ($ret) {
return $ret;
}
$namespaces = array(WEBDAV_GALLERY_NAMESPACE => 'G');
/* Format PROPPATCH response */
$ret = WebDavHelper::proppatchResponseHelper($webDavOptions, $path, $props, $namespaces);
if ($ret) {
return $ret;
}
return null;
}
/**
* Set item properties
* @param object GalleryItem reference $item
* @param array reference $propos DAV file properties
* @return object GalleryStatus a status code
*/
function _setItemProps(&$item, &$props) {
if (!method_exists($item, 'getClassName')) {
return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
}
list ($ret, $memberInfo) =
GalleryCoreApi::getExternalAccessMemberList($item->getClassName());
if ($ret) {
return $ret;
}
foreach ($props as $key => $prop) {
$name = $prop['name'];
if ($prop['ns'] == 'DAV:') {
if ($prop['name'] == 'displayname') {
$name = 'title';
/* Want to support any other DAV: properties? */
} else {
$props[$key]['status'] = '403 Forbidden';
continue;
}
} else if ($prop['ns'] != WEBDAV_GALLERY_NAMESPACE) {
$props[$key]['status'] = '403 Forbidden';
continue;
}
$setter = 'set' . $name;
if (!isset($memberInfo[$name])|| !$memberInfo[$name]['write']
|| !method_exists($item, $setter)) {
$props[$key]['status'] = '403 Forbidden';
continue;
}
$item->$setter($prop['value']);
}
return null;
}
/**
* Validate MKCOL requests.
*
* Copied from ItemAddAlbumController::handleRequest for consistancy. Maybe eventually should
* go in ItemAddAlbumController::validateRequest or a GalleryCoreApi method.
*
* @param int $parentId id of parent album
* @param string $pathComponent path component of new album
* @return array object GalleryStatus a status code
* array error strings
* @see ItemAddAlbumController::handleRequest
*/
function mkcolValidateHelper($parentId, $pathComponent) {
global $gallery;
$platform =& $gallery->getPlatform();
$error = array();
/* Make sure we have permission do edit this item */
$ret = GalleryCoreApi::assertHasItemPermission($parentId, 'core.addAlbumItem');
if ($ret) {
return array($ret, null);
}
if (empty($pathComponent)) {
$error[] = 'form[error][pathComponent][missing]';
} else if (!$platform->isLegalPathComponent($pathComponent)) {
$error[] = 'form[error][pathComponent][invalid]';
}
return array(null, $error);
}
/**
* MKCOL helper.
*
* Acquire locks, create album and set permissions.
*
* Copied from ItemAddAlbumController::handleRequest for consistancy. Maybe eventually should
* go in ItemAddAlbumController::requestHelper or a GalleryCoreApi method.
*
* @param int $parentId id of parent album
* @param string $pathComponent path component of new album
* @param string $title title of new album
* @param string $summary summary of new album
* @param string $description description of new album
* @param array $keywords keywords of new album
* @return object GalleryStatus a status code
* @see ItemAddAlbumController::handleRequest
*/
function mkcolHelper($parentId, $pathComponent, $title, $summary, $description, $keywords) {
list ($ret, $lockIds[]) = GalleryCoreApi::acquireReadLock($parentId);
if ($ret) {
return $ret;
}
list ($ret, $albumItem) = GalleryCoreApi::createAlbum($parentId, $pathComponent,
$title, $summary, $description, $keywords);
if ($ret) {
GalleryCoreApi::releaseLocks($lockIds);
return $ret;
}
if (!isset($albumItem)) {
GalleryCoreApi::releaseLocks($lockIds);
return GalleryCoreApi::error(ERROR_MISSING_OBJECT);
}
$ret = GalleryCoreApi::addUserPermission($albumItem->getId(), $albumItem->getOwnerId(),
'core.all', false);
if ($ret) {
GalleryCoreApi::releaseLocks($lockIds);
return $ret;
}
$ret = GalleryCoreApi::releaseLocks($lockIds);
if ($ret) {
return $ret;
}
}
/**
* MKCOL handler.
* @return object GalleryStatus a status code
*/
function mkcol() {
/* Body parsing not yet supported */
if (GalleryUtilities::getServerVar('CONTENT_LENGTH')) {
/*
* 415 (Unsupported Media Type) - The server does not support the request type of the
* body.
*/
WebDavServer::setResponseStatus('415 Unsupported Media Type');
return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
}
$path = GalleryUtilities::getRequestVariables('path');
$path = trim($path, '/');
list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
if ($ret && !($ret->getErrorCode() & ERROR_MISSING_OBJECT)) {
return $ret;
}
if (!$ret) {
/*
* 405 (Method Not Allowed) - MKCOL can only be executed on a deleted/non-existent
* resource.
*/
WebDavServer::setResponseStatus('405 Method Not Allowed');
return GalleryCoreApi::error(ERROR_COLLISION);
}
$pathComponent = basename($path);
list ($ret, $parentId) = WebDavHelper::getParentItemIdByPath($path);
if ($ret) {
if ($ret->getErrorCode() & ERROR_MISSING_OBJECT) {
/*
* 409 (Conflict) - A resource cannot be created at the destination until one or
* more intermediate collections have been created.
*/
WebDavServer::setResponseStatus('409 Conflict');
}
return $ret;
}
list ($ret, $error) = WebDavHelper::mkcolValidateHelper($parentId, $pathComponent);
if ($ret) {
return $ret;
}
if (!empty($error)) {
foreach ($error as $error) {
if (!strpos($error, 'permission')) {
return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
}
}
/* If all errors were permission denied return more specific error */
return GalleryCoreApi::error(ERROR_PERMISSION_DENIED);
}
$originalPath = GalleryUtilities::getRequestVariables('originalPath');
$title = empty($originalPath) ? $pathComponent : basename($originalPath);
$ret = WebDavHelper::mkcolHelper($parentId, $pathComponent, $title, '', '', '');
if ($ret) {
if ($ret->getErrorCode() & ERROR_ILLEGAL_CHILD) {
/*
* 403 (Forbidden) - This indicates at least one of two conditions: 1) the server
* does not allow the creation of collections at the given location in its
* namespace, or 2) the parent collection of the Request-URI exists but cannot
* accept members.
*/
WebDavServer::setResponseStatus('403 Forbidden');
}
return $ret;
}
/*
* 201 (Created) - The collection or structured resource was created in its entirety.
*/
WebDavServer::setResponseStatus('201 Created');
}
/**
* DELETE helper.
*
* For an array of item ids, delete the items if not the root album and the user has permission.
*
* Copied from ItemDeleteController::handleRequest for consistancy. Maybe eventually should go
* in ItemDeleteController::requestHelper or a GalleryCoreApi method.
*
* @param array $itemIds ids of items to delete
* @return array object GalleryStatus a status code
* int number of items deleted
* @see ItemDeleteController::handleRequest
*/
function deleteHelper($itemIds) {
if (!is_array($itemIds)) {
$itemIds = array($itemIds);
}
/* Get the rootId, so we don't try to delete it */
list ($ret, $rootId) = GalleryCoreApi::getDefaultAlbumId();
if ($ret) {
return array($ret, null);
}
$ret = GalleryCoreApi::studyPermissions($itemIds);
if ($ret) {
return array($ret, null);
}
foreach ($itemIds as $itemId) {
/* Make sure we have permission to delete this item */
list ($ret, $permissions) = GalleryCoreApi::getPermissions($itemId);
if ($ret) {
return array($ret, null);
}
if (!isset($permissions['core.delete'])) {
return array(GalleryCoreApi::error(ERROR_PERMISSION_DENIED, __FILE__, __LINE__,
"Don't have permission to delete this item"), null);
}
/* Make sure we're not deleting the root album */
if ($itemId == $rootId) {
return array(GalleryCoreApi::error(ERROR_BAD_PARAMETER, __FILE__, __LINE__,
"Can't delete the root album"), null);
}
}
/* If we're still here then all are deletable */
$count = 0;
foreach ($itemIds as $itemId) {
$ret = GalleryCoreApi::deleteEntityById($itemId);
if ($ret) {
return array($ret, null);
}
$count++;
}
return array(null, $count);
}
/**
* DELETE handler.
* @return object GalleryStatus a status code
*/
function delete() {
/* RFC2518 9.2 last paragraph */
if (GalleryUtilities::getServerVar('HTTP_DEPTH') != null
&& GalleryUtilities::getServerVar('HTTP_DEPTH') != 'infinity') {
WebDavServer::setResponseStatus('400 Bad Request');
return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
}
$path = GalleryUtilities::getRequestVariables('path');
$path = trim($path, '/');
/* Check resource is not locked */
$ret = WebDavHelper::checkLocks($path);
if ($ret) {
return $ret;
}
if (empty($path)) {
list ($ret, $itemId) = GalleryCoreApi::getPluginParameter('module', 'core',
'id.rootAlbum');
if ($ret) {
return $ret;
}
} else {
list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
if ($ret) {
return $ret;
}
}
list ($ret, $item) = GalleryCoreApi::loadEntitiesById($itemId);
if ($ret) {
return $ret;
}
list ($ret, $count) = WebDavHelper::deleteHelper($itemId);
if ($ret) {
return $ret;
}
/* What do we do if we weren't successful? No thumbnail, I guess. */
list ($ret, $success) = GalleryCoreApi::guaranteeAlbumHasThumbnail($item->getParentId());
if ($ret) {
return $ret;
}
WebDavServer::setResponseStatus('204 No Content');
return null;
}
/**
* PUT request helper.
*
* Wrapper around HTTP_WebDAV_Server::put_request_helper which prepares data-structures from PUT
* requests.
*
* @return array object GalleryStatus a status code
* array WebDAV library options
* resource request body file handle
* string request content type
* @see HTTP_WebDAV_Server::put_request_helper
*/
function putRequestHelper() {
$webDavServer =& WebDavHelper::getWebDavServer();
if (!$webDavServer->put_request_helper($webDavOptions)) {
/* WebDAV library found error in the request */
return array(GalleryCoreApi::error(ERROR_UNKNOWN), null, null, null);
}
return array(null, $webDavOptions,
$webDavOptions['stream'],
$webDavOptions['content_type']);
}
/**
* PUT response helper.
*
* Wrapper around HTTP_WebDAV_Server::put_response_helper which formats PUT responses.
*
* @param array $webDavOptions WebDAV library options
* @param resource $stream destination file handle
* @see HTTP_WebDAV_Server::put_response_helper
*/
function putResponseHelper($webDavOptions, $stream) {
$webDavServer =& WebDavHelper::getWebDavServer();
$webDavOptions['new'] = false;
$webDavServer->put_response_helper($webDavOptions, $stream);
}
/**
* COPY / MOVE request helper.
*
* Wrapper around HTTP_WebDAV_Server::copymove_request_helper which prepates data-structures
* from COPY / MOVE requests.
*
* @return array object GalleryStatus a status code
* array WebDAV library options
* int maximum depth of descendant paths
* boolean overwrite items at destination path
* string destination path
* @see HTTP_WebDAV_Server::copymove_request_helper
*/
function copyMoveRequestHelper() {
global $gallery;
$platform =& $gallery->getPlatform();
$webDavServer =& WebDavHelper::getWebDavServer();
/* Body parsing not yet supported */
if (GalleryUtilities::getServerVar('CONTENT_LENGTH')) {
/*
* 415 (Unsupported Media Type) - The server does not support the request type of the
* body.
*/
WebDavServer::setResponseStatus('415 Unsupported Media Type');
return array(GalleryCoreApi::error(ERROR_BAD_PARAMETER), null, null, null, null);
}
if (!$webDavServer->copymove_request_helper($webDavOptions)) {
/* WebDAV library found error in the request */
return array(GalleryCoreApi::error(ERROR_UNKNOWN), null, null, null, null);
}
/* Copying to remote servers not yet supported */
if (isset($webDavOptions['dest_url'])) {
/*
* 502 (Bad Gateway) - This may occur when the destination is on another server and the
* destination server refuses to accept the resource.
*/
WebDavServer::setResponseStatus('502 Bad Gateway');
return array(GalleryCoreApi::error(ERROR_BAD_PARAMETER), null, null, null, null);
}
/* Check destination is legal */
$pathComponent = basename($webDavOptions['dest']);
if (!$platform->isLegalPathComponent($pathComponent)) {
WebDavServer::setResponseStatus('403 Forbidden');
return array(GalleryCoreApi::error(ERROR_BAD_PATH), null, null, null, null);
}
return array(null, $webDavOptions,
$webDavOptions['depth'],
$webDavOptions['overwrite'],
$webDavOptions['dest']);
}
/**
* Validate MOVE requests.
*
* Copied from ItemMoveController::handleRequest for consistancy. Maybe eventually should go in
* ItemMoveController::validateRequest or a GalleryCoreApi method.
*
* @param array $items items to move
* @param object GalleryAlbumItem $newParent
* @return array object GalleryStatus a status code
* array error strings
* @see ItemMoveController::handleRequest
*/
function moveValidateHelper($items, $newParent) {
if (!is_array($items)) {
$items = array($items);
}
$error = array();
if (empty($newParent)) {
$error[] = 'form[error][destination][empty]';
}
if (!empty($newParent)) {
$newParentId = $newParent->getId();
list ($ret, $permissions) = GalleryCoreApi::getPermissions($newParentId);
if ($ret) {
return array($ret, null);
}
$canAddItem = isset($permissions['core.addDataItem']);
$canAddAlbum = isset($permissions['core.addAlbumItem']);
if (!$canAddAlbum && !$canAddItem) {
$error[] = 'form[error][destination][permission]';
}
if (!GalleryUtilities::isA($newParent, 'GalleryAlbumItem')) {
/* The view should never let this happen */
return array(GalleryCoreApi::error(ERROR_BAD_DATA_TYPE), null);
}
/* Load destination parent ids: We don't want recursive moves */
list ($ret, $newParentAncestorIds) = GalleryCoreApi::fetchParentSequence($newParentId);
if ($ret) {
return array($ret, null);
}
$newParentAncestorIds[] = $newParentId;
}
foreach ($items as $item) {
$itemId = $item->getId();
if (!empty($newParent)) {
/* Can't move into a tree that is included in the source */
if (in_array($itemId, $newParentAncestorIds)) {
$error[] = 'form[error][source][' . $itemId . '][selfMove]';
continue;
}
}
list ($ret, $permissions) = GalleryCoreApi::getPermissions($itemId);
if ($ret) {
return array($ret, null);
}
/* Can we delete this item from here? */
if (!isset($permissions['core.delete'])) {
$error[] = 'form[error][source][' . $itemId . '][permission][delete]';
}
if (!empty($newParent)) {
/* Check if the destination allows this source to be added */
if (GalleryUtilities::isA($item, 'GalleryDataItem')) {
if (!$canAddItem) {
$error[] = 'form[error][source][' . $itemId . '][permission][addDataItem]';
}
} else if (GalleryUtilities::isA($item, 'GalleryAlbumItem')) {
if (!$canAddAlbum) {
$error[] = 'form[error][source][' . $itemId . '][permission][addAlbumItem]';
}
} else {
/* The view should never let this happen */
return array(GalleryCoreApi::error(ERROR_BAD_DATA_TYPE), null);
}
}
}
return array(null, $error);
}
/**
* MOVE handler.
*
* Rename an item, change its parent, or both.
*
* @return object GalleryStatus a status code
*/
function move() {
$path = GalleryUtilities::getRequestVariables('path');
$path = trim($path, '/');
/* Check source is not locked */
$ret = WebDavHelper::checkLocks($path);
if ($ret) {
return $ret;
}
/* Validate before deleting a conflicting item */
list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
if ($ret) {
return $ret;
}
list ($ret, $rootId) = GalleryCoreApi::getPluginParameter('module', 'core', 'id.rootAlbum');
if ($ret) {
return $ret;
}
if ($itemId == $rootId) {
return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
}
list ($ret, $item) = GalleryCoreApi::loadEntitiesById($itemId);
if ($ret) {
return $ret;
}
/* Prepare data-structure from MOVE request */
list ($ret, $webDavOptions, $depth, $overwrite, $newPath) =
WebDavHelper::copyMoveRequestHelper();
if ($ret) {
return $ret;
}
/* Check destination is not locked */
$ret = WebDavHelper::checkLocks($newPath);
if ($ret) {
return $ret;
}
if (GalleryUtilities::isA($item, 'GalleryAlbumItem') && $depth != 'infinity') {
/*
* The MOVE method on a collection MUST act as if a "Depth: infinity" header was used on
* it. A client MUST NOT submit a Depth header on a MOVE on a collection with any value
* but "infinity".
*/
WebDavServer::setResponseStatus('400 Bad Request');
return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
}
list ($ret, $newParentId) = WebDavHelper::getParentItemIdByPath($newPath);
if ($ret) {
if ($ret->getErrorCode() & ERROR_MISSING_OBJECT) {
/*
* 409 (Conflict) - A resource cannot be created at the destination until
* one or more intermediate collections have been created.
*/
WebDavServer::setResponseStatus('409 Conflict');
}
return $ret;
}
list ($ret, $newParent) = GalleryCoreApi::loadEntitiesById($newParentId);
if ($ret) {
return $ret;
}
$pathComponent = basename($path);
$newPathComponent = basename($newPath);
$oldParentId = $item->getParentId();
if ($oldParentId != $newParentId) {
list ($ret, $error) = WebDavHelper::moveValidateHelper($item, $newParent);
if ($ret) {
return $ret;
}
if (!empty($error)) {
foreach ($error as $error) {
if (!strpos($error, 'permission')) {
return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
}
}
/* If all errors were permission denied return more specific error */
return GalleryCoreApi::error(ERROR_PERMISSION_DENIED);
}
}
list ($ret, $conflictingItemId) = GalleryCoreApi::fetchItemIdByPath($newPath);
if ($ret && !($ret->getErrorCode() & ERROR_MISSING_OBJECT)) {
return $ret;
}
if (!$ret) {
if (!$overwrite) {
/*
* 412 (Precondition Failed) - The server was unable to maintain the liveness of the
* properties listed in the propertybehavior XML element or the Overwrite header is
* "F" and the state of the destination resource is non-null.
*/
WebDavServer::setResponseStatus('412 Precondition Failed');
return GalleryCoreApi::error(ERROR_COLLISION);
}
list ($ret, $count) = WebDavHelper::deleteHelper($conflictingItemId);
if ($ret) {
return $ret;
}
}
if ($oldParentId != $newParentId) {
/*
* Read lock both parent hierarchies
* TODO Optimize this
*/
list ($ret, $lockIds[]) = GalleryCoreApi::acquireReadLockParents($newParentId);
if ($ret) {
return $ret;
}
list ($ret, $lockIds[]) = GalleryCoreApi::acquireReadLockParents($oldParentId);
if ($ret) {
GalleryCoreApi::releaseLocks($lockIds);
return $ret;
}
list ($ret, $lockIds[]) =
GalleryCoreApi::acquireReadLock(array($newParentId, $oldParentId));
if ($ret) {
GalleryCoreApi::releaseLocks($lockIds);
return $ret;
}
}
/* Write lock the item we're moving */
list ($ret, $lockIds[]) = GalleryCoreApi::acquireWriteLock($itemId);
if ($ret) {
GalleryCoreApi::releaseLocks($lockIds);
return $ret;
}
/* Refresh the item in case it changed before it was locked */
list ($ret, $item) = $item->refresh();
if ($ret) {
GalleryCoreApi::releaseLocks($lockIds);
return $ret;
}
/* Try renaming first - if it fails it's easier to undo */
if ($newPathComponent != $pathComponent) {
$ret = $item->rename($newPathComponent);
if ($ret) {
GalleryCoreApi::releaseLocks($lockIds);
return $ret;
}
}
if ($newParentId != $oldParentId) {
/* Do the move */
$ret = $item->move($newParentId);
if ($ret) {
if ($newPathComponent != $pathComponent) {
$item->rename($pathComponent);
/* Ignore cascading failures here */
}
GalleryCoreApi::releaseLocks($lockIds);
return $ret;
}
}
$ret = $item->save();
if ($ret) {
if ($newPathComponent != $pathComponent) {
$ret = $item->rename($pathComponent);
/* Ignore cascading failures here */
}
GalleryCoreApi::releaseLocks($lockIds);
return $ret;
}
if ($newParentId != $oldParentId) {
if (GalleryUtilities::isA($item, 'GalleryDataItem')) {
/* Update for derivative preferences of new parent */
$ret = GalleryCoreApi::addExistingItemToAlbum($item, $newParentId);
if ($ret) {
GalleryCoreApi::releaseLocks($lockIds);
return $ret;
}
}
}
/* Release all locks */
$ret = GalleryCoreApi::releaseLocks($lockIds);
if ($ret) {
return $ret;
}
/* Fix thumbnail integrity */
if ($newParentId != $oldParentId) {
/* What do we do if we weren't successful? No thumbnail, I guess. */
list ($ret, $success) = GalleryCoreApi::guaranteeAlbumHasThumbnail($oldParentId);
if ($ret) {
return $ret;
}
}
/* If an item was overwritten fix thumbnail integrity */
if (empty($count)) {
/*
* 201 (Created) - The source resource was successfully copied. The copy operation
* resulted in the creation of a new resource.
*/
WebDavServer::setResponseStatus('201 Created');
return null;
}
/* In case we only renamed the item */
if (empty($newParentId)) {
$newParentId = $item->getParentId();
}
/* What do we do if we weren't successful? No thumbnail, I guess. */
list ($ret, $success) = GalleryCoreApi::guaranteeAlbumHasThumbnail($newParentId);
if ($ret) {
return $ret;
}
/*
* 204 (No Content) - The source resource was successfully copied to a pre-existing
* destination resource.
*/
WebDavServer::setResponseStatus('204 No Content');
return null;
}
/**
* LOCK request helper.
*
* Wrapper around HTTP_WebDAV_Server::lock_request_helper which prepares data-structures from
* LOCK reqeusts.
*
* @return array object GalleryStatus a status code
* array WebDAV library options
* string token of WebDAV lock to refresh or null
* string scope of WebDAV lock (exclusive or shared)
* string type of WebDAV lock (read or write)
* int maximum depth of descendant paths
* string owner of WebDAV lock
* int timeout of WebDAV lock
* string token of WebDAV lock to create
* @see HTTP_WebDAV_Server::lock_request_helper
* @todo Simplify function signature
*/
function lockRequestHelper() {
$webDavServer =& WebDavHelper::getWebDavServer();
if (!$webDavServer->lock_request_helper($webDavOptions)) {
/* WebDAV library found error in the request */
return array(GalleryCoreApi::error(ERROR_UNKNOWN),
null, null, null, null, null, null, null, null);
}
if (isset($webDavOptions['update'])) {
return array(null, $webDavOptions,
$webDavOptions['update'], null, null, null, null, null, null);
}
return array(null, $webDavOptions,
null,
$webDavOptions['scope'],
$webDavOptions['type'],
$webDavOptions['depth'],
$webDavOptions['owner'],
$webDavOptions['timeout'],
$webDavOptions['token']);
}
/**
* LOCK response helper.
*
* Wrapper around HTTP_WebDAV_Server::lock_response_helper which formates LOCK responses.
*
* @param array $webDavOptions WebDAV library options
* @param array $locks for response (path)
* @param mixed $status HTTP response status
* @param string $scope of WebDAV lock (exclusive or shared)
* @param string $type of WebDAV lock (read or write)
* @param int $depth maximum depth of descendant paths
* @param string $owner of WebDAV lock
* @param int $timeout of WebDAV lock
* @param int $expires timestamp of WebDAV lock expiration
* @param string $token of WebDAV lock
* @see HTTP_WebDAV_Server::lock_response_helper
* @todo Simplify function signature
*/
function lockResponseHelper(
$webDavOptions, $locks, $status, $scope, $type, $depth, $owner, $expires, $token) {
$webDavServer =& WebDavHelper::getWebDavServer();
$webDavOptions['locks'] = $locks;
$webDavOptions['scope'] = $scope;
$webDavOptions['type'] = $type;
$webDavOptions['depth'] = $depth;
$webDavOptions['owner'] = $owner;
$webDavOptions['expires'] = $expires;
$webDavOptions['token'] = $token;
$webDavServer->lock_response_helper($webDavOptions, $status);
}
/**
* LOCK handler.
*
* WebDAV locks persist between requests.
*
* @return object GalleryStatus a status code
* @todo Make corresponding Gallery locks persist between requests
*/
function lock() {
global $gallery;
$path = GalleryUtilities::getRequestVariables('path');
$path = trim($path, '/');
/* Check resource is not locked */
$ret = WebDavHelper::checkLocks($path);
if ($ret) {
return $ret;
}
/* Prepare data-structure from LOCK request */
list ($ret, $webDavOptions, $update, $scope, $type, $depth, $owner, $timeout, $token) =
WebDavHelper::lockRequestHelper();
if ($ret) {
return $ret;
}
if (empty($path)) {
list ($ret, $itemId) = GalleryCoreApi::getDefaultAlbumId();
if ($ret) {
return $ret;
}
} else {
list ($ret, $itemId) = GalleryCoreApi::fetchItemIdByPath($path);
if ($ret) {
return $ret;
}
}
/* Refresh lock */
if (!empty($update)) {
/* Don't join with the Gallery lock table since we might be using flock system */
$query = '
SELECT
[WebDavLockMap::depth],
[WebDavLockMap::owner],
[WebDavLockMap::galleryLockId]
FROM
[WebDavLockMap]
WHERE
[WebDavLockMap::path] = ?
AND
[WebDavLockMap::token] = ?';
list ($ret, $results) = $gallery->search($query, array($path, $update));
if ($ret) {
return $ret;
}
/* Tried to refresh a lock which no longer exists */
if (($result = $results->nextResult()) === false) {
/*
* 412 (Precondition Failed) - The included lock token was not enforceable on this
* resource or the server could not satisfy the request in the lockinfo XML element.
*/
WebDavServer::setResponseStatus('412 Precondition Failed');
return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
}
/* Load WebDAV lock information */
$scope = 'exclusive';
$type = 'write';
$depth = $result[0];
$owner = $result[1];
$lockId = $result[2];
/* Check that the Gallery lock didn't disappear before the WebDAV lock */
if (GalleryCoreApi::isWriteLocked($itemId)) {
/* TODO: Need an interface to update g_freshUntil for only $lockId */
} else {
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($itemId);
if ($ret) {
return $ret;
}
}
} else {
/* Support only exclusive write locks */
if ($scope != 'exclusive' || $type != 'write') {
/*
* 412 (Precondition Failed) - The included lock token was not enforceable on this
* resource or the server could not satisfy the request in the lockinfo XML element.
*/
WebDavServer::setResponseStatus('412 Precondition Failed');
return GalleryCoreApi::error(ERROR_BAD_PARAMETER);
}
if ($depth == 'infinity') {
list ($ret, $locks) = WebDavHelper::getDescendentsLocks($path);
if ($ret) {
return $ret;
}
if (!empty($locks)) {
/* Format LOCK response */
return WebDavHelper::lockResponseHelper(
$webDavOptions, $locks, null, null, null, null, null, null, null);
}
}
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($itemId);
if ($ret) {
return $ret;
}
}
/* Use Gallery lock freshUntil for WebDAV lock timeout */
$query = '
SELECT
[Lock::freshUntil]
FROM
[Lock]
WHERE
[Lock::lockId] = ?';
list ($ret, $results) = $gallery->search($query, array($lockId));
if ($ret) {
return $ret;
}
if (($result = $results->nextResult()) !== false) {
$expires = $result[0];
} else {
/*
* Might be using flock system
* TODO: Get expires from flock locks
return GalleryCoreApi::error(ERROR_MISSING_VALUE);
*/
$expires = time() + 30;
}
/* Refresh lock */
if (!empty($update)) {
$ret = GalleryCoreApi::updateMapEntry('WebDavLockMap',
array('token' => $update, 'path' => $path),
array('expires' => $expires, 'galleryLockId' => $lockId));
if ($ret) {
return $ret;
}
} else {
$ret = GalleryCoreApi::addMapEntry('WebDavLockMap', array('depth' => $depth,
'owner' => $owner,
'expires' => $expires,
'token' => $token,
'path' => $path,
'galleryLockId' => $lockId));
if ($ret) {
return $ret;
}
}
/* Format LOCK response */
$ret = WebDavHelper::lockResponseHelper(
$webDavOptions, null, true, $scope, $type, $depth, $owner, $expires, $token);
if ($ret) {
return $ret;
}
}
/**
* UNLOCK request helper.
*
* Wrapper around HTTP_WebDAV_Server::unlock_request_helper wich prepares data-structures from
* UNLOCK requests.
*
* @return array object GalleryStatus a status code
* array WebDAV library options
* string token of WebDAV lock to clear
* @see HTTP_WebDAV_Server::unlock_request_helper
*/
function unlockRequestHelper() {
$webDavServer =& WebDavHelper::getWebDavServer();
if (!$webDavServer->unlock_request_helper($webDavOptions)) {
/* WebDAV library found error in the request */
return array(GalleryCoreApi::error(ERROR_UNKNOWN), null, null);
}
return array(null, $webDavOptions, $webDavOptions['token']);
}
/**
* UNLOCK handler.
* @return object GalleryStatus a status code
*/
function unlock() {
global $gallery;
/* Prepare data-structure from UNLOCK request */
list ($ret, $webDavOptions, $token) = WebDavHelper::unlockRequestHelper();
if ($ret) {
return $ret;
}
$path = GalleryUtilities::getRequestVariables('path');
$path = trim($path, '/');
$query = '
SELECT
[WebDavLockMap::galleryLockId]
FROM
[WebDavLockMap]
WHERE
[WebDavLockMap::path] = ?
AND
[WebDavLockMap::token] = ?';
list ($ret, $results) = $gallery->search($query, array($path, $token));
if ($ret) {
return $ret;
}
if (($result = $results->nextResult()) === false) {
return GalleryCoreApi::error(ERROR_MISSING_VALUE);
}
$lockId = $result[0];
$ret = GalleryCoreApi::releaseLocks($lockId);
if ($ret) {
return $ret;
}
$ret = GalleryCoreApi::removeMapEntry(
'WebDavLockMap', array('token' => $token, 'path' => $path));
if ($ret) {
return $ret;
}
/*
* The 204 (No Content) status code is used instead of 200 (OK) because there is no response
* entity body.
*/
WebDavServer::setResponseStatus('204 No Content');
return null;
}
}
/**
* Sub-class of HTTP_WebDAV_Server which overrides getHref, openRequestBody, setResponseHeader and
* setResponseStatus. getHref uses the URL generator. openRequestBody uses the platform, for
* testability. setResponseHeader and setResponseStatus use GalleryUtilities::setResponseHeader to
* avoid response headers being replaced elsewhere in Gallery and for testability, since
* GalleryUtilities::setResponseHeader uses $phpVm->header.
*/
class WebDavServer extends HTTP_WebDAV_Server {
/**
* @see HTTP_WebDAV_Server::getHref
*/
function getHref($path) {
global $gallery;
$urlGenerator =& $gallery->getUrlGenerator();
return $urlGenerator->generateUrl(
array('controller' => 'webdav.WebDav', 'path' => $path),
array('forceServerRelativeUrl' => true,
'forceSessionId' => false,
'useAuthToken' => false));
}
/**
* @see HTTP_WebDAV_Server::openRequestBody
*/
function openRequestBody() {
global $gallery;
$platform =& $gallery->getPlatform();
return $platform->fopen('php://input', 'rb');
}
/**
* @see HTTP_WebDAV_Server::setResponseHeader
*/
function setResponseHeader($header, $replace=true) {
GalleryUtilities::setResponseHeader($header, $replace);
}
/**
* @see HTTP_WebDAV_Server::setResponseStatus
*/
function setResponseStatus($status, $replace=true) {
GalleryUtilities::setResponseHeader("HTTP/1.0 $status", $replace);
}
}
?>