| 1 |
<?php
|
| 2 |
// $Id$
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* Provides connectivity for the WebDAV protocol, enabling other modules to
|
| 7 |
* export and use WebDAV resources.
|
| 8 |
*/
|
| 9 |
|
| 10 |
//////////////////////////////////////////////////////////////////////////////
|
| 11 |
// Module settings
|
| 12 |
|
| 13 |
define('DAV_ROOT', variable_get('dav_root', 'dav'));
|
| 14 |
define('DAV_ROOT_MODULE', variable_get('dav_root_module', 'dav_fs'));
|
| 15 |
define('DAV_SERVER', variable_get('dav_server', FALSE));
|
| 16 |
define('DAV_CLIENT', variable_get('dav_client', FALSE));
|
| 17 |
define('DAV_DOT_FILES', variable_get('dav_dot_files', TRUE));
|
| 18 |
define('DAV_TRACE', variable_get('dav_trace', FALSE));
|
| 19 |
define('DAV_ENCODE', variable_get('dav_encode', ' &<>|'));
|
| 20 |
define('DAV_ICONS', variable_get('dav_icons', module_exists('file')));
|
| 21 |
define('DAV_CRON', variable_get('dav_cron', TRUE));
|
| 22 |
|
| 23 |
define('DAV_WINDOWS_SERVER_DISCOVERY', variable_get('dav_windows_server_discovery', TRUE));
|
| 24 |
define('DAV_WINDOWS_BASIC_AUTH', variable_get('dav_windows_basic_auth', TRUE));
|
| 25 |
define('DAV_WINDOWS_NO_THUMBS_DB', variable_get('dav_windows_no_thumbs_db', TRUE));
|
| 26 |
define('DAV_MACOSX_NO_DS_STORE', variable_get('dav_macosx_no_ds_store', TRUE));
|
| 27 |
define('DAV_MACOSX_NO_FORKS', variable_get('dav_macosx_no_forks', TRUE));
|
| 28 |
|
| 29 |
define('DAV_PEAR_SERVER', 'http://pear.php.net/package/HTTP_WebDAV_Server');
|
| 30 |
define('DAV_PEAR_CLIENT', 'http://pear.php.net/package/HTTP_WebDAV_Client');
|
| 31 |
|
| 32 |
define('DAV_ROOT_COLLECTION', NULL);
|
| 33 |
define('DAV_TRANSIENT_RESOURCE', 'dav_blob');
|
| 34 |
|
| 35 |
//////////////////////////////////////////////////////////////////////////////
|
| 36 |
// Core API hooks
|
| 37 |
|
| 38 |
/**
|
| 39 |
* Implementation of hook_help().
|
| 40 |
*/
|
| 41 |
function dav_help($path, $arg = NULL) {
|
| 42 |
switch ($path) {
|
| 43 |
case 'admin/settings/dav':
|
| 44 |
return '<p>'. t('') .'</p>'; // TODO
|
| 45 |
}
|
| 46 |
}
|
| 47 |
|
| 48 |
/**
|
| 49 |
* Implementation of hook_perm().
|
| 50 |
*/
|
| 51 |
function dav_perm() {
|
| 52 |
return array(
|
| 53 |
'access DAV resources',
|
| 54 |
'create DAV resources',
|
| 55 |
'rename DAV resources',
|
| 56 |
'move DAV resources',
|
| 57 |
'update DAV resources',
|
| 58 |
'delete DAV resources',
|
| 59 |
);
|
| 60 |
}
|
| 61 |
|
| 62 |
/**
|
| 63 |
* Implementation of hook_menu().
|
| 64 |
*/
|
| 65 |
function dav_menu() {
|
| 66 |
return array(
|
| 67 |
// DAV endpoint
|
| 68 |
DAV_ROOT => array(
|
| 69 |
'title' => 'DAV',
|
| 70 |
'type' => MENU_CALLBACK,
|
| 71 |
'access callback' => 'is_bool',
|
| 72 |
'access arguments' => array(TRUE), // further access controls are enforced in the DAV server
|
| 73 |
'page callback' => 'dav_request',
|
| 74 |
),
|
| 75 |
// Administer >> Site configuration >> DAV settings
|
| 76 |
'admin/settings/dav' => array(
|
| 77 |
'title' => 'DAV settings',
|
| 78 |
'description' => 'Configure WebDAV server settings.',
|
| 79 |
'access arguments' => array('administer site configuration'),
|
| 80 |
'page callback' => 'drupal_get_form',
|
| 81 |
'page arguments' => array('dav_admin_settings'),
|
| 82 |
'file' => 'dav.admin.inc',
|
| 83 |
),
|
| 84 |
);
|
| 85 |
}
|
| 86 |
|
| 87 |
/**
|
| 88 |
* Implementation of hook_init().
|
| 89 |
*/
|
| 90 |
function dav_init() {
|
| 91 |
if (DAV_CLIENT) {
|
| 92 |
// Register the "webdav(s)://" PHP stream wrappers (aka fopen()
|
| 93 |
// wrappers) using the PEAR WebDAV Client
|
| 94 |
_dav_load_client();
|
| 95 |
}
|
| 96 |
}
|
| 97 |
|
| 98 |
/**
|
| 99 |
* Implementation of hook_cron().
|
| 100 |
*/
|
| 101 |
function dav_cron() {
|
| 102 |
if (DAV_CRON) {
|
| 103 |
// Purge all expired resource locks from the database
|
| 104 |
db_query("DELETE FROM {dav_locks} WHERE expires_at < %d", time());
|
| 105 |
}
|
| 106 |
}
|
| 107 |
|
| 108 |
/**
|
| 109 |
* Implementation of hook_theme()
|
| 110 |
*/
|
| 111 |
function dav_theme() {
|
| 112 |
return array(
|
| 113 |
'dav_page' => array(
|
| 114 |
'arguments' => array('path' => NULL, 'resources' => NULL),
|
| 115 |
'file' => 'dav.theme.inc',
|
| 116 |
),
|
| 117 |
'dav_resource' => array(
|
| 118 |
'arguments' => array('path' => NULL, 'name' => NULL, 'resource' => NULL),
|
| 119 |
'file' => 'dav.theme.inc',
|
| 120 |
),
|
| 121 |
'dav_footer' => array(
|
| 122 |
'arguments' => array(),
|
| 123 |
'file' => 'dav.theme.inc',
|
| 124 |
),
|
| 125 |
);
|
| 126 |
}
|
| 127 |
|
| 128 |
//////////////////////////////////////////////////////////////////////////////
|
| 129 |
// DAV API hooks (namespace/metadata)
|
| 130 |
|
| 131 |
/**
|
| 132 |
* Implementation of hook_dav_lookup().
|
| 133 |
*/
|
| 134 |
function dav_dav_lookup($collection, $name) {
|
| 135 |
global $user;
|
| 136 |
|
| 137 |
if (_dav_is_transient_file($name)) {
|
| 138 |
$resources = dav_dav_list($collection);
|
| 139 |
return isset($resources[$name]) ? $resources[$name] : NULL;
|
| 140 |
}
|
| 141 |
}
|
| 142 |
|
| 143 |
/**
|
| 144 |
* Implementation of hook_dav_list().
|
| 145 |
*/
|
| 146 |
function dav_dav_list($collection) {
|
| 147 |
global $user;
|
| 148 |
|
| 149 |
$resources = array();
|
| 150 |
if (($container_id = dav_intern_resource($collection, NULL, FALSE)) !== NULL) {
|
| 151 |
$result = db_query("SELECT * FROM {dav_resources} WHERE type = '%s' AND `key` LIKE '%s'", DAV_TRANSIENT_RESOURCE, $container_id .'#%');
|
| 152 |
while ($resource = db_fetch_object($result)) {
|
| 153 |
if (($blob = db_fetch_object(db_query("SELECT * FROM {dav_blobs} WHERE resource_id = %d AND user_id = %d", $resource->id, $user->uid)))) {
|
| 154 |
list(, $name) = explode('#', $resource->key, 2);
|
| 155 |
$resources[$name] = array(DAV_TRANSIENT_RESOURCE, $resource->key);
|
| 156 |
}
|
| 157 |
}
|
| 158 |
}
|
| 159 |
return $resources;
|
| 160 |
}
|
| 161 |
|
| 162 |
/**
|
| 163 |
* Implementation of hook_dav_propfind().
|
| 164 |
*/
|
| 165 |
function dav_dav_propfind($resource) {
|
| 166 |
global $user;
|
| 167 |
|
| 168 |
list($type, $key) = $resource;
|
| 169 |
$props = array();
|
| 170 |
switch ($type) {
|
| 171 |
// Somebody has to provide the metadata for the root collection, so we
|
| 172 |
// might as well do it right here to save DAV modules the trouble.
|
| 173 |
case DAV_ROOT_COLLECTION:
|
| 174 |
$props['displayname'] = '/';
|
| 175 |
$props['creationdate'] = time();
|
| 176 |
$props['getlastmodified'] = time();
|
| 177 |
$props['resourcetype'] = 'collection';
|
| 178 |
$props['getcontenttype'] = 'httpd/unix-directory';
|
| 179 |
break;
|
| 180 |
case DAV_TRANSIENT_RESOURCE:
|
| 181 |
if (($blob = _dav_get_transient_blob($resource))) {
|
| 182 |
list(, $name) = explode('#', $key, 2);
|
| 183 |
$props['displayname'] = $name;
|
| 184 |
$props['creationdate'] = time();
|
| 185 |
$props['getlastmodified'] = time();
|
| 186 |
$props['resourcetype'] = '';
|
| 187 |
$props['getcontenttype'] = $blob->content_type;
|
| 188 |
$props['getcontentlength'] = $blob->content_length;
|
| 189 |
}
|
| 190 |
break;
|
| 191 |
default:
|
| 192 |
// TODO: Obtain arbitrary properties for arbitrary resources
|
| 193 |
break;
|
| 194 |
}
|
| 195 |
return $props;
|
| 196 |
}
|
| 197 |
|
| 198 |
//////////////////////////////////////////////////////////////////////////////
|
| 199 |
// DAV API hooks (verbs)
|
| 200 |
|
| 201 |
/**
|
| 202 |
* Implementation of hook_dav_get().
|
| 203 |
*/
|
| 204 |
function dav_dav_get($resource, &$options) {
|
| 205 |
global $user;
|
| 206 |
|
| 207 |
if (_dav_is_transient_resource($resource)) {
|
| 208 |
if (($blob = _dav_get_transient_blob($resource))) {
|
| 209 |
$options['mtime'] = time();
|
| 210 |
$options['mimetype'] = $blob->content_type;
|
| 211 |
$options['size'] = $blob->content_length;
|
| 212 |
$options['data'] = $blob->content;
|
| 213 |
return $options;
|
| 214 |
}
|
| 215 |
return FALSE;
|
| 216 |
}
|
| 217 |
}
|
| 218 |
|
| 219 |
/**
|
| 220 |
* Implementation of hook_dav_put().
|
| 221 |
*/
|
| 222 |
function dav_dav_put($container, $name, &$options, $filepath = NULL) {
|
| 223 |
global $user;
|
| 224 |
|
| 225 |
if (_dav_is_transient_file($name)) {
|
| 226 |
if (empty($filepath)) { // pre-process upload
|
| 227 |
return TRUE; // accept the file upload
|
| 228 |
}
|
| 229 |
else { // post-process upload
|
| 230 |
$resource = array(DAV_TRANSIENT_RESOURCE, dav_intern_resource($container) .'#'. $name);
|
| 231 |
$resource_id = dav_intern_resource($resource, 'dav');
|
| 232 |
return db_query("REPLACE INTO {dav_blobs} (resource_id, user_id, content_type, content_length, content) VALUES (%d, %d, '%s', %d, %b)", $resource_id, $user->uid, $options['content_type'], filesize($filepath), file_get_contents($filepath));
|
| 233 |
}
|
| 234 |
}
|
| 235 |
}
|
| 236 |
|
| 237 |
/**
|
| 238 |
* Implementation of hook_dav_delete().
|
| 239 |
*/
|
| 240 |
function dav_dav_delete($resource, $container, $move = FALSE) {
|
| 241 |
global $user;
|
| 242 |
|
| 243 |
if (_dav_is_transient_resource($resource)) {
|
| 244 |
if (($resource_id = dav_intern_resource($resource, NULL, FALSE))) {
|
| 245 |
db_query("DELETE FROM {dav_blobs} WHERE resource_id = %d AND user_id = %d", $resource_id, $user->uid);
|
| 246 |
db_query("DELETE FROM {dav_resources} WHERE id = %d", $resource_id); // Hmm
|
| 247 |
return TRUE;
|
| 248 |
}
|
| 249 |
return FALSE;
|
| 250 |
}
|
| 251 |
}
|
| 252 |
|
| 253 |
/**
|
| 254 |
* Implementation of hook_dav_rename().
|
| 255 |
*/
|
| 256 |
function dav_dav_rename($resource, $source_name, $target_name) {
|
| 257 |
global $user;
|
| 258 |
|
| 259 |
// TODO: if attempting to rename to non-transient name, we should probably
|
| 260 |
// perform an internal PUT request to handle it correctly
|
| 261 |
if (_dav_is_transient_resource($resource)) {
|
| 262 |
if (($resource_id = dav_intern_resource($resource, NULL, FALSE))) {
|
| 263 |
$key = str_replace($source_name, $target_name, $resource[1]);
|
| 264 |
db_query("UPDATE {dav_resources} SET `key` WHERE id = %d", $key, $resource_id);
|
| 265 |
return TRUE;
|
| 266 |
}
|
| 267 |
}
|
| 268 |
}
|
| 269 |
|
| 270 |
//////////////////////////////////////////////////////////////////////////////
|
| 271 |
// DAV module implementation
|
| 272 |
|
| 273 |
/**
|
| 274 |
* Menu callback dispatching an incoming DAV request to the
|
| 275 |
* HTTP_WebDAV_Server-based implementation class.
|
| 276 |
*/
|
| 277 |
function dav_request() {
|
| 278 |
// If DAV server functionality has been disabled, we'll just spew out a
|
| 279 |
// 404 Not Found and pretend this never happened.
|
| 280 |
if (!DAV_SERVER) {
|
| 281 |
return drupal_not_found();
|
| 282 |
}
|
| 283 |
|
| 284 |
// Make sure the administrator understands that installing
|
| 285 |
// HTTP_WebDAV_Server really is not merely optional...
|
| 286 |
if (!_dav_load_server()) {
|
| 287 |
watchdog('dav', 'Unable to serve DAV request because the PEAR HTTP_WebDAV_Server library is not available.', array(), WATCHDOG_ERROR);
|
| 288 |
return drupal_access_denied();
|
| 289 |
}
|
| 290 |
|
| 291 |
// Make sure that at least one (other) module implements our API:
|
| 292 |
if (count(module_implements('dav_lookup')) < 2) {
|
| 293 |
watchdog('dav', 'Unable to serve DAV request because no modules implementing the DAV API are available.', array(), WATCHDOG_ERROR);
|
| 294 |
return drupal_access_denied();
|
| 295 |
}
|
| 296 |
|
| 297 |
// Calculate the request path for DAV.
|
| 298 |
$path = substr($_GET['q'], strlen(DAV_ROOT));
|
| 299 |
$path = !empty($path) ? $path : '/';
|
| 300 |
|
| 301 |
// Redirect to a URL ending in a slash if at the top level.
|
| 302 |
// This prevents some relative linking problems further down.
|
| 303 |
if ($path == '/' && !preg_match("/\/$/", $_SERVER['REQUEST_URI'])) {
|
| 304 |
drupal_goto(DAV_ROOT .'/', NULL, NULL, 301);
|
| 305 |
}
|
| 306 |
|
| 307 |
// All is green, so boot us up to DAV specs and hand over the reins to our
|
| 308 |
// bastard OO offspring of HTTP_WebDAV_Server, pretty much ending Drupal's
|
| 309 |
// active involvement in serving the request:
|
| 310 |
module_load_include('inc', 'dav');
|
| 311 |
drupal_dav_server::serve(url(DAV_ROOT), $path);
|
| 312 |
}
|
| 313 |
|
| 314 |
//////////////////////////////////////////////////////////////////////////////
|
| 315 |
// DAV core helpers
|
| 316 |
|
| 317 |
function dav_get_modules($op = NULL) {
|
| 318 |
switch ($op) {
|
| 319 |
case 'info':
|
| 320 |
$modules = dav_get_modules();
|
| 321 |
if (!empty($modules)) {
|
| 322 |
$result = db_query("SELECT name, info FROM {system} WHERE type = 'module' AND name IN (". db_placeholders($modules, 'varchar') .") ORDER BY weight ASC", $modules);
|
| 323 |
while ($row = db_fetch_object($result)) {
|
| 324 |
$modules[$row->name] = (object)unserialize($row->info);
|
| 325 |
}
|
| 326 |
}
|
| 327 |
return $modules;
|
| 328 |
case 'titles':
|
| 329 |
$modules = dav_get_modules('info');
|
| 330 |
foreach ($modules as $name => $info) {
|
| 331 |
$modules[$name] = $info->name;
|
| 332 |
}
|
| 333 |
return $modules;
|
| 334 |
case 'names':
|
| 335 |
default:
|
| 336 |
$modules = array_diff(module_implements('dav_list'), array('dav'));
|
| 337 |
return array_combine($modules, $modules);
|
| 338 |
}
|
| 339 |
}
|
| 340 |
|
| 341 |
function _dav_is_transient_file($name) {
|
| 342 |
return ($name[0] == '.'); // TODO
|
| 343 |
}
|
| 344 |
|
| 345 |
function _dav_is_transient_resource($resource) {
|
| 346 |
return is_array($resource) && $resource[0] == DAV_TRANSIENT_RESOURCE;
|
| 347 |
}
|
| 348 |
|
| 349 |
function _dav_get_transient_blob($resource) {
|
| 350 |
global $user;
|
| 351 |
if (_dav_is_transient_resource($resource)) {
|
| 352 |
if (($resource_id = dav_intern_resource($resource, NULL, FALSE)) !== NULL) {
|
| 353 |
if (($blob = db_fetch_object(db_query("SELECT * FROM {dav_blobs} WHERE resource_id = %d AND user_id = %d", $resource_id, $user->uid)))) {
|
| 354 |
$blob->content = db_decode_blob($blob->content);
|
| 355 |
return $blob;
|
| 356 |
}
|
| 357 |
}
|
| 358 |
}
|
| 359 |
}
|
| 360 |
|
| 361 |
function _dav_authenticate($name, $pass) {
|
| 362 |
// This is an ugly special case to support the LDAP auth module, which
|
| 363 |
// does not use Drupal's innate authentication API.
|
| 364 |
if (function_exists('ldapauth_authenticate')) {
|
| 365 |
// ldapauth_authenticate() sets the global $user variable if successful
|
| 366 |
ldapauth_authenticate(array('name' => $name, 'pass' => $pass));
|
| 367 |
return $GLOBALS['user'];
|
| 368 |
}
|
| 369 |
else {
|
| 370 |
// user_authenticate() returns a $user object
|
| 371 |
return user_authenticate(array('name' => $name, 'pass' => $pass));
|
| 372 |
}
|
| 373 |
}
|
| 374 |
|
| 375 |
function _dav_init_include_path() {
|
| 376 |
static $done = FALSE;
|
| 377 |
if (!$done) {
|
| 378 |
set_include_path(dirname(__FILE__) .'/vendor'. PATH_SEPARATOR . get_include_path());
|
| 379 |
$done = TRUE;
|
| 380 |
}
|
| 381 |
}
|
| 382 |
|
| 383 |
function _dav_load_server() {
|
| 384 |
_dav_init_include_path();
|
| 385 |
return (@include_once('HTTP/WebDAV/Server.php')) !== FALSE;
|
| 386 |
}
|
| 387 |
|
| 388 |
function _dav_load_client() {
|
| 389 |
_dav_init_include_path();
|
| 390 |
return (@include_once('HTTP/WebDAV/Client.php')) !== FALSE;
|
| 391 |
}
|