'icons/file', 'Sabre\\DAV\\ICollection' => 'icons/collection', 'Sabre\\DAVACL\\IPrincipal' => 'icons/principal', 'Sabre\\CalDAV\\ICalendar' => 'icons/calendar', 'Sabre\\CardDAV\\IAddressBook' => 'icons/addressbook', 'Sabre\\CardDAV\\ICard' => 'icons/card', ); /** * The file extension used for all icons * * @var string */ public $iconExtension = '.png'; /** * reference to server class * * @var Sabre\DAV\Server */ protected $server; /** * enablePost turns on the 'actions' panel, which allows people to create * folders and upload files straight from a browser. * * @var bool */ protected $enablePost = true; /** * By default the browser plugin will generate a favicon and other images. * To turn this off, set this property to false. * * @var bool */ protected $enableAssets = true; /** * Creates the object. * * By default it will allow file creation and uploads. * Specify the first argument as false to disable this * * @param bool $enablePost * @param bool $enableAssets */ public function __construct($enablePost=true, $enableAssets = true) { $this->enablePost = $enablePost; $this->enableAssets = $enableAssets; } /** * Initializes the plugin and subscribes to events * * @param DAV\Server $server * @return void */ public function initialize(DAV\Server $server) { $this->server = $server; $this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor')); $this->server->subscribeEvent('onHTMLActionsPanel', array($this, 'htmlActionsPanel'),200); if ($this->enablePost) $this->server->subscribeEvent('unknownMethod',array($this,'httpPOSTHandler')); } /** * This method intercepts GET requests to collections and returns the html * * @param string $method * @param string $uri * @return bool */ public function httpGetInterceptor($method, $uri) { if ($method !== 'GET') return true; // We're not using straight-up $_GET, because we want everything to be // unit testable. $getVars = array(); parse_str($this->server->httpRequest->getQueryString(), $getVars); if (isset($getVars['sabreAction']) && $getVars['sabreAction'] === 'asset' && isset($getVars['assetName'])) { $this->serveAsset($getVars['assetName']); return false; } try { $node = $this->server->tree->getNodeForPath($uri); } catch (DAV\Exception\NotFound $e) { // We're simply stopping when the file isn't found to not interfere // with other plugins. return; } if ($node instanceof DAV\IFile) return; $this->server->httpResponse->sendStatus(200); $this->server->httpResponse->setHeader('Content-Type','text/html; charset=utf-8'); $this->server->httpResponse->sendBody( $this->generateDirectoryIndex($uri) ); return false; } /** * Handles POST requests for tree operations. * * @param string $method * @param string $uri * @return bool */ public function httpPOSTHandler($method, $uri) { if ($method!='POST') return; $contentType = $this->server->httpRequest->getHeader('Content-Type'); list($contentType) = explode(';', $contentType); if ($contentType !== 'application/x-www-form-urlencoded' && $contentType !== 'multipart/form-data') { return; } $postVars = $this->server->httpRequest->getPostVars(); if (!isset($postVars['sabreAction'])) return; if ($this->server->broadcastEvent('onBrowserPostAction', array($uri, $postVars['sabreAction'], $postVars))) { switch($postVars['sabreAction']) { case 'mkcol' : if (isset($postVars['name']) && trim($postVars['name'])) { // Using basename() because we won't allow slashes list(, $folderName) = DAV\URLUtil::splitPath(trim($postVars['name'])); $this->server->createDirectory($uri . '/' . $folderName); } break; case 'put' : if ($_FILES) $file = current($_FILES); else break; list(, $newName) = DAV\URLUtil::splitPath(trim($file['name'])); if (isset($postVars['name']) && trim($postVars['name'])) $newName = trim($postVars['name']); // Making sure we only have a 'basename' component list(, $newName) = DAV\URLUtil::splitPath($newName); if (is_uploaded_file($file['tmp_name'])) { $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'],'r')); } break; } } $this->server->httpResponse->setHeader('Location',$this->server->httpRequest->getUri()); $this->server->httpResponse->sendStatus(302); return false; } /** * Escapes a string for html. * * @param string $value * @return string */ public function escapeHTML($value) { return htmlspecialchars($value,ENT_QUOTES,'UTF-8'); } /** * Generates the html directory index for a given url * * @param string $path * @return string */ public function generateDirectoryIndex($path) { $version = ''; if (DAV\Server::$exposeVersion) { $version = DAV\Version::VERSION ."-". DAV\Version::STABILITY; } $html = " Index for " . $this->escapeHTML($path) . "/ - SabreDAV " . $version . " "; if ($this->enableAssets) { $html.=''; } $html .= "

Index for " . $this->escapeHTML($path) . "/

"; $files = $this->server->getPropertiesForPath($path,array( '{DAV:}displayname', '{DAV:}resourcetype', '{DAV:}getcontenttype', '{DAV:}getcontentlength', '{DAV:}getlastmodified', ),1); $parent = $this->server->tree->getNodeForPath($path); if ($path) { list($parentUri) = DAV\URLUtil::splitPath($path); $fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri); $icon = $this->enableAssets?'Parent':''; $html.= ""; } foreach($files as $file) { // This is the current directory, we can skip it if (rtrim($file['href'],'/')==$path) continue; list(, $name) = DAV\URLUtil::splitPath($file['href']); $type = null; if (isset($file[200]['{DAV:}resourcetype'])) { $type = $file[200]['{DAV:}resourcetype']->getValue(); // resourcetype can have multiple values if (!is_array($type)) $type = array($type); foreach($type as $k=>$v) { // Some name mapping is preferred switch($v) { case '{DAV:}collection' : $type[$k] = 'Collection'; break; case '{DAV:}principal' : $type[$k] = 'Principal'; break; case '{urn:ietf:params:xml:ns:carddav}addressbook' : $type[$k] = 'Addressbook'; break; case '{urn:ietf:params:xml:ns:caldav}calendar' : $type[$k] = 'Calendar'; break; case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' : $type[$k] = 'Schedule Inbox'; break; case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' : $type[$k] = 'Schedule Outbox'; break; case '{http://calendarserver.org/ns/}calendar-proxy-read' : $type[$k] = 'Proxy-Read'; break; case '{http://calendarserver.org/ns/}calendar-proxy-write' : $type[$k] = 'Proxy-Write'; break; } } $type = implode(', ', $type); } // If no resourcetype was found, we attempt to use // the contenttype property if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { $type = $file[200]['{DAV:}getcontenttype']; } if (!$type) $type = 'Unknown'; $size = isset($file[200]['{DAV:}getcontentlength'])?(int)$file[200]['{DAV:}getcontentlength']:''; $lastmodified = isset($file[200]['{DAV:}getlastmodified'])?$file[200]['{DAV:}getlastmodified']->getTime()->format(\DateTime::ATOM):''; $fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/')); $displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name; $displayName = $this->escapeHTML($displayName); $type = $this->escapeHTML($type); $icon = ''; if ($this->enableAssets) { $node = $this->server->tree->getNodeForPath(($path?$path.'/':'') . $name); foreach(array_reverse($this->iconMap) as $class=>$iconName) { if ($node instanceof $class) { $icon = ''; break; } } } $html.= ""; } $html.= ""; $output = ''; if ($this->enablePost) { $this->server->broadcastEvent('onHTMLActionsPanel',array($parent, &$output)); } $html.=$output; $html.= "
NameTypeSizeLast modified

$icon .. [parent]
$icon {$displayName} {$type} {$size} {$lastmodified}

Generated by SabreDAV " . $version . " (c)2007-2013 http://code.google.com/p/sabredav/
"; return $html; } /** * This method is used to generate the 'actions panel' output for * collections. * * This specifically generates the interfaces for creating new files, and * creating new directories. * * @param DAV\INode $node * @param mixed $output * @return void */ public function htmlActionsPanel(DAV\INode $node, &$output) { if (!$node instanceof DAV\ICollection) return; // We also know fairly certain that if an object is a non-extended // SimpleCollection, we won't need to show the panel either. if (get_class($node)==='Sabre\\DAV\\SimpleCollection') return; $output.= '

Create new folder

Name:

Upload file

Name (optional):
File:
'; } /** * This method takes a path/name of an asset and turns it into url * suiteable for http access. * * @param string $assetName * @return string */ protected function getAssetUrl($assetName) { return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName); } /** * This method returns a local pathname to an asset. * * @param string $assetName * @return string */ protected function getLocalAssetPath($assetName) { $assetDir = __DIR__ . '/assets/'; $path = $assetDir . $assetName; // Making sure people aren't trying to escape from the base path. if (strpos(realpath($path), realpath($assetDir)) === 0) { return $path; } throw new DAV\Exception\Forbidden('Path does not exist, or escaping from the base path was detected'); } /** * This method reads an asset from disk and generates a full http response. * * @param string $assetName * @return void */ protected function serveAsset($assetName) { $assetPath = $this->getLocalAssetPath($assetName); if (!file_exists($assetPath)) { throw new DAV\Exception\NotFound('Could not find an asset with this name'); } // Rudimentary mime type detection switch(strtolower(substr($assetPath,strpos($assetPath,'.')+1))) { case 'ico' : $mime = 'image/vnd.microsoft.icon'; break; case 'png' : $mime = 'image/png'; break; default: $mime = 'application/octet-stream'; break; } $this->server->httpResponse->setHeader('Content-Type', $mime); $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath)); $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600'); $this->server->httpResponse->sendStatus(200); $this->server->httpResponse->sendBody(fopen($assetPath,'r')); } }