]> git.mxchange.org Git - friendica-addons.git/blob - dav/SabreDAV/lib/Sabre/DAV/Browser/Plugin.php
Merge remote branch 'upstream/master'
[friendica-addons.git] / dav / SabreDAV / lib / Sabre / DAV / Browser / Plugin.php
1 <?php
2
3 /**
4  * Browser Plugin
5  *
6  * This plugin provides a html representation, so that a WebDAV server may be accessed
7  * using a browser.
8  *
9  * The class intercepts GET requests to collection resources and generates a simple
10  * html index.
11  *
12  * @package Sabre
13  * @subpackage DAV
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
17  */
18 class Sabre_DAV_Browser_Plugin extends Sabre_DAV_ServerPlugin {
19
20     /**
21      * List of default icons for nodes.
22      *
23      * This is an array with class / interface names as keys, and asset names
24      * as values.
25      *
26      * The evaluation order is reversed. The last item in the list gets
27      * precendence.
28      *
29      * @var array
30      */
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',
38     );
39
40     /**
41      * The file extension used for all icons
42      *
43      * @var string
44      */
45     public $iconExtension = '.png';
46
47     /**
48      * reference to server class
49      *
50      * @var Sabre_DAV_Server
51      */
52     protected $server;
53
54     /**
55      * enablePost turns on the 'actions' panel, which allows people to create
56      * folders and upload files straight from a browser.
57      *
58      * @var bool
59      */
60     protected $enablePost = true;
61
62     /**
63      * By default the browser plugin will generate a favicon and other images.
64      * To turn this off, set this property to false.
65      *
66      * @var bool
67      */
68     protected $enableAssets = true;
69
70     /**
71      * Creates the object.
72      *
73      * By default it will allow file creation and uploads.
74      * Specify the first argument as false to disable this
75      *
76      * @param bool $enablePost
77      * @param bool $enableAssets
78      */
79     public function __construct($enablePost=true, $enableAssets = true) {
80
81         $this->enablePost = $enablePost;
82         $this->enableAssets = $enableAssets;
83
84     }
85
86     /**
87      * Initializes the plugin and subscribes to events
88      *
89      * @param Sabre_DAV_Server $server
90      * @return void
91      */
92     public function initialize(Sabre_DAV_Server $server) {
93
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'));
98     }
99
100     /**
101      * This method intercepts GET requests to collections and returns the html
102      *
103      * @param string $method
104      * @param string $uri
105      * @return bool
106      */
107     public function httpGetInterceptor($method, $uri) {
108
109         if ($method !== 'GET') return true;
110
111         // We're not using straight-up $_GET, because we want everything to be
112         // unit testable.
113         $getVars = array();
114         parse_str($this->server->httpRequest->getQueryString(), $getVars);
115
116         if (isset($getVars['sabreAction']) && $getVars['sabreAction'] === 'asset' && isset($getVars['assetName'])) {
117             $this->serveAsset($getVars['assetName']);
118             return false;
119         }
120
121         try {
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.
126             return;
127         }
128         if ($node instanceof Sabre_DAV_IFile)
129             return;
130
131         $this->server->httpResponse->sendStatus(200);
132         $this->server->httpResponse->setHeader('Content-Type','text/html; charset=utf-8');
133
134         $this->server->httpResponse->sendBody(
135             $this->generateDirectoryIndex($uri)
136         );
137
138         return false;
139
140     }
141
142     /**
143      * Handles POST requests for tree operations.
144      *
145      * @param string $method
146      * @param string $uri
147      * @return bool
148      */
149     public function httpPOSTHandler($method, $uri) {
150
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') {
156                 return;
157         }
158         $postVars = $this->server->httpRequest->getPostVars();
159
160         if (!isset($postVars['sabreAction']))
161             return;
162
163         if ($this->server->broadcastEvent('onBrowserPostAction', array($uri, $postVars['sabreAction'], $postVars))) {
164
165             switch($postVars['sabreAction']) {
166
167                 case 'mkcol' :
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);
172                     }
173                     break;
174                 case 'put' :
175                     if ($_FILES) $file = current($_FILES);
176                     else break;
177
178                     list(, $newName) = Sabre_DAV_URLUtil::splitPath(trim($file['name']));
179                     if (isset($postVars['name']) && trim($postVars['name']))
180                         $newName = trim($postVars['name']);
181
182                     // Making sure we only have a 'basename' component
183                     list(, $newName) = Sabre_DAV_URLUtil::splitPath($newName);
184
185                     if (is_uploaded_file($file['tmp_name'])) {
186                         $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'],'r'));
187                     }
188                     break;
189
190             }
191
192         }
193         $this->server->httpResponse->setHeader('Location',$this->server->httpRequest->getUri());
194         $this->server->httpResponse->sendStatus(302);
195         return false;
196
197     }
198
199     /**
200      * Escapes a string for html.
201      *
202      * @param string $value
203      * @return string
204      */
205     public function escapeHTML($value) {
206
207         return htmlspecialchars($value,ENT_QUOTES,'UTF-8');
208
209     }
210
211     /**
212      * Generates the html directory index for a given url
213      *
214      * @param string $path
215      * @return string
216      */
217     public function generateDirectoryIndex($path) {
218
219         $version = '';
220         if (Sabre_DAV_Server::$exposeVersion) {
221             $version = Sabre_DAV_Version::VERSION ."-". Sabre_DAV_Version::STABILITY;
222         }
223
224         $html = "<html>
225 <head>
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% }
230   </style>
231         ";
232
233         if ($this->enableAssets) {
234             $html.='<link rel="shortcut icon" href="'.$this->getAssetUrl('favicon.ico').'" type="image/vnd.microsoft.icon" />';
235         }
236
237         $html .= "</head>
238 <body>
239   <h1>Index for " . $this->escapeHTML($path) . "/</h1>
240   <table>
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>";
243
244         $files = $this->server->getPropertiesForPath($path,array(
245             '{DAV:}displayname',
246             '{DAV:}resourcetype',
247             '{DAV:}getcontenttype',
248             '{DAV:}getcontentlength',
249             '{DAV:}getlastmodified',
250         ),1);
251
252         $parent = $this->server->tree->getNodeForPath($path);
253
254
255         if ($path) {
256
257             list($parentUri) = Sabre_DAV_URLUtil::splitPath($path);
258             $fullPath = Sabre_DAV_URLUtil::encodePath($this->server->getBaseUri() . $parentUri);
259
260             $icon = $this->enableAssets?'<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="Parent" /></a>':'';
261             $html.= "<tr>
262     <td>$icon</td>
263     <td><a href=\"{$fullPath}\">..</a></td>
264     <td>[parent]</td>
265     <td></td>
266     <td></td>
267     </tr>";
268
269         }
270
271         foreach($files as $file) {
272
273             // This is the current directory, we can skip it
274             if (rtrim($file['href'],'/')==$path) continue;
275
276             list(, $name) = Sabre_DAV_URLUtil::splitPath($file['href']);
277
278             $type = null;
279
280
281             if (isset($file[200]['{DAV:}resourcetype'])) {
282                 $type = $file[200]['{DAV:}resourcetype']->getValue();
283
284                 // resourcetype can have multiple values
285                 if (!is_array($type)) $type = array($type);
286
287                 foreach($type as $k=>$v) {
288
289                     // Some name mapping is preferred
290                     switch($v) {
291                         case '{DAV:}collection' :
292                             $type[$k] = 'Collection';
293                             break;
294                         case '{DAV:}principal' :
295                             $type[$k] = 'Principal';
296                             break;
297                         case '{urn:ietf:params:xml:ns:carddav}addressbook' :
298                             $type[$k] = 'Addressbook';
299                             break;
300                         case '{urn:ietf:params:xml:ns:caldav}calendar' :
301                             $type[$k] = 'Calendar';
302                             break;
303                         case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' :
304                             $type[$k] = 'Schedule Inbox';
305                             break;
306                         case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' :
307                             $type[$k] = 'Schedule Outbox';
308                             break;
309                         case '{http://calendarserver.org/ns/}calendar-proxy-read' :
310                             $type[$k] = 'Proxy-Read';
311                             break;
312                         case '{http://calendarserver.org/ns/}calendar-proxy-write' :
313                             $type[$k] = 'Proxy-Write';
314                             break;
315                     }
316
317                 }
318                 $type = implode(', ', $type);
319             }
320
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'];
325             }
326             if (!$type) $type = 'Unknown';
327
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):'';
330
331             $fullPath = Sabre_DAV_URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/'));
332
333             $displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name;
334
335             $displayName = $this->escapeHTML($displayName);
336             $type = $this->escapeHTML($type);
337
338             $icon = '';
339
340             if ($this->enableAssets) {
341                 $node = $parent->getChild($name);
342                 foreach(array_reverse($this->iconMap) as $class=>$iconName) {
343
344                     if ($node instanceof $class) {
345                         $icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24" /></a>';
346                         break;
347                     }
348
349
350                 }
351
352             }
353
354             $html.= "<tr>
355     <td>$icon</td>
356     <td><a href=\"{$fullPath}\">{$displayName}</a></td>
357     <td>{$type}</td>
358     <td>{$size}</td>
359     <td>{$lastmodified}</td>
360     </tr>";
361
362         }
363
364         $html.= "<tr><td colspan=\"5\"><hr /></td></tr>";
365
366         $output = '';
367
368         if ($this->enablePost) {
369             $this->server->broadcastEvent('onHTMLActionsPanel',array($parent, &$output));
370         }
371
372         $html.=$output;
373
374         $html.= "</table>
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>
376         </body>
377         </html>";
378
379         return $html;
380
381     }
382
383     /**
384      * This method is used to generate the 'actions panel' output for
385      * collections.
386      *
387      * This specifically generates the interfaces for creating new files, and
388      * creating new directories.
389      *
390      * @param Sabre_DAV_INode $node
391      * @param mixed $output
392      * @return void
393      */
394     public function htmlActionsPanel(Sabre_DAV_INode $node, &$output) {
395
396         if (!$node instanceof Sabre_DAV_ICollection)
397             return;
398
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')
402             return;
403
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" />
409             </form>
410             <form method="post" action="" enctype="multipart/form-data">
411             <h3>Upload file</h3>
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" />
416             </form>
417             </td></tr>';
418
419     }
420
421     /**
422      * This method takes a path/name of an asset and turns it into url
423      * suiteable for http access.
424      *
425      * @param string $assetName
426      * @return string
427      */
428     protected function getAssetUrl($assetName) {
429
430         return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName);
431
432     }
433
434     /**
435      * This method returns a local pathname to an asset.
436      *
437      * @param string $assetName
438      * @return string
439      */
440     protected function getLocalAssetPath($assetName) {
441
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');
446         }
447         $path = __DIR__ . '/assets/' . $assetName;
448         return $path;
449
450     }
451
452     /**
453      * This method reads an asset from disk and generates a full http response.
454      *
455      * @param string $assetName
456      * @return void
457      */
458     protected function serveAsset($assetName) {
459
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');
463         }
464         // Rudimentary mime type detection
465         switch(strtolower(substr($assetPath,strpos($assetPath,'.')+1))) {
466
467         case 'ico' :
468             $mime = 'image/vnd.microsoft.icon';
469             break;
470
471         case 'png' :
472             $mime = 'image/png';
473             break;
474
475         default:
476             $mime = 'application/octet-stream';
477             break;
478
479         }
480
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'));
486
487     }
488
489 }