ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Plugin.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Sabre\DAV\Browser;
4 
5 use Sabre\DAV;
10 
24 class 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>
453 HTML;
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>
489 HTML;
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>
529 HTML;
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 
728  $html = new HtmlOutputHelper(
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 }
This interface represents a HTTP response.
The RequestInterface represents a HTTP request.
$path
Definition: aliased.php:25
The baseclass for all server plugins.
Browser Plugin.
Definition: Plugin.php:24
drawPropertyValue($html, $value)
Draws a table row for a property.
Definition: Plugin.php:744
setBody($body)
Updates the body resource with a new stream.
__construct($enablePost=true)
Creates the object.
Definition: Plugin.php:63
htmlActionsPanel(DAV\INode $node, &$output, $path)
This method is used to generate the &#39;actions panel&#39; output for collections.
Definition: Plugin.php:505
escapeHTML($value)
Escapes a string for html.
Definition: Plugin.php:253
foreach($paths as $path) $request
Definition: asyncclient.php:32
$type
httpPOST(RequestInterface $request, ResponseInterface $response)
Handles POST requests for tree operations.
Definition: Plugin.php:165
This class is used by the browser plugin to trick the system in returning every defined property...
Definition: PropFindAll.php:15
compareNodes($a, $b)
Sort helper function: compares two directory entries based on type and display name.
Definition: Plugin.php:608
generateDirectoryIndex($path)
Generates the html directory index for a given url.
Definition: Plugin.php:265
const VERSION
Full version number.
Definition: Version.php:17
initialize(DAV\Server $server)
Initializes the plugin and subscribes to events.
Definition: Plugin.php:75
generatePluginListing()
Generates the &#39;plugins&#39; page.
Definition: Plugin.php:382
The ICollection Interface.
Definition: ICollection.php:14
getAssetUrl($assetName)
This method takes a path/name of an asset and turns it into url suiteable for http access...
Definition: Plugin.php:540
$version
Definition: build.php:27
static http()
Fetches the global http state from ILIAS.
getQueryParameters()
Returns the list of query parameters.
setStatus($status)
Sets the HTTP status code.
getUrl()
Returns the request url.
input
Definition: langcheck.php:166
WebDAV properties that implement this interface are able to generate their own html output for the br...
Definition: HtmlOutput.php:16
Main DAV server class.
Definition: Server.php:23
getHeader($name)
Returns a specific HTTP header, based on it&#39;s name.
getPluginName()
Returns a plugin name.
Definition: Plugin.php:775
This interface represents a file in the directory tree.
Definition: IFile.php:16
getLocalAssetPath($assetName)
This method returns a local pathname to an asset.
Definition: Plugin.php:553
The INode interface is the base interface, and the parent class of both ICollection and IFile...
Definition: INode.php:12
serveAsset($assetName)
This method reads an asset from disk and generates a full http response.
Definition: Plugin.php:575
mapResourceType(array $resourceTypes, $node)
Maps a resource type to a human-readable string and icon.
Definition: Plugin.php:633
static $exposeVersion
Definition: Server.php:184
httpGetEarly(RequestInterface $request, ResponseInterface $response)
This method intercepts GET requests that have ?sabreAction=info appended to the URL.
Definition: Plugin.php:92
getPath()
Returns the relative path.
This class represents a MKCOL operation.
Definition: MkCol.php:23
httpGet(RequestInterface $request, ResponseInterface $response)
This method intercepts GET requests to collections and returns the html.
Definition: Plugin.php:108
generateHeader($title, $path=null)
Generates the first block of HTML, including the <head> tag and page header.
Definition: Plugin.php:419
getPluginInfo()
Returns a bunch of meta-data about the plugin.
Definition: Plugin.php:792
drawPropertyRow($name, $value)
Draws a table row for a property.
Definition: Plugin.php:726
html()
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
$info
Definition: index.php:5
static encodePath($path)
Encodes the path of a url.
Definition: URLUtil.php:29
$response
$key
Definition: croninfo.php:18
getPostData()
Returns the POST data.
$html
Definition: example_001.php:87
static splitPath($path)
Returns the &#39;dirname&#39; and &#39;basename&#39; for a path.
Definition: URLUtil.php:83
setHeader($name, $value)
Updates a HTTP header.
generateFooter()
Generates the page footer.
Definition: Plugin.php:479
This class provides a few utility functions for easily generating HTML for the browser plugin...