ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Plugin.php
Go to the documentation of this file.
1<?php
2
3namespace Sabre\DAV\Browser;
4
5use Sabre\DAV;
10
24class Plugin extends DAV\ServerPlugin {
25
31 protected $server;
32
39 protected $enablePost = true;
40
49 '{DAV:}supportedlock',
50 '{DAV:}acl-restrictions',
51// '{DAV:}supported-privilege-set',
52 '{DAV:}supported-method-set',
53 ];
54
63 function __construct($enablePost = true) {
64
65 $this->enablePost = $enablePost;
66
67 }
68
75 function initialize(DAV\Server $server) {
76
77 $this->server = $server;
78 $this->server->on('method:GET', [$this, 'httpGetEarly'], 90);
79 $this->server->on('method:GET', [$this, 'httpGet'], 200);
80 $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200);
81 if ($this->enablePost) $this->server->on('method:POST', [$this, 'httpPOST']);
82 }
83
93
94 $params = $request->getQueryParameters();
95 if (isset($params['sabreAction']) && $params['sabreAction'] === 'info') {
96 return $this->httpGet($request, $response);
97 }
98
99 }
100
109
110 // We're not using straight-up $_GET, because we want everything to be
111 // unit testable.
112 $getVars = $request->getQueryParameters();
113
114 // CSP headers
115 $response->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
116
117 $sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null;
118
119 switch ($sabreAction) {
120
121 case 'asset' :
122 // Asset handling, such as images
123 $this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null);
124 return false;
125 default :
126 case 'info' :
127 try {
128 $this->server->tree->getNodeForPath($request->getPath());
129 } catch (DAV\Exception\NotFound $e) {
130 // We're simply stopping when the file isn't found to not interfere
131 // with other plugins.
132 return;
133 }
134
135 $response->setStatus(200);
136 $response->setHeader('Content-Type', 'text/html; charset=utf-8');
137
138 $response->setBody(
139 $this->generateDirectoryIndex($request->getPath())
140 );
141
142 return false;
143
144 case 'plugins' :
145 $response->setStatus(200);
146 $response->setHeader('Content-Type', 'text/html; charset=utf-8');
147
148 $response->setBody(
149 $this->generatePluginListing()
150 );
151
152 return false;
153
154 }
155
156 }
157
166
167 $contentType = $request->getHeader('Content-Type');
168 list($contentType) = explode(';', $contentType);
169 if ($contentType !== 'application/x-www-form-urlencoded' &&
170 $contentType !== 'multipart/form-data') {
171 return;
172 }
173 $postVars = $request->getPostData();
174
175 if (!isset($postVars['sabreAction']))
176 return;
177
178 $uri = $request->getPath();
179
180 if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) {
181
182 switch ($postVars['sabreAction']) {
183
184 case 'mkcol' :
185 if (isset($postVars['name']) && trim($postVars['name'])) {
186 // Using basename() because we won't allow slashes
187 list(, $folderName) = URLUtil::splitPath(trim($postVars['name']));
188
189 if (isset($postVars['resourceType'])) {
190 $resourceType = explode(',', $postVars['resourceType']);
191 } else {
192 $resourceType = ['{DAV:}collection'];
193 }
194
195 $properties = [];
196 foreach ($postVars as $varName => $varValue) {
197 // Any _POST variable in clark notation is treated
198 // like a property.
199 if ($varName[0] === '{') {
200 // PHP will convert any dots to underscores.
201 // This leaves us with no way to differentiate
202 // the two.
203 // Therefore we replace the string *DOT* with a
204 // real dot. * is not allowed in uris so we
205 // should be good.
206 $varName = str_replace('*DOT*', '.', $varName);
207 $properties[$varName] = $varValue;
208 }
209 }
210
211 $mkCol = new MkCol(
212 $resourceType,
213 $properties
214 );
215 $this->server->createCollection($uri . '/' . $folderName, $mkCol);
216 }
217 break;
218
219 // @codeCoverageIgnoreStart
220 case 'put' :
221
222 if ($_FILES) $file = current($_FILES);
223 else break;
224
225 list(, $newName) = URLUtil::splitPath(trim($file['name']));
226 if (isset($postVars['name']) && trim($postVars['name']))
227 $newName = trim($postVars['name']);
228
229 // Making sure we only have a 'basename' component
230 list(, $newName) = URLUtil::splitPath($newName);
231
232 if (is_uploaded_file($file['tmp_name'])) {
233 $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'], 'r'));
234 }
235 break;
236 // @codeCoverageIgnoreEnd
237
238 }
239
240 }
241 $response->setHeader('Location', $request->getUrl());
242 $response->setStatus(302);
243 return false;
244
245 }
246
253 function escapeHTML($value) {
254
255 return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
256
257 }
258
266
267 $html = $this->generateHeader($path ? $path : '/', $path);
268
269 $node = $this->server->tree->getNodeForPath($path);
270 if ($node instanceof DAV\ICollection) {
271
272 $html .= "<section><h1>Nodes</h1>\n";
273 $html .= "<table class=\"nodeTable\">";
274
275 $subNodes = $this->server->getPropertiesForChildren($path, [
276 '{DAV:}displayname',
277 '{DAV:}resourcetype',
278 '{DAV:}getcontenttype',
279 '{DAV:}getcontentlength',
280 '{DAV:}getlastmodified',
281 ]);
282
283 foreach ($subNodes as $subPath => $subProps) {
284
285 $subNode = $this->server->tree->getNodeForPath($subPath);
286 $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($subPath);
287 list(, $displayPath) = URLUtil::splitPath($subPath);
288
289 $subNodes[$subPath]['subNode'] = $subNode;
290 $subNodes[$subPath]['fullPath'] = $fullPath;
291 $subNodes[$subPath]['displayPath'] = $displayPath;
292 }
293 uasort($subNodes, [$this, 'compareNodes']);
294
295 foreach ($subNodes as $subProps) {
296 $type = [
297 'string' => 'Unknown',
298 'icon' => 'cog',
299 ];
300 if (isset($subProps['{DAV:}resourcetype'])) {
301 $type = $this->mapResourceType($subProps['{DAV:}resourcetype']->getValue(), $subProps['subNode']);
302 }
303
304 $html .= '<tr>';
305 $html .= '<td class="nameColumn"><a href="' . $this->escapeHTML($subProps['fullPath']) . '"><span class="oi" data-glyph="' . $this->escapeHTML($type['icon']) . '"></span> ' . $this->escapeHTML($subProps['displayPath']) . '</a></td>';
306 $html .= '<td class="typeColumn">' . $this->escapeHTML($type['string']) . '</td>';
307 $html .= '<td>';
308 if (isset($subProps['{DAV:}getcontentlength'])) {
309 $html .= $this->escapeHTML($subProps['{DAV:}getcontentlength'] . ' bytes');
310 }
311 $html .= '</td><td>';
312 if (isset($subProps['{DAV:}getlastmodified'])) {
313 $lastMod = $subProps['{DAV:}getlastmodified']->getTime();
314 $html .= $this->escapeHTML($lastMod->format('F j, Y, g:i a'));
315 }
316 $html .= '</td>';
317
318 $buttonActions = '';
319 if ($subProps['subNode'] instanceof DAV\IFile) {
320 $buttonActions = '<a href="' . $this->escapeHTML($subProps['fullPath']) . '?sabreAction=info"><span class="oi" data-glyph="info"></span></a>';
321 }
322 $this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]);
323
324 $html .= '<td>' . $buttonActions . '</td>';
325 $html .= '</tr>';
326 }
327
328 $html .= '</table>';
329
330 }
331
332 $html .= "</section>";
333 $html .= "<section><h1>Properties</h1>";
334 $html .= "<table class=\"propTable\">";
335
336 // Allprops request
337 $propFind = new PropFindAll($path);
338 $properties = $this->server->getPropertiesByNode($propFind, $node);
339
340 $properties = $propFind->getResultForMultiStatus()[200];
341
342 foreach ($properties as $propName => $propValue) {
343 if (!in_array($propName, $this->uninterestingProperties)) {
344 $html .= $this->drawPropertyRow($propName, $propValue);
345 }
346
347 }
348
349
350 $html .= "</table>";
351 $html .= "</section>";
352
353 /* Start of generating actions */
354
355 $output = '';
356 if ($this->enablePost) {
357 $this->server->emit('onHTMLActionsPanel', [$node, &$output, $path]);
358 }
359
360 if ($output) {
361
362 $html .= "<section><h1>Actions</h1>";
363 $html .= "<div class=\"actions\">\n";
364 $html .= $output;
365 $html .= "</div>\n";
366 $html .= "</section>\n";
367 }
368
369 $html .= $this->generateFooter();
370
371 $this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
372
373 return $html;
374
375 }
376
383
384 $html = $this->generateHeader('Plugins');
385
386 $html .= "<section><h1>Plugins</h1>";
387 $html .= "<table class=\"propTable\">";
388 foreach ($this->server->getPlugins() as $plugin) {
389 $info = $plugin->getPluginInfo();
390 $html .= '<tr><th>' . $info['name'] . '</th>';
391 $html .= '<td>' . $info['description'] . '</td>';
392 $html .= '<td>';
393 if (isset($info['link']) && $info['link']) {
394 $html .= '<a href="' . $this->escapeHTML($info['link']) . '"><span class="oi" data-glyph="book"></span></a>';
395 }
396 $html .= '</td></tr>';
397 }
398 $html .= "</table>";
399 $html .= "</section>";
400
401 /* Start of generating actions */
402
403 $html .= $this->generateFooter();
404
405 return $html;
406
407 }
408
419 function generateHeader($title, $path = null) {
420
421 $version = '';
422 if (DAV\Server::$exposeVersion) {
424 }
425
426 $vars = [
427 'title' => $this->escapeHTML($title),
428 'favicon' => $this->escapeHTML($this->getAssetUrl('favicon.ico')),
429 'style' => $this->escapeHTML($this->getAssetUrl('sabredav.css')),
430 'iconstyle' => $this->escapeHTML($this->getAssetUrl('openiconic/open-iconic.css')),
431 'logo' => $this->escapeHTML($this->getAssetUrl('sabredav.png')),
432 'baseUrl' => $this->server->getBaseUri(),
433 ];
434
435 $html = <<<HTML
436<!DOCTYPE html>
437<html>
438<head>
439 <title>$vars[title] - sabre/dav $version</title>
440 <link rel="shortcut icon" href="$vars[favicon]" type="image/vnd.microsoft.icon" />
441 <link rel="stylesheet" href="$vars[style]" type="text/css" />
442 <link rel="stylesheet" href="$vars[iconstyle]" type="text/css" />
443
444</head>
445<body>
446 <header>
447 <div class="logo">
448 <a href="$vars[baseUrl]"><img src="$vars[logo]" alt="sabre/dav" /> $vars[title]</a>
449 </div>
450 </header>
451
452 <nav>
453HTML;
454
455 // If the path is empty, there's no parent.
456 if ($path) {
457 list($parentUri) = URLUtil::splitPath($path);
458 $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($parentUri);
459 $html .= '<a href="' . $fullPath . '" class="btn">⇤ Go to parent</a>';
460 } else {
461 $html .= '<span class="btn disabled">⇤ Go to parent</span>';
462 }
463
464 $html .= ' <a href="?sabreAction=plugins" class="btn"><span class="oi" data-glyph="puzzle-piece"></span> Plugins</a>';
465
466 $html .= "</nav>";
467
468 return $html;
469
470 }
471
479 function generateFooter() {
480
481 $version = '';
482 if (DAV\Server::$exposeVersion) {
484 }
485 return <<<HTML
486<footer>Generated by SabreDAV $version (c)2007-2016 <a href="http://sabre.io/">http://sabre.io/</a></footer>
487</body>
488</html>
489HTML;
490
491 }
492
505 function htmlActionsPanel(DAV\INode $node, &$output, $path) {
506
507 if (!$node instanceof DAV\ICollection)
508 return;
509
510 // We also know fairly certain that if an object is a non-extended
511 // SimpleCollection, we won't need to show the panel either.
512 if (get_class($node) === 'Sabre\\DAV\\SimpleCollection')
513 return;
514
515 $output .= <<<HTML
516<form method="post" action="">
517<h3>Create new folder</h3>
518<input type="hidden" name="sabreAction" value="mkcol" />
519<label>Name:</label> <input type="text" name="name" /><br />
520<input type="submit" value="create" />
521</form>
522<form method="post" action="" enctype="multipart/form-data">
523<h3>Upload file</h3>
524<input type="hidden" name="sabreAction" value="put" />
525<label>Name (optional):</label> <input type="text" name="name" /><br />
526<label>File:</label> <input type="file" name="file" /><br />
527<input type="submit" value="upload" />
528</form>
529HTML;
530
531 }
532
540 protected function getAssetUrl($assetName) {
541
542 return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName);
543
544 }
545
553 protected function getLocalAssetPath($assetName) {
554
555 $assetDir = __DIR__ . '/assets/';
556 $path = $assetDir . $assetName;
557
558 // Making sure people aren't trying to escape from the base path.
559 $path = str_replace('\\', '/', $path);
560 if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') {
561 throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
562 }
563 if (strpos(realpath($path), realpath($assetDir)) === 0 && file_exists($path)) {
564 return $path;
565 }
566 throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
567 }
568
575 protected function serveAsset($assetName) {
576
577 $assetPath = $this->getLocalAssetPath($assetName);
578
579 // Rudimentary mime type detection
580 $mime = 'application/octet-stream';
581 $map = [
582 'ico' => 'image/vnd.microsoft.icon',
583 'png' => 'image/png',
584 'css' => 'text/css',
585 ];
586
587 $ext = substr($assetName, strrpos($assetName, '.') + 1);
588 if (isset($map[$ext])) {
589 $mime = $map[$ext];
590 }
591
592 $this->server->httpResponse->setHeader('Content-Type', $mime);
593 $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath));
594 $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600');
595 $this->server->httpResponse->setStatus(200);
596 $this->server->httpResponse->setBody(fopen($assetPath, 'r'));
597
598 }
599
608 protected function compareNodes($a, $b) {
609
610 $typeA = (isset($a['{DAV:}resourcetype']))
611 ? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue()))
612 : false;
613
614 $typeB = (isset($b['{DAV:}resourcetype']))
615 ? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue()))
616 : false;
617
618 // If same type, sort alphabetically by filename:
619 if ($typeA === $typeB) {
620 return strnatcasecmp($a['displayPath'], $b['displayPath']);
621 }
622 return (($typeA < $typeB) ? 1 : -1);
623
624 }
625
633 private function mapResourceType(array $resourceTypes, $node) {
634
635 if (!$resourceTypes) {
636 if ($node instanceof DAV\IFile) {
637 return [
638 'string' => 'File',
639 'icon' => 'file',
640 ];
641 } else {
642 return [
643 'string' => 'Unknown',
644 'icon' => 'cog',
645 ];
646 }
647 }
648
649 $types = [
650 '{http://calendarserver.org/ns/}calendar-proxy-write' => [
651 'string' => 'Proxy-Write',
652 'icon' => 'people',
653 ],
654 '{http://calendarserver.org/ns/}calendar-proxy-read' => [
655 'string' => 'Proxy-Read',
656 'icon' => 'people',
657 ],
658 '{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [
659 'string' => 'Outbox',
660 'icon' => 'inbox',
661 ],
662 '{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [
663 'string' => 'Inbox',
664 'icon' => 'inbox',
665 ],
666 '{urn:ietf:params:xml:ns:caldav}calendar' => [
667 'string' => 'Calendar',
668 'icon' => 'calendar',
669 ],
670 '{http://calendarserver.org/ns/}shared-owner' => [
671 'string' => 'Shared',
672 'icon' => 'calendar',
673 ],
674 '{http://calendarserver.org/ns/}subscribed' => [
675 'string' => 'Subscription',
676 'icon' => 'calendar',
677 ],
678 '{urn:ietf:params:xml:ns:carddav}directory' => [
679 'string' => 'Directory',
680 'icon' => 'globe',
681 ],
682 '{urn:ietf:params:xml:ns:carddav}addressbook' => [
683 'string' => 'Address book',
684 'icon' => 'book',
685 ],
686 '{DAV:}principal' => [
687 'string' => 'Principal',
688 'icon' => 'person',
689 ],
690 '{DAV:}collection' => [
691 'string' => 'Collection',
692 'icon' => 'folder',
693 ],
694 ];
695
696 $info = [
697 'string' => [],
698 'icon' => 'cog',
699 ];
700 foreach ($resourceTypes as $k => $resourceType) {
701 if (isset($types[$resourceType])) {
702 $info['string'][] = $types[$resourceType]['string'];
703 } else {
704 $info['string'][] = $resourceType;
705 }
706 }
707 foreach ($types as $key => $resourceInfo) {
708 if (in_array($key, $resourceTypes)) {
709 $info['icon'] = $resourceInfo['icon'];
710 break;
711 }
712 }
713 $info['string'] = implode(', ', $info['string']);
714
715 return $info;
716
717 }
718
726 private function drawPropertyRow($name, $value) {
727
729 $this->server->getBaseUri(),
730 $this->server->xml->namespaceMap
731 );
732
733 return "<tr><th>" . $html->xmlName($name) . "</th><td>" . $this->drawPropertyValue($html, $value) . "</td></tr>";
734
735 }
736
744 private function drawPropertyValue($html, $value) {
745
746 if (is_scalar($value)) {
747 return $html->h($value);
748 } elseif ($value instanceof HtmlOutput) {
749 return $value->toHtml($html);
750 } elseif ($value instanceof \Sabre\Xml\XmlSerializable) {
751
752 // There's no default html output for this property, we're going
753 // to output the actual xml serialization instead.
754 $xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri());
755 // removing first and last line, as they contain our root
756 // element.
757 $xml = explode("\n", $xml);
758 $xml = array_slice($xml, 2, -2);
759 return "<pre>" . $html->h(implode("\n", $xml)) . "</pre>";
760
761 } else {
762 return "<em>unknown</em>";
763 }
764
765 }
766
775 function getPluginName() {
776
777 return 'browser';
778
779 }
780
792 function getPluginInfo() {
793
794 return [
795 'name' => $this->getPluginName(),
796 'description' => 'Generates HTML indexes and debug information for your sabre/dav server',
797 'link' => 'http://sabre.io/dav/browser-plugin/',
798 ];
799
800 }
801
802}
html()
$path
Definition: aliased.php:25
foreach($paths as $path) $request
Definition: asyncclient.php:32
$version
Definition: build.php:27
An exception for terminatinating execution or to throw for unit testing.
This class provides a few utility functions for easily generating HTML for the browser plugin.
Browser Plugin.
Definition: Plugin.php:24
drawPropertyRow($name, $value)
Draws a table row for a property.
Definition: Plugin.php:726
drawPropertyValue($html, $value)
Draws a table row for a property.
Definition: Plugin.php:744
getLocalAssetPath($assetName)
This method returns a local pathname to an asset.
Definition: Plugin.php:553
escapeHTML($value)
Escapes a string for html.
Definition: Plugin.php:253
getAssetUrl($assetName)
This method takes a path/name of an asset and turns it into url suiteable for http access.
Definition: Plugin.php:540
generateFooter()
Generates the page footer.
Definition: Plugin.php:479
getPluginInfo()
Returns a bunch of meta-data about the plugin.
Definition: Plugin.php:792
httpGetEarly(RequestInterface $request, ResponseInterface $response)
This method intercepts GET requests that have ?sabreAction=info appended to the URL.
Definition: Plugin.php:92
httpGet(RequestInterface $request, ResponseInterface $response)
This method intercepts GET requests to collections and returns the html.
Definition: Plugin.php:108
serveAsset($assetName)
This method reads an asset from disk and generates a full http response.
Definition: Plugin.php:575
generateHeader($title, $path=null)
Generates the first block of HTML, including the <head> tag and page header.
Definition: Plugin.php:419
compareNodes($a, $b)
Sort helper function: compares two directory entries based on type and display name.
Definition: Plugin.php:608
initialize(DAV\Server $server)
Initializes the plugin and subscribes to events.
Definition: Plugin.php:75
__construct($enablePost=true)
Creates the object.
Definition: Plugin.php:63
getPluginName()
Returns a plugin name.
Definition: Plugin.php:775
httpPOST(RequestInterface $request, ResponseInterface $response)
Handles POST requests for tree operations.
Definition: Plugin.php:165
htmlActionsPanel(DAV\INode $node, &$output, $path)
This method is used to generate the 'actions panel' output for collections.
Definition: Plugin.php:505
generatePluginListing()
Generates the 'plugins' page.
Definition: Plugin.php:382
generateDirectoryIndex($path)
Generates the html directory index for a given url.
Definition: Plugin.php:265
mapResourceType(array $resourceTypes, $node)
Maps a resource type to a human-readable string and icon.
Definition: Plugin.php:633
This class is used by the browser plugin to trick the system in returning every defined property.
Definition: PropFindAll.php:15
Main Exception class.
Definition: Exception.php:18
File class.
Definition: File.php:15
This class represents a MKCOL operation.
Definition: MkCol.php:23
The baseclass for all server plugins.
Main DAV server class.
Definition: Server.php:23
static $exposeVersion
Definition: Server.php:184
const VERSION
Full version number.
Definition: Version.php:17
URL utility class.
Definition: URLUtil.php:18
static encodePath($path)
Encodes the path of a url.
Definition: URLUtil.php:29
static splitPath($path)
Returns the 'dirname' and 'basename' for a path.
Definition: URLUtil.php:83
$key
Definition: croninfo.php:18
$html
Definition: example_001.php:87
WebDAV properties that implement this interface are able to generate their own html output for the br...
Definition: HtmlOutput.php:16
The ICollection Interface.
Definition: ICollection.php:14
This interface represents a file in the directory tree.
Definition: IFile.php:16
The INode interface is the base interface, and the parent class of both ICollection and IFile.
Definition: INode.php:12
The RequestInterface represents a HTTP request.
getPath()
Returns the relative path.
This interface represents a HTTP response.
input
Definition: langcheck.php:166
$info
Definition: index.php:5
if( $path[strlen( $path) - 1]==='/') if(is_dir($path)) if(!file_exists( $path)) if(preg_match('#\.php$#D', mb_strtolower($path, 'UTF-8'))) $contentType
Definition: module.php:144
static http()
Fetches the global http state from ILIAS.
$type
$response