6 * This plugin provides a html representation, so that a WebDAV server may be accessed
9 * The class intercepts GET requests to collection resources and generates a simple
14 * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
15 * @author Evert Pot (http://www.rooftopsolutions.nl/)
16 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
18 class Sabre_DAV_Browser_Plugin extends Sabre_DAV_ServerPlugin {
21 * List of default icons for nodes.
23 * This is an array with class / interface names as keys, and asset names
26 * The evaluation order is reversed. The last item in the list gets
31 public $iconMap = array(
32 'Sabre_DAV_IFile' => 'icons/file',
33 'Sabre_DAV_ICollection' => 'icons/collection',
34 'Sabre_DAVACL_IPrincipal' => 'icons/principal',
35 'Sabre_CalDAV_ICalendar' => 'icons/calendar',
36 'Sabre_CardDAV_IAddressBook' => 'icons/addressbook',
37 'Sabre_CardDAV_ICard' => 'icons/card',
41 * The file extension used for all icons
45 public $iconExtension = '.png';
48 * reference to server class
50 * @var Sabre_DAV_Server
55 * enablePost turns on the 'actions' panel, which allows people to create
56 * folders and upload files straight from a browser.
60 protected $enablePost = true;
63 * By default the browser plugin will generate a favicon and other images.
64 * To turn this off, set this property to false.
68 protected $enableAssets = true;
73 * By default it will allow file creation and uploads.
74 * Specify the first argument as false to disable this
76 * @param bool $enablePost
77 * @param bool $enableAssets
79 public function __construct($enablePost=true, $enableAssets = true) {
81 $this->enablePost = $enablePost;
82 $this->enableAssets = $enableAssets;
87 * Initializes the plugin and subscribes to events
89 * @param Sabre_DAV_Server $server
92 public function initialize(Sabre_DAV_Server $server) {
94 $this->server = $server;
95 $this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor'));
96 $this->server->subscribeEvent('onHTMLActionsPanel', array($this, 'htmlActionsPanel'),200);
97 if ($this->enablePost) $this->server->subscribeEvent('unknownMethod',array($this,'httpPOSTHandler'));
101 * This method intercepts GET requests to collections and returns the html
103 * @param string $method
107 public function httpGetInterceptor($method, $uri) {
109 if ($method !== 'GET') return true;
111 // We're not using straight-up $_GET, because we want everything to be
114 parse_str($this->server->httpRequest->getQueryString(), $getVars);
116 if (isset($getVars['sabreAction']) && $getVars['sabreAction'] === 'asset' && isset($getVars['assetName'])) {
117 $this->serveAsset($getVars['assetName']);
122 $node = $this->server->tree->getNodeForPath($uri);
123 } catch (Sabre_DAV_Exception_NotFound $e) {
124 // We're simply stopping when the file isn't found to not interfere
125 // with other plugins.
128 if ($node instanceof Sabre_DAV_IFile)
131 $this->server->httpResponse->sendStatus(200);
132 $this->server->httpResponse->setHeader('Content-Type','text/html; charset=utf-8');
134 $this->server->httpResponse->sendBody(
135 $this->generateDirectoryIndex($uri)
143 * Handles POST requests for tree operations.
145 * @param string $method
149 public function httpPOSTHandler($method, $uri) {
151 if ($method!='POST') return;
152 $contentType = $this->server->httpRequest->getHeader('Content-Type');
153 list($contentType) = explode(';', $contentType);
154 if ($contentType !== 'application/x-www-form-urlencoded' &&
155 $contentType !== 'multipart/form-data') {
158 $postVars = $this->server->httpRequest->getPostVars();
160 if (!isset($postVars['sabreAction']))
163 if ($this->server->broadcastEvent('onBrowserPostAction', array($uri, $postVars['sabreAction'], $postVars))) {
165 switch($postVars['sabreAction']) {
168 if (isset($postVars['name']) && trim($postVars['name'])) {
169 // Using basename() because we won't allow slashes
170 list(, $folderName) = Sabre_DAV_URLUtil::splitPath(trim($postVars['name']));
171 $this->server->createDirectory($uri . '/' . $folderName);
175 if ($_FILES) $file = current($_FILES);
178 list(, $newName) = Sabre_DAV_URLUtil::splitPath(trim($file['name']));
179 if (isset($postVars['name']) && trim($postVars['name']))
180 $newName = trim($postVars['name']);
182 // Making sure we only have a 'basename' component
183 list(, $newName) = Sabre_DAV_URLUtil::splitPath($newName);
185 if (is_uploaded_file($file['tmp_name'])) {
186 $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'],'r'));
193 $this->server->httpResponse->setHeader('Location',$this->server->httpRequest->getUri());
194 $this->server->httpResponse->sendStatus(302);
200 * Escapes a string for html.
202 * @param string $value
205 public function escapeHTML($value) {
207 return htmlspecialchars($value,ENT_QUOTES,'UTF-8');
212 * Generates the html directory index for a given url
214 * @param string $path
217 public function generateDirectoryIndex($path) {
220 if (Sabre_DAV_Server::$exposeVersion) {
221 $version = Sabre_DAV_Version::VERSION ."-". Sabre_DAV_Version::STABILITY;
226 <title>Index for " . $this->escapeHTML($path) . "/ - SabreDAV " . $version . "</title>
227 <style type=\"text/css\">
228 body { Font-family: arial}
229 h1 { font-size: 150% }
233 if ($this->enableAssets) {
234 $html.='<link rel="shortcut icon" href="'.$this->getAssetUrl('favicon.ico').'" type="image/vnd.microsoft.icon" />';
239 <h1>Index for " . $this->escapeHTML($path) . "/</h1>
241 <tr><th width=\"24\"></th><th>Name</th><th>Type</th><th>Size</th><th>Last modified</th></tr>
242 <tr><td colspan=\"5\"><hr /></td></tr>";
244 $files = $this->server->getPropertiesForPath($path,array(
246 '{DAV:}resourcetype',
247 '{DAV:}getcontenttype',
248 '{DAV:}getcontentlength',
249 '{DAV:}getlastmodified',
252 $parent = $this->server->tree->getNodeForPath($path);
257 list($parentUri) = Sabre_DAV_URLUtil::splitPath($path);
258 $fullPath = Sabre_DAV_URLUtil::encodePath($this->server->getBaseUri() . $parentUri);
260 $icon = $this->enableAssets?'<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="Parent" /></a>':'';
263 <td><a href=\"{$fullPath}\">..</a></td>
271 foreach($files as $file) {
273 // This is the current directory, we can skip it
274 if (rtrim($file['href'],'/')==$path) continue;
276 list(, $name) = Sabre_DAV_URLUtil::splitPath($file['href']);
281 if (isset($file[200]['{DAV:}resourcetype'])) {
282 $type = $file[200]['{DAV:}resourcetype']->getValue();
284 // resourcetype can have multiple values
285 if (!is_array($type)) $type = array($type);
287 foreach($type as $k=>$v) {
289 // Some name mapping is preferred
291 case '{DAV:}collection' :
292 $type[$k] = 'Collection';
294 case '{DAV:}principal' :
295 $type[$k] = 'Principal';
297 case '{urn:ietf:params:xml:ns:carddav}addressbook' :
298 $type[$k] = 'Addressbook';
300 case '{urn:ietf:params:xml:ns:caldav}calendar' :
301 $type[$k] = 'Calendar';
303 case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' :
304 $type[$k] = 'Schedule Inbox';
306 case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' :
307 $type[$k] = 'Schedule Outbox';
309 case '{http://calendarserver.org/ns/}calendar-proxy-read' :
310 $type[$k] = 'Proxy-Read';
312 case '{http://calendarserver.org/ns/}calendar-proxy-write' :
313 $type[$k] = 'Proxy-Write';
318 $type = implode(', ', $type);
321 // If no resourcetype was found, we attempt to use
322 // the contenttype property
323 if (!$type && isset($file[200]['{DAV:}getcontenttype'])) {
324 $type = $file[200]['{DAV:}getcontenttype'];
326 if (!$type) $type = 'Unknown';
328 $size = isset($file[200]['{DAV:}getcontentlength'])?(int)$file[200]['{DAV:}getcontentlength']:'';
329 $lastmodified = isset($file[200]['{DAV:}getlastmodified'])?$file[200]['{DAV:}getlastmodified']->getTime()->format(DateTime::ATOM):'';
331 $fullPath = Sabre_DAV_URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/'));
333 $displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name;
335 $displayName = $this->escapeHTML($displayName);
336 $type = $this->escapeHTML($type);
340 if ($this->enableAssets) {
341 $node = $parent->getChild($name);
342 foreach(array_reverse($this->iconMap) as $class=>$iconName) {
344 if ($node instanceof $class) {
345 $icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24" /></a>';
356 <td><a href=\"{$fullPath}\">{$displayName}</a></td>
359 <td>{$lastmodified}</td>
364 $html.= "<tr><td colspan=\"5\"><hr /></td></tr>";
368 if ($this->enablePost) {
369 $this->server->broadcastEvent('onHTMLActionsPanel',array($parent, &$output));
375 <address>Generated by SabreDAV " . $version . " (c)2007-2012 <a href=\"http://code.google.com/p/sabredav/\">http://code.google.com/p/sabredav/</a></address>
384 * This method is used to generate the 'actions panel' output for
387 * This specifically generates the interfaces for creating new files, and
388 * creating new directories.
390 * @param Sabre_DAV_INode $node
391 * @param mixed $output
394 public function htmlActionsPanel(Sabre_DAV_INode $node, &$output) {
396 if (!$node instanceof Sabre_DAV_ICollection)
399 // We also know fairly certain that if an object is a non-extended
400 // SimpleCollection, we won't need to show the panel either.
401 if (get_class($node)==='Sabre_DAV_SimpleCollection')
404 $output.= '<tr><td colspan="2"><form method="post" action="">
405 <h3>Create new folder</h3>
406 <input type="hidden" name="sabreAction" value="mkcol" />
407 Name: <input type="text" name="name" /><br />
408 <input type="submit" value="create" />
410 <form method="post" action="" enctype="multipart/form-data">
412 <input type="hidden" name="sabreAction" value="put" />
413 Name (optional): <input type="text" name="name" /><br />
414 File: <input type="file" name="file" /><br />
415 <input type="submit" value="upload" />
422 * This method takes a path/name of an asset and turns it into url
423 * suiteable for http access.
425 * @param string $assetName
428 protected function getAssetUrl($assetName) {
430 return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName);
435 * This method returns a local pathname to an asset.
437 * @param string $assetName
440 protected function getLocalAssetPath($assetName) {
442 // Making sure people aren't trying to escape from the base path.
443 $assetSplit = explode('/', $assetName);
444 if (in_array('..',$assetSplit)) {
445 throw new Sabre_DAV_Exception('Incorrect asset path');
447 $path = __DIR__ . '/assets/' . $assetName;
453 * This method reads an asset from disk and generates a full http response.
455 * @param string $assetName
458 protected function serveAsset($assetName) {
460 $assetPath = $this->getLocalAssetPath($assetName);
461 if (!file_exists($assetPath)) {
462 throw new Sabre_DAV_Exception_NotFound('Could not find an asset with this name');
464 // Rudimentary mime type detection
465 switch(strtolower(substr($assetPath,strpos($assetPath,'.')+1))) {
468 $mime = 'image/vnd.microsoft.icon';
476 $mime = 'application/octet-stream';
481 $this->server->httpResponse->setHeader('Content-Type', $mime);
482 $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath));
483 $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600');
484 $this->server->httpResponse->sendStatus(200);
485 $this->server->httpResponse->sendBody(fopen($assetPath,'r'));