commit vendor

This commit is contained in:
2025-11-11 14:49:30 +01:00
parent f33121a308
commit 6d03080c00
2436 changed files with 483781 additions and 0 deletions

View File

@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Auth\Backend;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* HTTP Basic authentication backend class.
*
* This class can be used by authentication objects wishing to use HTTP Basic
* Most of the digest logic is handled, implementors just need to worry about
* the validateUserPass method.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author James David Low (http://jameslow.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class AbstractBasic implements BackendInterface
{
/**
* Authentication Realm.
*
* The realm is often displayed by browser clients when showing the
* authentication dialog.
*
* @var string
*/
protected $realm = 'sabre/dav';
/**
* This is the prefix that will be used to generate principal urls.
*
* @var string
*/
protected $principalPrefix = 'principals/';
/**
* Validates a username and password.
*
* This method should return true or false depending on if login
* succeeded.
*
* @param string $username
* @param string $password
*
* @return bool
*/
abstract protected function validateUserPass($username, $password);
/**
* Sets the authentication realm for this backend.
*
* @param string $realm
*/
public function setRealm($realm)
{
$this->realm = $realm;
}
/**
* When this method is called, the backend must check if authentication was
* successful.
*
* The returned value must be one of the following
*
* [true, "principals/username"]
* [false, "reason for failure"]
*
* If authentication was successful, it's expected that the authentication
* backend returns a so-called principal url.
*
* Examples of a principal url:
*
* principals/admin
* principals/user1
* principals/users/joe
* principals/uid/123457
*
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
* return a string such as:
*
* principals/users/[username]
*
* @return array
*/
public function check(RequestInterface $request, ResponseInterface $response)
{
$auth = new HTTP\Auth\Basic(
$this->realm,
$request,
$response
);
$userpass = $auth->getCredentials();
if (!$userpass) {
return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"];
}
if (!$this->validateUserPass($userpass[0], $userpass[1])) {
return [false, 'Username or password was incorrect'];
}
return [true, $this->principalPrefix.$userpass[0]];
}
/**
* This method is called when a user could not be authenticated, and
* authentication was required for the current request.
*
* This gives you the opportunity to set authentication headers. The 401
* status code will already be set.
*
* In this case of Basic Auth, this would for example mean that the
* following header needs to be set:
*
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
*
* Keep in mind that in the case of multiple authentication backends, other
* WWW-Authenticate headers may already have been set, and you'll want to
* append your own WWW-Authenticate header instead of overwriting the
* existing one.
*/
public function challenge(RequestInterface $request, ResponseInterface $response)
{
$auth = new HTTP\Auth\Basic(
$this->realm,
$request,
$response
);
$auth->requireLogin();
}
}

View File

@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Auth\Backend;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* HTTP Bearer authentication backend class.
*
* This class can be used by authentication objects wishing to use HTTP Bearer
* Most of the digest logic is handled, implementors just need to worry about
* the validateBearerToken method.
*
* @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
* @author François Kooman (https://tuxed.net/)
* @author James David Low (http://jameslow.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class AbstractBearer implements BackendInterface
{
/**
* Authentication Realm.
*
* The realm is often displayed by browser clients when showing the
* authentication dialog.
*
* @var string
*/
protected $realm = 'sabre/dav';
/**
* Validates a Bearer token.
*
* This method should return the full principal url, or false if the
* token was incorrect.
*
* @param string $bearerToken
*
* @return string|false
*/
abstract protected function validateBearerToken($bearerToken);
/**
* Sets the authentication realm for this backend.
*
* @param string $realm
*/
public function setRealm($realm)
{
$this->realm = $realm;
}
/**
* When this method is called, the backend must check if authentication was
* successful.
*
* The returned value must be one of the following
*
* [true, "principals/username"]
* [false, "reason for failure"]
*
* If authentication was successful, it's expected that the authentication
* backend returns a so-called principal url.
*
* Examples of a principal url:
*
* principals/admin
* principals/user1
* principals/users/joe
* principals/uid/123457
*
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
* return a string such as:
*
* principals/users/[username]
*
* @return array
*/
public function check(RequestInterface $request, ResponseInterface $response)
{
$auth = new HTTP\Auth\Bearer(
$this->realm,
$request,
$response
);
$bearerToken = $auth->getToken($request);
if (!$bearerToken) {
return [false, "No 'Authorization: Bearer' header found. Either the client didn't send one, or the server is mis-configured"];
}
$principalUrl = $this->validateBearerToken($bearerToken);
if (!$principalUrl) {
return [false, 'Bearer token was incorrect'];
}
return [true, $principalUrl];
}
/**
* This method is called when a user could not be authenticated, and
* authentication was required for the current request.
*
* This gives you the opportunity to set authentication headers. The 401
* status code will already be set.
*
* In this case of Bearer Auth, this would for example mean that the
* following header needs to be set:
*
* $response->addHeader('WWW-Authenticate', 'Bearer realm=SabreDAV');
*
* Keep in mind that in the case of multiple authentication backends, other
* WWW-Authenticate headers may already have been set, and you'll want to
* append your own WWW-Authenticate header instead of overwriting the
* existing one.
*/
public function challenge(RequestInterface $request, ResponseInterface $response)
{
$auth = new HTTP\Auth\Bearer(
$this->realm,
$request,
$response
);
$auth->requireLogin();
}
}

View File

@ -0,0 +1,160 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Auth\Backend;
use Sabre\DAV;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* HTTP Digest authentication backend class.
*
* This class can be used by authentication objects wishing to use HTTP Digest
* Most of the digest logic is handled, implementors just need to worry about
* the getDigestHash method
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class AbstractDigest implements BackendInterface
{
/**
* Authentication Realm.
*
* The realm is often displayed by browser clients when showing the
* authentication dialog.
*
* @var string
*/
protected $realm = 'SabreDAV';
/**
* This is the prefix that will be used to generate principal urls.
*
* @var string
*/
protected $principalPrefix = 'principals/';
/**
* Sets the authentication realm for this backend.
*
* Be aware that for Digest authentication, the realm influences the digest
* hash. Choose the realm wisely, because if you change it later, all the
* existing hashes will break and nobody can authenticate.
*
* @param string $realm
*/
public function setRealm($realm)
{
$this->realm = $realm;
}
/**
* Returns a users digest hash based on the username and realm.
*
* If the user was not known, null must be returned.
*
* @param string $realm
* @param string $username
*
* @return string|null
*/
abstract public function getDigestHash($realm, $username);
/**
* When this method is called, the backend must check if authentication was
* successful.
*
* The returned value must be one of the following
*
* [true, "principals/username"]
* [false, "reason for failure"]
*
* If authentication was successful, it's expected that the authentication
* backend returns a so-called principal url.
*
* Examples of a principal url:
*
* principals/admin
* principals/user1
* principals/users/joe
* principals/uid/123457
*
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
* return a string such as:
*
* principals/users/[username]
*
* @return array
*/
public function check(RequestInterface $request, ResponseInterface $response)
{
$digest = new HTTP\Auth\Digest(
$this->realm,
$request,
$response
);
$digest->init();
$username = $digest->getUsername();
// No username was given
if (!$username) {
return [false, "No 'Authorization: Digest' header found. Either the client didn't send one, or the server is misconfigured"];
}
$hash = $this->getDigestHash($this->realm, $username);
// If this was false, the user account didn't exist
if (false === $hash || is_null($hash)) {
return [false, 'Username or password was incorrect'];
}
if (!is_string($hash)) {
throw new DAV\Exception('The returned value from getDigestHash must be a string or null');
}
// If this was false, the password or part of the hash was incorrect.
if (!$digest->validateA1($hash)) {
return [false, 'Username or password was incorrect'];
}
return [true, $this->principalPrefix.$username];
}
/**
* This method is called when a user could not be authenticated, and
* authentication was required for the current request.
*
* This gives you the opportunity to set authentication headers. The 401
* status code will already be set.
*
* In this case of Basic Auth, this would for example mean that the
* following header needs to be set:
*
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
*
* Keep in mind that in the case of multiple authentication backends, other
* WWW-Authenticate headers may already have been set, and you'll want to
* append your own WWW-Authenticate header instead of overwriting the
* existing one.
*/
public function challenge(RequestInterface $request, ResponseInterface $response)
{
$auth = new HTTP\Auth\Digest(
$this->realm,
$request,
$response
);
$auth->init();
$oldStatus = $response->getStatus() ?: 200;
$auth->requireLogin();
// Preventing the digest utility from modifying the http status code,
// this should be handled by the main plugin.
$response->setStatus($oldStatus);
}
}

View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Auth\Backend;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* Apache (or NGINX) authenticator.
*
* This authentication backend assumes that authentication has been
* configured in apache (or NGINX), rather than within SabreDAV.
*
* Make sure apache (or NGINX) is properly configured for this to work.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Apache implements BackendInterface
{
/**
* This is the prefix that will be used to generate principal urls.
*
* @var string
*/
protected $principalPrefix = 'principals/';
/**
* When this method is called, the backend must check if authentication was
* successful.
*
* The returned value must be one of the following
*
* [true, "principals/username"]
* [false, "reason for failure"]
*
* If authentication was successful, it's expected that the authentication
* backend returns a so-called principal url.
*
* Examples of a principal url:
*
* principals/admin
* principals/user1
* principals/users/joe
* principals/uid/123457
*
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
* return a string such as:
*
* principals/users/[username]
*
* @return array
*/
public function check(RequestInterface $request, ResponseInterface $response)
{
$remoteUser = $request->getRawServerValue('REMOTE_USER');
if (is_null($remoteUser)) {
$remoteUser = $request->getRawServerValue('REDIRECT_REMOTE_USER');
}
if (is_null($remoteUser)) {
$remoteUser = $request->getRawServerValue('PHP_AUTH_USER');
}
if (is_null($remoteUser)) {
return [false, 'No REMOTE_USER, REDIRECT_REMOTE_USER, or PHP_AUTH_USER property was found in the PHP $_SERVER super-global. This likely means your server is not configured correctly'];
}
return [true, $this->principalPrefix.$remoteUser];
}
/**
* This method is called when a user could not be authenticated, and
* authentication was required for the current request.
*
* This gives you the opportunity to set authentication headers. The 401
* status code will already be set.
*
* In this case of Basic Auth, this would for example mean that the
* following header needs to be set:
*
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
*
* Keep in mind that in the case of multiple authentication backends, other
* WWW-Authenticate headers may already have been set, and you'll want to
* append your own WWW-Authenticate header instead of overwriting the
* existing one.
*/
public function challenge(RequestInterface $request, ResponseInterface $response)
{
}
}

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Auth\Backend;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* This is the base class for any authentication object.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface BackendInterface
{
/**
* When this method is called, the backend must check if authentication was
* successful.
*
* The returned value must be one of the following
*
* [true, "principals/username"]
* [false, "reason for failure"]
*
* If authentication was successful, it's expected that the authentication
* backend returns a so-called principal url.
*
* Examples of a principal url:
*
* principals/admin
* principals/user1
* principals/users/joe
* principals/uid/123457
*
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
* return a string such as:
*
* principals/users/[username]
*
* @return array
*/
public function check(RequestInterface $request, ResponseInterface $response);
/**
* This method is called when a user could not be authenticated, and
* authentication was required for the current request.
*
* This gives you the opportunity to set authentication headers. The 401
* status code will already be set.
*
* In this case of Basic Auth, this would for example mean that the
* following header needs to be set:
*
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
*
* Keep in mind that in the case of multiple authentication backends, other
* WWW-Authenticate headers may already have been set, and you'll want to
* append your own WWW-Authenticate header instead of overwriting the
* existing one.
*/
public function challenge(RequestInterface $request, ResponseInterface $response);
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Auth\Backend;
/**
* Extremely simply HTTP Basic auth backend.
*
* This backend basically works by calling a callback, which receives a
* username and password.
* The callback must return true or false depending on if authentication was
* correct.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class BasicCallBack extends AbstractBasic
{
/**
* Callback.
*
* @var callable
*/
protected $callBack;
/**
* Creates the backend.
*
* A callback must be provided to handle checking the username and
* password.
*/
public function __construct(callable $callBack)
{
$this->callBack = $callBack;
}
/**
* Validates a username and password.
*
* This method should return true or false depending on if login
* succeeded.
*
* @param string $username
* @param string $password
*
* @return bool
*/
protected function validateUserPass($username, $password)
{
$cb = $this->callBack;
return $cb($username, $password);
}
}

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Auth\Backend;
use Sabre\DAV;
/**
* This is an authentication backend that uses a file to manage passwords.
*
* The backend file must conform to Apache's htdigest format
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class File extends AbstractDigest
{
/**
* List of users.
*
* @var array
*/
protected $users = [];
/**
* Creates the backend object.
*
* If the filename argument is passed in, it will parse out the specified file first.
*
* @param string|null $filename
*/
public function __construct($filename = null)
{
if (!is_null($filename)) {
$this->loadFile($filename);
}
}
/**
* Loads an htdigest-formatted file. This method can be called multiple times if
* more than 1 file is used.
*
* @param string $filename
*/
public function loadFile($filename)
{
foreach (file($filename, FILE_IGNORE_NEW_LINES) as $line) {
if (2 !== substr_count($line, ':')) {
throw new DAV\Exception('Malformed htdigest file. Every line should contain 2 colons');
}
list($username, $realm, $A1) = explode(':', $line);
if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1)) {
throw new DAV\Exception('Malformed htdigest file. Invalid md5 hash');
}
$this->users[$realm.':'.$username] = $A1;
}
}
/**
* Returns a users' information.
*
* @param string $realm
* @param string $username
*
* @return string
*/
public function getDigestHash($realm, $username)
{
return isset($this->users[$realm.':'.$username]) ? $this->users[$realm.':'.$username] : false;
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Sabre\DAV\Auth\Backend;
/**
* This is an authentication backend that uses imap.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Michael Niewöhner (foss@mniewoehner.de)
* @author rosali (https://github.com/rosali)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class IMAP extends AbstractBasic
{
/**
* IMAP server in the form {host[:port][/flag1/flag2...]}.
*
* @see http://php.net/manual/en/function.imap-open.php
*
* @var string
*/
protected $mailbox;
/**
* Creates the backend object.
*
* @param string $mailbox
*/
public function __construct($mailbox)
{
$this->mailbox = $mailbox;
}
/**
* Connects to an IMAP server and tries to authenticate.
*
* @param string $username
* @param string $password
*
* @return bool
*/
protected function imapOpen($username, $password)
{
$success = false;
try {
$imap = imap_open($this->mailbox, $username, $password, OP_HALFOPEN | OP_READONLY, 1);
if ($imap) {
$success = true;
}
} catch (\ErrorException $e) {
error_log($e->getMessage());
}
$errors = imap_errors();
if ($errors) {
foreach ($errors as $error) {
error_log($error);
}
}
if (isset($imap) && $imap) {
imap_close($imap);
}
return $success;
}
/**
* Validates a username and password by trying to authenticate against IMAP.
*
* @param string $username
* @param string $password
*
* @return bool
*/
protected function validateUserPass($username, $password)
{
return $this->imapOpen($username, $password);
}
}

View File

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Auth\Backend;
/**
* This is an authentication backend that uses a database to manage passwords.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class PDO extends AbstractDigest
{
/**
* Reference to PDO connection.
*
* @var PDO
*/
protected $pdo;
/**
* PDO table name we'll be using.
*
* @var string
*/
public $tableName = 'users';
/**
* Creates the backend object.
*
* If the filename argument is passed in, it will parse out the specified file fist.
*/
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* Returns the digest hash for a user.
*
* @param string $realm
* @param string $username
*
* @return string|null
*/
public function getDigestHash($realm, $username)
{
$stmt = $this->pdo->prepare('SELECT digesta1 FROM '.$this->tableName.' WHERE username = ?');
$stmt->execute([$username]);
return $stmt->fetchColumn() ?: null;
}
}

259
vendor/sabre/dav/lib/DAV/Auth/Plugin.php vendored Normal file
View File

@ -0,0 +1,259 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Auth;
use Sabre\DAV\Exception\NotAuthenticated;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* This plugin provides Authentication for a WebDAV server.
*
* It works by providing a Auth\Backend class. Several examples of these
* classes can be found in the Backend directory.
*
* It's possible to provide more than one backend to this plugin. If more than
* one backend was provided, each backend will attempt to authenticate. Only if
* all backends fail, we throw a 401.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends ServerPlugin
{
/**
* By default this plugin will require that the user is authenticated,
* and refuse any access if the user is not authenticated.
*
* If this setting is set to false, we let the user through, whether they
* are authenticated or not.
*
* This is useful if you want to allow both authenticated and
* unauthenticated access to your server.
*
* @param bool
*/
public $autoRequireLogin = true;
/**
* authentication backends.
*/
protected $backends;
/**
* The currently logged in principal. Will be `null` if nobody is currently
* logged in.
*
* @var string|null
*/
protected $currentPrincipal;
/**
* Creates the authentication plugin.
*
* @param Backend\BackendInterface $authBackend
*/
public function __construct(Backend\BackendInterface $authBackend = null)
{
if (!is_null($authBackend)) {
$this->addBackend($authBackend);
}
}
/**
* Adds an authentication backend to the plugin.
*/
public function addBackend(Backend\BackendInterface $authBackend)
{
$this->backends[] = $authBackend;
}
/**
* Initializes the plugin. This function is automatically called by the server.
*/
public function initialize(Server $server)
{
$server->on('beforeMethod:*', [$this, 'beforeMethod'], 10);
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return 'auth';
}
/**
* Returns the currently logged-in principal.
*
* This will return a string such as:
*
* principals/username
* principals/users/username
*
* This method will return null if nobody is logged in.
*
* @return string|null
*/
public function getCurrentPrincipal()
{
return $this->currentPrincipal;
}
/**
* This method is called before any HTTP method and forces users to be authenticated.
*
* @return bool
*/
public function beforeMethod(RequestInterface $request, ResponseInterface $response)
{
if ($this->currentPrincipal) {
// We already have authentication information. This means that the
// event has already fired earlier, and is now likely fired for a
// sub-request.
//
// We don't want to authenticate users twice, so we simply don't do
// anything here. See Issue #700 for additional reasoning.
//
// This is not a perfect solution, but will be fixed once the
// "currently authenticated principal" is information that's not
// not associated with the plugin, but rather per-request.
//
// See issue #580 for more information about that.
return;
}
$authResult = $this->check($request, $response);
if ($authResult[0]) {
// Auth was successful
$this->currentPrincipal = $authResult[1];
$this->loginFailedReasons = null;
return;
}
// If we got here, it means that no authentication backend was
// successful in authenticating the user.
$this->currentPrincipal = null;
$this->loginFailedReasons = $authResult[1];
if ($this->autoRequireLogin) {
$this->challenge($request, $response);
throw new NotAuthenticated(implode(', ', $authResult[1]));
}
}
/**
* Checks authentication credentials, and logs the user in if possible.
*
* This method returns an array. The first item in the array is a boolean
* indicating if login was successful.
*
* If login was successful, the second item in the array will contain the
* current principal url/path of the logged in user.
*
* If login was not successful, the second item in the array will contain a
* an array with strings. The strings are a list of reasons why login was
* unsuccessful. For every auth backend there will be one reason, so usually
* there's just one.
*
* @return array
*/
public function check(RequestInterface $request, ResponseInterface $response)
{
if (!$this->backends) {
throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
}
$reasons = [];
foreach ($this->backends as $backend) {
$result = $backend->check(
$request,
$response
);
if (!is_array($result) || 2 !== count($result) || !is_bool($result[0]) || !is_string($result[1])) {
throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.');
}
if ($result[0]) {
$this->currentPrincipal = $result[1];
// Exit early
return [true, $result[1]];
}
$reasons[] = $result[1];
}
return [false, $reasons];
}
/**
* This method sends authentication challenges to the user.
*
* This method will for example cause a HTTP Basic backend to set a
* WWW-Authorization header, indicating to the client that it should
* authenticate.
*
* @return array
*/
public function challenge(RequestInterface $request, ResponseInterface $response)
{
foreach ($this->backends as $backend) {
$backend->challenge($request, $response);
}
}
/**
* List of reasons why login failed for the last login operation.
*
* @var string[]|null
*/
protected $loginFailedReasons;
/**
* Returns a list of reasons why login was unsuccessful.
*
* This method will return the login failed reasons for the last login
* operation. One for each auth backend.
*
* This method returns null if the last authentication attempt was
* successful, or if there was no authentication attempt yet.
*
* @return string[]|null
*/
public function getLoginFailedReasons()
{
return $this->loginFailedReasons;
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
public function getPluginInfo()
{
return [
'name' => $this->getPluginName(),
'description' => 'Generic authentication plugin',
'link' => 'http://sabre.io/dav/authentication/',
];
}
}

View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Browser;
use Sabre\DAV;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\Uri;
/**
* GuessContentType plugin.
*
* A lot of the built-in File objects just return application/octet-stream
* as a content-type by default. This is a problem for some clients, because
* they expect a correct contenttype.
*
* There's really no accurate, fast and portable way to determine the contenttype
* so this extension does what the rest of the world does, and guesses it based
* on the file extension.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class GuessContentType extends DAV\ServerPlugin
{
/**
* List of recognized file extensions.
*
* Feel free to add more
*
* @var array
*/
public $extensionMap = [
// images
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
// groupware
'ics' => 'text/calendar',
'vcf' => 'text/vcard',
// text
'txt' => 'text/plain',
];
/**
* Initializes the plugin.
*/
public function initialize(DAV\Server $server)
{
// Using a relatively low priority (200) to allow other extensions
// to set the content-type first.
$server->on('propFind', [$this, 'propFind'], 200);
}
/**
* Our PROPFIND handler.
*
* Here we set a contenttype, if the node didn't already have one.
*/
public function propFind(PropFind $propFind, INode $node)
{
$propFind->handle('{DAV:}getcontenttype', function () use ($propFind) {
list(, $fileName) = Uri\split($propFind->getPath());
return $this->getContentType($fileName);
});
}
/**
* Simple method to return the contenttype.
*
* @param string $fileName
*
* @return string
*/
protected function getContentType($fileName)
{
if (null !== $fileName) {
// Just grabbing the extension
$extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1));
if (isset($this->extensionMap[$extension])) {
return $this->extensionMap[$extension];
}
}
return 'application/octet-stream';
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Browser;
/**
* WebDAV properties that implement this interface are able to generate their
* own html output for the browser plugin.
*
* This is only useful for display purposes, and might make it a bit easier for
* people to read and understand the value of some properties.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface HtmlOutput
{
/**
* Generate html representation for this value.
*
* The html output is 100% trusted, and no effort is being made to sanitize
* it. It's up to the implementor to sanitize user provided values.
*
* The output must be in UTF-8.
*
* The baseUri parameter is a url to the root of the application, and can
* be used to construct local links.
*
* @return string
*/
public function toHtml(HtmlOutputHelper $html);
}

View File

@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Browser;
use Sabre\Uri;
use Sabre\Xml\Service as XmlService;
/**
* This class provides a few utility functions for easily generating HTML for
* the browser plugin.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class HtmlOutputHelper
{
/**
* Link to the root of the application.
*
* @var string
*/
protected $baseUri;
/**
* List of xml namespaces.
*
* @var array
*/
protected $namespaceMap;
/**
* Creates the object.
*
* baseUri must point to the root of the application. This will be used to
* easily generate links.
*
* The namespaceMap contains an array with the list of xml namespaces and
* their prefixes. WebDAV uses a lot of XML with complex namespaces, so
* that can be used to make output a lot shorter.
*
* @param string $baseUri
*/
public function __construct($baseUri, array $namespaceMap)
{
$this->baseUri = $baseUri;
$this->namespaceMap = $namespaceMap;
}
/**
* Generates a 'full' url based on a relative one.
*
* For relative urls, the base of the application is taken as the reference
* url, not the 'current url of the current request'.
*
* Absolute urls are left alone.
*
* @param string $path
*
* @return string
*/
public function fullUrl($path)
{
return Uri\resolve($this->baseUri, $path);
}
/**
* Escape string for HTML output.
*
* @param scalar $input
*
* @return string
*/
public function h($input)
{
return htmlspecialchars((string) $input, ENT_COMPAT, 'UTF-8');
}
/**
* Generates a full <a>-tag.
*
* Url is automatically expanded. If label is not specified, we re-use the
* url.
*
* @param string $url
* @param string $label
*
* @return string
*/
public function link($url, $label = null)
{
$url = $this->h($this->fullUrl($url));
return '<a href="'.$url.'">'.($label ? $this->h($label) : $url).'</a>';
}
/**
* This method takes an xml element in clark-notation, and turns it into a
* shortened version with a prefix, if it was a known namespace.
*
* @param string $element
*
* @return string
*/
public function xmlName($element)
{
list($ns, $localName) = XmlService::parseClarkNotation($element);
if (isset($this->namespaceMap[$ns])) {
$propName = $this->namespaceMap[$ns].':'.$localName;
} else {
$propName = $element;
}
return '<span title="'.$this->h($element).'">'.$this->h($propName).'</span>';
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Browser;
use Sabre\DAV;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* This is a simple plugin that will map any GET request for non-files to
* PROPFIND allprops-requests.
*
* This should allow easy debugging of PROPFIND
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class MapGetToPropFind extends DAV\ServerPlugin
{
/**
* reference to server class.
*
* @var DAV\Server
*/
protected $server;
/**
* Initializes the plugin and subscribes to events.
*/
public function initialize(DAV\Server $server)
{
$this->server = $server;
$this->server->on('method:GET', [$this, 'httpGet'], 90);
}
/**
* This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request.
*
* @return bool
*/
public function httpGet(RequestInterface $request, ResponseInterface $response)
{
$node = $this->server->tree->getNodeForPath($request->getPath());
if ($node instanceof DAV\IFile) {
return;
}
$subRequest = clone $request;
$subRequest->setMethod('PROPFIND');
$this->server->invokeMethod($subRequest, $response);
return false;
}
}

View File

@ -0,0 +1,789 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Browser;
use Sabre\DAV;
use Sabre\DAV\MkCol;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\Uri;
/**
* Browser Plugin.
*
* This plugin provides a html representation, so that a WebDAV server may be accessed
* using a browser.
*
* The class intercepts GET requests to collection resources and generates a simple
* html index.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends DAV\ServerPlugin
{
/**
* reference to server class.
*
* @var 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;
/**
* A list of properties that are usually not interesting. This can cut down
* the browser output a bit by removing the properties that most people
* will likely not want to see.
*
* @var array
*/
public $uninterestingProperties = [
'{DAV:}supportedlock',
'{DAV:}acl-restrictions',
// '{DAV:}supported-privilege-set',
'{DAV:}supported-method-set',
];
/**
* Creates the object.
*
* By default it will allow file creation and uploads.
* Specify the first argument as false to disable this
*
* @param bool $enablePost
*/
public function __construct($enablePost = true)
{
$this->enablePost = $enablePost;
}
/**
* Initializes the plugin and subscribes to events.
*/
public function initialize(DAV\Server $server)
{
$this->server = $server;
$this->server->on('method:GET', [$this, 'httpGetEarly'], 90);
$this->server->on('method:GET', [$this, 'httpGet'], 200);
$this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200);
if ($this->enablePost) {
$this->server->on('method:POST', [$this, 'httpPOST']);
}
}
/**
* This method intercepts GET requests that have ?sabreAction=info
* appended to the URL.
*
* @return bool
*/
public function httpGetEarly(RequestInterface $request, ResponseInterface $response)
{
$params = $request->getQueryParameters();
if (isset($params['sabreAction']) && 'info' === $params['sabreAction']) {
return $this->httpGet($request, $response);
}
}
/**
* This method intercepts GET requests to collections and returns the html.
*
* @return bool
*/
public function httpGet(RequestInterface $request, ResponseInterface $response)
{
// We're not using straight-up $_GET, because we want everything to be
// unit testable.
$getVars = $request->getQueryParameters();
// CSP headers
$response->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
$sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null;
switch ($sabreAction) {
case 'asset':
// Asset handling, such as images
$this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null);
return false;
default:
case 'info':
try {
$this->server->tree->getNodeForPath($request->getPath());
} catch (DAV\Exception\NotFound $e) {
// We're simply stopping when the file isn't found to not interfere
// with other plugins.
return;
}
$response->setStatus(200);
$response->setHeader('Content-Type', 'text/html; charset=utf-8');
$response->setBody(
$this->generateDirectoryIndex($request->getPath())
);
return false;
case 'plugins':
$response->setStatus(200);
$response->setHeader('Content-Type', 'text/html; charset=utf-8');
$response->setBody(
$this->generatePluginListing()
);
return false;
}
}
/**
* Handles POST requests for tree operations.
*
* @return bool
*/
public function httpPOST(RequestInterface $request, ResponseInterface $response)
{
$contentType = $request->getHeader('Content-Type');
list($contentType) = explode(';', $contentType);
if ('application/x-www-form-urlencoded' !== $contentType &&
'multipart/form-data' !== $contentType) {
return;
}
$postVars = $request->getPostData();
if (!isset($postVars['sabreAction'])) {
return;
}
$uri = $request->getPath();
if ($this->server->emit('onBrowserPostAction', [$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) = Uri\split(trim($postVars['name']));
if (isset($postVars['resourceType'])) {
$resourceType = explode(',', $postVars['resourceType']);
} else {
$resourceType = ['{DAV:}collection'];
}
$properties = [];
foreach ($postVars as $varName => $varValue) {
// Any _POST variable in clark notation is treated
// like a property.
if ('{' === $varName[0]) {
// PHP will convert any dots to underscores.
// This leaves us with no way to differentiate
// the two.
// Therefore we replace the string *DOT* with a
// real dot. * is not allowed in uris so we
// should be good.
$varName = str_replace('*DOT*', '.', $varName);
$properties[$varName] = $varValue;
}
}
$mkCol = new MkCol(
$resourceType,
$properties
);
$this->server->createCollection($uri.'/'.$folderName, $mkCol);
}
break;
// @codeCoverageIgnoreStart
case 'put':
if ($_FILES) {
$file = current($_FILES);
} else {
break;
}
list(, $newName) = Uri\split(trim($file['name']));
if (isset($postVars['name']) && trim($postVars['name'])) {
$newName = trim($postVars['name']);
}
// Making sure we only have a 'basename' component
list(, $newName) = Uri\split($newName);
if (is_uploaded_file($file['tmp_name'])) {
$this->server->createFile($uri.'/'.$newName, fopen($file['tmp_name'], 'r'));
}
break;
// @codeCoverageIgnoreEnd
}
}
$response->setHeader('Location', $request->getUrl());
$response->setStatus(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)
{
$html = $this->generateHeader($path ? $path : '/', $path);
$node = $this->server->tree->getNodeForPath($path);
if ($node instanceof DAV\ICollection) {
$html .= "<section><h1>Nodes</h1>\n";
$html .= '<table class="nodeTable">';
$subNodes = $this->server->getPropertiesForChildren($path, [
'{DAV:}displayname',
'{DAV:}resourcetype',
'{DAV:}getcontenttype',
'{DAV:}getcontentlength',
'{DAV:}getlastmodified',
]);
foreach ($subNodes as $subPath => $subProps) {
$subNode = $this->server->tree->getNodeForPath($subPath);
$fullPath = $this->server->getBaseUri().HTTP\encodePath($subPath);
list(, $displayPath) = Uri\split($subPath);
$subNodes[$subPath]['subNode'] = $subNode;
$subNodes[$subPath]['fullPath'] = $fullPath;
$subNodes[$subPath]['displayPath'] = $displayPath;
}
uasort($subNodes, [$this, 'compareNodes']);
foreach ($subNodes as $subProps) {
$type = [
'string' => 'Unknown',
'icon' => 'cog',
];
if (isset($subProps['{DAV:}resourcetype'])) {
$type = $this->mapResourceType($subProps['{DAV:}resourcetype']->getValue(), $subProps['subNode']);
}
$html .= '<tr>';
$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>';
$html .= '<td class="typeColumn">'.$this->escapeHTML($type['string']).'</td>';
$html .= '<td>';
if (isset($subProps['{DAV:}getcontentlength'])) {
$html .= $this->escapeHTML($subProps['{DAV:}getcontentlength'].' bytes');
}
$html .= '</td><td>';
if (isset($subProps['{DAV:}getlastmodified'])) {
$lastMod = $subProps['{DAV:}getlastmodified']->getTime();
$html .= $this->escapeHTML($lastMod->format('F j, Y, g:i a'));
}
$html .= '</td><td>';
if (isset($subProps['{DAV:}displayname'])) {
$html .= $this->escapeHTML($subProps['{DAV:}displayname']);
}
$html .= '</td>';
$buttonActions = '';
if ($subProps['subNode'] instanceof DAV\IFile) {
$buttonActions = '<a href="'.$this->escapeHTML($subProps['fullPath']).'?sabreAction=info"><span class="oi" data-glyph="info"></span></a>';
}
$this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]);
$html .= '<td>'.$buttonActions.'</td>';
$html .= '</tr>';
}
$html .= '</table>';
}
$html .= '</section>';
$html .= '<section><h1>Properties</h1>';
$html .= '<table class="propTable">';
// Allprops request
$propFind = new PropFindAll($path);
$properties = $this->server->getPropertiesByNode($propFind, $node);
$properties = $propFind->getResultForMultiStatus()[200];
foreach ($properties as $propName => $propValue) {
if (!in_array($propName, $this->uninterestingProperties)) {
$html .= $this->drawPropertyRow($propName, $propValue);
}
}
$html .= '</table>';
$html .= '</section>';
/* Start of generating actions */
$output = '';
if ($this->enablePost) {
$this->server->emit('onHTMLActionsPanel', [$node, &$output, $path]);
}
if ($output) {
$html .= '<section><h1>Actions</h1>';
$html .= "<div class=\"actions\">\n";
$html .= $output;
$html .= "</div>\n";
$html .= "</section>\n";
}
$html .= $this->generateFooter();
$this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");
return $html;
}
/**
* Generates the 'plugins' page.
*
* @return string
*/
public function generatePluginListing()
{
$html = $this->generateHeader('Plugins');
$html .= '<section><h1>Plugins</h1>';
$html .= '<table class="propTable">';
foreach ($this->server->getPlugins() as $plugin) {
$info = $plugin->getPluginInfo();
$html .= '<tr><th>'.$info['name'].'</th>';
$html .= '<td>'.$info['description'].'</td>';
$html .= '<td>';
if (isset($info['link']) && $info['link']) {
$html .= '<a href="'.$this->escapeHTML($info['link']).'"><span class="oi" data-glyph="book"></span></a>';
}
$html .= '</td></tr>';
}
$html .= '</table>';
$html .= '</section>';
/* Start of generating actions */
$html .= $this->generateFooter();
return $html;
}
/**
* Generates the first block of HTML, including the <head> tag and page
* header.
*
* Returns footer.
*
* @param string $title
* @param string $path
*
* @return string
*/
public function generateHeader($title, $path = null)
{
$version = '';
if (DAV\Server::$exposeVersion) {
$version = DAV\Version::VERSION;
}
$vars = [
'title' => $this->escapeHTML($title),
'favicon' => $this->escapeHTML($this->getAssetUrl('favicon.ico')),
'style' => $this->escapeHTML($this->getAssetUrl('sabredav.css')),
'iconstyle' => $this->escapeHTML($this->getAssetUrl('openiconic/open-iconic.css')),
'logo' => $this->escapeHTML($this->getAssetUrl('sabredav.png')),
'baseUrl' => $this->server->getBaseUri(),
];
$html = <<<HTML
<!DOCTYPE html>
<html>
<head>
<title>$vars[title] - sabre/dav $version</title>
<link rel="shortcut icon" href="$vars[favicon]" type="image/vnd.microsoft.icon" />
<link rel="stylesheet" href="$vars[style]" type="text/css" />
<link rel="stylesheet" href="$vars[iconstyle]" type="text/css" />
</head>
<body>
<header>
<div class="logo">
<a href="$vars[baseUrl]"><img src="$vars[logo]" alt="sabre/dav" /> $vars[title]</a>
</div>
</header>
<nav>
HTML;
// If the path is empty, there's no parent.
if ($path) {
list($parentUri) = Uri\split($path);
$fullPath = $this->server->getBaseUri().HTTP\encodePath($parentUri);
$html .= '<a href="'.$fullPath.'" class="btn">⇤ Go to parent</a>';
} else {
$html .= '<span class="btn disabled">⇤ Go to parent</span>';
}
$html .= ' <a href="?sabreAction=plugins" class="btn"><span class="oi" data-glyph="puzzle-piece"></span> Plugins</a>';
$html .= '</nav>';
return $html;
}
/**
* Generates the page footer.
*
* Returns html.
*
* @return string
*/
public function generateFooter()
{
$version = '';
if (DAV\Server::$exposeVersion) {
$version = DAV\Version::VERSION;
}
$year = date('Y');
return <<<HTML
<footer>Generated by SabreDAV $version (c)2007-$year <a href="http://sabre.io/">http://sabre.io/</a></footer>
</body>
</html>
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 mixed $output
* @param string $path
*/
public function htmlActionsPanel(DAV\INode $node, &$output, $path)
{
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 ('Sabre\\DAV\\SimpleCollection' === get_class($node)) {
return;
}
$output .= <<<HTML
<form method="post" action="">
<h3>Create new folder</h3>
<input type="hidden" name="sabreAction" value="mkcol" />
<label>Name:</label> <input type="text" name="name" /><br />
<input type="submit" value="create" />
</form>
<form method="post" action="" enctype="multipart/form-data">
<h3>Upload file</h3>
<input type="hidden" name="sabreAction" value="put" />
<label>Name (optional):</label> <input type="text" name="name" /><br />
<label>File:</label> <input type="file" name="file" /><br />
<input type="submit" value="upload" />
</form>
HTML;
}
/**
* 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
*
* @throws DAV\Exception\NotFound
*
* @return string
*/
protected function getLocalAssetPath($assetName)
{
$assetDir = __DIR__.'/assets/';
$path = $assetDir.$assetName;
// Making sure people aren't trying to escape from the base path.
$path = str_replace('\\', '/', $path);
if (false !== strpos($path, '/../') || '/..' === strrchr($path, '/')) {
throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
}
$realPath = realpath($path);
if ($realPath && 0 === strpos($realPath, realpath($assetDir)) && file_exists($path)) {
return $path;
}
throw new DAV\Exception\NotFound('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
*/
protected function serveAsset($assetName)
{
$assetPath = $this->getLocalAssetPath($assetName);
// Rudimentary mime type detection
$mime = 'application/octet-stream';
$map = [
'ico' => 'image/vnd.microsoft.icon',
'png' => 'image/png',
'css' => 'text/css',
];
$ext = substr($assetName, strrpos($assetName, '.') + 1);
if (isset($map[$ext])) {
$mime = $map[$ext];
}
$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->setStatus(200);
$this->server->httpResponse->setBody(fopen($assetPath, 'r'));
}
/**
* Sort helper function: compares two directory entries based on type and
* display name. Collections sort above other types.
*
* @param array $a
* @param array $b
*
* @return int
*/
protected function compareNodes($a, $b)
{
$typeA = (isset($a['{DAV:}resourcetype']))
? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue()))
: false;
$typeB = (isset($b['{DAV:}resourcetype']))
? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue()))
: false;
// If same type, sort alphabetically by filename:
if ($typeA === $typeB) {
return strnatcasecmp($a['displayPath'], $b['displayPath']);
}
return ($typeA < $typeB) ? 1 : -1;
}
/**
* Maps a resource type to a human-readable string and icon.
*
* @param DAV\INode $node
*
* @return array
*/
private function mapResourceType(array $resourceTypes, $node)
{
if (!$resourceTypes) {
if ($node instanceof DAV\IFile) {
return [
'string' => 'File',
'icon' => 'file',
];
} else {
return [
'string' => 'Unknown',
'icon' => 'cog',
];
}
}
$types = [
'{http://calendarserver.org/ns/}calendar-proxy-write' => [
'string' => 'Proxy-Write',
'icon' => 'people',
],
'{http://calendarserver.org/ns/}calendar-proxy-read' => [
'string' => 'Proxy-Read',
'icon' => 'people',
],
'{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [
'string' => 'Outbox',
'icon' => 'inbox',
],
'{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [
'string' => 'Inbox',
'icon' => 'inbox',
],
'{urn:ietf:params:xml:ns:caldav}calendar' => [
'string' => 'Calendar',
'icon' => 'calendar',
],
'{http://calendarserver.org/ns/}shared-owner' => [
'string' => 'Shared',
'icon' => 'calendar',
],
'{http://calendarserver.org/ns/}subscribed' => [
'string' => 'Subscription',
'icon' => 'calendar',
],
'{urn:ietf:params:xml:ns:carddav}directory' => [
'string' => 'Directory',
'icon' => 'globe',
],
'{urn:ietf:params:xml:ns:carddav}addressbook' => [
'string' => 'Address book',
'icon' => 'book',
],
'{DAV:}principal' => [
'string' => 'Principal',
'icon' => 'person',
],
'{DAV:}collection' => [
'string' => 'Collection',
'icon' => 'folder',
],
];
$info = [
'string' => [],
'icon' => 'cog',
];
foreach ($resourceTypes as $k => $resourceType) {
if (isset($types[$resourceType])) {
$info['string'][] = $types[$resourceType]['string'];
} else {
$info['string'][] = $resourceType;
}
}
foreach ($types as $key => $resourceInfo) {
if (in_array($key, $resourceTypes)) {
$info['icon'] = $resourceInfo['icon'];
break;
}
}
$info['string'] = implode(', ', $info['string']);
return $info;
}
/**
* Draws a table row for a property.
*
* @param string $name
* @param mixed $value
*
* @return string
*/
private function drawPropertyRow($name, $value)
{
$html = new HtmlOutputHelper(
$this->server->getBaseUri(),
$this->server->xml->namespaceMap
);
return '<tr><th>'.$html->xmlName($name).'</th><td>'.$this->drawPropertyValue($html, $value).'</td></tr>';
}
/**
* Draws a table row for a property.
*
* @param HtmlOutputHelper $html
* @param mixed $value
*
* @return string
*/
private function drawPropertyValue($html, $value)
{
if (is_scalar($value)) {
return $html->h($value);
} elseif ($value instanceof HtmlOutput) {
return $value->toHtml($html);
} elseif ($value instanceof \Sabre\Xml\XmlSerializable) {
// There's no default html output for this property, we're going
// to output the actual xml serialization instead.
$xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri());
// removing first and last line, as they contain our root
// element.
$xml = explode("\n", $xml);
$xml = array_slice($xml, 2, -2);
return '<pre>'.$html->h(implode("\n", $xml)).'</pre>';
} else {
return '<em>unknown</em>';
}
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins;
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return 'browser';
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
public function getPluginInfo()
{
return [
'name' => $this->getPluginName(),
'description' => 'Generates HTML indexes and debug information for your sabre/dav server',
'link' => 'http://sabre.io/dav/browser-plugin/',
];
}
}

View File

@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Browser;
use Sabre\DAV\PropFind;
/**
* This class is used by the browser plugin to trick the system in returning
* every defined property.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class PropFindAll extends PropFind
{
/**
* Creates the PROPFIND object.
*
* @param string $path
*/
public function __construct($path)
{
parent::__construct($path, []);
}
/**
* Handles a specific property.
*
* This method checks whether the specified property was requested in this
* PROPFIND request, and if so, it will call the callback and use the
* return value for it's value.
*
* Example:
*
* $propFind->handle('{DAV:}displayname', function() {
* return 'hello';
* });
*
* Note that handle will only work the first time. If null is returned, the
* value is ignored.
*
* It's also possible to not pass a callback, but immediately pass a value
*
* @param string $propertyName
* @param mixed $valueOrCallBack
*/
public function handle($propertyName, $valueOrCallBack)
{
if (is_callable($valueOrCallBack)) {
$value = $valueOrCallBack();
} else {
$value = $valueOrCallBack;
}
if (!is_null($value)) {
$this->result[$propertyName] = [200, $value];
}
}
/**
* Sets the value of the property.
*
* If status is not supplied, the status will default to 200 for non-null
* properties, and 404 for null properties.
*
* @param string $propertyName
* @param mixed $value
* @param int $status
*/
public function set($propertyName, $value, $status = null)
{
if (is_null($status)) {
$status = is_null($value) ? 404 : 200;
}
$this->result[$propertyName] = [$status, $value];
}
/**
* Returns the current value for a property.
*
* @param string $propertyName
*
* @return mixed
*/
public function get($propertyName)
{
return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null;
}
/**
* Returns the current status code for a property name.
*
* If the property does not appear in the list of requested properties,
* null will be returned.
*
* @param string $propertyName
*
* @return int|null
*/
public function getStatus($propertyName)
{
return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : 404;
}
/**
* Returns all propertynames that have a 404 status, and thus don't have a
* value yet.
*
* @return array
*/
public function get404Properties()
{
$result = [];
foreach ($this->result as $propertyName => $stuff) {
if (404 === $stuff[0]) {
$result[] = $propertyName;
}
}
// If there's nothing in this list, we're adding one fictional item.
if (!$result) {
$result[] = '{http://sabredav.org/ns}idk';
}
return $result;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Waybury
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,510 @@
@font-face {
font-family: 'Icons';
src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot');
src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot?#iconic-sm') format('embedded-opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.woff') format('woff'), url('?sabreAction=asset&assetName=openiconic/open-iconic.ttf') format('truetype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.otf') format('opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.svg#iconic-sm') format('svg');
font-weight: normal;
font-style: normal;
}
.oi[data-glyph].oi-text-replace {
font-size: 0;
line-height: 0;
}
.oi[data-glyph].oi-text-replace:before {
width: 1em;
text-align: center;
}
.oi[data-glyph]:before {
font-family: 'Icons';
display: inline-block;
speak: none;
line-height: 1;
vertical-align: baseline;
font-weight: normal;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.oi[data-glyph]:empty:before {
width: 1em;
text-align: center;
box-sizing: content-box;
}
.oi[data-glyph].oi-align-left:before {
text-align: left;
}
.oi[data-glyph].oi-align-right:before {
text-align: right;
}
.oi[data-glyph].oi-align-center:before {
text-align: center;
}
.oi[data-glyph].oi-flip-horizontal:before {
-webkit-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
transform: scale(-1, 1);
}
.oi[data-glyph].oi-flip-vertical:before {
-webkit-transform: scale(1, -1);
-ms-transform: scale(-1, 1);
transform: scale(1, -1);
}
.oi[data-glyph].oi-flip-horizontal-vertical:before {
-webkit-transform: scale(-1, -1);
-ms-transform: scale(-1, 1);
transform: scale(-1, -1);
}
.oi[data-glyph=account-login]:before { content:'\e000'; }
.oi[data-glyph=account-logout]:before { content:'\e001'; }
.oi[data-glyph=action-redo]:before { content:'\e002'; }
.oi[data-glyph=action-undo]:before { content:'\e003'; }
.oi[data-glyph=align-center]:before { content:'\e004'; }
.oi[data-glyph=align-left]:before { content:'\e005'; }
.oi[data-glyph=align-right]:before { content:'\e006'; }
.oi[data-glyph=aperture]:before { content:'\e007'; }
.oi[data-glyph=arrow-bottom]:before { content:'\e008'; }
.oi[data-glyph=arrow-circle-bottom]:before { content:'\e009'; }
.oi[data-glyph=arrow-circle-left]:before { content:'\e00a'; }
.oi[data-glyph=arrow-circle-right]:before { content:'\e00b'; }
.oi[data-glyph=arrow-circle-top]:before { content:'\e00c'; }
.oi[data-glyph=arrow-left]:before { content:'\e00d'; }
.oi[data-glyph=arrow-right]:before { content:'\e00e'; }
.oi[data-glyph=arrow-thick-bottom]:before { content:'\e00f'; }
.oi[data-glyph=arrow-thick-left]:before { content:'\e010'; }
.oi[data-glyph=arrow-thick-right]:before { content:'\e011'; }
.oi[data-glyph=arrow-thick-top]:before { content:'\e012'; }
.oi[data-glyph=arrow-top]:before { content:'\e013'; }
.oi[data-glyph=audio-spectrum]:before { content:'\e014'; }
.oi[data-glyph=audio]:before { content:'\e015'; }
.oi[data-glyph=badge]:before { content:'\e016'; }
.oi[data-glyph=ban]:before { content:'\e017'; }
.oi[data-glyph=bar-chart]:before { content:'\e018'; }
.oi[data-glyph=basket]:before { content:'\e019'; }
.oi[data-glyph=battery-empty]:before { content:'\e01a'; }
.oi[data-glyph=battery-full]:before { content:'\e01b'; }
.oi[data-glyph=beaker]:before { content:'\e01c'; }
.oi[data-glyph=bell]:before { content:'\e01d'; }
.oi[data-glyph=bluetooth]:before { content:'\e01e'; }
.oi[data-glyph=bold]:before { content:'\e01f'; }
.oi[data-glyph=bolt]:before { content:'\e020'; }
.oi[data-glyph=book]:before { content:'\e021'; }
.oi[data-glyph=bookmark]:before { content:'\e022'; }
.oi[data-glyph=box]:before { content:'\e023'; }
.oi[data-glyph=briefcase]:before { content:'\e024'; }
.oi[data-glyph=british-pound]:before { content:'\e025'; }
.oi[data-glyph=browser]:before { content:'\e026'; }
.oi[data-glyph=brush]:before { content:'\e027'; }
.oi[data-glyph=bug]:before { content:'\e028'; }
.oi[data-glyph=bullhorn]:before { content:'\e029'; }
.oi[data-glyph=calculator]:before { content:'\e02a'; }
.oi[data-glyph=calendar]:before { content:'\e02b'; }
.oi[data-glyph=camera-slr]:before { content:'\e02c'; }
.oi[data-glyph=caret-bottom]:before { content:'\e02d'; }
.oi[data-glyph=caret-left]:before { content:'\e02e'; }
.oi[data-glyph=caret-right]:before { content:'\e02f'; }
.oi[data-glyph=caret-top]:before { content:'\e030'; }
.oi[data-glyph=cart]:before { content:'\e031'; }
.oi[data-glyph=chat]:before { content:'\e032'; }
.oi[data-glyph=check]:before { content:'\e033'; }
.oi[data-glyph=chevron-bottom]:before { content:'\e034'; }
.oi[data-glyph=chevron-left]:before { content:'\e035'; }
.oi[data-glyph=chevron-right]:before { content:'\e036'; }
.oi[data-glyph=chevron-top]:before { content:'\e037'; }
.oi[data-glyph=circle-check]:before { content:'\e038'; }
.oi[data-glyph=circle-x]:before { content:'\e039'; }
.oi[data-glyph=clipboard]:before { content:'\e03a'; }
.oi[data-glyph=clock]:before { content:'\e03b'; }
.oi[data-glyph=cloud-download]:before { content:'\e03c'; }
.oi[data-glyph=cloud-upload]:before { content:'\e03d'; }
.oi[data-glyph=cloud]:before { content:'\e03e'; }
.oi[data-glyph=cloudy]:before { content:'\e03f'; }
.oi[data-glyph=code]:before { content:'\e040'; }
.oi[data-glyph=cog]:before { content:'\e041'; }
.oi[data-glyph=collapse-down]:before { content:'\e042'; }
.oi[data-glyph=collapse-left]:before { content:'\e043'; }
.oi[data-glyph=collapse-right]:before { content:'\e044'; }
.oi[data-glyph=collapse-up]:before { content:'\e045'; }
.oi[data-glyph=command]:before { content:'\e046'; }
.oi[data-glyph=comment-square]:before { content:'\e047'; }
.oi[data-glyph=compass]:before { content:'\e048'; }
.oi[data-glyph=contrast]:before { content:'\e049'; }
.oi[data-glyph=copywriting]:before { content:'\e04a'; }
.oi[data-glyph=credit-card]:before { content:'\e04b'; }
.oi[data-glyph=crop]:before { content:'\e04c'; }
.oi[data-glyph=dashboard]:before { content:'\e04d'; }
.oi[data-glyph=data-transfer-download]:before { content:'\e04e'; }
.oi[data-glyph=data-transfer-upload]:before { content:'\e04f'; }
.oi[data-glyph=delete]:before { content:'\e050'; }
.oi[data-glyph=dial]:before { content:'\e051'; }
.oi[data-glyph=document]:before { content:'\e052'; }
.oi[data-glyph=dollar]:before { content:'\e053'; }
.oi[data-glyph=double-quote-sans-left]:before { content:'\e054'; }
.oi[data-glyph=double-quote-sans-right]:before { content:'\e055'; }
.oi[data-glyph=double-quote-serif-left]:before { content:'\e056'; }
.oi[data-glyph=double-quote-serif-right]:before { content:'\e057'; }
.oi[data-glyph=droplet]:before { content:'\e058'; }
.oi[data-glyph=eject]:before { content:'\e059'; }
.oi[data-glyph=elevator]:before { content:'\e05a'; }
.oi[data-glyph=ellipses]:before { content:'\e05b'; }
.oi[data-glyph=envelope-closed]:before { content:'\e05c'; }
.oi[data-glyph=envelope-open]:before { content:'\e05d'; }
.oi[data-glyph=euro]:before { content:'\e05e'; }
.oi[data-glyph=excerpt]:before { content:'\e05f'; }
.oi[data-glyph=expand-down]:before { content:'\e060'; }
.oi[data-glyph=expand-left]:before { content:'\e061'; }
.oi[data-glyph=expand-right]:before { content:'\e062'; }
.oi[data-glyph=expand-up]:before { content:'\e063'; }
.oi[data-glyph=external-link]:before { content:'\e064'; }
.oi[data-glyph=eye]:before { content:'\e065'; }
.oi[data-glyph=eyedropper]:before { content:'\e066'; }
.oi[data-glyph=file]:before { content:'\e067'; }
.oi[data-glyph=fire]:before { content:'\e068'; }
.oi[data-glyph=flag]:before { content:'\e069'; }
.oi[data-glyph=flash]:before { content:'\e06a'; }
.oi[data-glyph=folder]:before { content:'\e06b'; }
.oi[data-glyph=fork]:before { content:'\e06c'; }
.oi[data-glyph=fullscreen-enter]:before { content:'\e06d'; }
.oi[data-glyph=fullscreen-exit]:before { content:'\e06e'; }
.oi[data-glyph=globe]:before { content:'\e06f'; }
.oi[data-glyph=graph]:before { content:'\e070'; }
.oi[data-glyph=grid-four-up]:before { content:'\e071'; }
.oi[data-glyph=grid-three-up]:before { content:'\e072'; }
.oi[data-glyph=grid-two-up]:before { content:'\e073'; }
.oi[data-glyph=hard-drive]:before { content:'\e074'; }
.oi[data-glyph=header]:before { content:'\e075'; }
.oi[data-glyph=headphones]:before { content:'\e076'; }
.oi[data-glyph=heart]:before { content:'\e077'; }
.oi[data-glyph=home]:before { content:'\e078'; }
.oi[data-glyph=image]:before { content:'\e079'; }
.oi[data-glyph=inbox]:before { content:'\e07a'; }
.oi[data-glyph=infinity]:before { content:'\e07b'; }
.oi[data-glyph=info]:before { content:'\e07c'; }
.oi[data-glyph=italic]:before { content:'\e07d'; }
.oi[data-glyph=justify-center]:before { content:'\e07e'; }
.oi[data-glyph=justify-left]:before { content:'\e07f'; }
.oi[data-glyph=justify-right]:before { content:'\e080'; }
.oi[data-glyph=key]:before { content:'\e081'; }
.oi[data-glyph=laptop]:before { content:'\e082'; }
.oi[data-glyph=layers]:before { content:'\e083'; }
.oi[data-glyph=lightbulb]:before { content:'\e084'; }
.oi[data-glyph=link-broken]:before { content:'\e085'; }
.oi[data-glyph=link-intact]:before { content:'\e086'; }
.oi[data-glyph=list-rich]:before { content:'\e087'; }
.oi[data-glyph=list]:before { content:'\e088'; }
.oi[data-glyph=location]:before { content:'\e089'; }
.oi[data-glyph=lock-locked]:before { content:'\e08a'; }
.oi[data-glyph=lock-unlocked]:before { content:'\e08b'; }
.oi[data-glyph=loop-circular]:before { content:'\e08c'; }
.oi[data-glyph=loop-square]:before { content:'\e08d'; }
.oi[data-glyph=loop]:before { content:'\e08e'; }
.oi[data-glyph=magnifying-glass]:before { content:'\e08f'; }
.oi[data-glyph=map-marker]:before { content:'\e090'; }
.oi[data-glyph=map]:before { content:'\e091'; }
.oi[data-glyph=media-pause]:before { content:'\e092'; }
.oi[data-glyph=media-play]:before { content:'\e093'; }
.oi[data-glyph=media-record]:before { content:'\e094'; }
.oi[data-glyph=media-skip-backward]:before { content:'\e095'; }
.oi[data-glyph=media-skip-forward]:before { content:'\e096'; }
.oi[data-glyph=media-step-backward]:before { content:'\e097'; }
.oi[data-glyph=media-step-forward]:before { content:'\e098'; }
.oi[data-glyph=media-stop]:before { content:'\e099'; }
.oi[data-glyph=medical-cross]:before { content:'\e09a'; }
.oi[data-glyph=menu]:before { content:'\e09b'; }
.oi[data-glyph=microphone]:before { content:'\e09c'; }
.oi[data-glyph=minus]:before { content:'\e09d'; }
.oi[data-glyph=monitor]:before { content:'\e09e'; }
.oi[data-glyph=moon]:before { content:'\e09f'; }
.oi[data-glyph=move]:before { content:'\e0a0'; }
.oi[data-glyph=musical-note]:before { content:'\e0a1'; }
.oi[data-glyph=paperclip]:before { content:'\e0a2'; }
.oi[data-glyph=pencil]:before { content:'\e0a3'; }
.oi[data-glyph=people]:before { content:'\e0a4'; }
.oi[data-glyph=person]:before { content:'\e0a5'; }
.oi[data-glyph=phone]:before { content:'\e0a6'; }
.oi[data-glyph=pie-chart]:before { content:'\e0a7'; }
.oi[data-glyph=pin]:before { content:'\e0a8'; }
.oi[data-glyph=play-circle]:before { content:'\e0a9'; }
.oi[data-glyph=plus]:before { content:'\e0aa'; }
.oi[data-glyph=power-standby]:before { content:'\e0ab'; }
.oi[data-glyph=print]:before { content:'\e0ac'; }
.oi[data-glyph=project]:before { content:'\e0ad'; }
.oi[data-glyph=pulse]:before { content:'\e0ae'; }
.oi[data-glyph=puzzle-piece]:before { content:'\e0af'; }
.oi[data-glyph=question-mark]:before { content:'\e0b0'; }
.oi[data-glyph=rain]:before { content:'\e0b1'; }
.oi[data-glyph=random]:before { content:'\e0b2'; }
.oi[data-glyph=reload]:before { content:'\e0b3'; }
.oi[data-glyph=resize-both]:before { content:'\e0b4'; }
.oi[data-glyph=resize-height]:before { content:'\e0b5'; }
.oi[data-glyph=resize-width]:before { content:'\e0b6'; }
.oi[data-glyph=rss-alt]:before { content:'\e0b7'; }
.oi[data-glyph=rss]:before { content:'\e0b8'; }
.oi[data-glyph=script]:before { content:'\e0b9'; }
.oi[data-glyph=share-boxed]:before { content:'\e0ba'; }
.oi[data-glyph=share]:before { content:'\e0bb'; }
.oi[data-glyph=shield]:before { content:'\e0bc'; }
.oi[data-glyph=signal]:before { content:'\e0bd'; }
.oi[data-glyph=signpost]:before { content:'\e0be'; }
.oi[data-glyph=sort-ascending]:before { content:'\e0bf'; }
.oi[data-glyph=sort-descending]:before { content:'\e0c0'; }
.oi[data-glyph=spreadsheet]:before { content:'\e0c1'; }
.oi[data-glyph=star]:before { content:'\e0c2'; }
.oi[data-glyph=sun]:before { content:'\e0c3'; }
.oi[data-glyph=tablet]:before { content:'\e0c4'; }
.oi[data-glyph=tag]:before { content:'\e0c5'; }
.oi[data-glyph=tags]:before { content:'\e0c6'; }
.oi[data-glyph=target]:before { content:'\e0c7'; }
.oi[data-glyph=task]:before { content:'\e0c8'; }
.oi[data-glyph=terminal]:before { content:'\e0c9'; }
.oi[data-glyph=text]:before { content:'\e0ca'; }
.oi[data-glyph=thumb-down]:before { content:'\e0cb'; }
.oi[data-glyph=thumb-up]:before { content:'\e0cc'; }
.oi[data-glyph=timer]:before { content:'\e0cd'; }
.oi[data-glyph=transfer]:before { content:'\e0ce'; }
.oi[data-glyph=trash]:before { content:'\e0cf'; }
.oi[data-glyph=underline]:before { content:'\e0d0'; }
.oi[data-glyph=vertical-align-bottom]:before { content:'\e0d1'; }
.oi[data-glyph=vertical-align-center]:before { content:'\e0d2'; }
.oi[data-glyph=vertical-align-top]:before { content:'\e0d3'; }
.oi[data-glyph=video]:before { content:'\e0d4'; }
.oi[data-glyph=volume-high]:before { content:'\e0d5'; }
.oi[data-glyph=volume-low]:before { content:'\e0d6'; }
.oi[data-glyph=volume-off]:before { content:'\e0d7'; }
.oi[data-glyph=warning]:before { content:'\e0d8'; }
.oi[data-glyph=wifi]:before { content:'\e0d9'; }
.oi[data-glyph=wrench]:before { content:'\e0da'; }
.oi[data-glyph=x]:before { content:'\e0db'; }
.oi[data-glyph=yen]:before { content:'\e0dc'; }
.oi[data-glyph=zoom-in]:before { content:'\e0dd'; }
.oi[data-glyph=zoom-out]:before { content:'\e0de'; }

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,543 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2014-4-30: Created.
-->
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
Created by FontForge 20120731 at Wed Apr 30 22:56:47 2014
By P.J. Onori
Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net)
</metadata>
<defs>
<font id="open-iconic" horiz-adv-x="800" >
<font-face
font-family="Icons"
font-weight="400"
font-stretch="normal"
units-per-em="800"
panose-1="2 0 5 3 0 0 0 0 0 0"
ascent="800"
descent="0"
bbox="-0.25 -101 802 800.126"
underline-thickness="50"
underline-position="-100"
unicode-range="U+E000-E0DE"
/>
<missing-glyph />
<glyph glyph-name="" unicode="&#xe000;"
d="M300 700h500v-700h-500v100h400v500h-400v100zM400 500l200 -150l-200 -150v100h-400v100h400v100z" />
<glyph glyph-name="1" unicode="&#xe001;"
d="M300 700h500v-700h-500v100h400v500h-400v100zM200 500v-100h400v-100h-400v-100l-200 150z" />
<glyph glyph-name="2" unicode="&#xe002;"
d="M350 700c193 0 350 -157 350 -350v-50h100l-200 -200l-200 200h100v50c0 138 -112 250 -250 250s-250 -112 -250 -250c0 193 157 350 350 350z" />
<glyph glyph-name="3" unicode="&#xe003;"
d="M450 700c193 0 350 -157 350 -350c0 138 -112 250 -250 250s-250 -112 -250 -250v-50h100l-200 -200l-200 200h100v50c0 193 157 350 350 350z" />
<glyph glyph-name="4" unicode="&#xe004;"
d="M0 700h800v-100h-800v100zM100 500h600v-100h-600v100zM0 300h800v-100h-800v100zM100 100h600v-100h-600v100z" />
<glyph glyph-name="5" unicode="&#xe005;"
d="M0 700h800v-100h-800v100zM0 500h600v-100h-600v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100z" />
<glyph glyph-name="6" unicode="&#xe006;"
d="M0 700h800v-100h-800v100zM200 500h600v-100h-600v100zM0 300h800v-100h-800v100zM200 100h600v-100h-600v100z" />
<glyph glyph-name="7" unicode="&#xe007;"
d="M400 700c75 0 144 -23 203 -59l-72 -225l-322 234c57 31 122 50 191 50zM125 588l191 -138l-310 -222c-4 23 -6 47 -6 72c0 114 49 215 125 288zM688 575c69 -72 112 -168 112 -275c0 -35 -4 -68 -12 -100h-222zM216 256l112 -350c-128 23 -232 109 -287 222zM372 100
h372c-64 -109 -177 -186 -310 -197z" />
<glyph glyph-name="8" unicode="&#xe008;" horiz-adv-x="600"
d="M200 800h100v-500h200l-247 -300l-253 300h200v500z" />
<glyph glyph-name="9" unicode="&#xe009;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM300 700v-300h-200l300 -300l300 300h-200v300h-200z" />
<glyph glyph-name="a" unicode="&#xe00a;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700l-300 -300l300 -300v200h300v200h-300v200z" />
<glyph glyph-name="b" unicode="&#xe00b;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700v-200h-300v-200h300v-200l300 300z" />
<glyph glyph-name="c" unicode="&#xe00c;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700l-300 -300h200v-300h200v300h200z" />
<glyph glyph-name="d" unicode="&#xe00d;"
d="M300 600v-200h500v-100h-500v-200l-300 247z" />
<glyph glyph-name="e" unicode="&#xe00e;"
d="M500 600l300 -247l-300 -253v200h-500v100h500v200z" />
<glyph glyph-name="f" unicode="&#xe00f;" horiz-adv-x="600"
d="M200 800h200v-500h200l-297 -300l-303 300h200v500z" />
<glyph glyph-name="10" unicode="&#xe010;"
d="M300 700v-200h500v-200h-500v-200l-300 297z" />
<glyph glyph-name="11" unicode="&#xe011;"
d="M500 700l300 -297l-300 -303v200h-500v200h500v200z" />
<glyph glyph-name="12" unicode="&#xe012;" horiz-adv-x="600"
d="M297 800l303 -300h-200v-500h-200v500h-200z" />
<glyph glyph-name="13" unicode="&#xe013;" horiz-adv-x="600"
d="M247 800l253 -300h-200v-500h-100v500h-200z" />
<glyph glyph-name="14" unicode="&#xe014;"
d="M400 800h100v-800h-100v800zM200 700h100v-600h-100v600zM600 600h100v-400h-100v400zM0 500h100v-200h-100v200z" />
<glyph glyph-name="15" unicode="&#xe015;"
d="M119 600l69 -72c-55 -54 -88 -130 -88 -212s33 -156 88 -210l-69 -72c-73 72 -119 172 -119 282s46 212 119 284zM681 600c73 -73 119 -174 119 -284s-46 -210 -119 -282l-69 72c55 54 88 126 88 210s-33 157 -88 212zM259 460l69 -72c-18 -18 -28 -45 -28 -72
s10 -51 28 -69l-69 -72c-36 36 -59 86 -59 141s23 108 59 144zM541 459c36 -36 59 -87 59 -143s-23 -105 -59 -141l-69 72c18 18 28 41 28 69s-10 54 -28 72z" />
<glyph glyph-name="16" unicode="&#xe016;" horiz-adv-x="400"
d="M200 800c110 0 200 -90 200 -200s-90 -200 -200 -200s-200 90 -200 200s90 200 200 200zM100 319c31 -11 65 -19 100 -19s69 8 100 19v-319l-100 100l-100 -100v319z" />
<glyph glyph-name="17" unicode="&#xe017;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300c0 -66 21 -126 56 -175l419 419c-49 35 -109 56 -175 56zM644 575l-419 -419c49 -35 109 -56 175 -56c166 0 300 134 300 300
c0 66 -21 126 -56 175z" />
<glyph glyph-name="18" unicode="&#xe018;"
d="M0 700h100v-600h700v-100h-800v700zM500 700h200v-500h-200v500zM200 500h200v-300h-200v300z" />
<glyph glyph-name="19" unicode="&#xe019;"
d="M397 800c13 1 24 -4 34 -13c2 -1 214 -254 241 -287h128v-100h-100v-366c0 -18 -16 -34 -34 -34h-532c-18 0 -34 16 -34 34v366h-100v100h128l234 281c8 10 22 18 35 19zM400 672l-144 -172h288zM250 300c-28 0 -50 -22 -50 -50v-100c0 -28 22 -50 50 -50s50 22 50 50
v100c0 28 -22 50 -50 50zM550 300c-28 0 -50 -22 -50 -50v-100c0 -28 22 -50 50 -50s50 22 50 50v100c0 28 -22 50 -50 50z" />
<glyph glyph-name="1a" unicode="&#xe01a;"
d="M9 700h682c6 0 9 -4 9 -10v-190h100v-200h-100v-191c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v582c0 6 3 9 9 9zM100 600v-400h500v400h-500z" />
<glyph glyph-name="1b" unicode="&#xe01b;"
d="M9 700h682c6 0 9 -4 9 -10v-190h100v-200h-100v-191c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v582c0 6 3 9 9 9z" />
<glyph glyph-name="1c" unicode="&#xe01c;"
d="M92 650c0 23 19 50 45 50h3h5h5h500c28 0 50 -22 50 -50s-22 -50 -50 -50h-50v-141c9 -17 120 -231 166 -309c15 -26 34 -61 34 -106c0 -39 -15 -77 -41 -103c-27 -27 -65 -41 -103 -41h-512c-39 0 -77 15 -103 41c-27 27 -41 65 -41 103c0 45 19 79 34 106
c46 78 157 292 166 309v141h-50c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51zM500 600h-200v-162l-6 -10s-65 -123 -122 -228h456c-57 105 -122 228 -122 228l-6 10v162z" />
<glyph glyph-name="1d" unicode="&#xe01d;"
d="M400 800c110 0 200 -90 200 -200c0 -104 52 -198 134 -266c42 -34 66 -82 66 -134h-800c0 52 24 100 66 134c82 68 134 162 134 266c0 110 90 200 200 200zM300 100h200c0 -55 -45 -100 -100 -100s-100 45 -100 100z" />
<glyph glyph-name="1e" unicode="&#xe01e;" horiz-adv-x="600"
d="M150 800h50l350 -250l-225 -147l225 -153l-350 -250h-50v250l-75 -75l-75 75l150 150l-150 150l75 75l75 -75v250zM250 650v-200l150 100zM250 350v-200l150 100z" />
<glyph glyph-name="1f" unicode="&#xe01f;"
d="M0 800h500c110 0 200 -90 200 -200c0 -47 -17 -91 -44 -125c85 -40 144 -125 144 -225c0 -138 -112 -250 -250 -250h-550v100c55 0 100 45 100 100v400c0 55 -45 100 -100 100v100zM300 700v-200h100c55 0 100 45 100 100s-45 100 -100 100h-100zM300 400v-300h150
c83 0 150 67 150 150s-67 150 -150 150h-150z" />
<glyph glyph-name="20" unicode="&#xe020;" horiz-adv-x="600"
d="M300 800v-300h200l-300 -500v300h-200z" />
<glyph glyph-name="21" unicode="&#xe021;"
d="M100 800h300v-300l100 100l100 -100v300h50c28 0 50 -22 50 -50v-550h-550c-28 0 -50 -22 -50 -50s22 -50 50 -50h550v-100h-550c-83 0 -150 67 -150 150v550l3 19c8 39 39 70 78 78z" />
<glyph glyph-name="22" unicode="&#xe022;" horiz-adv-x="400"
d="M0 800h400v-800l-200 200l-200 -200v800z" />
<glyph glyph-name="23" unicode="&#xe023;"
d="M0 800h800v-100h-800v100zM0 600h300v-103h203v103h297v-591c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v591z" />
<glyph glyph-name="24" unicode="&#xe024;"
d="M300 800h200c55 0 100 -46 100 -100v-100h191c6 0 9 -3 9 -9v-241c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v241c0 6 3 9 9 9h191v100c0 54 45 100 100 100zM300 700v-100h200v100h-200zM0 209c16 -5 32 -9 50 -9h700c18 0 34 4 50 9v-200c0 -6 -3 -9 -9 -9h-782
c-6 0 -9 3 -9 9v200z" />
<glyph glyph-name="25" unicode="&#xe025;" horiz-adv-x="600"
d="M300 800c58 0 110 -16 147 -53s53 -89 53 -147h-100c0 39 -11 61 -25 75s-36 25 -75 25c-35 0 -55 -10 -72 -31s-28 -55 -28 -94c0 -51 20 -107 28 -175h172v-100h-178c-14 -60 -49 -127 -113 -200h491v-100h-600v122l16 12c69 69 95 121 106 166h-122v100h125
c-8 50 -25 106 -25 175c0 58 16 113 50 156s88 69 150 69z" />
<glyph glyph-name="26" unicode="&#xe026;"
d="M34 700h4h3h4h5h700c28 0 50 -22 50 -50v-700c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v700v2c0 20 15 42 34 48zM150 600c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50zM350 600c-28 0 -50 -22 -50 -50s22 -50 50 -50h300c28 0 50 22 50 50
s-22 50 -50 50h-300zM100 400v-400h600v400h-600z" />
<glyph glyph-name="27" unicode="&#xe027;"
d="M744 797l9 -3l41 -41c4 -4 3 -11 0 -15l-266 -375c-3 -5 -10 -11 -15 -13l-25 -12c-23 72 -78 127 -150 150l12 25l13 15l375 266zM266 400c74 0 134 -61 134 -134c0 -148 -118 -266 -266 -266c-48 0 -94 15 -134 38c80 46 134 129 134 228c0 73 59 134 132 134z" />
<glyph glyph-name="28" unicode="&#xe028;"
d="M9 451c0 23 19 50 46 50c8 0 19 -3 26 -7l131 -66l29 22c-79 81 -1 250 118 250s197 -167 119 -250l28 -22l131 66c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-115 -56c9 -16 19 -33 25 -50h68c28 0 50 -22 50 -50s-22 -50 -50 -50h-50
c0 -23 -2 -45 -6 -66l78 -40c21 -5 37 -28 37 -49c0 -28 -22 -50 -50 -50c-10 0 -23 5 -31 11l-65 35c-24 -46 -59 -83 -100 -107c-35 19 -63 42 -63 69v135v4v5v6v5v5v87c0 28 -22 50 -50 50c-25 0 -46 -17 -50 -40c1 -3 1 -8 1 -11s0 -8 -1 -11v-82v-4v-5v-144
c0 -28 -27 -53 -62 -72c-41 25 -76 64 -100 110l-66 -35c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50c0 21 16 44 37 49l78 40c-4 21 -6 43 -6 66h-50h-5c-28 0 -50 22 -50 50c0 26 22 50 50 50h5h69c6 17 16 34 25 50l-116 56c-16 7 -28 27 -28 45z" />
<glyph glyph-name="29" unicode="&#xe029;"
d="M610 700h81c6 0 9 -3 9 -9v-582c0 -6 -3 -9 -9 -9h-91v597zM210 503l290 147v-500l-250 125v-3c-14 0 -25 -11 -28 -25l72 -178c11 -25 0 -55 -25 -66s-55 0 -66 25l-103 272h-91c-6 0 -9 3 -9 9v182c0 6 3 9 9 9h182z" />
<glyph glyph-name="2a" unicode="&#xe02a;"
d="M9 800h682c6 0 9 -3 9 -9v-782c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v782c0 6 3 9 9 9zM100 700v-200h500v200h-500zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400v-300h100v300h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100z" />
<glyph glyph-name="2b" unicode="&#xe02b;"
d="M0 800h700v-200h-700v200zM0 500h700v-491c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v491zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100z" />
<glyph glyph-name="2c" unicode="&#xe02c;"
d="M409 800h182c6 0 10 -4 12 -9l94 -182c2 -5 6 -9 12 -9h82c6 0 9 -3 9 -9v-582c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v441c0 83 67 150 150 150h141c6 0 10 4 12 9l94 182c2 5 6 9 12 9zM150 500c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z
M500 500c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200zM500 400c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" />
<glyph glyph-name="2d" unicode="&#xe02d;"
d="M0 600h800l-400 -400z" />
<glyph glyph-name="2e" unicode="&#xe02e;" horiz-adv-x="400"
d="M400 800v-800l-400 400z" />
<glyph glyph-name="2f" unicode="&#xe02f;" horiz-adv-x="400"
d="M0 800l400 -400l-400 -400v800z" />
<glyph glyph-name="30" unicode="&#xe030;"
d="M400 600l400 -400h-800z" />
<glyph glyph-name="31" unicode="&#xe031;"
d="M0 550c0 23 20 50 46 50h3h5h4h200c17 0 37 -13 44 -28l38 -72h444c14 0 19 -10 15 -22l-81 -253c-4 -13 -21 -25 -35 -25h-350c-14 0 -30 12 -34 25c-27 83 -54 167 -81 250l-10 25h-150c-2 0 -5 -1 -7 -1c-28 0 -51 23 -51 51zM358 100c28 0 50 -22 50 -50
s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM658 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
<glyph glyph-name="32" unicode="&#xe032;"
d="M0 700h500v-100h-300v-300h-100l-100 -100v500zM300 500h500v-500l-100 100h-400v400z" />
<glyph glyph-name="33" unicode="&#xe033;"
d="M641 700l140 -141c-137 -143 -279 -280 -418 -421l-72 -69c-76 71 -148 146 -222 219l-69 71l141 141c51 -48 101 -97 150 -147c117 116 231 234 350 347z" />
<glyph glyph-name="34" unicode="&#xe034;"
d="M150 600l250 -250l250 250l150 -150l-400 -400l-400 400z" />
<glyph glyph-name="35" unicode="&#xe035;" horiz-adv-x="600"
d="M400 800l150 -150l-250 -250l250 -250l-150 -150l-400 400z" />
<glyph glyph-name="36" unicode="&#xe036;" horiz-adv-x="600"
d="M150 800l400 -400l-400 -400l-150 150l250 250l-250 250z" />
<glyph glyph-name="37" unicode="&#xe037;"
d="M400 600l400 -400l-150 -150l-250 250l-250 -250l-150 150z" />
<glyph glyph-name="38" unicode="&#xe038;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM600 622l-250 -250l-100 100l-72 -72l172 -172l322 322z" />
<glyph glyph-name="39" unicode="&#xe039;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM250 622l-72 -72l150 -150l-150 -150l72 -72l150 150l150 -150l72 72l-150 150l150 150l-72 72l-150 -150z" />
<glyph glyph-name="3a" unicode="&#xe03a;"
d="M350 800c28 0 50 -22 50 -50v-50h75c14 0 25 -11 25 -25v-75h-300v75c0 14 11 25 25 25h75v50c0 28 22 50 50 50zM25 700h75v-200h500v200h75c14 0 25 -11 25 -25v-650c0 -14 -11 -25 -25 -25h-650c-14 0 -25 11 -25 25v650c0 14 11 25 25 25z" />
<glyph glyph-name="3b" unicode="&#xe03b;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM350 600h100v-181c23 -24 47 -47 72 -69l-72 -72c-27 30 -55 59 -84 88l-16 12
v222z" />
<glyph glyph-name="3c" unicode="&#xe03c;"
d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -18 -4 -34 -9 -50h-191v50c0 83 -67 150 -150 150s-150 -67 -150 -150v-50h-272c-17 30 -28 63 -28 100c0 110 90 200 200 200c23 114 129 200 250 200zM434 400h3h4c3 0 6 1 9 1c28 0 50 -22 50 -50v-1
v-150h150l-200 -200l-200 200h150v150v2c0 20 15 42 34 48z" />
<glyph glyph-name="3d" unicode="&#xe03d;"
d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -18 -4 -34 -9 -50h-141l-200 200l-200 -200h-222c-17 30 -28 63 -28 100c0 110 90 200 200 200c23 114 129 200 250 200zM450 350l250 -250h-200v-50c0 -28 -22 -50 -50 -50s-50 22 -50 50v50h-200z" />
<glyph glyph-name="3e" unicode="&#xe03e;"
d="M450 700c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -83 -67 -150 -150 -150h-450c-110 0 -200 90 -200 200s90 200 200 200c23 114 129 200 250 200z" />
<glyph glyph-name="3f" unicode="&#xe03f;"
d="M250 800c82 0 154 -40 200 -100c-143 -1 -270 -84 -325 -209c-37 -9 -70 -26 -100 -47c-16 32 -25 67 -25 106c0 138 112 250 250 250zM450 600c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -83 -67 -150 -150 -150h-450c-110 0 -200 90 -200 200
s90 200 200 200c23 114 129 200 250 200z" />
<glyph glyph-name="40" unicode="&#xe040;"
d="M500 700h100l-300 -600h-100zM100 600h100l-100 -200l100 -200h-100l-100 200zM600 600h100l100 -200l-100 -200h-100l100 200z" />
<glyph glyph-name="41" unicode="&#xe041;"
d="M350 800h100l50 -119l28 -12l119 50l69 -72l-47 -119l12 -28l119 -50v-100l-119 -50l-12 -28l50 -119l-72 -72l-119 50l-28 -12l-50 -119h-100l-50 119l-28 12l-119 -50l-72 72l50 116l-12 31l-119 50v100l119 50l12 28l-50 119l72 72l119 -50l28 12zM400 550
c-83 0 -150 -67 -150 -150s67 -150 150 -150s150 67 150 150s-67 150 -150 150z" />
<glyph glyph-name="42" unicode="&#xe042;"
d="M0 800h800v-200h-800v200zM200 500h400l-200 -200zM0 100h800v-100h-800v100z" />
<glyph glyph-name="43" unicode="&#xe043;"
d="M0 800h100v-800h-100v800zM600 800h200v-800h-200v800zM500 600v-400l-200 200z" />
<glyph glyph-name="44" unicode="&#xe044;"
d="M0 800h200v-800h-200v800zM700 800h100v-800h-100v800zM300 600l200 -200l-200 -200v400z" />
<glyph glyph-name="45" unicode="&#xe045;"
d="M0 800h800v-100h-800v100zM400 500l200 -200h-400zM0 200h800v-200h-800v200z" />
<glyph glyph-name="46" unicode="&#xe046;"
d="M150 700c83 0 150 -67 150 -150v-50h100v50c0 83 67 150 150 150s150 -67 150 -150s-67 -150 -150 -150h-50v-100h50c83 0 150 -67 150 -150s-67 -150 -150 -150s-150 67 -150 150v50h-100v-50c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150h50v100h-50
c-83 0 -150 67 -150 150s67 150 150 150zM150 600c-28 0 -50 -22 -50 -50s22 -50 50 -50h50v50c0 28 -22 50 -50 50zM550 600c-28 0 -50 -22 -50 -50v-50h50c28 0 50 22 50 50s-22 50 -50 50zM300 400v-100h100v100h-100zM150 200c-28 0 -50 -22 -50 -50s22 -50 50 -50
s50 22 50 50v50h-50zM500 200v-50c0 -28 22 -50 50 -50s50 22 50 50s-22 50 -50 50h-50z" />
<glyph glyph-name="47" unicode="&#xe047;"
d="M0 791c0 6 3 9 9 9h782c6 0 9 -4 9 -10v-790l-200 200h-591c-6 0 -9 3 -9 9v582z" />
<glyph glyph-name="48" unicode="&#xe048;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM600 600l-100 -300l-300 -100l100 300zM400 450c-28 0 -50 -22 -50 -50
s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="49" unicode="&#xe049;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700v-600c166 0 300 134 300 300s-134 300 -300 300z" />
<glyph glyph-name="4a" unicode="&#xe04a;"
d="M0 800h800v-100h-800v100zM0 600h500v-100h-500v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100zM750 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
<glyph glyph-name="4b" unicode="&#xe04b;"
d="M25 700h750c14 0 25 -11 25 -25v-75h-800v75c0 14 11 25 25 25zM0 500h800v-375c0 -14 -11 -25 -25 -25h-750c-14 0 -25 11 -25 25v375zM100 300v-100h100v100h-100zM300 300v-100h100v100h-100z" />
<glyph glyph-name="4c" unicode="&#xe04c;"
d="M100 800h100v-100h450l100 100l50 -50l-100 -100v-450h100v-100h-100v-100h-100v100h-500v500h-100v100h100v100zM200 600v-350l350 350h-350zM600 550l-350 -350h350v350z" />
<glyph glyph-name="4d" unicode="&#xe04d;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM400 600c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z
M200 452c0 20 15 42 34 48h3h3h8c12 0 28 -7 36 -16l91 -90l25 6c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100l6 25l-90 91c-9 8 -16 24 -16 36zM550 500c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
<glyph glyph-name="4e" unicode="&#xe04e;"
d="M300 800h200v-300h200l-300 -300l-300 300h200v300zM0 100h800v-100h-800v100z" />
<glyph glyph-name="4f" unicode="&#xe04f;"
d="M0 800h800v-100h-800v100zM400 600l300 -300h-200v-300h-200v300h-200z" />
<glyph glyph-name="50" unicode="&#xe050;"
d="M200 700h600v-600h-600l-200 300zM350 622l-72 -72l150 -150l-150 -150l72 -72l150 150l150 -150l72 72l-150 150l150 150l-72 72l-150 -150z" />
<glyph glyph-name="51" unicode="&#xe051;"
d="M400 700c220 0 400 -180 400 -400h-100c0 166 -134 300 -300 300s-300 -134 -300 -300h-100c0 220 180 400 400 400zM341 491l59 -88l59 88c82 -25 141 -101 141 -191c0 -110 -90 -200 -200 -200s-200 90 -200 200c0 90 59 166 141 191z" />
<glyph glyph-name="52" unicode="&#xe052;"
d="M0 800h300v-400h400v-400h-700v800zM400 800l300 -300h-300v300zM100 600v-100h100v100h-100zM100 400v-100h100v100h-100zM100 200v-100h400v100h-400z" />
<glyph glyph-name="53" unicode="&#xe053;" horiz-adv-x="600"
d="M200 700h100v-100h75c30 0 58 -6 81 -22c24 -15 44 -44 44 -78v-100h-100v94c-4 3 -13 6 -25 6h-250c-13 0 -25 -12 -25 -25v-50c0 -14 20 -40 34 -44l257 -65c66 -16 109 -73 109 -141v-50c0 -69 -56 -125 -125 -125h-75v-100h-100v100h-75c-30 0 -58 6 -81 22
c-24 15 -44 44 -44 78v100h100v-94c4 -3 13 -6 25 -6h250c13 0 25 12 25 25v50c0 14 -20 40 -34 44l-257 65c-66 16 -109 73 -109 141v50c0 69 56 125 125 125h75v100z" />
<glyph glyph-name="54" unicode="&#xe054;"
d="M0 700h300v-300l-300 -300v600zM500 700h300v-300l-300 -300v600z" />
<glyph glyph-name="55" unicode="&#xe055;"
d="M300 700v-600h-300v300zM800 700v-600h-300v300z" />
<glyph glyph-name="56" unicode="&#xe056;"
d="M300 700v-100c-111 0 -200 -89 -200 -200h200v-300h-300v300c0 165 135 300 300 300zM800 700v-100c-111 0 -200 -89 -200 -200h200v-300h-300v300c0 165 135 300 300 300z" />
<glyph glyph-name="57" unicode="&#xe057;"
d="M0 700h300v-300c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-200v300zM500 700h300v-300c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-200v300z" />
<glyph glyph-name="58" unicode="&#xe058;" horiz-adv-x="600"
d="M300 800l34 -34c11 -11 266 -269 266 -488c0 -165 -135 -300 -300 -300s-300 135 -300 300c0 219 255 477 266 488zM150 328c-28 0 -50 -22 -50 -50c0 -110 90 -200 200 -200c28 0 50 22 50 50s-22 50 -50 50c-55 0 -100 45 -100 100c0 28 -22 50 -50 50z" />
<glyph glyph-name="59" unicode="&#xe059;"
d="M400 800l400 -500h-800zM0 200h800v-200h-800v200z" />
<glyph glyph-name="5a" unicode="&#xe05a;" horiz-adv-x="600"
d="M300 800l300 -300h-600zM0 300h600l-300 -300z" />
<glyph glyph-name="5b" unicode="&#xe05b;"
d="M0 500h200v-200h-200v200zM300 500h200v-200h-200v200zM600 500h200v-200h-200v200z" />
<glyph glyph-name="5c" unicode="&#xe05c;"
d="M0 700h800v-100l-400 -200l-400 200v100zM0 500l400 -200l400 200v-400h-800v400z" />
<glyph glyph-name="5d" unicode="&#xe05d;"
d="M400 800l400 -200v-600h-800v600zM400 688l-300 -150v-188l300 -150l300 150v188zM200 500h400v-100l-200 -100l-200 100v100z" />
<glyph glyph-name="5e" unicode="&#xe05e;"
d="M600 700c69 0 134 -19 191 -50l-16 -106c-49 35 -109 56 -175 56c-131 0 -240 -84 -281 -200h331l-16 -100h-334c0 -36 8 -68 19 -100h297l-16 -100h-222c55 -61 133 -100 222 -100c78 0 147 30 200 78v-122c-59 -35 -127 -56 -200 -56c-147 0 -274 82 -344 200h-256
l19 100h197c-8 32 -16 66 -16 100h-200l25 100h191c45 172 198 300 384 300z" />
<glyph glyph-name="5f" unicode="&#xe05f;"
d="M0 700h700v-100h-700v100zM0 500h500v-100h-500v100zM0 300h800v-100h-800v100zM0 100h100v-100h-100v100zM200 100h100v-100h-100v100zM400 100h100v-100h-100v100z" />
<glyph glyph-name="60" unicode="&#xe060;"
d="M0 800h800v-100h-800v100zM200 600h400l-200 -200zM0 200h800v-200h-800v200z" />
<glyph glyph-name="61" unicode="&#xe061;"
d="M0 800h100v-800h-100v800zM600 800h200v-800h-200v800zM200 600l200 -200l-200 -200v400z" />
<glyph glyph-name="62" unicode="&#xe062;"
d="M0 800h200v-800h-200v800zM700 800h100v-800h-100v800zM600 600v-400l-200 200z" />
<glyph glyph-name="63" unicode="&#xe063;"
d="M0 800h800v-200h-800v200zM400 400l200 -200h-400zM0 100h800v-100h-800v100z" />
<glyph glyph-name="64" unicode="&#xe064;"
d="M0 800h200v-100h-100v-600h600v100h100v-200h-800v800zM400 800h400v-400l-150 150l-250 -250l-100 100l250 250z" />
<glyph glyph-name="65" unicode="&#xe065;"
d="M403 700c247 0 397 -300 397 -300s-150 -300 -397 -300c-253 0 -403 300 -403 300s150 300 403 300zM400 600c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200zM400 500c10 0 19 -3 28 -6c-16 -8 -28 -24 -28 -44c0 -28 22 -50 50 -50
c20 0 36 12 44 28c3 -9 6 -18 6 -28c0 -55 -45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" />
<glyph glyph-name="66" unicode="&#xe066;" horiz-adv-x="900"
d="M331 700h3h3c3 1 7 1 10 1c12 0 29 -8 37 -17l94 -93l66 65c57 57 156 57 212 0c59 -58 59 -154 0 -212l-65 -66l93 -94c10 -8 18 -25 18 -38c0 -28 -22 -50 -50 -50c-13 0 -32 9 -40 20l-62 65l-366 -365l-12 -16h-272v272l375 381l-63 63c-9 8 -16 24 -16 36
c0 20 16 42 35 48zM447 481l-313 -315l128 -132l316 316z" />
<glyph glyph-name="67" unicode="&#xe067;"
d="M0 800h300v-400h400v-400h-700v800zM400 800l300 -300h-300v300z" />
<glyph glyph-name="68" unicode="&#xe068;"
d="M200 800c0 0 200 -100 200 -300s-298 -302 -200 -500c0 0 -200 100 -200 300s300 300 200 500zM500 500c0 0 200 -100 200 -300c0 -150 -60 -200 -100 -200h-300c0 200 300 300 200 500z" />
<glyph glyph-name="69" unicode="&#xe069;"
d="M0 800h100v-800h-100v800zM200 800h300v-100h300l-200 -203l200 -197h-400v100h-200v400z" />
<glyph glyph-name="6a" unicode="&#xe06a;" horiz-adv-x="400"
d="M150 800h150l-100 -200h200l-150 -300h150l-300 -300l-100 300h134l66 200h-200z" />
<glyph glyph-name="6b" unicode="&#xe06b;"
d="M0 800h300v-100h500v-100h-800v200zM0 500h800v-450c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v450z" />
<glyph glyph-name="6c" unicode="&#xe06c;"
d="M150 800c83 0 150 -67 150 -150c0 -66 -41 -121 -100 -141v-118c15 5 33 9 50 9h200c28 0 50 22 50 50v59c-59 20 -100 75 -100 141c0 83 67 150 150 150s150 -67 150 -150c0 -66 -41 -121 -100 -141v-59c0 -82 -68 -150 -150 -150h-200c-14 0 -25 -7 -34 -16
c50 -24 84 -74 84 -134c0 -83 -67 -150 -150 -150s-150 67 -150 150c0 66 41 121 100 141v218c-59 20 -100 75 -100 141c0 83 67 150 150 150z" />
<glyph glyph-name="6d" unicode="&#xe06d;"
d="M0 800h400l-150 -150l150 -150l-100 -100l-150 150l-150 -150v400zM500 400l150 -150l150 150v-400h-400l150 150l-150 150z" />
<glyph glyph-name="6e" unicode="&#xe06e;"
d="M100 800l150 -150l150 150v-400h-400l150 150l-150 150zM400 400h400l-150 -150l150 -150l-100 -100l-150 150l-150 -150v400z" />
<glyph glyph-name="6f" unicode="&#xe06f;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700c-56 0 -108 -17 -153 -44l22 -19c33 -18 13 -48 -13 -59c-29 -13 -77 10 -65 -41c14 -55 -27 -3 -47 -15c-43 -25 49 -152 31 -156c-14 -3 -40 34 -59 34
c-8 0 -13 -5 -16 -10c1 -30 10 -57 19 -84c28 -10 74 1 97 -22c47 -28 100 -118 78 -162c33 -13 68 -22 106 -22c100 0 189 49 244 125c3 24 -5 44 -47 44c-69 0 -156 14 -153 97c2 46 101 108 66 143c-30 31 12 39 12 66c0 37 -65 32 -69 50s20 36 41 56
c-30 10 -61 19 -94 19zM631 591c-38 -11 -95 -35 -87 -53c5 -15 55 -2 68 -13c11 -10 15 -58 44 -31l19 22c-12 27 -26 53 -44 75z" />
<glyph glyph-name="70" unicode="&#xe070;"
d="M703 800l97 -100l-400 -400l-100 100l-200 -203l-100 100l300 303l100 -100zM0 100h800v-100h-800v100z" />
<glyph glyph-name="71" unicode="&#xe071;"
d="M0 700h100v-100h-100v100zM200 700h100v-100h-100v100zM400 700h100v-100h-100v100zM600 700h100v-100h-100v100zM0 500h100v-100h-100v100zM200 500h100v-100h-100v100zM400 500h100v-100h-100v100zM600 500h100v-100h-100v100zM0 300h100v-100h-100v100zM200 300h100
v-100h-100v100zM400 300h100v-100h-100v100zM600 300h100v-100h-100v100zM0 100h100v-100h-100v100zM200 100h100v-100h-100v100zM400 100h100v-100h-100v100zM600 100h100v-100h-100v100z" />
<glyph glyph-name="72" unicode="&#xe072;"
d="M0 800h200v-200h-200v200zM300 800h200v-200h-200v200zM600 800h200v-200h-200v200zM0 500h200v-200h-200v200zM300 500h200v-200h-200v200zM600 500h200v-200h-200v200zM0 200h200v-200h-200v200zM300 200h200v-200h-200v200zM600 200h200v-200h-200v200z" />
<glyph glyph-name="73" unicode="&#xe073;"
d="M0 800h300v-300h-300v300zM500 800h300v-300h-300v300zM0 300h300v-300h-300v300zM500 300h300v-300h-300v300z" />
<glyph glyph-name="74" unicode="&#xe074;"
d="M19 800h662c11 0 19 -8 19 -19v-331c0 -28 -22 -50 -50 -50h-600c-28 0 -50 22 -50 50v331c0 11 8 19 19 19zM0 309c16 -5 32 -9 50 -9h600c18 0 34 4 50 9v-290c0 -11 -8 -19 -19 -19h-662c-11 0 -19 8 -19 19v290zM550 200c-28 0 -50 -22 -50 -50s22 -50 50 -50
s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="75" unicode="&#xe075;"
d="M0 700h300v-100h-50c-28 0 -50 -22 -50 -50v-150h300v150c0 28 -22 50 -50 50h-50v100h300v-100h-50c-28 0 -50 -22 -50 -50v-400c0 -28 22 -50 50 -50h50v-100h-300v100h50c28 0 50 22 50 50v150h-300v-150c0 -28 22 -50 50 -50h50v-100h-300v100h50c28 0 50 22 50 50
v400c0 28 -22 50 -50 50h-50v100z" />
<glyph glyph-name="76" unicode="&#xe076;"
d="M400 700c165 0 300 -135 300 -300v-100h50c28 0 50 -22 50 -50v-200c0 -28 -22 -50 -50 -50h-100c-28 0 -50 22 -50 50v350c0 111 -89 200 -200 200s-200 -89 -200 -200v-350c0 -28 -22 -50 -50 -50h-100c-28 0 -50 22 -50 50v200c0 28 22 50 50 50h50v100
c0 165 135 300 300 300z" />
<glyph glyph-name="77" unicode="&#xe077;"
d="M0 500c0 109 91 200 200 200s200 -91 200 -200c0 109 91 200 200 200s200 -91 200 -200c0 -55 -22 -104 -59 -141l-341 -343l-341 343c-37 36 -59 86 -59 141z" />
<glyph glyph-name="78" unicode="&#xe078;"
d="M400 700l400 -300l-100 3v-403h-200v200h-200v-200h-200v400h-100z" />
<glyph glyph-name="79" unicode="&#xe079;"
d="M0 800h800v-800h-800v800zM100 700v-300l100 100l400 -400h100v100l-200 200l100 100l100 -100v300h-600z" />
<glyph glyph-name="7a" unicode="&#xe07a;"
d="M19 800h762c11 0 19 -8 19 -19v-762c0 -11 -8 -19 -19 -19h-762c-11 0 -19 8 -19 19v762c0 11 8 19 19 19zM100 600v-300h100l100 -100h200l100 100h100v300h-600z" />
<glyph glyph-name="7b" unicode="&#xe07b;"
d="M200 600c79 0 143 -56 200 -122c58 66 119 122 200 122c131 0 200 -101 200 -200s-69 -200 -200 -200c-81 0 -142 56 -200 122c-58 -66 -121 -122 -200 -122c-131 0 -200 101 -200 200s69 200 200 200zM200 500c-74 0 -100 -54 -100 -100s26 -100 100 -100
c42 0 88 47 134 100c-46 53 -92 100 -134 100zM600 500c-43 0 -89 -47 -134 -100c45 -53 91 -100 134 -100c74 0 100 54 100 100s-26 100 -100 100z" />
<glyph glyph-name="7c" unicode="&#xe07c;" horiz-adv-x="400"
d="M300 800c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100zM150 550c83 0 150 -69 150 -150c0 -66 -100 -214 -100 -250c0 -28 22 -50 50 -50s50 22 50 50h100c0 -83 -67 -150 -150 -150s-150 64 -150 150s100 222 100 250s-22 50 -50 50
s-50 -22 -50 -50h-100c0 83 67 150 150 150z" />
<glyph glyph-name="7d" unicode="&#xe07d;"
d="M200 800h500v-100h-122c-77 -197 -156 -392 -234 -588l-6 -12h162v-100h-500v100h122c77 197 156 392 234 588l7 12h-163v100z" />
<glyph glyph-name="7e" unicode="&#xe07e;"
d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM100 100h600v-100h-600v100z" />
<glyph glyph-name="7f" unicode="&#xe07f;"
d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100z" />
<glyph glyph-name="80" unicode="&#xe080;"
d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM200 100h600v-100h-600v100z" />
<glyph glyph-name="81" unicode="&#xe081;"
d="M550 800c138 0 250 -112 250 -250s-112 -250 -250 -250c-16 0 -30 3 -44 6l-6 -6v-100h-200v-200h-300v200l306 306c-3 14 -6 28 -6 44c0 138 112 250 250 250zM600 700c-55 0 -100 -45 -100 -100s45 -100 100 -100s100 45 100 100s-45 100 -100 100z" />
<glyph glyph-name="82" unicode="&#xe082;"
d="M134 600h3h4h4h5h500c28 0 50 -22 50 -50v-350h100v-150c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v150h100v350v2c0 20 15 42 34 48zM200 500v-300h100v-100h200v100h100v300h-400z" />
<glyph glyph-name="83" unicode="&#xe083;"
d="M0 800h400v-400h-400v400zM500 600h100v-400h-400v100h300v300zM700 400h100v-400h-400v100h300v300z" />
<glyph glyph-name="84" unicode="&#xe084;" horiz-adv-x="600"
d="M337 694c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-300 -150c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50c0 21 16 44 37 49zM437 544c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-400 -200c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50
c0 21 16 44 37 49zM437 344c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-106 -56c24 -4 43 -26 43 -50c0 -28 -23 -51 -51 -51c-2 0 -6 1 -8 1h-200c-26 1 -48 24 -48 50c0 16 12 36 26 44zM151 -50c0 23 20 50 46 50h3h4h5h100c28 0 50 -22 50 -50
s-22 -50 -50 -50h-100c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51z" />
<glyph glyph-name="85" unicode="&#xe085;"
d="M199 800h100v-200h-200v100h100v100zM587 797c18 1 38 1 56 -3c36 -8 69 -26 97 -54c78 -77 78 -203 0 -281l-150 -150c-8 -13 -27 -24 -42 -24c-28 0 -50 22 -50 50c0 15 10 35 23 43l150 150c40 40 40 105 0 144c-40 40 -110 34 -144 0l-43 -44c-8 -13 -28 -24 -43 -24
c-28 0 -50 22 -50 50c0 15 11 35 24 43l44 44c33 33 72 53 128 56zM209 490c4 5 13 16 21 16h4c2 0 6 1 8 1c28 0 50 -22 50 -50c0 -11 -7 -27 -15 -35l-150 -150c-40 -40 -40 -105 0 -144c40 -40 110 -34 144 0l44 44c8 13 28 24 43 24c28 0 50 -22 50 -50
c0 -15 -11 -35 -24 -43l-44 -44c-22 -22 -48 -37 -75 -47c-71 -25 -150 -9 -206 47c-78 77 -78 203 0 281zM499 200h200v-100h-100v-100h-100v200z" />
<glyph glyph-name="86" unicode="&#xe086;"
d="M587 797c18 1 38 1 56 -3c36 -8 69 -26 97 -54c78 -77 78 -203 0 -281l-150 -150c-62 -62 -131 -81 -181 -78s-70 17 -85 25s-26 27 -26 44c0 28 22 51 50 51c8 0 19 -3 26 -7c0 0 15 -11 41 -13c26 -1 63 4 106 47l150 150c40 40 40 105 0 144c-40 40 -110 34 -144 0
c-8 -13 -27 -24 -42 -24c-28 0 -50 22 -50 50c0 15 11 35 24 43c33 33 72 53 128 56zM387 566c50 -2 63 -17 84 -22s38 -28 38 -49c0 -28 -22 -50 -50 -50c-10 0 -24 5 -32 11c0 0 -19 9 -47 10s-63 -4 -103 -44l-150 -150c-40 -40 -40 -105 0 -144c40 -40 110 -34 144 0
c8 13 28 24 43 24c28 0 50 -22 50 -50c0 -15 -11 -35 -24 -43c-22 -22 -48 -37 -75 -47c-71 -25 -150 -9 -206 47c-78 77 -78 203 0 281l150 150c60 60 128 78 178 76z" />
<glyph glyph-name="87" unicode="&#xe087;"
d="M0 700h300v-300h-300v300zM400 700h400v-100h-400v100zM400 500h300v-100h-300v100zM0 300h300v-300h-300v300zM400 300h400v-100h-400v100zM400 100h300v-100h-300v100z" />
<glyph glyph-name="88" unicode="&#xe088;"
d="M50 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 700h600v-100h-600v100zM50 500c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 500h600v-100h-600v100zM50 300c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50
s22 50 50 50zM200 300h600v-100h-600v100zM50 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 100h600v-100h-600v100z" />
<glyph glyph-name="89" unicode="&#xe089;"
d="M800 800l-400 -800l-100 300l-300 100z" />
<glyph glyph-name="8a" unicode="&#xe08a;" horiz-adv-x="600"
d="M300 700c110 0 200 -90 200 -200v-100h100v-400h-600v400h100v100c0 110 90 200 200 200zM300 600c-56 0 -100 -44 -100 -100v-100h200v100c0 56 -44 100 -100 100z" />
<glyph glyph-name="8b" unicode="&#xe08b;" horiz-adv-x="600"
d="M300 800c110 0 200 -90 200 -200v-200h100v-400h-600v400h400v200c0 56 -44 100 -100 100s-100 -44 -100 -100h-100c0 110 90 200 200 200z" />
<glyph glyph-name="8c" unicode="&#xe08c;"
d="M400 700v-100c-111 0 -200 -89 -200 -200h100l-150 -200l-150 200h100c0 165 135 300 300 300zM650 600l150 -200h-100c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-100z" />
<glyph glyph-name="8d" unicode="&#xe08d;"
d="M100 800h600v-300h100l-150 -250l-150 250h100v200h-400v-100h-100v200zM150 550l150 -250h-100v-200h400v100h100v-200h-600v300h-100z" />
<glyph glyph-name="8e" unicode="&#xe08e;"
d="M600 700l200 -150l-200 -150v100h-500v-100h-100v100c0 55 45 100 100 100h500v100zM200 300v-100h500v100h100v-100c0 -54 -46 -100 -100 -100h-500v-100l-200 150z" />
<glyph glyph-name="8f" unicode="&#xe08f;" horiz-adv-x="900"
d="M350 800c193 0 350 -157 350 -350c0 -60 -17 -117 -44 -166c5 -3 12 -8 16 -12l100 -100c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-100 100c-4 3 -9 9 -12 13c-49 -26 -107 -41 -166 -41c-193 0 -350 157 -350 350s157 350 350 350zM350 200
c142 0 250 108 250 250c0 139 -111 250 -250 250s-250 -111 -250 -250s111 -250 250 -250z" />
<glyph glyph-name="90" unicode="&#xe090;" horiz-adv-x="600"
d="M300 800c166 0 300 -134 300 -300c0 -200 -300 -500 -300 -500s-300 300 -300 500c0 166 134 300 300 300zM300 700c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200z" />
<glyph glyph-name="91" unicode="&#xe091;" horiz-adv-x="900"
d="M0 800h800v-541c1 -3 1 -8 1 -11s0 -7 -1 -10v-238h-800v800zM495 250c0 26 22 50 50 50h5h150v400h-600v-600h600v100h-150h-5c-28 0 -50 22 -50 50zM350 600c83 0 150 -67 150 -150c0 -100 -150 -250 -150 -250s-150 150 -150 250c0 83 67 150 150 150zM350 500
c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="92" unicode="&#xe092;" horiz-adv-x="600"
d="M0 700h200v-600h-200v600zM400 700h200v-600h-200v600z" />
<glyph glyph-name="93" unicode="&#xe093;" horiz-adv-x="600"
d="M0 700l600 -300l-600 -300v600z" />
<glyph glyph-name="94" unicode="&#xe094;" horiz-adv-x="600"
d="M300 700c166 0 300 -134 300 -300s-134 -300 -300 -300s-300 134 -300 300s134 300 300 300z" />
<glyph glyph-name="95" unicode="&#xe095;"
d="M400 700v-600l-400 300zM400 400l400 300v-600z" />
<glyph glyph-name="96" unicode="&#xe096;"
d="M0 700l400 -300l-400 -300v600zM400 100v600l400 -300z" />
<glyph glyph-name="97" unicode="&#xe097;"
d="M0 700h200v-600h-200v600zM200 400l500 300v-600z" />
<glyph glyph-name="98" unicode="&#xe098;"
d="M0 700l500 -300l-500 -300v600zM500 100v600h200v-600h-200z" />
<glyph glyph-name="99" unicode="&#xe099;" horiz-adv-x="600"
d="M0 700h600v-600h-600v600z" />
<glyph glyph-name="9a" unicode="&#xe09a;"
d="M200 800h400v-200h200v-400h-200v-200h-400v200h-200v400h200v200z" />
<glyph glyph-name="9b" unicode="&#xe09b;"
d="M0 700h800v-100h-800v100zM0 403h800v-100h-800v100zM0 103h800v-100h-800v100z" />
<glyph glyph-name="9c" unicode="&#xe09c;" horiz-adv-x="600"
d="M278 700c7 2 13 4 22 4c55 0 100 -45 100 -100v-4v-200c0 -55 -45 -100 -100 -100s-100 45 -100 100v200v2c0 44 35 88 78 98zM34 500h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-50c0 -111 89 -200 200 -200s200 89 200 200v50c0 28 22 50 50 50s50 -22 50 -50v-50
c0 -148 -109 -270 -250 -294v-106h50c55 0 100 -45 100 -100h-400c0 55 45 100 100 100h50v106c-141 24 -250 146 -250 294v50v2c0 20 15 42 34 48z" />
<glyph glyph-name="9d" unicode="&#xe09d;"
d="M0 500h800v-200h-800v200z" />
<glyph glyph-name="9e" unicode="&#xe09e;"
d="M34 700h4h3h4h5h700c28 0 50 -22 50 -50v-500c0 -28 -22 -50 -50 -50h-250v-100h100c55 0 100 -45 100 -100h-600c0 55 45 100 100 100h100v100h-250c-28 0 -50 22 -50 50v500v2c0 20 15 42 34 48zM100 600v-400h600v400h-600z" />
<glyph glyph-name="9f" unicode="&#xe09f;"
d="M272 700c-14 -40 -22 -84 -22 -128c0 -221 179 -400 400 -400c45 0 88 11 128 25c-53 -158 -202 -275 -378 -275c-221 0 -400 179 -400 400c0 176 114 325 272 378z" />
<glyph glyph-name="a0" unicode="&#xe0a0;"
d="M350 700l150 -150h-100v-150h150v100l150 -150l-150 -150v100h-150v-150h100l-150 -150l-150 150h100v150h-150v-100l-150 150l150 150v-100h150v150h-100z" />
<glyph glyph-name="a1" unicode="&#xe0a1;"
d="M800 800v-550c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150c17 0 35 -4 50 -9v206c-201 -6 -327 -27 -400 -50v-397c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150c17 0 35 -4 50 -9v409s100 100 600 100z" />
<glyph glyph-name="a2" unicode="&#xe0a2;" horiz-adv-x="700"
d="M499 700c51 0 102 -20 141 -59c78 -77 78 -203 0 -281l-250 -244c-48 -48 -127 -48 -175 0s-48 127 0 175c32 32 64 65 96 97l69 -69l-59 -63l-38 -34c-10 -10 -10 -28 0 -38s28 -10 38 0l250 247c38 40 39 103 0 141c-40 40 -105 40 -144 0v-3l-278 -272
c-67 -69 -68 -179 0 -247c69 -69 181 -69 250 0c39 44 83 84 125 125l69 -69l-125 -125c-107 -107 -281 -107 -388 0s-107 281 0 388l278 272c38 39 90 59 141 59z" />
<glyph glyph-name="a3" unicode="&#xe0a3;"
d="M600 800l200 -200l-100 -100l-200 200zM400 600l200 -200l-400 -400h-200v200z" />
<glyph glyph-name="a4" unicode="&#xe0a4;"
d="M550 800c83 0 150 -90 150 -200s-67 -200 -150 -200c-22 0 -41 5 -59 16c6 27 9 55 9 84c0 85 -27 158 -72 212c27 52 71 88 122 88zM250 700c83 0 150 -90 150 -200s-67 -200 -150 -200s-150 90 -150 200s67 200 150 200zM725 384c44 -22 75 -66 75 -118v-166h-200v66
c0 50 -17 96 -44 134c67 2 126 33 169 84zM75 284c44 -53 106 -84 175 -84s131 31 175 84c44 -22 75 -66 75 -118v-166h-500v166c0 52 31 96 75 118z" />
<glyph glyph-name="a5" unicode="&#xe0a5;"
d="M400 800c110 0 200 -112 200 -250s-90 -250 -200 -250s-200 112 -200 250s90 250 200 250zM191 300c54 -61 128 -100 209 -100s155 39 209 100c107 -4 191 -92 191 -200v-100h-800v100c0 108 84 196 191 200z" />
<glyph glyph-name="a6" unicode="&#xe0a6;" horiz-adv-x="600"
d="M19 800h462c11 0 19 -8 19 -19v-762c0 -11 -8 -19 -19 -19h-462c-11 0 -19 8 -19 19v762c0 11 8 19 19 19zM100 700v-500h300v500h-300zM250 150c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="a7" unicode="&#xe0a7;"
d="M350 800c17 0 34 0 50 -3v-397l-297 297c63 64 150 103 247 103zM500 694c169 -24 300 -168 300 -344c0 -193 -157 -350 -350 -350c-85 0 -162 31 -222 81l272 272v341zM91 562l237 -234l-216 -212c-69 54 -112 139 -112 234c0 84 36 158 91 212z" />
<glyph glyph-name="a8" unicode="&#xe0a8;"
d="M92 650c0 23 20 50 46 50h3h4h5h400c28 0 50 -22 50 -50s-22 -50 -50 -50h-50v-200h100c55 0 100 -45 100 -100h-300v-300l-56 -100l-44 100v300h-300c0 55 45 100 100 100h100v200h-50c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51z" />
<glyph glyph-name="a9" unicode="&#xe0a9;"
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM300 600v-400l300 200z" />
<glyph glyph-name="aa" unicode="&#xe0aa;"
d="M300 800h200v-300h300v-200h-300v-300h-200v300h-300v200h300v300z" />
<glyph glyph-name="ab" unicode="&#xe0ab;"
d="M300 800h100v-400h-100v400zM172 656l62 -78l-40 -31c-58 -46 -94 -117 -94 -197c0 -139 111 -250 250 -250s250 111 250 250c0 80 -39 151 -97 197l-37 31l62 78l38 -31c82 -64 134 -163 134 -275c0 -193 -157 -350 -350 -350s-350 157 -350 350c0 112 54 211 134 275z
" />
<glyph glyph-name="ac" unicode="&#xe0ac;"
d="M200 800h400v-200h-400v200zM9 500h782c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-91v200h-600v-200h-91c-6 0 -9 3 -9 9v282c0 6 3 9 9 9zM200 300h400v-300h-400v300z" />
<glyph glyph-name="ad" unicode="&#xe0ad;"
d="M0 700h100v-700h-100v700zM700 700h100v-700h-100v700zM200 600h200v-100h-200v100zM300 400h200v-100h-200v100zM400 200h200v-100h-200v100z" />
<glyph glyph-name="ae" unicode="&#xe0ae;"
d="M325 700c42 -141 87 -280 131 -419c29 74 59 148 88 222c29 -58 57 -116 87 -172h169v-100h-231l-13 28c-37 -92 -74 -184 -112 -275c-39 127 -79 255 -119 382c-41 -133 -83 -265 -125 -397c-28 88 -56 175 -84 262h-116v100h188l9 -34l3 -6c42 137 83 273 125 409z" />
<glyph glyph-name="af" unicode="&#xe0af;"
d="M200 700c0 58 42 100 100 100s100 -42 100 -100c0 -28 -20 -48 -31 -72c-2 -6 -3 -16 -3 -28h234v-234c12 0 22 3 28 6c24 10 44 28 72 28c58 0 100 -42 100 -100s-42 -100 -100 -100c-28 0 -48 20 -72 31c-6 2 -16 3 -28 3v-234h-234c0 12 3 22 6 28c10 24 28 44 28 72
c0 58 -42 100 -100 100s-100 -42 -100 -100c0 -28 20 -48 31 -72c2 -6 3 -16 3 -28h-234v600h234c0 12 -3 22 -6 28c-10 24 -28 44 -28 72z" />
<glyph glyph-name="b0" unicode="&#xe0b0;" horiz-adv-x="500"
d="M247 700c83 0 147 -20 190 -59s60 -93 60 -141c0 -117 -66 -181 -116 -225s-84 -67 -84 -150v-25h-100v25c0 117 69 181 119 225s81 67 81 150c0 25 -8 48 -28 66s-56 34 -122 34s-97 -18 -116 -37s-27 -43 -31 -69l-100 12c5 38 19 88 59 128s103 66 188 66zM197 0h100
v-100h-100v100z" />
<glyph glyph-name="b1" unicode="&#xe0b1;"
d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -69 -48 -127 -112 -144c-22 55 -75 94 -138 94c-20 0 -39 -5 -56 -12c-17 64 -75 112 -144 112s-127 -48 -144 -112c-17 7 -36 12 -56 12c-37 0 -71 -16 -97 -38c-33 36 -53 86 -53 138
c0 110 90 200 200 200c23 114 129 200 250 200zM334 300h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-200c0 -28 -22 -50 -50 -50s-50 22 -50 50v200v2c0 20 15 42 34 48zM134 200h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-100c0 -28 -22 -50 -50 -50s-50 22 -50 50v100v2
c0 20 15 42 34 48zM534 200h3h4c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-100c0 -28 -22 -50 -50 -50s-50 22 -50 50v100v2c0 20 15 42 34 48z" />
<glyph glyph-name="b2" unicode="&#xe0b2;"
d="M600 700l200 -150l-200 -150v100h-53v-3l-150 -187l175 -207v-3h28v100l200 -150l-200 -150v100h-25c-35 0 -57 10 -78 34v4l-163 190l-153 -190c-21 -26 -46 -38 -81 -38h-100v100h103v3l163 203l-163 191v3h-103v100h100c35 0 57 -10 78 -34v-4l150 -174l141 174
c21 26 46 38 81 38h50v100z" />
<glyph glyph-name="b3" unicode="&#xe0b3;"
d="M400 700c109 0 208 -47 281 -119l119 119v-300h-300l109 110c-55 55 -126 90 -209 90c-166 0 -300 -134 -300 -300s134 -300 300 -300c84 0 158 33 212 88l69 -69c-72 -73 -171 -119 -281 -119c-220 0 -400 180 -400 400s180 400 400 400z" />
<glyph glyph-name="b4" unicode="&#xe0b4;"
d="M400 800h400v-400l-166 166l-400 -400l166 -166h-400v400l166 -166l400 400z" />
<glyph glyph-name="b5" unicode="&#xe0b5;" horiz-adv-x="600"
d="M250 800l250 -300h-200v-200h200l-250 -300l-250 300h200v200h-200z" />
<glyph glyph-name="b6" unicode="&#xe0b6;"
d="M300 600v-200h200v200l300 -250l-300 -250v200h-200v-200l-300 250z" />
<glyph glyph-name="b7" unicode="&#xe0b7;"
d="M0 800c441 0 800 -359 800 -800h-200c0 333 -267 600 -600 600v200zM0 500c275 0 500 -225 500 -500h-200c0 167 -133 300 -300 300v200zM0 200c111 0 200 -89 200 -200h-200v200z" />
<glyph glyph-name="b8" unicode="&#xe0b8;"
d="M100 800c386 0 700 -314 700 -700h-100c0 332 -268 600 -600 600v100zM100 600c276 0 500 -224 500 -500h-100c0 222 -178 400 -400 400v100zM100 400c165 0 300 -135 300 -300h-100c0 111 -89 200 -200 200v100zM100 200c55 0 100 -45 100 -100s-45 -100 -100 -100
s-100 45 -100 100s45 100 100 100z" />
<glyph glyph-name="b9" unicode="&#xe0b9;"
d="M300 800h400c55 0 100 -45 100 -100v-200h-400v150c0 28 -22 50 -50 50s-50 -22 -50 -50v-250h400v-300c0 -55 -45 -100 -100 -100h-500c-55 0 -100 45 -100 100v200h100v-150c0 -28 22 -50 50 -50s50 22 50 50v550c0 55 45 100 100 100z" />
<glyph glyph-name="ba" unicode="&#xe0ba;"
d="M75 700h225v-100h-200v-500h400v100h100v-125c0 -40 -35 -75 -75 -75h-450c-40 0 -75 35 -75 75v550c0 40 35 75 75 75zM600 700l200 -200l-200 -200v100h-200c-94 0 -173 -65 -194 -153c23 199 189 353 394 353v100z" />
<glyph glyph-name="bb" unicode="&#xe0bb;"
d="M500 700l300 -284l-300 -316v200h-100c-200 0 -348 -102 -400 -300c0 295 100 500 500 500v200z" />
<glyph glyph-name="bc" unicode="&#xe0bc;"
d="M381 791l19 9l19 -9c127 -53 253 -108 381 -160v-31c0 -166 -67 -313 -147 -419c-40 -53 -83 -97 -125 -128s-82 -53 -128 -53s-86 22 -128 53s-85 75 -125 128c-80 107 -147 253 -147 419v31c128 52 254 107 381 160zM400 100v591l-294 -122c8 -126 58 -243 122 -328
c35 -46 73 -86 106 -110s62 -31 66 -31z" />
<glyph glyph-name="bd" unicode="&#xe0bd;"
d="M600 800h100v-800h-100v800zM400 700h100v-700h-100v700zM200 500h100v-500h-100v500zM0 300h100v-300h-100v300z" />
<glyph glyph-name="be" unicode="&#xe0be;"
d="M300 800h100v-200h200l100 -100l-100 -100h-200v-400h-100v500h-200l-100 100l100 100h200v100z" />
<glyph glyph-name="bf" unicode="&#xe0bf;"
d="M200 800h100v-600h200l-250 -200l-250 200h200v600zM400 800h200v-100h-200v100zM400 600h300v-100h-300v100zM400 400h400v-100h-400v100z" />
<glyph glyph-name="c0" unicode="&#xe0c0;"
d="M200 800h100v-600h200l-250 -200l-250 200h200v600zM400 800h400v-100h-400v100zM400 600h300v-100h-300v100zM400 400h200v-100h-200v100z" />
<glyph glyph-name="c1" unicode="&#xe0c1;"
d="M75 700h650c40 0 75 -35 75 -75v-550c0 -40 -35 -75 -75 -75h-650c-40 0 -75 35 -75 75v550c0 40 35 75 75 75zM100 600v-100h100v100h-100zM300 600v-100h400v100h-400zM100 400v-100h100v100h-100zM300 400v-100h400v100h-400zM100 200v-100h100v100h-100zM300 200
v-100h400v100h-400z" />
<glyph glyph-name="c2" unicode="&#xe0c2;"
d="M400 800l100 -300h300l-250 -200l100 -300l-250 200l-250 -200l100 300l-250 200h300z" />
<glyph glyph-name="c3" unicode="&#xe0c3;"
d="M400 800c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM150 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM650 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM400 600c110 0 200 -90 200 -200
s-90 -200 -200 -200s-200 90 -200 200s90 200 200 200zM50 450c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM750 450c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM150 200c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50
s22 50 50 50zM650 200c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM400 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
<glyph glyph-name="c4" unicode="&#xe0c4;"
d="M34 800h632c18 0 34 -16 34 -34v-732c0 -18 -16 -34 -34 -34h-632c-18 0 -34 16 -34 34v732c0 18 16 34 34 34zM100 700v-500h500v500h-500zM350 150c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="c5" unicode="&#xe0c5;"
d="M0 800h300l500 -500l-300 -300l-500 500v300zM200 700c-55 0 -100 -45 -100 -100s45 -100 100 -100s100 45 100 100s-45 100 -100 100z" />
<glyph glyph-name="c6" unicode="&#xe0c6;"
d="M0 600h200l300 -300l-200 -200l-300 300v200zM340 600h160l300 -300l-200 -200l-78 78l119 122zM150 500c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="c7" unicode="&#xe0c7;"
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM400 600c110 0 200 -90 200 -200s-90 -200 -200 -200s-200 90 -200 200
s90 200 200 200zM400 500c-56 0 -100 -44 -100 -100s44 -100 100 -100s100 44 100 100s-44 100 -100 100z" />
<glyph glyph-name="c8" unicode="&#xe0c8;"
d="M0 700h559l-100 -100h-359v-500h500v159l100 100v-359h-700v700zM700 700l100 -100l-400 -400l-200 200l100 100l100 -100z" />
<glyph glyph-name="c9" unicode="&#xe0c9;"
d="M9 800h782c6 0 9 -3 9 -9v-782c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v782c0 6 3 9 9 9zM150 722l-72 -72l100 -100l-100 -100l72 -72l172 172zM400 500v-100h300v100h-300z" />
<glyph glyph-name="ca" unicode="&#xe0ca;"
d="M0 800h800v-200h-50c0 55 -45 100 -100 100h-150v-550c0 -28 22 -50 50 -50h50v-100h-400v100h50c28 0 50 22 50 50v550h-150c-55 0 -100 -45 -100 -100h-50v200z" />
<glyph glyph-name="cb" unicode="&#xe0cb;"
d="M0 700h100v-400h-100v400zM200 700h350c21 0 39 -13 47 -31c0 0 103 -291 103 -319s-22 -50 -50 -50h-150c-28 0 -50 -25 -50 -50s39 -158 47 -184c8 -27 -5 -55 -31 -63c-27 -8 -53 5 -66 31s-110 219 -128 238c-19 18 -44 28 -72 28v400z" />
<glyph glyph-name="cc" unicode="&#xe0cc;"
d="M444 700l22 -3c26 -8 39 -36 31 -63s-47 -159 -47 -184s22 -50 50 -50h150c28 0 50 -22 50 -50s-103 -319 -103 -319c-8 -18 -26 -31 -47 -31h-350v400c28 0 53 10 72 28c18 19 115 212 128 238c10 20 25 32 44 34zM0 400h100v-400h-100v400z" />
<glyph glyph-name="cd" unicode="&#xe0cd;"
d="M200 700h300v-100h-100v-6c25 -4 50 -8 72 -16l-35 -94c-29 10 -57 16 -87 16c-139 0 -250 -111 -250 -250s111 -250 250 -250s250 111 250 250c0 31 -5 61 -16 91l94 34c13 -38 22 -80 22 -125c0 -193 -157 -350 -350 -350s-350 157 -350 350c0 176 130 323 300 347v3
h-100v100zM700 588c0 0 -296 -353 -316 -372c-20 -20 -52 -20 -72 0s-20 52 0 72s388 300 388 300z" />
<glyph glyph-name="ce" unicode="&#xe0ce;"
d="M600 700l200 -150l-200 -150v100h-600v100h600v100zM200 300v-100h600v-100h-600v-100l-200 150z" />
<glyph glyph-name="cf" unicode="&#xe0cf;"
d="M300 800h100c55 0 100 -45 100 -100h100c55 0 100 -45 100 -100h-700c0 55 45 100 100 100h100c0 55 45 100 100 100zM100 500h100v-350c0 -28 22 -50 50 -50s50 22 50 50v350h100v-350c0 -28 22 -50 50 -50s50 22 50 50v350h100v-481c0 -11 -8 -19 -19 -19h-462
c-11 0 -19 8 -19 19v481z" />
<glyph glyph-name="d0" unicode="&#xe0d0;"
d="M100 800h200v-400c0 -55 45 -100 100 -100s100 45 100 100v400h100v-400c0 -110 -90 -200 -200 -200h-50c-138 0 -250 90 -250 200v400zM0 100h700v-100h-700v100z" />
<glyph glyph-name="d1" unicode="&#xe0d1;"
d="M9 700h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM609 700h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM309 500h182c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v282
c0 6 3 9 9 9zM0 100h800v-100h-800v100z" />
<glyph glyph-name="d2" unicode="&#xe0d2;"
d="M10 700h181c6 0 9 -3 9 -9v-191h-200v191c0 6 4 9 10 9zM610 700h181c6 0 9 -3 9 -9v-191h-200v191c0 6 4 9 10 9zM310 600h181c6 0 9 -3 9 -9v-91h-200v91c0 6 4 9 10 9zM0 400h800v-100h-800v100zM0 200h200v-191c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v191zM300 200
h200v-91c0 -6 -3 -9 -9 -9h-181c-6 0 -10 3 -10 9v91zM600 200h200v-191c0 -6 -3 -9 -9 -9h-181c-6 0 -10 3 -10 9v191z" />
<glyph glyph-name="d3" unicode="&#xe0d3;"
d="M0 700h800v-100h-800v100zM9 500h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM309 500h182c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v282c0 6 3 9 9 9zM609 500h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182
c-6 0 -9 3 -9 9v482c0 6 3 9 9 9z" />
<glyph glyph-name="d4" unicode="&#xe0d4;"
d="M50 600h500c28 0 50 -22 50 -50v-150l100 100h100v-300h-100l-100 100v-150c0 -28 -22 -50 -50 -50h-500c-28 0 -50 22 -50 50v400c0 28 22 50 50 50z" />
<glyph glyph-name="d5" unicode="&#xe0d5;"
d="M334 800h66v-800h-66l-134 200h-200v400h200zM500 600v100c26 0 52 -4 75 -10c130 -32 225 -150 225 -290s-95 -259 -225 -291c-23 -6 -49 -9 -75 -9v100c16 0 33 2 50 6c86 22 150 100 150 194s-64 172 -150 194h-3c-16 4 -32 6 -47 6zM500 500l22 -3h3v-3
c42 -12 75 -49 75 -94c0 -46 -32 -85 -75 -97l-25 -3v200z" />
<glyph glyph-name="d6" unicode="&#xe0d6;" horiz-adv-x="600"
d="M334 800h66v-800h-66l-134 200h-200v400h200zM500 500l22 -3h3v-3c42 -12 75 -49 75 -94c0 -46 -32 -85 -75 -97l-25 -3v200z" />
<glyph glyph-name="d7" unicode="&#xe0d7;" horiz-adv-x="400"
d="M334 800h66v-800h-66l-134 200h-200v400h200z" />
<glyph glyph-name="d8" unicode="&#xe0d8;"
d="M309 800h82c6 0 10 -4 12 -9l294 -682l3 -19v-81c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v81l3 19l294 682c2 5 6 9 12 9zM300 500v-200h100v200h-100zM300 200v-100h100v100h-100z" />
<glyph glyph-name="d9" unicode="&#xe0d9;"
d="M375 700c138 0 269 -39 378 -109l-53 -82c-93 60 -205 91 -325 91c-119 0 -229 -35 -322 -94l-53 88c109 69 238 106 375 106zM378 400c79 0 151 -23 213 -62l-53 -85c-46 29 -101 47 -160 47c-60 0 -114 -17 -162 -47l-54 85c62 40 136 62 216 62zM375 100
c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" />
<glyph glyph-name="da" unicode="&#xe0da;" horiz-adv-x="900"
d="M551 700c16 0 30 -3 44 -6l-94 -94v-200h200l94 94c3 -14 6 -28 6 -44c0 -138 -112 -250 -250 -250c-32 0 -62 6 -90 16l-288 -288c-20 -19 -46 -28 -72 -28s-52 11 -72 31c-39 39 -39 102 0 141l291 287c-11 28 -19 59 -19 91c0 138 112 250 250 250zM101 50
c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
<glyph glyph-name="db" unicode="&#xe0db;"
d="M141 700c85 -80 167 -164 250 -247c83 82 165 167 250 247l140 -141c-80 -85 -164 -167 -247 -250c82 -83 167 -165 247 -250l-140 -140c-85 80 -167 164 -250 247c-83 -82 -165 -167 -250 -247l-141 140c80 85 164 167 247 250c-82 83 -167 165 -247 250z" />
<glyph glyph-name="dc" unicode="&#xe0dc;"
d="M0 800h100l231 -300h38l231 300h100l-225 -300h225v-100h-300v-100h300v-100h-300v-200h-100v200h-300v100h300v100h-300v100h225z" />
<glyph glyph-name="dd" unicode="&#xe0dd;" horiz-adv-x="900"
d="M350 800c193 0 350 -157 350 -350c0 -60 -17 -117 -44 -166c4 -2 10 -6 13 -9l103 -103c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-103 103c-3 2 -7 7 -9 10c-49 -26 -107 -41 -166 -41c-193 0 -350 157 -350 350s157 350 350 350zM350 700
c-139 0 -250 -111 -250 -250s111 -250 250 -250c60 0 120 22 160 59c8 12 21 26 34 32l3 3c34 43 53 97 53 156c0 139 -111 250 -250 250zM300 600h100v-100h100v-100h-100v-100h-100v100h-100v100h100v100z" />
<glyph glyph-name="de" unicode="&#xe0de;" horiz-adv-x="900"
d="M350 800c193 0 350 -157 350 -350c0 -60 -17 -117 -44 -166c4 -2 10 -6 13 -9l103 -103c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-103 103c-3 2 -7 7 -9 10c-49 -26 -107 -41 -166 -41c-193 0 -350 157 -350 350s157 350 350 350zM350 700
c-139 0 -250 -111 -250 -250s111 -250 250 -250c60 0 120 22 160 59c8 12 21 26 34 32l3 3c34 43 53 97 53 156c0 139 -111 250 -250 250zM200 500h300v-100h-300v100z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,228 @@
/* Start of reset */
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body {
margin: 0;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}
/** End of reset */
body {
font-family: 'Roboto', sans-serif;
font-size: 14px;
line-height: 22px;
font-weight: 300;
}
h1 {
font-size: 42px;
line-height: 44px;
padding-bottom: 5px;
color: #b10610;
margin-top: 10px;
margin-bottom: 30px;
}
h2 {
color: #333333;
font-size: 28px;
line-height: 44px;
font-weight: 300;
}
h3 {
font-size: 21px;
margin-top: 20px;
margin-bottom: 10px;
}
a {
color: #31a1cd;
}
h1 a {
text-decoration: none;
}
h2 a {
color: #333333;
}
a:visited {
color: #6098a2;
}
h2 a:visited {
color: #333333;
}
a:hover {
color: #b10610;
}
hr {
border: none;
border-top: 1px dashed #c9ea75;
margin-top: 30px;
margin-bottom: 30px;
}
header {
background: #eeeeee;
}
header a {
font-size: 28px;
font-weight: 500;
color: #333;
text-decoration: none;
}
.logo {
padding: 5px 10px;
}
.logo img {
vertical-align: middle;
border: 0;
}
input, button, select {
font: inherit;
color: inherit;
}
input[type=text], select {
border: 1px solid #bbbbbb;
line-height: 22px;
padding: 5px 10px;
border-radius: 3px;
}
nav {
padding: 5px;
}
.btn, button, input[type=submit] {
display: inline-block;
color: white;
background: #4fa3ac;
padding: 9px 15px;
border-radius: 2px;
border: 0;
text-decoration: none;
}
a.btn:visited {
color: white;
}
.btn.disabled {
background: #eeeeee;
color: #bbbbbb;
}
section {
margin: 40px 10px;
}
section table {
height: 40px;
}
.nodeTable tr {
border-bottom: 3px solid white;
}
.nodeTable td {
padding: 10px 10px 10px 10px;
}
.nodeTable a {
text-decoration: none;
}
.nodeTable .nameColumn {
font-weight: bold;
padding: 10px 20px;
background: #ebf5f6;
min-width: 200px;
}
.nodeTable .oi {
color: #b10610;
}
.propTable tr {
height: 40px;
}
.propTable th {
background: #f6f6f6;
padding: 0 10px;
text-align: left;
}
.propTable td {
padding: 0 10px;
background: #eeeeee;
}
.propTable pre {
font-size: 80%;
background: #f8f8f8;
}
.actions {
border: 1px dotted #76baa6;
padding: 20px;
margin-bottom: 20px;
}
.actions h3 {
margin-top: 10px;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #eeeeee;
}
.actions label {
width: 150px;
display: inline-block;
line-height: 40px;
}
.actions input[type=text], select {
width: 450px;
}
.actions input[type=submit] {
display: inline-block;
margin-left: 153px;
}
footer {
padding: 50px 0;
font-size: 80%;
text-align: center;
}
ul.tree {
list-style: none;
margin: 0;
padding: 0;
}
ul.tree ul {
list-style: none;
padding-left: 10px;
border-left: 4px solid #ccc;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

429
vendor/sabre/dav/lib/DAV/Client.php vendored Normal file
View File

@ -0,0 +1,429 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
use Sabre\HTTP;
use Sabre\Uri;
/**
* SabreDAV DAV client.
*
* This client wraps around Curl to provide a convenient API to a WebDAV
* server.
*
* NOTE: This class is experimental, it's api will likely change in the future.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Client extends HTTP\Client
{
/**
* The xml service.
*
* Uset this service to configure the property and namespace maps.
*
* @var mixed
*/
public $xml;
/**
* The elementMap.
*
* This property is linked via reference to $this->xml->elementMap.
* It's deprecated as of version 3.0.0, and should no longer be used.
*
* @deprecated
*
* @var array
*/
public $propertyMap = [];
/**
* Base URI.
*
* This URI will be used to resolve relative urls.
*
* @var string
*/
protected $baseUri;
/**
* Basic authentication.
*/
const AUTH_BASIC = 1;
/**
* Digest authentication.
*/
const AUTH_DIGEST = 2;
/**
* NTLM authentication.
*/
const AUTH_NTLM = 4;
/**
* Identity encoding, which basically does not nothing.
*/
const ENCODING_IDENTITY = 1;
/**
* Deflate encoding.
*/
const ENCODING_DEFLATE = 2;
/**
* Gzip encoding.
*/
const ENCODING_GZIP = 4;
/**
* Sends all encoding headers.
*/
const ENCODING_ALL = 7;
/**
* Content-encoding.
*
* @var int
*/
protected $encoding = self::ENCODING_IDENTITY;
/**
* Constructor.
*
* Settings are provided through the 'settings' argument. The following
* settings are supported:
*
* * baseUri
* * userName (optional)
* * password (optional)
* * proxy (optional)
* * authType (optional)
* * encoding (optional)
*
* authType must be a bitmap, using self::AUTH_BASIC, self::AUTH_DIGEST
* and self::AUTH_NTLM. If you know which authentication method will be
* used, it's recommended to set it, as it will save a great deal of
* requests to 'discover' this information.
*
* Encoding is a bitmap with one of the ENCODING constants.
*/
public function __construct(array $settings)
{
if (!isset($settings['baseUri'])) {
throw new \InvalidArgumentException('A baseUri must be provided');
}
parent::__construct();
$this->baseUri = $settings['baseUri'];
if (isset($settings['proxy'])) {
$this->addCurlSetting(CURLOPT_PROXY, $settings['proxy']);
}
if (isset($settings['userName'])) {
$userName = $settings['userName'];
$password = isset($settings['password']) ? $settings['password'] : '';
if (isset($settings['authType'])) {
$curlType = 0;
if ($settings['authType'] & self::AUTH_BASIC) {
$curlType |= CURLAUTH_BASIC;
}
if ($settings['authType'] & self::AUTH_DIGEST) {
$curlType |= CURLAUTH_DIGEST;
}
if ($settings['authType'] & self::AUTH_NTLM) {
$curlType |= CURLAUTH_NTLM;
}
} else {
$curlType = CURLAUTH_BASIC | CURLAUTH_DIGEST;
}
$this->addCurlSetting(CURLOPT_HTTPAUTH, $curlType);
$this->addCurlSetting(CURLOPT_USERPWD, $userName.':'.$password);
}
if (isset($settings['encoding'])) {
$encoding = $settings['encoding'];
$encodings = [];
if ($encoding & self::ENCODING_IDENTITY) {
$encodings[] = 'identity';
}
if ($encoding & self::ENCODING_DEFLATE) {
$encodings[] = 'deflate';
}
if ($encoding & self::ENCODING_GZIP) {
$encodings[] = 'gzip';
}
$this->addCurlSetting(CURLOPT_ENCODING, implode(',', $encodings));
}
$this->addCurlSetting(CURLOPT_USERAGENT, 'sabre-dav/'.Version::VERSION.' (http://sabre.io/)');
$this->xml = new Xml\Service();
// BC
$this->propertyMap = &$this->xml->elementMap;
}
/**
* Does a PROPFIND request.
*
* The list of requested properties must be specified as an array, in clark
* notation.
*
* The returned array will contain a list of filenames as keys, and
* properties as values.
*
* The properties array will contain the list of properties. Only properties
* that are actually returned from the server (without error) will be
* returned, anything else is discarded.
*
* Depth should be either 0 or 1. A depth of 1 will cause a request to be
* made to the server to also return all child resources.
*
* @param string $url
* @param int $depth
*
* @return array
*/
public function propFind($url, array $properties, $depth = 0)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$root = $dom->createElementNS('DAV:', 'd:propfind');
$prop = $dom->createElement('d:prop');
foreach ($properties as $property) {
list(
$namespace,
$elementName
) = \Sabre\Xml\Service::parseClarkNotation($property);
if ('DAV:' === $namespace) {
$element = $dom->createElement('d:'.$elementName);
} else {
$element = $dom->createElementNS($namespace, 'x:'.$elementName);
}
$prop->appendChild($element);
}
$dom->appendChild($root)->appendChild($prop);
$body = $dom->saveXML();
$url = $this->getAbsoluteUrl($url);
$request = new HTTP\Request('PROPFIND', $url, [
'Depth' => $depth,
'Content-Type' => 'application/xml',
], $body);
$response = $this->send($request);
if ((int) $response->getStatus() >= 400) {
throw new HTTP\ClientHttpException($response);
}
$result = $this->parseMultiStatus($response->getBodyAsString());
// If depth was 0, we only return the top item
if (0 === $depth) {
reset($result);
$result = current($result);
return isset($result[200]) ? $result[200] : [];
}
$newResult = [];
foreach ($result as $href => $statusList) {
$newResult[$href] = isset($statusList[200]) ? $statusList[200] : [];
}
return $newResult;
}
/**
* Updates a list of properties on the server.
*
* The list of properties must have clark-notation properties for the keys,
* and the actual (string) value for the value. If the value is null, an
* attempt is made to delete the property.
*
* @param string $url
*
* @return bool
*/
public function propPatch($url, array $properties)
{
$propPatch = new Xml\Request\PropPatch();
$propPatch->properties = $properties;
$xml = $this->xml->write(
'{DAV:}propertyupdate',
$propPatch
);
$url = $this->getAbsoluteUrl($url);
$request = new HTTP\Request('PROPPATCH', $url, [
'Content-Type' => 'application/xml',
], $xml);
$response = $this->send($request);
if ($response->getStatus() >= 400) {
throw new HTTP\ClientHttpException($response);
}
if (207 === $response->getStatus()) {
// If it's a 207, the request could still have failed, but the
// information is hidden in the response body.
$result = $this->parseMultiStatus($response->getBodyAsString());
$errorProperties = [];
foreach ($result as $href => $statusList) {
foreach ($statusList as $status => $properties) {
if ($status >= 400) {
foreach ($properties as $propName => $propValue) {
$errorProperties[] = $propName.' ('.$status.')';
}
}
}
}
if ($errorProperties) {
throw new HTTP\ClientException('PROPPATCH failed. The following properties errored: '.implode(', ', $errorProperties));
}
}
return true;
}
/**
* Performs an HTTP options request.
*
* This method returns all the features from the 'DAV:' header as an array.
* If there was no DAV header, or no contents this method will return an
* empty array.
*
* @return array
*/
public function options()
{
$request = new HTTP\Request('OPTIONS', $this->getAbsoluteUrl(''));
$response = $this->send($request);
$dav = $response->getHeader('Dav');
if (!$dav) {
return [];
}
$features = explode(',', $dav);
foreach ($features as &$v) {
$v = trim($v);
}
return $features;
}
/**
* Performs an actual HTTP request, and returns the result.
*
* If the specified url is relative, it will be expanded based on the base
* url.
*
* The returned array contains 3 keys:
* * body - the response body
* * httpCode - a HTTP code (200, 404, etc)
* * headers - a list of response http headers. The header names have
* been lowercased.
*
* For large uploads, it's highly recommended to specify body as a stream
* resource. You can easily do this by simply passing the result of
* fopen(..., 'r').
*
* This method will throw an exception if an HTTP error was received. Any
* HTTP status code above 399 is considered an error.
*
* Note that it is no longer recommended to use this method, use the send()
* method instead.
*
* @param string $method
* @param string $url
* @param string|resource|null $body
*
* @throws clientException, in case a curl error occurred
*
* @return array
*/
public function request($method, $url = '', $body = null, array $headers = [])
{
$url = $this->getAbsoluteUrl($url);
$response = $this->send(new HTTP\Request($method, $url, $headers, $body));
return [
'body' => $response->getBodyAsString(),
'statusCode' => (int) $response->getStatus(),
'headers' => array_change_key_case($response->getHeaders()),
];
}
/**
* Returns the full url based on the given url (which may be relative). All
* urls are expanded based on the base url as given by the server.
*
* @param string $url
*
* @return string
*/
public function getAbsoluteUrl($url)
{
return Uri\resolve(
$this->baseUri,
$url
);
}
/**
* Parses a WebDAV multistatus response body.
*
* This method returns an array with the following structure
*
* [
* 'url/to/resource' => [
* '200' => [
* '{DAV:}property1' => 'value1',
* '{DAV:}property2' => 'value2',
* ],
* '404' => [
* '{DAV:}property1' => null,
* '{DAV:}property2' => null,
* ],
* ],
* 'url/to/resource2' => [
* .. etc ..
* ]
* ]
*
* @param string $body xml body
*
* @return array
*/
public function parseMultiStatus($body)
{
$multistatus = $this->xml->expect('{DAV:}multistatus', $body);
$result = [];
foreach ($multistatus->getResponses() as $response) {
$result[$response->getHref()] = $response->getResponseProperties();
}
return $result;
}
}

106
vendor/sabre/dav/lib/DAV/Collection.php vendored Normal file
View File

@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* Collection class.
*
* This is a helper class, that should aid in getting collections classes setup.
* Most of its methods are implemented, and throw permission denied exceptions
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class Collection extends Node implements ICollection
{
/**
* Returns a child object, by its name.
*
* This method makes use of the getChildren method to grab all the child
* nodes, and compares the name.
* Generally its wise to override this, as this can usually be optimized
*
* This method must throw Sabre\DAV\Exception\NotFound if the node does not
* exist.
*
* @param string $name
*
* @throws Exception\NotFound
*
* @return INode
*/
public function getChild($name)
{
foreach ($this->getChildren() as $child) {
if ($child->getName() === $name) {
return $child;
}
}
throw new Exception\NotFound('File not found: '.$name);
}
/**
* Checks is a child-node exists.
*
* It is generally a good idea to try and override this. Usually it can be optimized.
*
* @param string $name
*
* @return bool
*/
public function childExists($name)
{
try {
$this->getChild($name);
return true;
} catch (Exception\NotFound $e) {
return false;
}
}
/**
* Creates a new file in the directory.
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After successful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
*
* @return string|null
*/
public function createFile($name, $data = null)
{
throw new Exception\Forbidden('Permission denied to create file (filename '.$name.')');
}
/**
* Creates a new subdirectory.
*
* @param string $name
*
* @throws Exception\Forbidden
*/
public function createDirectory($name)
{
throw new Exception\Forbidden('Permission denied to create directory');
}
}

902
vendor/sabre/dav/lib/DAV/CorePlugin.php vendored Normal file
View File

@ -0,0 +1,902 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
use Sabre\DAV\Exception\BadRequest;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\Xml\ParseException;
/**
* The core plugin provides all the basic features for a WebDAV server.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class CorePlugin extends ServerPlugin
{
/**
* Reference to server object.
*
* @var Server
*/
protected $server;
/**
* Sets up the plugin.
*/
public function initialize(Server $server)
{
$this->server = $server;
$server->on('method:GET', [$this, 'httpGet']);
$server->on('method:OPTIONS', [$this, 'httpOptions']);
$server->on('method:HEAD', [$this, 'httpHead']);
$server->on('method:DELETE', [$this, 'httpDelete']);
$server->on('method:PROPFIND', [$this, 'httpPropFind']);
$server->on('method:PROPPATCH', [$this, 'httpPropPatch']);
$server->on('method:PUT', [$this, 'httpPut']);
$server->on('method:MKCOL', [$this, 'httpMkcol']);
$server->on('method:MOVE', [$this, 'httpMove']);
$server->on('method:COPY', [$this, 'httpCopy']);
$server->on('method:REPORT', [$this, 'httpReport']);
$server->on('propPatch', [$this, 'propPatchProtectedPropertyCheck'], 90);
$server->on('propPatch', [$this, 'propPatchNodeUpdate'], 200);
$server->on('propFind', [$this, 'propFind']);
$server->on('propFind', [$this, 'propFindNode'], 120);
$server->on('propFind', [$this, 'propFindLate'], 200);
$server->on('exception', [$this, 'exception']);
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return 'core';
}
/**
* This is the default implementation for the GET method.
*
* @return bool
*/
public function httpGet(RequestInterface $request, ResponseInterface $response)
{
$path = $request->getPath();
$node = $this->server->tree->getNodeForPath($path);
if (!$node instanceof IFile) {
return;
}
if ('HEAD' === $request->getHeader('X-Sabre-Original-Method')) {
$body = '';
} else {
$body = $node->get();
// Converting string into stream, if needed.
if (is_string($body)) {
$stream = fopen('php://temp', 'r+');
fwrite($stream, $body);
rewind($stream);
$body = $stream;
}
}
/*
* TODO: getetag, getlastmodified, getsize should also be used using
* this method
*/
$httpHeaders = $this->server->getHTTPHeaders($path);
/* ContentType needs to get a default, because many webservers will otherwise
* default to text/html, and we don't want this for security reasons.
*/
if (!isset($httpHeaders['Content-Type'])) {
$httpHeaders['Content-Type'] = 'application/octet-stream';
}
if (isset($httpHeaders['Content-Length'])) {
$nodeSize = $httpHeaders['Content-Length'];
// Need to unset Content-Length, because we'll handle that during figuring out the range
unset($httpHeaders['Content-Length']);
} else {
$nodeSize = null;
}
$response->addHeaders($httpHeaders);
$range = $this->server->getHTTPRange();
$ifRange = $request->getHeader('If-Range');
$ignoreRangeHeader = false;
// If ifRange is set, and range is specified, we first need to check
// the precondition.
if ($nodeSize && $range && $ifRange) {
// if IfRange is parsable as a date we'll treat it as a DateTime
// otherwise, we must treat it as an etag.
try {
$ifRangeDate = new \DateTime($ifRange);
// It's a date. We must check if the entity is modified since
// the specified date.
if (!isset($httpHeaders['Last-Modified'])) {
$ignoreRangeHeader = true;
} else {
$modified = new \DateTime($httpHeaders['Last-Modified']);
if ($modified > $ifRangeDate) {
$ignoreRangeHeader = true;
}
}
} catch (\Exception $e) {
// It's an entity. We can do a simple comparison.
if (!isset($httpHeaders['ETag'])) {
$ignoreRangeHeader = true;
} elseif ($httpHeaders['ETag'] !== $ifRange) {
$ignoreRangeHeader = true;
}
}
}
// We're only going to support HTTP ranges if the backend provided a filesize
if (!$ignoreRangeHeader && $nodeSize && $range) {
// Determining the exact byte offsets
if (!is_null($range[0])) {
$start = $range[0];
$end = $range[1] ? $range[1] : $nodeSize - 1;
if ($start >= $nodeSize) {
throw new Exception\RequestedRangeNotSatisfiable('The start offset ('.$range[0].') exceeded the size of the entity ('.$nodeSize.')');
}
if ($end < $start) {
throw new Exception\RequestedRangeNotSatisfiable('The end offset ('.$range[1].') is lower than the start offset ('.$range[0].')');
}
if ($end >= $nodeSize) {
$end = $nodeSize - 1;
}
} else {
$start = $nodeSize - $range[1];
$end = $nodeSize - 1;
if ($start < 0) {
$start = 0;
}
}
// Streams may advertise themselves as seekable, but still not
// actually allow fseek. We'll manually go forward in the stream
// if fseek failed.
if (!stream_get_meta_data($body)['seekable'] || -1 === fseek($body, $start, SEEK_SET)) {
$consumeBlock = 8192;
for ($consumed = 0; $start - $consumed > 0;) {
if (feof($body)) {
throw new Exception\RequestedRangeNotSatisfiable('The start offset ('.$start.') exceeded the size of the entity ('.$consumed.')');
}
$consumed += strlen(fread($body, min($start - $consumed, $consumeBlock)));
}
}
$response->setHeader('Content-Length', $end - $start + 1);
$response->setHeader('Content-Range', 'bytes '.$start.'-'.$end.'/'.$nodeSize);
$response->setStatus(206);
$response->setBody($body);
} else {
if ($nodeSize) {
$response->setHeader('Content-Length', $nodeSize);
}
$response->setStatus(200);
$response->setBody($body);
}
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* HTTP OPTIONS.
*
* @return bool
*/
public function httpOptions(RequestInterface $request, ResponseInterface $response)
{
$methods = $this->server->getAllowedMethods($request->getPath());
$response->setHeader('Allow', strtoupper(implode(', ', $methods)));
$features = ['1', '3', 'extended-mkcol'];
foreach ($this->server->getPlugins() as $plugin) {
$features = array_merge($features, $plugin->getFeatures());
}
$response->setHeader('DAV', implode(', ', $features));
$response->setHeader('MS-Author-Via', 'DAV');
$response->setHeader('Accept-Ranges', 'bytes');
$response->setHeader('Content-Length', '0');
$response->setStatus(200);
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* HTTP HEAD.
*
* This method is normally used to take a peak at a url, and only get the
* HTTP response headers, without the body. This is used by clients to
* determine if a remote file was changed, so they can use a local cached
* version, instead of downloading it again
*
* @return bool
*/
public function httpHead(RequestInterface $request, ResponseInterface $response)
{
// This is implemented by changing the HEAD request to a GET request,
// and telling the request handler that is doesn't need to create the body.
$subRequest = clone $request;
$subRequest->setMethod('GET');
$subRequest->setHeader('X-Sabre-Original-Method', 'HEAD');
try {
$this->server->invokeMethod($subRequest, $response, false);
} catch (Exception\NotImplemented $e) {
// Some clients may do HEAD requests on collections, however, GET
// requests and HEAD requests _may_ not be defined on a collection,
// which would trigger a 501.
// This breaks some clients though, so we're transforming these
// 501s into 200s.
$response->setStatus(200);
$response->setBody('');
$response->setHeader('Content-Type', 'text/plain');
$response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode());
}
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* HTTP Delete.
*
* The HTTP delete method, deletes a given uri
*/
public function httpDelete(RequestInterface $request, ResponseInterface $response)
{
$path = $request->getPath();
if (!$this->server->emit('beforeUnbind', [$path])) {
return false;
}
$this->server->tree->delete($path);
$this->server->emit('afterUnbind', [$path]);
$response->setStatus(204);
$response->setHeader('Content-Length', '0');
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* WebDAV PROPFIND.
*
* This WebDAV method requests information about an uri resource, or a list of resources
* If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value
* If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory)
*
* The request body contains an XML data structure that has a list of properties the client understands
* The response body is also an xml document, containing information about every uri resource and the requested properties
*
* It has to return a HTTP 207 Multi-status status code
*/
public function httpPropFind(RequestInterface $request, ResponseInterface $response)
{
$path = $request->getPath();
$requestBody = $request->getBodyAsString();
if (strlen($requestBody)) {
try {
$propFindXml = $this->server->xml->expect('{DAV:}propfind', $requestBody);
} catch (ParseException $e) {
throw new BadRequest($e->getMessage(), 0, $e);
}
} else {
$propFindXml = new Xml\Request\PropFind();
$propFindXml->allProp = true;
$propFindXml->properties = [];
}
$depth = $this->server->getHTTPDepth(1);
// The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
if (!$this->server->enablePropfindDepthInfinity && 0 != $depth) {
$depth = 1;
}
$newProperties = $this->server->getPropertiesIteratorForPath($path, $propFindXml->properties, $depth);
// This is a multi-status response
$response->setStatus(207);
$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
$response->setHeader('Vary', 'Brief,Prefer');
// Normally this header is only needed for OPTIONS responses, however..
// iCal seems to also depend on these being set for PROPFIND. Since
// this is not harmful, we'll add it.
$features = ['1', '3', 'extended-mkcol'];
foreach ($this->server->getPlugins() as $plugin) {
$features = array_merge($features, $plugin->getFeatures());
}
$response->setHeader('DAV', implode(', ', $features));
$prefer = $this->server->getHTTPPrefer();
$minimal = 'minimal' === $prefer['return'];
$data = $this->server->generateMultiStatus($newProperties, $minimal);
$response->setBody($data);
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* WebDAV PROPPATCH.
*
* This method is called to update properties on a Node. The request is an XML body with all the mutations.
* In this XML body it is specified which properties should be set/updated and/or deleted
*
* @return bool
*/
public function httpPropPatch(RequestInterface $request, ResponseInterface $response)
{
$path = $request->getPath();
try {
$propPatch = $this->server->xml->expect('{DAV:}propertyupdate', $request->getBody());
} catch (ParseException $e) {
throw new BadRequest($e->getMessage(), 0, $e);
}
$newProperties = $propPatch->properties;
$result = $this->server->updateProperties($path, $newProperties);
$prefer = $this->server->getHTTPPrefer();
$response->setHeader('Vary', 'Brief,Prefer');
if ('minimal' === $prefer['return']) {
// If return-minimal is specified, we only have to check if the
// request was successful, and don't need to return the
// multi-status.
$ok = true;
foreach ($result as $prop => $code) {
if ((int) $code > 299) {
$ok = false;
}
}
if ($ok) {
$response->setStatus(204);
return false;
}
}
$response->setStatus(207);
$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
// Reorganizing the result for generateMultiStatus
$multiStatus = [];
foreach ($result as $propertyName => $code) {
if (isset($multiStatus[$code])) {
$multiStatus[$code][$propertyName] = null;
} else {
$multiStatus[$code] = [$propertyName => null];
}
}
$multiStatus['href'] = $path;
$response->setBody(
$this->server->generateMultiStatus([$multiStatus])
);
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* HTTP PUT method.
*
* This HTTP method updates a file, or creates a new one.
*
* If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content
*
* @return bool
*/
public function httpPut(RequestInterface $request, ResponseInterface $response)
{
$body = $request->getBodyAsStream();
$path = $request->getPath();
// Intercepting Content-Range
if ($request->getHeader('Content-Range')) {
/*
An origin server that allows PUT on a given target resource MUST send
a 400 (Bad Request) response to a PUT request that contains a
Content-Range header field.
Reference: http://tools.ietf.org/html/rfc7231#section-4.3.4
*/
throw new Exception\BadRequest('Content-Range on PUT requests are forbidden.');
}
// Intercepting the Finder problem
if (($expected = $request->getHeader('X-Expected-Entity-Length')) && $expected > 0) {
/*
Many webservers will not cooperate well with Finder PUT requests,
because it uses 'Chunked' transfer encoding for the request body.
The symptom of this problem is that Finder sends files to the
server, but they arrive as 0-length files in PHP.
If we don't do anything, the user might think they are uploading
files successfully, but they end up empty on the server. Instead,
we throw back an error if we detect this.
The reason Finder uses Chunked, is because it thinks the files
might change as it's being uploaded, and therefore the
Content-Length can vary.
Instead it sends the X-Expected-Entity-Length header with the size
of the file at the very start of the request. If this header is set,
but we don't get a request body we will fail the request to
protect the end-user.
*/
// Only reading first byte
$firstByte = fread($body, 1);
if (1 !== strlen($firstByte)) {
throw new Exception\Forbidden('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.');
}
// The body needs to stay intact, so we copy everything to a
// temporary stream.
$newBody = fopen('php://temp', 'r+');
fwrite($newBody, $firstByte);
stream_copy_to_stream($body, $newBody);
rewind($newBody);
$body = $newBody;
}
if ($this->server->tree->nodeExists($path)) {
$node = $this->server->tree->getNodeForPath($path);
// If the node is a collection, we'll deny it
if (!($node instanceof IFile)) {
throw new Exception\Conflict('PUT is not allowed on non-files.');
}
if (!$this->server->updateFile($path, $body, $etag)) {
return false;
}
$response->setHeader('Content-Length', '0');
if ($etag) {
$response->setHeader('ETag', $etag);
}
$response->setStatus(204);
} else {
$etag = null;
// If we got here, the resource didn't exist yet.
if (!$this->server->createFile($path, $body, $etag)) {
// For one reason or another the file was not created.
return false;
}
$response->setHeader('Content-Length', '0');
if ($etag) {
$response->setHeader('ETag', $etag);
}
$response->setStatus(201);
}
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* WebDAV MKCOL.
*
* The MKCOL method is used to create a new collection (directory) on the server
*
* @return bool
*/
public function httpMkcol(RequestInterface $request, ResponseInterface $response)
{
$requestBody = $request->getBodyAsString();
$path = $request->getPath();
if ($requestBody) {
$contentType = $request->getHeader('Content-Type');
if (null === $contentType || (0 !== strpos($contentType, 'application/xml') && 0 !== strpos($contentType, 'text/xml'))) {
// We must throw 415 for unsupported mkcol bodies
throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type');
}
try {
$mkcol = $this->server->xml->expect('{DAV:}mkcol', $requestBody);
} catch (\Sabre\Xml\ParseException $e) {
throw new Exception\BadRequest($e->getMessage(), 0, $e);
}
$properties = $mkcol->getProperties();
if (!isset($properties['{DAV:}resourcetype'])) {
throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property');
}
$resourceType = $properties['{DAV:}resourcetype']->getValue();
unset($properties['{DAV:}resourcetype']);
} else {
$properties = [];
$resourceType = ['{DAV:}collection'];
}
$mkcol = new MkCol($resourceType, $properties);
$result = $this->server->createCollection($path, $mkcol);
if (is_array($result)) {
$response->setStatus(207);
$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
$response->setBody(
$this->server->generateMultiStatus([$result])
);
} else {
$response->setHeader('Content-Length', '0');
$response->setStatus(201);
}
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* WebDAV HTTP MOVE method.
*
* This method moves one uri to a different uri. A lot of the actual request processing is done in getCopyMoveInfo
*
* @return bool
*/
public function httpMove(RequestInterface $request, ResponseInterface $response)
{
$path = $request->getPath();
$moveInfo = $this->server->getCopyAndMoveInfo($request);
if ($moveInfo['destinationExists']) {
if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) {
return false;
}
}
if (!$this->server->emit('beforeUnbind', [$path])) {
return false;
}
if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) {
return false;
}
if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) {
return false;
}
if ($moveInfo['destinationExists']) {
$this->server->tree->delete($moveInfo['destination']);
$this->server->emit('afterUnbind', [$moveInfo['destination']]);
}
$this->server->tree->move($path, $moveInfo['destination']);
// Its important afterMove is called before afterUnbind, because it
// allows systems to transfer data from one path to another.
// PropertyStorage uses this. If afterUnbind was first, it would clean
// up all the properties before it has a chance.
$this->server->emit('afterMove', [$path, $moveInfo['destination']]);
$this->server->emit('afterUnbind', [$path]);
$this->server->emit('afterBind', [$moveInfo['destination']]);
// If a resource was overwritten we should send a 204, otherwise a 201
$response->setHeader('Content-Length', '0');
$response->setStatus($moveInfo['destinationExists'] ? 204 : 201);
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* WebDAV HTTP COPY method.
*
* This method copies one uri to a different uri, and works much like the MOVE request
* A lot of the actual request processing is done in getCopyMoveInfo
*
* @return bool
*/
public function httpCopy(RequestInterface $request, ResponseInterface $response)
{
$path = $request->getPath();
$copyInfo = $this->server->getCopyAndMoveInfo($request);
if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) {
return false;
}
if ($copyInfo['destinationExists']) {
if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) {
return false;
}
$this->server->tree->delete($copyInfo['destination']);
}
$this->server->tree->copy($path, $copyInfo['destination']);
$this->server->emit('afterBind', [$copyInfo['destination']]);
// If a resource was overwritten we should send a 204, otherwise a 201
$response->setHeader('Content-Length', '0');
$response->setStatus($copyInfo['destinationExists'] ? 204 : 201);
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* HTTP REPORT method implementation.
*
* Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253)
* It's used in a lot of extensions, so it made sense to implement it into the core.
*
* @return bool
*/
public function httpReport(RequestInterface $request, ResponseInterface $response)
{
$path = $request->getPath();
$result = $this->server->xml->parse(
$request->getBody(),
$request->getUrl(),
$rootElementName
);
if ($this->server->emit('report', [$rootElementName, $result, $path])) {
// If emit returned true, it means the report was not supported
throw new Exception\ReportNotSupported();
}
// Sending back false will interrupt the event chain and tell the server
// we've handled this method.
return false;
}
/**
* This method is called during property updates.
*
* Here we check if a user attempted to update a protected property and
* ensure that the process fails if this is the case.
*
* @param string $path
*/
public function propPatchProtectedPropertyCheck($path, PropPatch $propPatch)
{
// Comparing the mutation list to the list of protected properties.
$mutations = $propPatch->getMutations();
$protected = array_intersect(
$this->server->protectedProperties,
array_keys($mutations)
);
if ($protected) {
$propPatch->setResultCode($protected, 403);
}
}
/**
* This method is called during property updates.
*
* Here we check if a node implements IProperties and let the node handle
* updating of (some) properties.
*
* @param string $path
*/
public function propPatchNodeUpdate($path, PropPatch $propPatch)
{
// This should trigger a 404 if the node doesn't exist.
$node = $this->server->tree->getNodeForPath($path);
if ($node instanceof IProperties) {
$node->propPatch($propPatch);
}
}
/**
* This method is called when properties are retrieved.
*
* Here we add all the default properties.
*/
public function propFind(PropFind $propFind, INode $node)
{
$propFind->handle('{DAV:}getlastmodified', function () use ($node) {
$lm = $node->getLastModified();
if ($lm) {
return new Xml\Property\GetLastModified($lm);
}
});
if ($node instanceof IFile) {
$propFind->handle('{DAV:}getcontentlength', [$node, 'getSize']);
$propFind->handle('{DAV:}getetag', [$node, 'getETag']);
$propFind->handle('{DAV:}getcontenttype', [$node, 'getContentType']);
}
if ($node instanceof IQuota) {
$quotaInfo = null;
$propFind->handle('{DAV:}quota-used-bytes', function () use (&$quotaInfo, $node) {
$quotaInfo = $node->getQuotaInfo();
return $quotaInfo[0];
});
$propFind->handle('{DAV:}quota-available-bytes', function () use (&$quotaInfo, $node) {
if (!$quotaInfo) {
$quotaInfo = $node->getQuotaInfo();
}
return $quotaInfo[1];
});
}
$propFind->handle('{DAV:}supported-report-set', function () use ($propFind) {
$reports = [];
foreach ($this->server->getPlugins() as $plugin) {
$reports = array_merge($reports, $plugin->getSupportedReportSet($propFind->getPath()));
}
return new Xml\Property\SupportedReportSet($reports);
});
$propFind->handle('{DAV:}resourcetype', function () use ($node) {
return new Xml\Property\ResourceType($this->server->getResourceTypeForNode($node));
});
$propFind->handle('{DAV:}supported-method-set', function () use ($propFind) {
return new Xml\Property\SupportedMethodSet(
$this->server->getAllowedMethods($propFind->getPath())
);
});
}
/**
* Fetches properties for a node.
*
* This event is called a bit later, so plugins have a chance first to
* populate the result.
*/
public function propFindNode(PropFind $propFind, INode $node)
{
if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) {
$nodeProperties = $node->getProperties($propertyNames);
foreach ($nodeProperties as $propertyName => $propertyValue) {
$propFind->set($propertyName, $propertyValue, 200);
}
}
}
/**
* This method is called when properties are retrieved.
*
* This specific handler is called very late in the process, because we
* want other systems to first have a chance to handle the properties.
*/
public function propFindLate(PropFind $propFind, INode $node)
{
$propFind->handle('{http://calendarserver.org/ns/}getctag', function () use ($propFind) {
// If we already have a sync-token from the current propFind
// request, we can re-use that.
$val = $propFind->get('{http://sabredav.org/ns}sync-token');
if ($val) {
return $val;
}
$val = $propFind->get('{DAV:}sync-token');
if ($val && is_scalar($val)) {
return $val;
}
if ($val && $val instanceof Xml\Property\Href) {
return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
}
// If we got here, the earlier two properties may simply not have
// been part of the earlier request. We're going to fetch them.
$result = $this->server->getProperties($propFind->getPath(), [
'{http://sabredav.org/ns}sync-token',
'{DAV:}sync-token',
]);
if (isset($result['{http://sabredav.org/ns}sync-token'])) {
return $result['{http://sabredav.org/ns}sync-token'];
}
if (isset($result['{DAV:}sync-token'])) {
$val = $result['{DAV:}sync-token'];
if (is_scalar($val)) {
return $val;
} elseif ($val instanceof Xml\Property\Href) {
return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
}
}
});
}
/**
* Listens for exception events, and automatically logs them.
*
* @param Exception $e
*/
public function exception($e)
{
$logLevel = \Psr\Log\LogLevel::CRITICAL;
if ($e instanceof \Sabre\DAV\Exception) {
// If it's a standard sabre/dav exception, it means we have a http
// status code available.
$code = $e->getHTTPCode();
if ($code >= 400 && $code < 500) {
// user error
$logLevel = \Psr\Log\LogLevel::INFO;
} else {
// Server-side error. We mark it's as an error, but it's not
// critical.
$logLevel = \Psr\Log\LogLevel::ERROR;
}
}
$this->server->getLogger()->log(
$logLevel,
'Uncaught exception',
[
'exception' => $e,
]
);
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
public function getPluginInfo()
{
return [
'name' => $this->getPluginName(),
'description' => 'The Core plugin provides a lot of the basic functionality required by WebDAV, such as a default implementation for all HTTP and WebDAV methods.',
'link' => null,
];
}
}

50
vendor/sabre/dav/lib/DAV/Exception.php vendored Normal file
View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* Main Exception class.
*
* This class defines a getHTTPCode method, which should return the appropriate HTTP code for the Exception occurred.
* The default for this is 500.
*
* This class also allows you to generate custom xml data for your exceptions. This will be displayed
* in the 'error' element in the failing response.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Exception extends \Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 500;
}
/**
* This method allows the exception to include additional information into the WebDAV error response.
*/
public function serialize(Server $server, \DOMElement $errorNode)
{
}
/**
* This method allows the exception to return any extra HTTP response headers.
*
* The headers must be returned as an array.
*
* @return array
*/
public function getHTTPHeaders(Server $server)
{
return [];
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* BadRequest.
*
* The BadRequest is thrown when the user submitted an invalid HTTP request
* BadRequest
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class BadRequest extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 400;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* Conflict.
*
* A 409 Conflict is thrown when a user tried to make a directory over an existing
* file or in a parent directory that doesn't exist.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Conflict extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 409;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* ConflictingLock.
*
* Similar to the Locked exception, this exception thrown when a LOCK request
* was made, on a resource which was already locked
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ConflictingLock extends Locked
{
/**
* This method allows the exception to include additional information into the WebDAV error response.
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode)
{
if ($this->lock) {
$error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:no-conflicting-lock');
$errorNode->appendChild($error);
$error->appendChild($errorNode->ownerDocument->createElementNS('DAV:', 'd:href', $this->lock->uri));
}
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* Forbidden.
*
* This exception is thrown whenever a user tries to do an operation he's not allowed to
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Forbidden extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 403;
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* InsufficientStorage.
*
* This Exception can be thrown, when for example a harddisk is full or a quota is exceeded
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class InsufficientStorage extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 507;
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
/**
* InvalidResourceType.
*
* This exception is thrown when the user tried to create a new collection, with
* a special resourcetype value that was not recognized by the server.
*
* See RFC5689 section 3.3
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class InvalidResourceType extends Forbidden
{
/**
* This method allows the exception to include additional information into the WebDAV error response.
*/
public function serialize(\Sabre\DAV\Server $server, \DOMElement $errorNode)
{
$error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:valid-resourcetype');
$errorNode->appendChild($error);
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* InvalidSyncToken.
*
* This exception is emited for the {DAV:}valid-sync-token pre-condition, as
* defined in rfc6578, section 3.2.
*
* http://tools.ietf.org/html/rfc6578#section-3.2
*
* This is emitted in cases where the the sync-token, supplied by a client is
* either completely unknown, or has expired.
*
* @author Evert Pot (http://evertpot.com/)
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class InvalidSyncToken extends Forbidden
{
/**
* This method allows the exception to include additional information into the WebDAV error response.
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode)
{
$error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:valid-sync-token');
$errorNode->appendChild($error);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* LengthRequired.
*
* This exception is thrown when a request was made that required a
* Content-Length header, but did not contain one.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class LengthRequired extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 411;
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* LockTokenMatchesRequestUri.
*
* This exception is thrown by UNLOCK if a supplied lock-token is invalid
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class LockTokenMatchesRequestUri extends Conflict
{
/**
* Creates the exception.
*/
public function __construct()
{
parent::__construct('The locktoken supplied does not match any locks on this entity');
}
/**
* This method allows the exception to include additional information into the WebDAV error response.
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode)
{
$error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-matches-request-uri');
$errorNode->appendChild($error);
}
}

View File

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* Locked.
*
* The 423 is thrown when a client tried to access a resource that was locked, without supplying a valid lock token
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Locked extends DAV\Exception
{
/**
* Lock information.
*
* @var \Sabre\DAV\Locks\LockInfo
*/
protected $lock;
/**
* Creates the exception.
*
* A LockInfo object should be passed if the user should be informed
* which lock actually has the file locked.
*
* @param DAV\Locks\LockInfo $lock
*/
public function __construct(DAV\Locks\LockInfo $lock = null)
{
parent::__construct();
$this->lock = $lock;
}
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 423;
}
/**
* This method allows the exception to include additional information into the WebDAV error response.
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode)
{
if ($this->lock) {
$error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-submitted');
$errorNode->appendChild($error);
$href = $errorNode->ownerDocument->createElementNS('DAV:', 'd:href');
$href->appendChild($errorNode->ownerDocument->createTextNode($this->lock->uri));
$error->appendChild(
$href
);
}
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* MethodNotAllowed.
*
* The 405 is thrown when a client tried to create a directory on an already existing directory
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class MethodNotAllowed extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 405;
}
/**
* This method allows the exception to return any extra HTTP response headers.
*
* The headers must be returned as an array.
*
* @return array
*/
public function getHTTPHeaders(\Sabre\DAV\Server $server)
{
$methods = $server->getAllowedMethods($server->getRequestUri());
return [
'Allow' => strtoupper(implode(', ', $methods)),
];
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* NotAuthenticated.
*
* This exception is thrown when the client did not provide valid
* authentication credentials.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class NotAuthenticated extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 401;
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* NotFound.
*
* This Exception is thrown when a Node couldn't be found. It returns HTTP error code 404
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class NotFound extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 404;
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* NotImplemented.
*
* This exception is thrown when the client tried to call an unsupported HTTP method or other feature
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class NotImplemented extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 501;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* Payment Required.
*
* The PaymentRequired exception may be thrown in a case where a user must pay
* to access a certain resource or operation.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class PaymentRequired extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 402;
}
}

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* PreconditionFailed.
*
* This exception is normally thrown when a client submitted a conditional request,
* like for example an If, If-None-Match or If-Match header, which caused the HTTP
* request to not execute (the condition of the header failed)
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class PreconditionFailed extends DAV\Exception
{
/**
* When this exception is thrown, the header-name might be set.
*
* This allows the exception-catching code to determine which HTTP header
* caused the exception.
*
* @var string
*/
public $header = null;
/**
* Create the exception.
*
* @param string $message
* @param string $header
*/
public function __construct($message, $header = null)
{
parent::__construct($message);
$this->header = $header;
}
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 412;
}
/**
* This method allows the exception to include additional information into the WebDAV error response.
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode)
{
if ($this->header) {
$prop = $errorNode->ownerDocument->createElement('s:header');
$prop->nodeValue = $this->header;
$errorNode->appendChild($prop);
}
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* ReportNotSupported.
*
* This exception is thrown when the client requested an unknown report through the REPORT method
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ReportNotSupported extends UnsupportedMediaType
{
/**
* This method allows the exception to include additional information into the WebDAV error response.
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode)
{
$error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:supported-report');
$errorNode->appendChild($error);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* RequestedRangeNotSatisfiable.
*
* This exception is normally thrown when the user
* request a range that is out of the entity bounds.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class RequestedRangeNotSatisfiable extends DAV\Exception
{
/**
* returns the http statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 416;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* ServiceUnavailable.
*
* This exception is thrown in case the service
* is currently not available (e.g. down for maintenance).
*
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class ServiceUnavailable extends DAV\Exception
{
/**
* Returns the HTTP statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 503;
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* TooManyMatches.
*
* This exception is emited for the {DAV:}number-of-matches-within-limits
* post-condition, as defined in rfc6578, section 3.2.
*
* http://tools.ietf.org/html/rfc6578#section-3.2
*
* This is emitted in cases where the response to a {DAV:}sync-collection would
* generate more results than the implementation is willing to send back.
*
* @author Evert Pot (http://evertpot.com/)
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class TooManyMatches extends Forbidden
{
/**
* This method allows the exception to include additional information into the WebDAV error response.
*/
public function serialize(DAV\Server $server, \DOMElement $errorNode)
{
$error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:number-of-matches-within-limits');
$errorNode->appendChild($error);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Exception;
use Sabre\DAV;
/**
* UnSupportedMediaType.
*
* The 415 Unsupported Media Type status code is generally sent back when the client
* tried to call an HTTP method, with a body the server didn't understand
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class UnsupportedMediaType extends DAV\Exception
{
/**
* returns the http statuscode for this exception.
*
* @return int
*/
public function getHTTPCode()
{
return 415;
}
}

View File

@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\FS;
use Sabre\DAV;
/**
* Directory class.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Directory extends Node implements DAV\ICollection, DAV\IQuota
{
/**
* Creates a new file in the directory.
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After successful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
*
* @return string|null
*/
public function createFile($name, $data = null)
{
$newPath = $this->path.'/'.$name;
file_put_contents($newPath, $data);
clearstatcache(true, $newPath);
}
/**
* Creates a new subdirectory.
*
* @param string $name
*/
public function createDirectory($name)
{
$newPath = $this->path.'/'.$name;
mkdir($newPath);
clearstatcache(true, $newPath);
}
/**
* Returns a specific child node, referenced by its name.
*
* This method must throw DAV\Exception\NotFound if the node does not
* exist.
*
* @param string $name
*
* @throws DAV\Exception\NotFound
*
* @return DAV\INode
*/
public function getChild($name)
{
$path = $this->path.'/'.$name;
if (!file_exists($path)) {
throw new DAV\Exception\NotFound('File with name '.$path.' could not be located');
}
if (is_dir($path)) {
return new self($path);
} else {
return new File($path);
}
}
/**
* Returns an array with all the child nodes.
*
* @return DAV\INode[]
*/
public function getChildren()
{
$nodes = [];
$iterator = new \FilesystemIterator(
$this->path,
\FilesystemIterator::CURRENT_AS_SELF
| \FilesystemIterator::SKIP_DOTS
);
foreach ($iterator as $entry) {
$nodes[] = $this->getChild($entry->getFilename());
}
return $nodes;
}
/**
* Checks if a child exists.
*
* @param string $name
*
* @return bool
*/
public function childExists($name)
{
$path = $this->path.'/'.$name;
return file_exists($path);
}
/**
* Deletes all files in this directory, and then itself.
*/
public function delete()
{
foreach ($this->getChildren() as $child) {
$child->delete();
}
rmdir($this->path);
}
/**
* Returns available diskspace information.
*
* @return array
*/
public function getQuotaInfo()
{
$absolute = realpath($this->path);
return [
disk_total_space($absolute) - disk_free_space($absolute),
disk_free_space($absolute),
];
}
}

87
vendor/sabre/dav/lib/DAV/FS/File.php vendored Normal file
View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\FS;
use Sabre\DAV;
/**
* File class.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class File extends Node implements DAV\IFile
{
/**
* Updates the data.
*
* @param resource $data
*/
public function put($data)
{
file_put_contents($this->path, $data);
clearstatcache(true, $this->path);
}
/**
* Returns the data.
*
* @return resource
*/
public function get()
{
return fopen($this->path, 'r');
}
/**
* Delete the current file.
*/
public function delete()
{
unlink($this->path);
}
/**
* Returns the size of the node, in bytes.
*
* @return int
*/
public function getSize()
{
return filesize($this->path);
}
/**
* Returns the ETag for a file.
*
* An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* Return null if the ETag can not effectively be determined
*
* @return mixed
*/
public function getETag()
{
return '"'.sha1(
fileinode($this->path).
filesize($this->path).
filemtime($this->path)
).'"';
}
/**
* Returns the mime-type for a file.
*
* If null is returned, we'll assume application/octet-stream
*
* @return mixed
*/
public function getContentType()
{
return null;
}
}

96
vendor/sabre/dav/lib/DAV/FS/Node.php vendored Normal file
View File

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\FS;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\INode;
use Sabre\Uri;
/**
* Base node-class.
*
* The node class implements the method used by both the File and the Directory classes
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class Node implements INode
{
/**
* The path to the current node.
*
* @var string
*/
protected $path;
/**
* The overridden name of the node.
*
* @var string
*/
protected $overrideName;
/**
* Sets up the node, expects a full path name.
*
* If $overrideName is set, this node shows up in the tree under a
* different name. In this case setName() will be disabled.
*
* @param string $path
* @param string $overrideName
*/
public function __construct($path, $overrideName = null)
{
$this->path = $path;
$this->overrideName = $overrideName;
}
/**
* Returns the name of the node.
*
* @return string
*/
public function getName()
{
if ($this->overrideName) {
return $this->overrideName;
}
list(, $name) = Uri\split($this->path);
return $name;
}
/**
* Renames the node.
*
* @param string $name The new name
*/
public function setName($name)
{
if ($this->overrideName) {
throw new Forbidden('This node cannot be renamed');
}
list($parentPath) = Uri\split($this->path);
list(, $newName) = Uri\split($name);
$newPath = $parentPath.'/'.$newName;
rename($this->path, $newPath);
$this->path = $newPath;
}
/**
* Returns the last modification time, as a unix timestamp.
*
* @return int
*/
public function getLastModified()
{
return filemtime($this->path);
}
}

View File

@ -0,0 +1,212 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\FSExt;
use Sabre\DAV;
use Sabre\DAV\FS\Node;
/**
* Directory class.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Directory extends Node implements DAV\ICollection, DAV\IQuota, DAV\IMoveTarget
{
/**
* Creates a new file in the directory.
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After successful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
*
* @return string|null
*/
public function createFile($name, $data = null)
{
// We're not allowing dots
if ('.' == $name || '..' == $name) {
throw new DAV\Exception\Forbidden('Permission denied to . and ..');
}
$newPath = $this->path.'/'.$name;
file_put_contents($newPath, $data);
clearstatcache(true, $newPath);
return '"'.sha1(
fileinode($newPath).
filesize($newPath).
filemtime($newPath)
).'"';
}
/**
* Creates a new subdirectory.
*
* @param string $name
*/
public function createDirectory($name)
{
// We're not allowing dots
if ('.' == $name || '..' == $name) {
throw new DAV\Exception\Forbidden('Permission denied to . and ..');
}
$newPath = $this->path.'/'.$name;
mkdir($newPath);
clearstatcache(true, $newPath);
}
/**
* Returns a specific child node, referenced by its name.
*
* This method must throw Sabre\DAV\Exception\NotFound if the node does not
* exist.
*
* @param string $name
*
* @throws DAV\Exception\NotFound
*
* @return DAV\INode
*/
public function getChild($name)
{
$path = $this->path.'/'.$name;
if (!file_exists($path)) {
throw new DAV\Exception\NotFound('File could not be located');
}
if ('.' == $name || '..' == $name) {
throw new DAV\Exception\Forbidden('Permission denied to . and ..');
}
if (is_dir($path)) {
return new self($path);
} else {
return new File($path);
}
}
/**
* Checks if a child exists.
*
* @param string $name
*
* @return bool
*/
public function childExists($name)
{
if ('.' == $name || '..' == $name) {
throw new DAV\Exception\Forbidden('Permission denied to . and ..');
}
$path = $this->path.'/'.$name;
return file_exists($path);
}
/**
* Returns an array with all the child nodes.
*
* @return DAV\INode[]
*/
public function getChildren()
{
$nodes = [];
$iterator = new \FilesystemIterator(
$this->path,
\FilesystemIterator::CURRENT_AS_SELF
| \FilesystemIterator::SKIP_DOTS
);
foreach ($iterator as $entry) {
$nodes[] = $this->getChild($entry->getFilename());
}
return $nodes;
}
/**
* Deletes all files in this directory, and then itself.
*
* @return bool
*/
public function delete()
{
// Deleting all children
foreach ($this->getChildren() as $child) {
$child->delete();
}
// Removing the directory itself
rmdir($this->path);
return true;
}
/**
* Returns available diskspace information.
*
* @return array
*/
public function getQuotaInfo()
{
$total = disk_total_space(realpath($this->path));
$free = disk_free_space(realpath($this->path));
return [
$total - $free,
$free,
];
}
/**
* Moves a node into this collection.
*
* It is up to the implementors to:
* 1. Create the new resource.
* 2. Remove the old resource.
* 3. Transfer any properties or other data.
*
* Generally you should make very sure that your collection can easily move
* the move.
*
* If you don't, just return false, which will trigger sabre/dav to handle
* the move itself. If you return true from this function, the assumption
* is that the move was successful.
*
* @param string $targetName new local file/collection name
* @param string $sourcePath Full path to source node
* @param DAV\INode $sourceNode Source node itself
*
* @return bool
*/
public function moveInto($targetName, $sourcePath, DAV\INode $sourceNode)
{
// We only support FSExt\Directory or FSExt\File objects, so
// anything else we want to quickly reject.
if (!$sourceNode instanceof self && !$sourceNode instanceof File) {
return false;
}
// PHP allows us to access protected properties from other objects, as
// long as they are defined in a class that has a shared inheritance
// with the current class.
return rename($sourceNode->path, $this->path.'/'.$targetName);
}
}

150
vendor/sabre/dav/lib/DAV/FSExt/File.php vendored Normal file
View File

@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\FSExt;
use Sabre\DAV;
use Sabre\DAV\FS\Node;
/**
* File class.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class File extends Node implements DAV\PartialUpdate\IPatchSupport
{
/**
* Updates the data.
*
* Data is a readable stream resource.
*
* @param resource|string $data
*
* @return string
*/
public function put($data)
{
file_put_contents($this->path, $data);
clearstatcache(true, $this->path);
return $this->getETag();
}
/**
* Updates the file based on a range specification.
*
* The first argument is the data, which is either a readable stream
* resource or a string.
*
* The second argument is the type of update we're doing.
* This is either:
* * 1. append
* * 2. update based on a start byte
* * 3. update based on an end byte
*;
* The third argument is the start or end byte.
*
* After a successful put operation, you may choose to return an ETag. The
* ETAG must always be surrounded by double-quotes. These quotes must
* appear in the actual string you're returning.
*
* Clients may use the ETag from a PUT request to later on make sure that
* when they update the file, the contents haven't changed in the mean
* time.
*
* @param resource|string $data
* @param int $rangeType
* @param int $offset
*
* @return string|null
*/
public function patch($data, $rangeType, $offset = null)
{
switch ($rangeType) {
case 1:
$f = fopen($this->path, 'a');
break;
case 2:
$f = fopen($this->path, 'c');
fseek($f, $offset);
break;
case 3:
$f = fopen($this->path, 'c');
fseek($f, $offset, SEEK_END);
break;
}
if (is_string($data)) {
fwrite($f, $data);
} else {
stream_copy_to_stream($data, $f);
}
fclose($f);
clearstatcache(true, $this->path);
return $this->getETag();
}
/**
* Returns the data.
*
* @return resource
*/
public function get()
{
return fopen($this->path, 'r');
}
/**
* Delete the current file.
*
* @return bool
*/
public function delete()
{
return unlink($this->path);
}
/**
* Returns the ETag for a file.
*
* An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* Return null if the ETag can not effectively be determined
*
* @return string|null
*/
public function getETag()
{
return '"'.sha1(
fileinode($this->path).
filesize($this->path).
filemtime($this->path)
).'"';
}
/**
* Returns the mime-type for a file.
*
* If null is returned, we'll assume application/octet-stream
*
* @return string|null
*/
public function getContentType()
{
return null;
}
/**
* Returns the size of the file, in bytes.
*
* @return int
*/
public function getSize()
{
return filesize($this->path);
}
}

93
vendor/sabre/dav/lib/DAV/File.php vendored Normal file
View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* File class.
*
* This is a helper class, that should aid in getting file classes setup.
* Most of its methods are implemented, and throw permission denied exceptions
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class File extends Node implements IFile
{
/**
* Replaces the contents of the file.
*
* The data argument is a readable stream resource.
*
* After a successful put operation, you may choose to return an ETag. The
* etag must always be surrounded by double-quotes. These quotes must
* appear in the actual string you're returning.
*
* Clients may use the ETag from a PUT request to later on make sure that
* when they update the file, the contents haven't changed in the mean
* time.
*
* If you don't plan to store the file byte-by-byte, and you return a
* different object on a subsequent GET you are strongly recommended to not
* return an ETag, and just return null.
*
* @param string|resource $data
*
* @return string|null
*/
public function put($data)
{
throw new Exception\Forbidden('Permission denied to change data');
}
/**
* Returns the data.
*
* This method may either return a string or a readable stream resource
*
* @return mixed
*/
public function get()
{
throw new Exception\Forbidden('Permission denied to read this file');
}
/**
* Returns the size of the file, in bytes.
*
* @return int
*/
public function getSize()
{
return 0;
}
/**
* Returns the ETag for a file.
*
* An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* Return null if the ETag can not effectively be determined
*
* @return string|null
*/
public function getETag()
{
return null;
}
/**
* Returns the mime-type for a file.
*
* If null is returned, we'll assume application/octet-stream
*
* @return string|null
*/
public function getContentType()
{
return null;
}
}

View File

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* The ICollection Interface.
*
* This interface should be implemented by each class that represents a collection
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ICollection extends INode
{
/**
* Creates a new file in the directory.
*
* Data will either be supplied as a stream resource, or in certain cases
* as a string. Keep in mind that you may have to support either.
*
* After successful creation of the file, you may choose to return the ETag
* of the new file here.
*
* The returned ETag must be surrounded by double-quotes (The quotes should
* be part of the actual string).
*
* If you cannot accurately determine the ETag, you should not return it.
* If you don't store the file exactly as-is (you're transforming it
* somehow) you should also not return an ETag.
*
* This means that if a subsequent GET to this new file does not exactly
* return the same contents of what was submitted here, you are strongly
* recommended to omit the ETag.
*
* @param string $name Name of the file
* @param resource|string $data Initial payload
*
* @return string|null
*/
public function createFile($name, $data = null);
/**
* Creates a new subdirectory.
*
* @param string $name
*/
public function createDirectory($name);
/**
* Returns a specific child node, referenced by its name.
*
* This method must throw Sabre\DAV\Exception\NotFound if the node does not
* exist.
*
* @param string $name
*
* @return INode
*/
public function getChild($name);
/**
* Returns an array with all the child nodes.
*
* @return INode[]
*/
public function getChildren();
/**
* Checks if a child-node with the specified name exists.
*
* @param string $name
*
* @return bool
*/
public function childExists($name);
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* By implementing this interface, a collection can effectively say "other
* nodes may be copied into this collection".
*
* If a backend supports a better optimized copy operation, e.g. by avoiding
* copying the contents, this can trigger some huge speed gains.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ICopyTarget extends ICollection
{
/**
* Copies a node into this collection.
*
* It is up to the implementors to:
* 1. Create the new resource.
* 2. Copy the data and any properties.
*
* If you return true from this function, the assumption
* is that the copy was successful.
* If you return false, sabre/dav will handle the copy itself.
*
* @param string $targetName new local file/collection name
* @param string $sourcePath Full path to source node
* @param INode $sourceNode Source node itself
*
* @return bool
*/
public function copyInto($targetName, $sourcePath, INode $sourceNode);
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* The IExtendedCollection interface.
*
* This interface can be used to create special-type of collection-resources
* as defined by RFC 5689.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IExtendedCollection extends ICollection
{
/**
* Creates a new collection.
*
* This method will receive a MkCol object with all the information about
* the new collection that's being created.
*
* The MkCol object contains information about the resourceType of the new
* collection. If you don't support the specified resourceType, you should
* throw Exception\InvalidResourceType.
*
* The object also contains a list of WebDAV properties for the new
* collection.
*
* You should call the handle() method on this object to specify exactly
* which properties you are storing. This allows the system to figure out
* exactly which properties you didn't store, which in turn allows other
* plugins (such as the propertystorage plugin) to handle storing the
* property for you.
*
* @param string $name
*
* @throws Exception\InvalidResourceType
*/
public function createExtendedCollection($name, MkCol $mkCol);
}

83
vendor/sabre/dav/lib/DAV/IFile.php vendored Normal file
View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* This interface represents a file in the directory tree.
*
* A file is a bit of a broad definition. In general it implies that on
* this specific node a PUT or GET method may be performed, to either update,
* or retrieve the contents of the file.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IFile extends INode
{
/**
* Replaces the contents of the file.
*
* The data argument is a readable stream resource.
*
* After a successful put operation, you may choose to return an ETag. The
* etag must always be surrounded by double-quotes. These quotes must
* appear in the actual string you're returning.
*
* Clients may use the ETag from a PUT request to later on make sure that
* when they update the file, the contents haven't changed in the mean
* time.
*
* If you don't plan to store the file byte-by-byte, and you return a
* different object on a subsequent GET you are strongly recommended to not
* return an ETag, and just return null.
*
* @param resource|string $data
*
* @return string|null
*/
public function put($data);
/**
* Returns the data.
*
* This method may either return a string or a readable stream resource
*
* @return mixed
*/
public function get();
/**
* Returns the mime-type for a file.
*
* If null is returned, we'll assume application/octet-stream
*
* @return string|null
*/
public function getContentType();
/**
* Returns the ETag for a file.
*
* An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
*
* Return null if the ETag can not effectively be determined.
*
* The ETag must be surrounded by double-quotes, so something like this
* would make a valid ETag:
*
* return '"someetag"';
*
* @return string|null
*/
public function getETag();
/**
* Returns the size of the node, in bytes.
*
* @return int
*/
public function getSize();
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* By implementing this interface, a collection can effectively say "other
* nodes may be moved into this collection".
*
* The benefit of this, is that sabre/dav will by default perform a move, by
* transferring an entire directory tree, copying every collection, and deleting
* every item.
*
* If a backend supports a better optimized move operation, this can trigger
* some huge speed gains.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IMoveTarget extends ICollection
{
/**
* Moves a node into this collection.
*
* It is up to the implementors to:
* 1. Create the new resource.
* 2. Remove the old resource.
* 3. Transfer any properties or other data.
*
* Generally you should make very sure that your collection can easily move
* the move.
*
* If you don't, just return false, which will trigger sabre/dav to handle
* the move itself. If you return true from this function, the assumption
* is that the move was successful.
*
* @param string $targetName new local file/collection name
* @param string $sourcePath Full path to source node
* @param INode $sourceNode Source node itself
*
* @return bool
*/
public function moveInto($targetName, $sourcePath, INode $sourceNode);
}

38
vendor/sabre/dav/lib/DAV/IMultiGet.php vendored Normal file
View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* IMultiGet.
*
* This interface adds a tiny bit of functionality to collections.
*
* There a certain situations, in particular in relation to WebDAV-Sync, CalDAV
* and CardDAV, where information for a list of items will be requested.
*
* Because the getChild() call is the main abstraction method, this can in
* reality result in many database calls, which could potentially be
* optimized.
*
* The MultiGet interface is used by the server in these cases.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IMultiGet extends ICollection
{
/**
* This method receives a list of paths in it's first argument.
* It must return an array with Node objects.
*
* If any children are not found, you do not have to return them.
*
* @param string[] $paths
*
* @return array
*/
public function getMultipleChildren(array $paths);
}

44
vendor/sabre/dav/lib/DAV/INode.php vendored Normal file
View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* The INode interface is the base interface, and the parent class of both ICollection and IFile.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface INode
{
/**
* Deleted the current node.
*/
public function delete();
/**
* Returns the name of the node.
*
* This is used to generate the url.
*
* @return string
*/
public function getName();
/**
* Renames the node.
*
* @param string $name The new name
*/
public function setName($name);
/**
* Returns the last modification time, as a unix timestamp. Return null
* if the information is not available.
*
* @return int|null
*/
public function getLastModified();
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* IProperties interface.
*
* Implement this interface to support custom WebDAV properties requested and sent from clients.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IProperties extends INode
{
/**
* Updates properties on this node.
*
* This method received a PropPatch object, which contains all the
* information about the update.
*
* To update specific properties, call the 'handle' method on this object.
* Read the PropPatch documentation for more information.
*/
public function propPatch(PropPatch $propPatch);
/**
* Returns a list of properties for this nodes.
*
* The properties list is a list of propertynames the client requested,
* encoded in clark-notation {xmlnamespace}tagname
*
* If the array is empty, it means 'all properties' were requested.
*
* Note that it's fine to liberally give properties back, instead of
* conforming to the list of requested properties.
* The Server class will filter out the extra.
*
* @param array $properties
*
* @return array
*/
public function getProperties($properties);
}

27
vendor/sabre/dav/lib/DAV/IQuota.php vendored Normal file
View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* IQuota interface.
*
* Implement this interface to add the ability to return quota information. The ObjectTree
* will check for quota information on any given node. If the information is not available it will
* attempt to fetch the information from the root node.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IQuota extends ICollection
{
/**
* Returns the quota information.
*
* This method MUST return an array with 2 values, the first being the total used space,
* the second the available space (in bytes)
*/
public function getQuotaInfo();
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Locks\Backend;
/**
* This is an Abstract clas for lock backends.
*
* Currently this backend has no function, but it exists for consistency, and
* to ensure that if default code is required in the backend, there will be a
* non-bc-breaking way to do so.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class AbstractBackend implements BackendInterface
{
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Locks\Backend;
use Sabre\DAV\Locks;
/**
* If you are defining your own Locks backend, you must implement this
* interface.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface BackendInterface
{
/**
* Returns a list of Sabre\DAV\Locks\LockInfo objects.
*
* This method should return all the locks for a particular uri, including
* locks that might be set on a parent uri.
*
* If returnChildLocks is set to true, this method should also look for
* any locks in the subtree of the uri for locks.
*
* @param string $uri
* @param bool $returnChildLocks
*
* @return array
*/
public function getLocks($uri, $returnChildLocks);
/**
* Locks a uri.
*
* @param string $uri
*
* @return bool
*/
public function lock($uri, Locks\LockInfo $lockInfo);
/**
* Removes a lock from a uri.
*
* @param string $uri
*
* @return bool
*/
public function unlock($uri, Locks\LockInfo $lockInfo);
}

View File

@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Locks\Backend;
use Sabre\DAV\Locks\LockInfo;
/**
* This Locks backend stores all locking information in a single file.
*
* Note that this is not nearly as robust as a database. If you are considering
* using this backend, keep in mind that the PDO backend can work with SqLite,
* which is designed to be a good file-based database.
*
* It literally solves the problem this class solves as well, but much better.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class File extends AbstractBackend
{
/**
* The storage file.
*
* @var string
*/
private $locksFile;
/**
* Constructor.
*
* @param string $locksFile path to file
*/
public function __construct($locksFile)
{
$this->locksFile = $locksFile;
}
/**
* Returns a list of Sabre\DAV\Locks\LockInfo objects.
*
* This method should return all the locks for a particular uri, including
* locks that might be set on a parent uri.
*
* If returnChildLocks is set to true, this method should also look for
* any locks in the subtree of the uri for locks.
*
* @param string $uri
* @param bool $returnChildLocks
*
* @return array
*/
public function getLocks($uri, $returnChildLocks)
{
$newLocks = [];
$locks = $this->getData();
foreach ($locks as $lock) {
if ($lock->uri === $uri ||
//deep locks on parents
(0 != $lock->depth && 0 === strpos($uri, $lock->uri.'/')) ||
// locks on children
($returnChildLocks && (0 === strpos($lock->uri, $uri.'/')))) {
$newLocks[] = $lock;
}
}
// Checking if we can remove any of these locks
foreach ($newLocks as $k => $lock) {
if (time() > $lock->timeout + $lock->created) {
unset($newLocks[$k]);
}
}
return $newLocks;
}
/**
* Locks a uri.
*
* @param string $uri
*
* @return bool
*/
public function lock($uri, LockInfo $lockInfo)
{
// We're making the lock timeout 30 minutes
$lockInfo->timeout = 1800;
$lockInfo->created = time();
$lockInfo->uri = $uri;
$locks = $this->getData();
foreach ($locks as $k => $lock) {
if (
($lock->token == $lockInfo->token) ||
(time() > $lock->timeout + $lock->created)
) {
unset($locks[$k]);
}
}
$locks[] = $lockInfo;
$this->putData($locks);
return true;
}
/**
* Removes a lock from a uri.
*
* @param string $uri
*
* @return bool
*/
public function unlock($uri, LockInfo $lockInfo)
{
$locks = $this->getData();
foreach ($locks as $k => $lock) {
if ($lock->token == $lockInfo->token) {
unset($locks[$k]);
$this->putData($locks);
return true;
}
}
return false;
}
/**
* Loads the lockdata from the filesystem.
*
* @return array
*/
protected function getData()
{
if (!file_exists($this->locksFile)) {
return [];
}
// opening up the file, and creating a shared lock
$handle = fopen($this->locksFile, 'r');
flock($handle, LOCK_SH);
// Reading data until the eof
$data = stream_get_contents($handle);
// We're all good
flock($handle, LOCK_UN);
fclose($handle);
// Unserializing and checking if the resource file contains data for this file
$data = unserialize($data);
if (!$data) {
return [];
}
return $data;
}
/**
* Saves the lockdata.
*/
protected function putData(array $newData)
{
// opening up the file, and creating an exclusive lock
$handle = fopen($this->locksFile, 'a+');
flock($handle, LOCK_EX);
// We can only truncate and rewind once the lock is acquired.
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, serialize($newData));
flock($handle, LOCK_UN);
fclose($handle);
}
}

View File

@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Locks\Backend;
use Sabre\DAV\Locks\LockInfo;
/**
* The Lock manager allows you to handle all file-locks centrally.
*
* This Lock Manager stores all its data in a database. You must pass a PDO
* connection object in the constructor.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class PDO extends AbstractBackend
{
/**
* The PDO tablename this backend uses.
*
* @var string
*/
public $tableName = 'locks';
/**
* The PDO connection object.
*
* @var pdo
*/
protected $pdo;
/**
* Constructor.
*/
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* Returns a list of Sabre\DAV\Locks\LockInfo objects.
*
* This method should return all the locks for a particular uri, including
* locks that might be set on a parent uri.
*
* If returnChildLocks is set to true, this method should also look for
* any locks in the subtree of the uri for locks.
*
* @param string $uri
* @param bool $returnChildLocks
*
* @return array
*/
public function getLocks($uri, $returnChildLocks)
{
// NOTE: the following 10 lines or so could be easily replaced by
// pure sql. MySQL's non-standard string concatenation prevents us
// from doing this though.
$query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM '.$this->tableName.' WHERE (created > (? - timeout)) AND ((uri = ?)';
$params = [time(), $uri];
// We need to check locks for every part in the uri.
$uriParts = explode('/', $uri);
// We already covered the last part of the uri
array_pop($uriParts);
$currentPath = '';
foreach ($uriParts as $part) {
if ($currentPath) {
$currentPath .= '/';
}
$currentPath .= $part;
$query .= ' OR (depth!=0 AND uri = ?)';
$params[] = $currentPath;
}
if ($returnChildLocks) {
$query .= ' OR (uri LIKE ?)';
$params[] = $uri.'/%';
}
$query .= ')';
$stmt = $this->pdo->prepare($query);
$stmt->execute($params);
$result = $stmt->fetchAll();
$lockList = [];
foreach ($result as $row) {
$lockInfo = new LockInfo();
$lockInfo->owner = $row['owner'];
$lockInfo->token = $row['token'];
$lockInfo->timeout = $row['timeout'];
$lockInfo->created = $row['created'];
$lockInfo->scope = $row['scope'];
$lockInfo->depth = $row['depth'];
$lockInfo->uri = $row['uri'];
$lockList[] = $lockInfo;
}
return $lockList;
}
/**
* Locks a uri.
*
* @param string $uri
*
* @return bool
*/
public function lock($uri, LockInfo $lockInfo)
{
// We're making the lock timeout 30 minutes
$lockInfo->timeout = 30 * 60;
$lockInfo->created = time();
$lockInfo->uri = $uri;
$locks = $this->getLocks($uri, false);
$exists = false;
foreach ($locks as $lock) {
if ($lock->token == $lockInfo->token) {
$exists = true;
}
}
if ($exists) {
$stmt = $this->pdo->prepare('UPDATE '.$this->tableName.' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?');
$stmt->execute([
$lockInfo->owner,
$lockInfo->timeout,
$lockInfo->scope,
$lockInfo->depth,
$uri,
$lockInfo->created,
$lockInfo->token,
]);
} else {
$stmt = $this->pdo->prepare('INSERT INTO '.$this->tableName.' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)');
$stmt->execute([
$lockInfo->owner,
$lockInfo->timeout,
$lockInfo->scope,
$lockInfo->depth,
$uri,
$lockInfo->created,
$lockInfo->token,
]);
}
return true;
}
/**
* Removes a lock from a uri.
*
* @param string $uri
*
* @return bool
*/
public function unlock($uri, LockInfo $lockInfo)
{
$stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE uri = ? AND token = ?');
$stmt->execute([$uri, $lockInfo->token]);
return 1 === $stmt->rowCount();
}
}

View File

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Locks;
/**
* LockInfo class.
*
* An object of the LockInfo class holds all the information relevant to a
* single lock.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class LockInfo
{
/**
* A shared lock.
*/
const SHARED = 1;
/**
* An exclusive lock.
*/
const EXCLUSIVE = 2;
/**
* A never expiring timeout.
*/
const TIMEOUT_INFINITE = -1;
/**
* The owner of the lock.
*
* @var string
*/
public $owner;
/**
* The locktoken.
*
* @var string
*/
public $token;
/**
* How long till the lock is expiring.
*
* @var int
*/
public $timeout;
/**
* UNIX Timestamp of when this lock was created.
*
* @var int
*/
public $created;
/**
* Exclusive or shared lock.
*
* @var int
*/
public $scope = self::EXCLUSIVE;
/**
* Depth of lock, can be 0 or Sabre\DAV\Server::DEPTH_INFINITY.
*/
public $depth = 0;
/**
* The uri this lock locks.
*
* TODO: This value is not always set
*
* @var mixed
*/
public $uri;
}

View File

@ -0,0 +1,546 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Locks;
use Sabre\DAV;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* Locking plugin.
*
* This plugin provides locking support to a WebDAV server.
* The easiest way to get started, is by hooking it up as such:
*
* $lockBackend = new Sabre\DAV\Locks\Backend\File('./mylockdb');
* $lockPlugin = new Sabre\DAV\Locks\Plugin($lockBackend);
* $server->addPlugin($lockPlugin);
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends DAV\ServerPlugin
{
/**
* locksBackend.
*
* @var Backend\BackendInterface
*/
protected $locksBackend;
/**
* server.
*
* @var DAV\Server
*/
protected $server;
/**
* __construct.
*/
public function __construct(Backend\BackendInterface $locksBackend)
{
$this->locksBackend = $locksBackend;
}
/**
* Initializes the plugin.
*
* This method is automatically called by the Server class after addPlugin.
*/
public function initialize(DAV\Server $server)
{
$this->server = $server;
$this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock';
$server->on('method:LOCK', [$this, 'httpLock']);
$server->on('method:UNLOCK', [$this, 'httpUnlock']);
$server->on('validateTokens', [$this, 'validateTokens']);
$server->on('propFind', [$this, 'propFind']);
$server->on('afterUnbind', [$this, 'afterUnbind']);
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using Sabre\DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return 'locks';
}
/**
* This method is called after most properties have been found
* it allows us to add in any Lock-related properties.
*/
public function propFind(DAV\PropFind $propFind, DAV\INode $node)
{
$propFind->handle('{DAV:}supportedlock', function () {
return new DAV\Xml\Property\SupportedLock();
});
$propFind->handle('{DAV:}lockdiscovery', function () use ($propFind) {
return new DAV\Xml\Property\LockDiscovery(
$this->getLocks($propFind->getPath())
);
});
}
/**
* Use this method to tell the server this plugin defines additional
* HTTP methods.
*
* This method is passed a uri. It should only return HTTP methods that are
* available for the specified uri.
*
* @param string $uri
*
* @return array
*/
public function getHTTPMethods($uri)
{
return ['LOCK', 'UNLOCK'];
}
/**
* Returns a list of features for the HTTP OPTIONS Dav: header.
*
* In this case this is only the number 2. The 2 in the Dav: header
* indicates the server supports locks.
*
* @return array
*/
public function getFeatures()
{
return [2];
}
/**
* Returns all lock information on a particular uri.
*
* This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array.
*
* Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree
* If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object
* for any possible locks and return those as well.
*
* @param string $uri
* @param bool $returnChildLocks
*
* @return array
*/
public function getLocks($uri, $returnChildLocks = false)
{
return $this->locksBackend->getLocks($uri, $returnChildLocks);
}
/**
* Locks an uri.
*
* The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
* If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type
* of lock (shared or exclusive) and the owner of the lock
*
* If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
*
* Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3
*
* @return bool
*/
public function httpLock(RequestInterface $request, ResponseInterface $response)
{
$uri = $request->getPath();
$existingLocks = $this->getLocks($uri);
if ($body = $request->getBodyAsString()) {
// This is a new lock request
$existingLock = null;
// Checking if there's already non-shared locks on the uri.
foreach ($existingLocks as $existingLock) {
if (LockInfo::EXCLUSIVE === $existingLock->scope) {
throw new DAV\Exception\ConflictingLock($existingLock);
}
}
$lockInfo = $this->parseLockRequest($body);
$lockInfo->depth = $this->server->getHTTPDepth();
$lockInfo->uri = $uri;
if ($existingLock && LockInfo::SHARED != $lockInfo->scope) {
throw new DAV\Exception\ConflictingLock($existingLock);
}
} else {
// Gonna check if this was a lock refresh.
$existingLocks = $this->getLocks($uri);
$conditions = $this->server->getIfConditions($request);
$found = null;
foreach ($existingLocks as $existingLock) {
foreach ($conditions as $condition) {
foreach ($condition['tokens'] as $token) {
if ($token['token'] === 'opaquelocktoken:'.$existingLock->token) {
$found = $existingLock;
break 3;
}
}
}
}
// If none were found, this request is in error.
if (is_null($found)) {
if ($existingLocks) {
throw new DAV\Exception\Locked(reset($existingLocks));
} else {
throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
}
}
// This must have been a lock refresh
$lockInfo = $found;
// The resource could have been locked through another uri.
if ($uri != $lockInfo->uri) {
$uri = $lockInfo->uri;
}
}
if ($timeout = $this->getTimeoutHeader()) {
$lockInfo->timeout = $timeout;
}
$newFile = false;
// If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first
try {
$this->server->tree->getNodeForPath($uri);
// We need to call the beforeWriteContent event for RFC3744
// Edit: looks like this is not used, and causing problems now.
//
// See Issue 222
// $this->server->emit('beforeWriteContent',array($uri));
} catch (DAV\Exception\NotFound $e) {
// It didn't, lets create it
$this->server->createFile($uri, fopen('php://memory', 'r'));
$newFile = true;
}
$this->lockNode($uri, $lockInfo);
$response->setHeader('Content-Type', 'application/xml; charset=utf-8');
$response->setHeader('Lock-Token', '<opaquelocktoken:'.$lockInfo->token.'>');
$response->setStatus($newFile ? 201 : 200);
$response->setBody($this->generateLockResponse($lockInfo));
// Returning false will interrupt the event chain and mark this method
// as 'handled'.
return false;
}
/**
* Unlocks a uri.
*
* This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header
* The server should return 204 (No content) on success
*/
public function httpUnlock(RequestInterface $request, ResponseInterface $response)
{
$lockToken = $request->getHeader('Lock-Token');
// If the locktoken header is not supplied, we need to throw a bad request exception
if (!$lockToken) {
throw new DAV\Exception\BadRequest('No lock token was supplied');
}
$path = $request->getPath();
$locks = $this->getLocks($path);
// Windows sometimes forgets to include < and > in the Lock-Token
// header
if ('<' !== $lockToken[0]) {
$lockToken = '<'.$lockToken.'>';
}
foreach ($locks as $lock) {
if ('<opaquelocktoken:'.$lock->token.'>' == $lockToken) {
$this->unlockNode($path, $lock);
$response->setHeader('Content-Length', '0');
$response->setStatus(204);
// Returning false will break the method chain, and mark the
// method as 'handled'.
return false;
}
}
// If we got here, it means the locktoken was invalid
throw new DAV\Exception\LockTokenMatchesRequestUri();
}
/**
* This method is called after a node is deleted.
*
* We use this event to clean up any locks that still exist on the node.
*
* @param string $path
*/
public function afterUnbind($path)
{
$locks = $this->getLocks($path, $includeChildren = true);
foreach ($locks as $lock) {
$this->unlockNode($path, $lock);
}
}
/**
* Locks a uri.
*
* All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored
* It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client
*
* @param string $uri
*
* @return bool
*/
public function lockNode($uri, LockInfo $lockInfo)
{
if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) {
return;
}
return $this->locksBackend->lock($uri, $lockInfo);
}
/**
* Unlocks a uri.
*
* This method removes a lock from a uri. It is assumed all the supplied information is correct and verified
*
* @param string $uri
*
* @return bool
*/
public function unlockNode($uri, LockInfo $lockInfo)
{
if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) {
return;
}
return $this->locksBackend->unlock($uri, $lockInfo);
}
/**
* Returns the contents of the HTTP Timeout header.
*
* The method formats the header into an integer.
*
* @return int
*/
public function getTimeoutHeader()
{
$header = $this->server->httpRequest->getHeader('Timeout');
if ($header) {
if (0 === stripos($header, 'second-')) {
$header = (int) (substr($header, 7));
} elseif (0 === stripos($header, 'infinite')) {
$header = LockInfo::TIMEOUT_INFINITE;
} else {
throw new DAV\Exception\BadRequest('Invalid HTTP timeout header');
}
} else {
$header = 0;
}
return $header;
}
/**
* Generates the response for successful LOCK requests.
*
* @return string
*/
protected function generateLockResponse(LockInfo $lockInfo)
{
return $this->server->xml->write('{DAV:}prop', [
'{DAV:}lockdiscovery' => new DAV\Xml\Property\LockDiscovery([$lockInfo]),
]);
}
/**
* The validateTokens event is triggered before every request.
*
* It's a moment where this plugin can check all the supplied lock tokens
* in the If: header, and check if they are valid.
*
* In addition, it will also ensure that it checks any missing lokens that
* must be present in the request, and reject requests without the proper
* tokens.
*
* @param mixed $conditions
*/
public function validateTokens(RequestInterface $request, &$conditions)
{
// First we need to gather a list of locks that must be satisfied.
$mustLocks = [];
$method = $request->getMethod();
// Methods not in that list are operations that doesn't alter any
// resources, and we don't need to check the lock-states for.
switch ($method) {
case 'DELETE':
$mustLocks = array_merge($mustLocks, $this->getLocks(
$request->getPath(),
true
));
break;
case 'MKCOL':
case 'MKCALENDAR':
case 'PROPPATCH':
case 'PUT':
case 'PATCH':
$mustLocks = array_merge($mustLocks, $this->getLocks(
$request->getPath(),
false
));
break;
case 'MOVE':
$mustLocks = array_merge($mustLocks, $this->getLocks(
$request->getPath(),
true
));
$mustLocks = array_merge($mustLocks, $this->getLocks(
$this->server->calculateUri($request->getHeader('Destination')),
false
));
break;
case 'COPY':
$mustLocks = array_merge($mustLocks, $this->getLocks(
$this->server->calculateUri($request->getHeader('Destination')),
false
));
break;
case 'LOCK':
//Temporary measure.. figure out later why this is needed
// Here we basically ignore all incoming tokens...
foreach ($conditions as $ii => $condition) {
foreach ($condition['tokens'] as $jj => $token) {
$conditions[$ii]['tokens'][$jj]['validToken'] = true;
}
}
return;
}
// It's possible that there's identical locks, because of shared
// parents. We're removing the duplicates here.
$tmp = [];
foreach ($mustLocks as $lock) {
$tmp[$lock->token] = $lock;
}
$mustLocks = array_values($tmp);
foreach ($conditions as $kk => $condition) {
foreach ($condition['tokens'] as $ii => $token) {
// Lock tokens always start with opaquelocktoken:
if ('opaquelocktoken:' !== substr($token['token'], 0, 16)) {
continue;
}
$checkToken = substr($token['token'], 16);
// Looping through our list with locks.
foreach ($mustLocks as $jj => $mustLock) {
if ($mustLock->token == $checkToken) {
// We have a match!
// Removing this one from mustlocks
unset($mustLocks[$jj]);
// Marking the condition as valid.
$conditions[$kk]['tokens'][$ii]['validToken'] = true;
// Advancing to the next token
continue 2;
}
}
// If we got here, it means that there was a
// lock-token, but it was not in 'mustLocks'.
//
// This is an edge-case, as it could mean that token
// was specified with a url that was not 'required' to
// check. So we're doing one extra lookup to make sure
// we really don't know this token.
//
// This also gets triggered when the user specified a
// lock-token that was expired.
$oddLocks = $this->getLocks($condition['uri']);
foreach ($oddLocks as $oddLock) {
if ($oddLock->token === $checkToken) {
// We have a hit!
$conditions[$kk]['tokens'][$ii]['validToken'] = true;
continue 2;
}
}
// If we get all the way here, the lock-token was
// really unknown.
}
}
// If there's any locks left in the 'mustLocks' array, it means that
// the resource was locked and we must block it.
if ($mustLocks) {
throw new DAV\Exception\Locked(reset($mustLocks));
}
}
/**
* Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object.
*
* @param string $body
*
* @return LockInfo
*/
protected function parseLockRequest($body)
{
$result = $this->server->xml->expect(
'{DAV:}lockinfo',
$body
);
$lockInfo = new LockInfo();
$lockInfo->owner = $result->owner;
$lockInfo->token = DAV\UUIDUtil::getUUID();
$lockInfo->scope = $result->scope;
return $lockInfo;
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
public function getPluginInfo()
{
return [
'name' => $this->getPluginName(),
'description' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK',
'link' => 'http://sabre.io/dav/locks/',
];
}
}

71
vendor/sabre/dav/lib/DAV/MkCol.php vendored Normal file
View File

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* This class represents a MKCOL operation.
*
* MKCOL creates a new collection. MKCOL comes in two flavours:
*
* 1. MKCOL with no body, signifies the creation of a simple collection.
* 2. MKCOL with a request body. This can create a collection with a specific
* resource type, and a set of properties that should be set on the new
* collection. This can be used to create caldav calendars, carddav address
* books, etc.
*
* Property updates must always be atomic. This means that a property update
* must either completely succeed, or completely fail.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class MkCol extends PropPatch
{
/**
* A list of resource-types in clark-notation.
*
* @var array
*/
protected $resourceType;
/**
* Creates the MKCOL object.
*
* @param string[] $resourceType list of resourcetype values
* @param array $mutations list of new properties values
*/
public function __construct(array $resourceType, array $mutations)
{
$this->resourceType = $resourceType;
parent::__construct($mutations);
}
/**
* Returns the resourcetype of the new collection.
*
* @return string[]
*/
public function getResourceType()
{
return $this->resourceType;
}
/**
* Returns true or false if the MKCOL operation has at least the specified
* resource type.
*
* If the resourcetype is specified as an array, all resourcetypes are
* checked.
*
* @param string|string[] $resourceType
*
* @return bool
*/
public function hasResourceType($resourceType)
{
return 0 === count(array_diff((array) $resourceType, $this->resourceType));
}
}

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Mount;
use Sabre\DAV;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* This plugin provides support for RFC4709: Mounting WebDAV servers.
*
* Simply append ?mount to any collection to generate the davmount response.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends DAV\ServerPlugin
{
/**
* Reference to Server class.
*
* @var DAV\Server
*/
protected $server;
/**
* Initializes the plugin and registers event handles.
*/
public function initialize(DAV\Server $server)
{
$this->server = $server;
$this->server->on('method:GET', [$this, 'httpGet'], 90);
}
/**
* 'beforeMethod' event handles. This event handles intercepts GET requests ending
* with ?mount.
*
* @return bool
*/
public function httpGet(RequestInterface $request, ResponseInterface $response)
{
$queryParams = $request->getQueryParameters();
if (!array_key_exists('mount', $queryParams)) {
return;
}
$currentUri = $request->getAbsoluteUrl();
// Stripping off everything after the ?
list($currentUri) = explode('?', $currentUri);
$this->davMount($response, $currentUri);
// Returning false to break the event chain
return false;
}
/**
* Generates the davmount response.
*
* @param string $uri absolute uri
*/
public function davMount(ResponseInterface $response, $uri)
{
$response->setStatus(200);
$response->setHeader('Content-Type', 'application/davmount+xml');
ob_start();
echo '<?xml version="1.0"?>', "\n";
echo "<dm:mount xmlns:dm=\"http://purl.org/NET/webdav/mount\">\n";
echo ' <dm:url>', htmlspecialchars($uri, ENT_NOQUOTES, 'UTF-8'), "</dm:url>\n";
echo '</dm:mount>';
$response->setBody(ob_get_clean());
}
}

51
vendor/sabre/dav/lib/DAV/Node.php vendored Normal file
View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* Node class.
*
* This is a helper class, that should aid in getting nodes setup.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class Node implements INode
{
/**
* Returns the last modification time as a unix timestamp.
*
* If the information is not available, return null.
*
* @return int
*/
public function getLastModified()
{
return null;
}
/**
* Deletes the current node.
*
* @throws Exception\Forbidden
*/
public function delete()
{
throw new Exception\Forbidden('Permission denied to delete node');
}
/**
* Renames the node.
*
* @param string $name The new name
*
* @throws Exception\Forbidden
*/
public function setName($name)
{
throw new Exception\Forbidden('Permission denied to rename file');
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\PartialUpdate;
use Sabre\DAV;
/**
* This interface provides a way to modify only part of a target resource
* It may be used to update a file chunk, upload big a file into smaller
* chunks or resume an upload.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface IPatchSupport extends DAV\IFile
{
/**
* Updates the file based on a range specification.
*
* The first argument is the data, which is either a readable stream
* resource or a string.
*
* The second argument is the type of update we're doing.
* This is either:
* * 1. append
* * 2. update based on a start byte
* * 3. update based on an end byte
*;
* The third argument is the start or end byte.
*
* After a successful put operation, you may choose to return an ETag. The
* etag must always be surrounded by double-quotes. These quotes must
* appear in the actual string you're returning.
*
* Clients may use the ETag from a PUT request to later on make sure that
* when they update the file, the contents haven't changed in the mean
* time.
*
* @param resource|string $data
* @param int $rangeType
* @param int $offset
*
* @return string|null
*/
public function patch($data, $rangeType, $offset = null);
}

View File

@ -0,0 +1,212 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\PartialUpdate;
use Sabre\DAV;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* Partial update plugin (Patch method).
*
* This plugin provides a way to modify only part of a target resource
* It may bu used to update a file chunk, upload big a file into smaller
* chunks or resume an upload.
*
* $patchPlugin = new \Sabre\DAV\PartialUpdate\Plugin();
* $server->addPlugin($patchPlugin);
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends DAV\ServerPlugin
{
const RANGE_APPEND = 1;
const RANGE_START = 2;
const RANGE_END = 3;
/**
* Reference to server.
*
* @var DAV\Server
*/
protected $server;
/**
* Initializes the plugin.
*
* This method is automatically called by the Server class after addPlugin.
*/
public function initialize(DAV\Server $server)
{
$this->server = $server;
$server->on('method:PATCH', [$this, 'httpPatch']);
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return 'partialupdate';
}
/**
* Use this method to tell the server this plugin defines additional
* HTTP methods.
*
* This method is passed a uri. It should only return HTTP methods that are
* available for the specified uri.
*
* We claim to support PATCH method (partirl update) if and only if
* - the node exist
* - the node implements our partial update interface
*
* @param string $uri
*
* @return array
*/
public function getHTTPMethods($uri)
{
$tree = $this->server->tree;
if ($tree->nodeExists($uri)) {
$node = $tree->getNodeForPath($uri);
if ($node instanceof IPatchSupport) {
return ['PATCH'];
}
}
return [];
}
/**
* Returns a list of features for the HTTP OPTIONS Dav: header.
*
* @return array
*/
public function getFeatures()
{
return ['sabredav-partialupdate'];
}
/**
* Patch an uri.
*
* The WebDAV patch request can be used to modify only a part of an
* existing resource. If the resource does not exist yet and the first
* offset is not 0, the request fails
*/
public function httpPatch(RequestInterface $request, ResponseInterface $response)
{
$path = $request->getPath();
// Get the node. Will throw a 404 if not found
$node = $this->server->tree->getNodeForPath($path);
if (!$node instanceof IPatchSupport) {
throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.');
}
$range = $this->getHTTPUpdateRange($request);
if (!$range) {
throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers');
}
$contentType = strtolower(
(string) $request->getHeader('Content-Type')
);
if ('application/x-sabredav-partialupdate' != $contentType) {
throw new DAV\Exception\UnsupportedMediaType('Unknown Content-Type header "'.$contentType.'"');
}
$len = $this->server->httpRequest->getHeader('Content-Length');
if (!$len) {
throw new DAV\Exception\LengthRequired('A Content-Length header is required');
}
switch ($range[0]) {
case self::RANGE_START:
// Calculate the end-range if it doesn't exist.
if (!$range[2]) {
$range[2] = $range[1] + $len - 1;
} else {
if ($range[2] < $range[1]) {
throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset ('.$range[2].') is lower than the start offset ('.$range[1].')');
}
if ($range[2] - $range[1] + 1 != $len) {
throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length ('.$len.') is not consistent with begin ('.$range[1].') and end ('.$range[2].') offsets');
}
}
break;
}
if (!$this->server->emit('beforeWriteContent', [$path, $node, null])) {
return;
}
$body = $this->server->httpRequest->getBody();
$etag = $node->patch($body, $range[0], isset($range[1]) ? $range[1] : null);
$this->server->emit('afterWriteContent', [$path, $node]);
$response->setHeader('Content-Length', '0');
if ($etag) {
$response->setHeader('ETag', $etag);
}
$response->setStatus(204);
// Breaks the event chain
return false;
}
/**
* Returns the HTTP custom range update header.
*
* This method returns null if there is no well-formed HTTP range request
* header. It returns array(1) if it was an append request, array(2,
* $start, $end) if it's a start and end range, lastly it's array(3,
* $endoffset) if the offset was negative, and should be calculated from
* the end of the file.
*
* Examples:
*
* null - invalid
* [1] - append
* [2,10,15] - update bytes 10, 11, 12, 13, 14, 15
* [2,10,null] - update bytes 10 until the end of the patch body
* [3,-5] - update from 5 bytes from the end of the file.
*
* @return array|null
*/
public function getHTTPUpdateRange(RequestInterface $request)
{
$range = $request->getHeader('X-Update-Range');
if (is_null($range)) {
return null;
}
// Matching "Range: bytes=1234-5678: both numbers are optional
if (!preg_match('/^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i', $range, $matches)) {
return null;
}
if ('append' === $matches[1]) {
return [self::RANGE_APPEND];
} elseif (strlen($matches[2]) > 0) {
return [self::RANGE_START, (int) $matches[2], (int) $matches[3] ?: null];
} else {
return [self::RANGE_END, (int) $matches[4]];
}
}
}

335
vendor/sabre/dav/lib/DAV/PropFind.php vendored Normal file
View File

@ -0,0 +1,335 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* This class holds all the information about a PROPFIND request.
*
* It contains the type of PROPFIND request, which properties were requested
* and also the returned items.
*/
class PropFind
{
/**
* A normal propfind.
*/
const NORMAL = 0;
/**
* An allprops request.
*
* While this was originally intended for instructing the server to really
* fetch every property, because it was used so often and it's so heavy
* this turned into a small list of default properties after a while.
*
* So 'all properties' now means a hardcoded list.
*/
const ALLPROPS = 1;
/**
* A propname request. This just returns a list of properties that are
* defined on a node, without their values.
*/
const PROPNAME = 2;
/**
* Creates the PROPFIND object.
*
* @param string $path
* @param int $depth
* @param int $requestType
*/
public function __construct($path, array $properties, $depth = 0, $requestType = self::NORMAL)
{
$this->path = $path;
$this->properties = $properties;
$this->depth = $depth;
$this->requestType = $requestType;
if (self::ALLPROPS === $requestType) {
$this->properties = [
'{DAV:}getlastmodified',
'{DAV:}getcontentlength',
'{DAV:}resourcetype',
'{DAV:}quota-used-bytes',
'{DAV:}quota-available-bytes',
'{DAV:}getetag',
'{DAV:}getcontenttype',
];
}
foreach ($this->properties as $propertyName) {
// Seeding properties with 404's.
$this->result[$propertyName] = [404, null];
}
$this->itemsLeft = count($this->result);
}
/**
* Handles a specific property.
*
* This method checks whether the specified property was requested in this
* PROPFIND request, and if so, it will call the callback and use the
* return value for it's value.
*
* Example:
*
* $propFind->handle('{DAV:}displayname', function() {
* return 'hello';
* });
*
* Note that handle will only work the first time. If null is returned, the
* value is ignored.
*
* It's also possible to not pass a callback, but immediately pass a value
*
* @param string $propertyName
* @param mixed $valueOrCallBack
*/
public function handle($propertyName, $valueOrCallBack)
{
if ($this->itemsLeft && isset($this->result[$propertyName]) && 404 === $this->result[$propertyName][0]) {
if (is_callable($valueOrCallBack)) {
$value = $valueOrCallBack();
} else {
$value = $valueOrCallBack;
}
if (!is_null($value)) {
--$this->itemsLeft;
$this->result[$propertyName] = [200, $value];
}
}
}
/**
* Sets the value of the property.
*
* If status is not supplied, the status will default to 200 for non-null
* properties, and 404 for null properties.
*
* @param string $propertyName
* @param mixed $value
* @param int $status
*/
public function set($propertyName, $value, $status = null)
{
if (is_null($status)) {
$status = is_null($value) ? 404 : 200;
}
// If this is an ALLPROPS request and the property is
// unknown, add it to the result; else ignore it:
if (!isset($this->result[$propertyName])) {
if (self::ALLPROPS === $this->requestType) {
$this->result[$propertyName] = [$status, $value];
}
return;
}
if (404 !== $status && 404 === $this->result[$propertyName][0]) {
--$this->itemsLeft;
} elseif (404 === $status && 404 !== $this->result[$propertyName][0]) {
++$this->itemsLeft;
}
$this->result[$propertyName] = [$status, $value];
}
/**
* Returns the current value for a property.
*
* @param string $propertyName
*
* @return mixed
*/
public function get($propertyName)
{
return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null;
}
/**
* Returns the current status code for a property name.
*
* If the property does not appear in the list of requested properties,
* null will be returned.
*
* @param string $propertyName
*
* @return int|null
*/
public function getStatus($propertyName)
{
return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : null;
}
/**
* Updates the path for this PROPFIND.
*
* @param string $path
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* Returns the path this PROPFIND request is for.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Returns the depth of this propfind request.
*
* @return int
*/
public function getDepth()
{
return $this->depth;
}
/**
* Updates the depth of this propfind request.
*
* @param int $depth
*/
public function setDepth($depth)
{
$this->depth = $depth;
}
/**
* Returns all propertynames that have a 404 status, and thus don't have a
* value yet.
*
* @return array
*/
public function get404Properties()
{
if (0 === $this->itemsLeft) {
return [];
}
$result = [];
foreach ($this->result as $propertyName => $stuff) {
if (404 === $stuff[0]) {
$result[] = $propertyName;
}
}
return $result;
}
/**
* Returns the full list of requested properties.
*
* This returns just their names, not a status or value.
*
* @return array
*/
public function getRequestedProperties()
{
return $this->properties;
}
/**
* Returns true if this was an '{DAV:}allprops' request.
*
* @return bool
*/
public function isAllProps()
{
return self::ALLPROPS === $this->requestType;
}
/**
* Returns a result array that's often used in multistatus responses.
*
* The array uses status codes as keys, and property names and value pairs
* as the value of the top array.. such as :
*
* [
* 200 => [ '{DAV:}displayname' => 'foo' ],
* ]
*
* @return array
*/
public function getResultForMultiStatus()
{
$r = [
200 => [],
404 => [],
];
foreach ($this->result as $propertyName => $info) {
if (!isset($r[$info[0]])) {
$r[$info[0]] = [$propertyName => $info[1]];
} else {
$r[$info[0]][$propertyName] = $info[1];
}
}
// Removing the 404's for multi-status requests.
if (self::ALLPROPS === $this->requestType) {
unset($r[404]);
}
return $r;
}
/**
* The path that we're fetching properties for.
*
* @var string
*/
protected $path;
/**
* The Depth of the request.
*
* 0 means only the current item. 1 means the current item + its children.
* It can also be DEPTH_INFINITY if this is enabled in the server.
*
* @var int
*/
protected $depth = 0;
/**
* The type of request. See the TYPE constants.
*/
protected $requestType;
/**
* A list of requested properties.
*
* @var array
*/
protected $properties = [];
/**
* The result of the operation.
*
* The keys in this array are property names.
* The values are an array with two elements: the http status code and then
* optionally a value.
*
* Example:
*
* [
* "{DAV:}owner" : [404],
* "{DAV:}displayname" : [200, "Admin"]
* ]
*
* @var array
*/
protected $result = [];
/**
* This is used as an internal counter for the number of properties that do
* not yet have a value.
*
* @var int
*/
protected $itemsLeft;
}

337
vendor/sabre/dav/lib/DAV/PropPatch.php vendored Normal file
View File

@ -0,0 +1,337 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
use UnexpectedValueException;
/**
* This class represents a set of properties that are going to be updated.
*
* Usually this is simply a PROPPATCH request, but it can also be used for
* internal updates.
*
* Property updates must always be atomic. This means that a property update
* must either completely succeed, or completely fail.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class PropPatch
{
/**
* Properties that are being updated.
*
* This is a key-value list. If the value is null, the property is supposed
* to be deleted.
*
* @var array
*/
protected $mutations;
/**
* A list of properties and the result of the update. The result is in the
* form of a HTTP status code.
*
* @var array
*/
protected $result = [];
/**
* This is the list of callbacks when we're performing the actual update.
*
* @var array
*/
protected $propertyUpdateCallbacks = [];
/**
* This property will be set to true if the operation failed.
*
* @var bool
*/
protected $failed = false;
/**
* Constructor.
*
* @param array $mutations A list of updates
*/
public function __construct(array $mutations)
{
$this->mutations = $mutations;
}
/**
* Call this function if you wish to handle updating certain properties.
* For instance, your class may be responsible for handling updates for the
* {DAV:}displayname property.
*
* In that case, call this method with the first argument
* "{DAV:}displayname" and a second argument that's a method that does the
* actual updating.
*
* It's possible to specify more than one property as an array.
*
* The callback must return a boolean or an it. If the result is true, the
* operation was considered successful. If it's false, it's consided
* failed.
*
* If the result is an integer, we'll use that integer as the http status
* code associated with the operation.
*
* @param string|string[] $properties
*/
public function handle($properties, callable $callback)
{
$usedProperties = [];
foreach ((array) $properties as $propertyName) {
if (array_key_exists($propertyName, $this->mutations) && !isset($this->result[$propertyName])) {
$usedProperties[] = $propertyName;
// HTTP Accepted
$this->result[$propertyName] = 202;
}
}
// Only registering if there's any unhandled properties.
if (!$usedProperties) {
return;
}
$this->propertyUpdateCallbacks[] = [
// If the original argument to this method was a string, we need
// to also make sure that it stays that way, so the commit function
// knows how to format the arguments to the callback.
is_string($properties) ? $properties : $usedProperties,
$callback,
];
}
/**
* Call this function if you wish to handle _all_ properties that haven't
* been handled by anything else yet. Note that you effectively claim with
* this that you promise to process _all_ properties that are coming in.
*/
public function handleRemaining(callable $callback)
{
$properties = $this->getRemainingMutations();
if (!$properties) {
// Nothing to do, don't register callback
return;
}
foreach ($properties as $propertyName) {
// HTTP Accepted
$this->result[$propertyName] = 202;
$this->propertyUpdateCallbacks[] = [
$properties,
$callback,
];
}
}
/**
* Sets the result code for one or more properties.
*
* @param string|string[] $properties
* @param int $resultCode
*/
public function setResultCode($properties, $resultCode)
{
foreach ((array) $properties as $propertyName) {
$this->result[$propertyName] = $resultCode;
}
if ($resultCode >= 400) {
$this->failed = true;
}
}
/**
* Sets the result code for all properties that did not have a result yet.
*
* @param int $resultCode
*/
public function setRemainingResultCode($resultCode)
{
$this->setResultCode(
$this->getRemainingMutations(),
$resultCode
);
}
/**
* Returns the list of properties that don't have a result code yet.
*
* This method returns a list of property names, but not its values.
*
* @return string[]
*/
public function getRemainingMutations()
{
$remaining = [];
foreach ($this->mutations as $propertyName => $propValue) {
if (!isset($this->result[$propertyName])) {
$remaining[] = $propertyName;
}
}
return $remaining;
}
/**
* Returns the list of properties that don't have a result code yet.
*
* This method returns list of properties and their values.
*
* @return array
*/
public function getRemainingValues()
{
$remaining = [];
foreach ($this->mutations as $propertyName => $propValue) {
if (!isset($this->result[$propertyName])) {
$remaining[$propertyName] = $propValue;
}
}
return $remaining;
}
/**
* Performs the actual update, and calls all callbacks.
*
* This method returns true or false depending on if the operation was
* successful.
*
* @return bool
*/
public function commit()
{
// First we validate if every property has a handler
foreach ($this->mutations as $propertyName => $value) {
if (!isset($this->result[$propertyName])) {
$this->failed = true;
$this->result[$propertyName] = 403;
}
}
foreach ($this->propertyUpdateCallbacks as $callbackInfo) {
if ($this->failed) {
break;
}
if (is_string($callbackInfo[0])) {
$this->doCallbackSingleProp($callbackInfo[0], $callbackInfo[1]);
} else {
$this->doCallbackMultiProp($callbackInfo[0], $callbackInfo[1]);
}
}
/*
* If anywhere in this operation updating a property failed, we must
* update all other properties accordingly.
*/
if ($this->failed) {
foreach ($this->result as $propertyName => $status) {
if (202 === $status) {
// Failed dependency
$this->result[$propertyName] = 424;
}
}
}
return !$this->failed;
}
/**
* Executes a property callback with the single-property syntax.
*
* @param string $propertyName
*/
private function doCallBackSingleProp($propertyName, callable $callback)
{
$result = $callback($this->mutations[$propertyName]);
if (is_bool($result)) {
if ($result) {
if (is_null($this->mutations[$propertyName])) {
// Delete
$result = 204;
} else {
// Update
$result = 200;
}
} else {
// Fail
$result = 403;
}
}
if (!is_int($result)) {
throw new UnexpectedValueException('A callback sent to handle() did not return an int or a bool');
}
$this->result[$propertyName] = $result;
if ($result >= 400) {
$this->failed = true;
}
}
/**
* Executes a property callback with the multi-property syntax.
*/
private function doCallBackMultiProp(array $propertyList, callable $callback)
{
$argument = [];
foreach ($propertyList as $propertyName) {
$argument[$propertyName] = $this->mutations[$propertyName];
}
$result = $callback($argument);
if (is_array($result)) {
foreach ($propertyList as $propertyName) {
if (!isset($result[$propertyName])) {
$resultCode = 500;
} else {
$resultCode = $result[$propertyName];
}
if ($resultCode >= 400) {
$this->failed = true;
}
$this->result[$propertyName] = $resultCode;
}
} elseif (true === $result) {
// Success
foreach ($argument as $propertyName => $propertyValue) {
$this->result[$propertyName] = is_null($propertyValue) ? 204 : 200;
}
} elseif (false === $result) {
// Fail :(
$this->failed = true;
foreach ($propertyList as $propertyName) {
$this->result[$propertyName] = 403;
}
} else {
throw new UnexpectedValueException('A callback sent to handle() did not return an array or a bool');
}
}
/**
* Returns the result of the operation.
*
* @return array
*/
public function getResult()
{
return $this->result;
}
/**
* Returns the full list of mutations.
*
* @return array
*/
public function getMutations()
{
return $this->mutations;
}
}

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\PropertyStorage\Backend;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
/**
* Propertystorage backend interface.
*
* Propertystorage backends must implement this interface to be used by the
* propertystorage plugin.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface BackendInterface
{
/**
* Fetches properties for a path.
*
* This method received a PropFind object, which contains all the
* information about the properties that need to be fetched.
*
* Usually you would just want to call 'get404Properties' on this object,
* as this will give you the _exact_ list of properties that need to be
* fetched, and haven't yet.
*
* However, you can also support the 'allprops' property here. In that
* case, you should check for $propFind->isAllProps().
*
* @param string $path
*/
public function propFind($path, PropFind $propFind);
/**
* Updates properties for a path.
*
* This method received a PropPatch object, which contains all the
* information about the update.
*
* Usually you would want to call 'handleRemaining' on this object, to get;
* a list of all properties that need to be stored.
*
* @param string $path
*/
public function propPatch($path, PropPatch $propPatch);
/**
* This method is called after a node is deleted.
*
* This allows a backend to clean up all associated properties.
*
* The delete method will get called once for the deletion of an entire
* tree.
*
* @param string $path
*/
public function delete($path);
/**
* This method is called after a successful MOVE.
*
* This should be used to migrate all properties from one path to another.
* Note that entire collections may be moved, so ensure that all properties
* for children are also moved along.
*
* @param string $source
* @param string $destination
*/
public function move($source, $destination);
}

View File

@ -0,0 +1,224 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\PropertyStorage\Backend;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Xml\Property\Complex;
/**
* PropertyStorage PDO backend.
*
* This backend class uses a PDO-enabled database to store webdav properties.
* Both sqlite and mysql have been tested.
*
* The database structure can be found in the examples/sql/ directory.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class PDO implements BackendInterface
{
/**
* Value is stored as string.
*/
const VT_STRING = 1;
/**
* Value is stored as XML fragment.
*/
const VT_XML = 2;
/**
* Value is stored as a property object.
*/
const VT_OBJECT = 3;
/**
* PDO.
*
* @var \PDO
*/
protected $pdo;
/**
* PDO table name we'll be using.
*
* @var string
*/
public $tableName = 'propertystorage';
/**
* Creates the PDO property storage engine.
*/
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
}
/**
* Fetches properties for a path.
*
* This method received a PropFind object, which contains all the
* information about the properties that need to be fetched.
*
* Usually you would just want to call 'get404Properties' on this object,
* as this will give you the _exact_ list of properties that need to be
* fetched, and haven't yet.
*
* However, you can also support the 'allprops' property here. In that
* case, you should check for $propFind->isAllProps().
*
* @param string $path
*/
public function propFind($path, PropFind $propFind)
{
if (!$propFind->isAllProps() && 0 === count($propFind->get404Properties())) {
return;
}
$query = 'SELECT name, value, valuetype FROM '.$this->tableName.' WHERE path = ?';
$stmt = $this->pdo->prepare($query);
$stmt->execute([$path]);
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
if ('resource' === gettype($row['value'])) {
$row['value'] = stream_get_contents($row['value']);
}
switch ($row['valuetype']) {
case null:
case self::VT_STRING:
$propFind->set($row['name'], $row['value']);
break;
case self::VT_XML:
$propFind->set($row['name'], new Complex($row['value']));
break;
case self::VT_OBJECT:
$propFind->set($row['name'], unserialize($row['value']));
break;
}
}
}
/**
* Updates properties for a path.
*
* This method received a PropPatch object, which contains all the
* information about the update.
*
* Usually you would want to call 'handleRemaining' on this object, to get;
* a list of all properties that need to be stored.
*
* @param string $path
*/
public function propPatch($path, PropPatch $propPatch)
{
$propPatch->handleRemaining(function ($properties) use ($path) {
if ('pgsql' === $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
$updateSql = <<<SQL
INSERT INTO {$this->tableName} (path, name, valuetype, value)
VALUES (:path, :name, :valuetype, :value)
ON CONFLICT (path, name)
DO UPDATE SET valuetype = :valuetype, value = :value
SQL;
} else {
$updateSql = <<<SQL
REPLACE INTO {$this->tableName} (path, name, valuetype, value)
VALUES (:path, :name, :valuetype, :value)
SQL;
}
$updateStmt = $this->pdo->prepare($updateSql);
$deleteStmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE path = ? AND name = ?');
foreach ($properties as $name => $value) {
if (!is_null($value)) {
if (is_scalar($value)) {
$valueType = self::VT_STRING;
} elseif ($value instanceof Complex) {
$valueType = self::VT_XML;
$value = $value->getXml();
} else {
$valueType = self::VT_OBJECT;
$value = serialize($value);
}
$updateStmt->bindParam('path', $path, \PDO::PARAM_STR);
$updateStmt->bindParam('name', $name, \PDO::PARAM_STR);
$updateStmt->bindParam('valuetype', $valueType, \PDO::PARAM_INT);
$updateStmt->bindParam('value', $value, \PDO::PARAM_LOB);
$updateStmt->execute();
} else {
$deleteStmt->execute([$path, $name]);
}
}
return true;
});
}
/**
* This method is called after a node is deleted.
*
* This allows a backend to clean up all associated properties.
*
* The delete method will get called once for the deletion of an entire
* tree.
*
* @param string $path
*/
public function delete($path)
{
$stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName." WHERE path = ? OR path LIKE ? ESCAPE '='");
$childPath = strtr(
$path,
[
'=' => '==',
'%' => '=%',
'_' => '=_',
]
).'/%';
$stmt->execute([$path, $childPath]);
}
/**
* This method is called after a successful MOVE.
*
* This should be used to migrate all properties from one path to another.
* Note that entire collections may be moved, so ensure that all properties
* for children are also moved along.
*
* @param string $source
* @param string $destination
*/
public function move($source, $destination)
{
// I don't know a way to write this all in a single sql query that's
// also compatible across db engines, so we're letting PHP do all the
// updates. Much slower, but it should still be pretty fast in most
// cases.
$select = $this->pdo->prepare('SELECT id, path FROM '.$this->tableName.' WHERE path = ? OR path LIKE ?');
$select->execute([$source, $source.'/%']);
$update = $this->pdo->prepare('UPDATE '.$this->tableName.' SET path = ? WHERE id = ?');
while ($row = $select->fetch(\PDO::FETCH_ASSOC)) {
// Sanity check. SQL may select too many records, such as records
// with different cases.
if ($row['path'] !== $source && 0 !== strpos($row['path'], $source.'/')) {
continue;
}
$trailingPart = substr($row['path'], strlen($source) + 1);
$newPath = $destination;
if ($trailingPart) {
$newPath .= '/'.$trailingPart;
}
$update->execute([$newPath, $row['id']]);
}
}
}

View File

@ -0,0 +1,176 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\PropertyStorage;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
/**
* PropertyStorage Plugin.
*
* Adding this plugin to your server allows clients to store any arbitrary
* WebDAV property.
*
* See:
* http://sabre.io/dav/property-storage/
*
* for more information.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends ServerPlugin
{
/**
* If you only want this plugin to store properties for a limited set of
* paths, you can use a pathFilter to do this.
*
* The pathFilter should be a callable. The callable retrieves a path as
* its argument, and should return true or false whether it allows
* properties to be stored.
*
* @var callable
*/
public $pathFilter;
/**
* @var Backend\BackendInterface
*/
public $backend;
/**
* Creates the plugin.
*/
public function __construct(Backend\BackendInterface $backend)
{
$this->backend = $backend;
}
/**
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*/
public function initialize(Server $server)
{
$server->on('propFind', [$this, 'propFind'], 130);
$server->on('propPatch', [$this, 'propPatch'], 300);
$server->on('afterMove', [$this, 'afterMove']);
$server->on('afterUnbind', [$this, 'afterUnbind']);
}
/**
* Called during PROPFIND operations.
*
* If there's any requested properties that don't have a value yet, this
* plugin will look in the property storage backend to find them.
*/
public function propFind(PropFind $propFind, INode $node)
{
$path = $propFind->getPath();
$pathFilter = $this->pathFilter;
if ($pathFilter && !$pathFilter($path)) {
return;
}
$this->backend->propFind($propFind->getPath(), $propFind);
}
/**
* Called during PROPPATCH operations.
*
* If there's any updated properties that haven't been stored, the
* propertystorage backend can handle it.
*
* @param string $path
*/
public function propPatch($path, PropPatch $propPatch)
{
$pathFilter = $this->pathFilter;
if ($pathFilter && !$pathFilter($path)) {
return;
}
$this->backend->propPatch($path, $propPatch);
}
/**
* Called after a node is deleted.
*
* This allows the backend to clean up any properties still in the
* database.
*
* @param string $path
*/
public function afterUnbind($path)
{
$pathFilter = $this->pathFilter;
if ($pathFilter && !$pathFilter($path)) {
return;
}
$this->backend->delete($path);
}
/**
* Called after a node is moved.
*
* This allows the backend to move all the associated properties.
*
* @param string $source
* @param string $destination
*/
public function afterMove($source, $destination)
{
$pathFilter = $this->pathFilter;
if ($pathFilter && !$pathFilter($source)) {
return;
}
// If the destination is filtered, afterUnbind will handle cleaning up
// the properties.
if ($pathFilter && !$pathFilter($destination)) {
return;
}
$this->backend->move($source, $destination);
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return 'property-storage';
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
public function getPluginInfo()
{
return [
'name' => $this->getPluginName(),
'description' => 'This plugin allows any arbitrary WebDAV property to be set on any resource.',
'link' => 'http://sabre.io/dav/property-storage/',
];
}
}

1672
vendor/sabre/dav/lib/DAV/Server.php vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* The baseclass for all server plugins.
*
* Plugins can modify or extend the servers behaviour.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
abstract class ServerPlugin
{
/**
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*/
abstract public function initialize(Server $server);
/**
* This method should return a list of server-features.
*
* This is for example 'versioning' and is added to the DAV: header
* in an OPTIONS response.
*
* @return array
*/
public function getFeatures()
{
return [];
}
/**
* Use this method to tell the server this plugin defines additional
* HTTP methods.
*
* This method is passed a uri. It should only return HTTP methods that are
* available for the specified uri.
*
* @param string $path
*
* @return array
*/
public function getHTTPMethods($path)
{
return [];
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return get_class($this);
}
/**
* Returns a list of reports this plugin supports.
*
* This will be used in the {DAV:}supported-report-set property.
* Note that you still need to subscribe to the 'report' event to actually
* implement them
*
* @param string $uri
*
* @return array
*/
public function getSupportedReportSet($uri)
{
return [];
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
public function getPluginInfo()
{
return [
'name' => $this->getPluginName(),
'description' => null,
'link' => null,
];
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Sharing;
use Sabre\DAV\INode;
/**
* This interface represents a resource that has sharing capabilities, either
* because it's possible for an owner to share the resource, or because this is
* an instance of a shared resource.
*
* @copyright Copyright (C) fruux GmbH. (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ISharedNode extends INode
{
/**
* Returns the 'access level' for the instance of this shared resource.
*
* The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_
* constants.
*
* @return int
*/
public function getShareAccess();
/**
* This function must return a URI that uniquely identifies the shared
* resource. This URI should be identical across instances, and is
* also used in several other XML bodies to connect invites to
* resources.
*
* This may simply be a relative reference to the original shared instance,
* but it could also be a urn. As long as it's a valid URI and unique.
*
* @return string
*/
public function getShareResourceUri();
/**
* Updates the list of sharees.
*
* Every item must be a Sharee object.
*
* @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
*/
public function updateInvites(array $sharees);
/**
* Returns the list of people whom this resource is shared with.
*
* Every item in the returned array must be a Sharee object with
* at least the following properties set:
*
* * $href
* * $shareAccess
* * $inviteStatus
*
* and optionally:
*
* * $properties
*
* @return \Sabre\DAV\Xml\Element\Sharee[]
*/
public function getInvites();
}

View File

@ -0,0 +1,313 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Sharing;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Xml\Element\Sharee;
use Sabre\DAV\Xml\Property;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* This plugin implements HTTP requests and properties related to:.
*
* draft-pot-webdav-resource-sharing
*
* This specification allows people to share webdav resources with others.
*
* @copyright Copyright (C) 2007-2015 fruux GmbH. (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends ServerPlugin
{
const ACCESS_NOTSHARED = 0;
const ACCESS_SHAREDOWNER = 1;
const ACCESS_READ = 2;
const ACCESS_READWRITE = 3;
const ACCESS_NOACCESS = 4;
const INVITE_NORESPONSE = 1;
const INVITE_ACCEPTED = 2;
const INVITE_DECLINED = 3;
const INVITE_INVALID = 4;
/**
* Reference to SabreDAV server object.
*
* @var Server
*/
protected $server;
/**
* This method should return a list of server-features.
*
* This is for example 'versioning' and is added to the DAV: header
* in an OPTIONS response.
*
* @return array
*/
public function getFeatures()
{
return ['resource-sharing'];
}
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return 'sharing';
}
/**
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*/
public function initialize(Server $server)
{
$this->server = $server;
$server->xml->elementMap['{DAV:}share-resource'] = 'Sabre\\DAV\\Xml\\Request\\ShareResource';
array_push(
$server->protectedProperties,
'{DAV:}share-mode'
);
$server->on('method:POST', [$this, 'httpPost']);
$server->on('propFind', [$this, 'propFind']);
$server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
$server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
$server->on('onBrowserPostAction', [$this, 'browserPostAction']);
}
/**
* Updates the list of sharees on a shared resource.
*
* The sharees array is a list of people that are to be added modified
* or removed in the list of shares.
*
* @param string $path
* @param Sharee[] $sharees
*/
public function shareResource($path, array $sharees)
{
$node = $this->server->tree->getNodeForPath($path);
if (!$node instanceof ISharedNode) {
throw new Forbidden('Sharing is not allowed on this node');
}
// Getting ACL info
$acl = $this->server->getPlugin('acl');
// If there's no ACL support, we allow everything
if ($acl) {
$acl->checkPrivileges($path, '{DAV:}share');
}
foreach ($sharees as $sharee) {
// We're going to attempt to get a local principal uri for a share
// href by emitting the getPrincipalByUri event.
$principal = null;
$this->server->emit('getPrincipalByUri', [$sharee->href, &$principal]);
$sharee->principal = $principal;
}
$node->updateInvites($sharees);
}
/**
* This event is triggered when properties are requested for nodes.
*
* This allows us to inject any sharings-specific properties.
*/
public function propFind(PropFind $propFind, INode $node)
{
if ($node instanceof ISharedNode) {
$propFind->handle('{DAV:}share-access', function () use ($node) {
return new Property\ShareAccess($node->getShareAccess());
});
$propFind->handle('{DAV:}invite', function () use ($node) {
return new Property\Invite($node->getInvites());
});
$propFind->handle('{DAV:}share-resource-uri', function () use ($node) {
return new Property\Href($node->getShareResourceUri());
});
}
}
/**
* We intercept this to handle POST requests on shared resources.
*
* @return bool|null
*/
public function httpPost(RequestInterface $request, ResponseInterface $response)
{
$path = $request->getPath();
$contentType = $request->getHeader('Content-Type');
if (null === $contentType) {
return;
}
// We're only interested in the davsharing content type.
if (false === strpos($contentType, 'application/davsharing+xml')) {
return;
}
$message = $this->server->xml->parse(
$request->getBody(),
$request->getUrl(),
$documentType
);
switch ($documentType) {
case '{DAV:}share-resource':
$this->shareResource($path, $message->sharees);
$response->setStatus(200);
// Adding this because sending a response body may cause issues,
// and I wanted some type of indicator the response was handled.
$response->setHeader('X-Sabre-Status', 'everything-went-well');
// Breaking the event chain
return false;
default:
throw new BadRequest('Unexpected document type: '.$documentType.' for this Content-Type');
}
}
/**
* This method is triggered whenever a subsystem reqeuests the privileges
* hat are supported on a particular node.
*
* We need to add a number of privileges for scheduling purposes.
*/
public function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet)
{
if ($node instanceof ISharedNode) {
$supportedPrivilegeSet['{DAV:}share'] = [
'abstract' => false,
'aggregates' => [],
];
}
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
public function getPluginInfo()
{
return [
'name' => $this->getPluginName(),
'description' => 'This plugin implements WebDAV resource sharing',
'link' => 'https://github.com/evert/webdav-sharing',
];
}
/**
* This method is used to generate HTML output for the
* DAV\Browser\Plugin.
*
* @param string $output
* @param string $path
*
* @return bool|null
*/
public function htmlActionsPanel(INode $node, &$output, $path)
{
if (!$node instanceof ISharedNode) {
return;
}
$aclPlugin = $this->server->getPlugin('acl');
if ($aclPlugin) {
if (!$aclPlugin->checkPrivileges($path, '{DAV:}share', \Sabre\DAVACL\Plugin::R_PARENT, false)) {
// Sharing is not permitted, we will not draw this interface.
return;
}
}
$output .= '<tr><td colspan="2"><form method="post" action="">
<h3>Share this resource</h3>
<input type="hidden" name="sabreAction" value="share" />
<label>Share with (uri):</label> <input type="text" name="href" placeholder="mailto:user@example.org"/><br />
<label>Access</label>
<select name="access">
<option value="readwrite">Read-write</option>
<option value="read">Read-only</option>
<option value="no-access">Revoke access</option>
</select><br />
<input type="submit" value="share" />
</form>
</td></tr>';
}
/**
* This method is triggered for POST actions generated by the browser
* plugin.
*
* @param string $path
* @param string $action
* @param array $postVars
*/
public function browserPostAction($path, $action, $postVars)
{
if ('share' !== $action) {
return;
}
if (empty($postVars['href'])) {
throw new BadRequest('The "href" POST parameter is required');
}
if (empty($postVars['access'])) {
throw new BadRequest('The "access" POST parameter is required');
}
$accessMap = [
'readwrite' => self::ACCESS_READWRITE,
'read' => self::ACCESS_READ,
'no-access' => self::ACCESS_NOACCESS,
];
if (!isset($accessMap[$postVars['access']])) {
throw new BadRequest('The "access" POST must be readwrite, read or no-access');
}
$sharee = new Sharee([
'href' => $postVars['href'],
'access' => $accessMap[$postVars['access']],
]);
$this->shareResource(
$path,
[$sharee]
);
return false;
}
}

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
use InvalidArgumentException;
/**
* SimpleCollection.
*
* The SimpleCollection is used to quickly setup static directory structures.
* Just create the object with a proper name, and add children to use it.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class SimpleCollection extends Collection
{
/**
* List of childnodes.
*
* @var INode[]
*/
protected $children = [];
/**
* Name of this resource.
*
* @var string
*/
protected $name;
/**
* Creates this node.
*
* The name of the node must be passed, child nodes can also be passed.
* This nodes must be instances of INode
*
* @param string $name
* @param INode[] $children
*/
public function __construct($name, array $children = [])
{
$this->name = $name;
foreach ($children as $key => $child) {
if (is_string($child)) {
$child = new SimpleFile($key, $child);
} elseif (is_array($child)) {
$child = new self($key, $child);
} elseif (!$child instanceof INode) {
throw new InvalidArgumentException('Children must be specified as strings, arrays or instances of Sabre\DAV\INode');
}
$this->addChild($child);
}
}
/**
* Adds a new childnode to this collection.
*/
public function addChild(INode $child)
{
$this->children[$child->getName()] = $child;
}
/**
* Returns the name of the collection.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Returns a child object, by its name.
*
* This method makes use of the getChildren method to grab all the child nodes, and compares the name.
* Generally its wise to override this, as this can usually be optimized
*
* This method must throw Sabre\DAV\Exception\NotFound if the node does not
* exist.
*
* @param string $name
*
* @throws Exception\NotFound
*
* @return INode
*/
public function getChild($name)
{
if (isset($this->children[$name])) {
return $this->children[$name];
}
throw new Exception\NotFound('File not found: '.$name.' in \''.$this->getName().'\'');
}
/**
* Returns a list of children for this collection.
*
* @return INode[]
*/
public function getChildren()
{
return array_values($this->children);
}
}

118
vendor/sabre/dav/lib/DAV/SimpleFile.php vendored Normal file
View File

@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* SimpleFile.
*
* The 'SimpleFile' class is used to easily add read-only immutable files to
* the directory structure. One usecase would be to add a 'readme.txt' to a
* root of a webserver with some standard content.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class SimpleFile extends File
{
/**
* File contents.
*
* @var string
*/
protected $contents = [];
/**
* Name of this resource.
*
* @var string
*/
protected $name;
/**
* A mimetype, such as 'text/plain' or 'text/html'.
*
* @var string
*/
protected $mimeType;
/**
* Creates this node.
*
* The name of the node must be passed, as well as the contents of the
* file.
*
* @param string $name
* @param string $contents
* @param string|null $mimeType
*/
public function __construct($name, $contents, $mimeType = null)
{
$this->name = $name;
$this->contents = $contents;
$this->mimeType = $mimeType;
}
/**
* Returns the node name for this file.
*
* This name is used to construct the url.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Returns the data.
*
* This method may either return a string or a readable stream resource
*
* @return mixed
*/
public function get()
{
return $this->contents;
}
/**
* Returns the size of the file, in bytes.
*
* @return int
*/
public function getSize()
{
return strlen($this->contents);
}
/**
* Returns the ETag for a file.
*
* An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
* The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
*
* Return null if the ETag can not effectively be determined
*
* @return string
*/
public function getETag()
{
return '"'.sha1($this->contents).'"';
}
/**
* Returns the mime-type for a file.
*
* If null is returned, we'll assume application/octet-stream
*
* @return string
*/
public function getContentType()
{
return $this->mimeType;
}
}

88
vendor/sabre/dav/lib/DAV/StringUtil.php vendored Normal file
View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* String utility.
*
* This class is mainly used to implement the 'text-match' filter, used by both
* the CalDAV calendar-query REPORT, and CardDAV addressbook-query REPORT.
* Because they both need it, it was decided to put it in Sabre\DAV instead.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class StringUtil
{
/**
* Checks if a needle occurs in a haystack ;).
*
* @param string $haystack
* @param string $needle
* @param string $collation
* @param string $matchType
*
* @return bool
*/
public static function textMatch($haystack, $needle, $collation, $matchType = 'contains')
{
switch ($collation) {
case 'i;ascii-casemap':
// default strtolower takes locale into consideration
// we don't want this.
$haystack = str_replace(range('a', 'z'), range('A', 'Z'), $haystack);
$needle = str_replace(range('a', 'z'), range('A', 'Z'), $needle);
break;
case 'i;octet':
// Do nothing
break;
case 'i;unicode-casemap':
$haystack = mb_strtoupper($haystack, 'UTF-8');
$needle = mb_strtoupper($needle, 'UTF-8');
break;
default:
throw new Exception\BadRequest('Collation type: '.$collation.' is not supported');
}
switch ($matchType) {
case 'contains':
return false !== strpos($haystack, $needle);
case 'equals':
return $haystack === $needle;
case 'starts-with':
return 0 === strpos($haystack, $needle);
case 'ends-with':
return strrpos($haystack, $needle) === strlen($haystack) - strlen($needle);
default:
throw new Exception\BadRequest('Match-type: '.$matchType.' is not supported');
}
}
/**
* This method takes an input string, checks if it's not valid UTF-8 and
* attempts to convert it to UTF-8 if it's not.
*
* Note that currently this can only convert ISO-8859-1 to UTF-8 (latin-1),
* anything else will likely fail.
*
* @param string $input
*
* @return string
*/
public static function ensureUTF8($input)
{
$encoding = mb_detect_encoding($input, ['UTF-8', 'ISO-8859-1'], true);
if ('ISO-8859-1' === $encoding) {
return utf8_encode($input);
} else {
return $input;
}
}
}

View File

@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Sync;
use Sabre\DAV;
/**
* If a class extends ISyncCollection, it supports WebDAV-sync.
*
* You are responsible for maintaining a changelist for this collection. This
* means that if any child nodes in this collection was created, modified or
* deleted in any way, you should maintain an updated changelist.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
interface ISyncCollection extends DAV\ICollection
{
/**
* This method returns the current sync-token for this collection.
* This can be any string.
*
* If null is returned from this function, the plugin assumes there's no
* sync information available.
*
* @return string|null
*/
public function getSyncToken();
/**
* The getChanges method returns all the changes that have happened, since
* the specified syncToken and the current collection.
*
* This function should return an array, such as the following:
*
* [
* 'syncToken' => 'The current synctoken',
* 'added' => [
* 'new.txt',
* ],
* 'modified' => [
* 'modified.txt',
* ],
* 'deleted' => array(
* 'foo.php.bak',
* 'old.txt'
* )
* ];
*
* The syncToken property should reflect the *current* syncToken of the
* collection, as reported getSyncToken(). This is needed here too, to
* ensure the operation is atomic.
*
* If the syncToken is specified as null, this is an initial sync, and all
* members should be reported.
*
* The modified property is an array of nodenames that have changed since
* the last token.
*
* The deleted property is an array with nodenames, that have been deleted
* from collection.
*
* The second argument is basically the 'depth' of the report. If it's 1,
* you only have to report changes that happened only directly in immediate
* descendants. If it's 2, it should also include changes from the nodes
* below the child collections. (grandchildren)
*
* The third (optional) argument allows a client to specify how many
* results should be returned at most. If the limit is not specified, it
* should be treated as infinite.
*
* If the limit (infinite or not) is higher than you're willing to return,
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
*
* If the syncToken is expired (due to data cleanup) or unknown, you must
* return null.
*
* The limit is 'suggestive'. You are free to ignore it.
*
* @param string $syncToken
* @param int $syncLevel
* @param int $limit
*
* @return array
*/
public function getChanges($syncToken, $syncLevel, $limit = null);
}

240
vendor/sabre/dav/lib/DAV/Sync/Plugin.php vendored Normal file
View File

@ -0,0 +1,240 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Sync;
use Sabre\DAV;
use Sabre\DAV\Xml\Request\SyncCollectionReport;
use Sabre\HTTP\RequestInterface;
/**
* This plugin all WebDAV-sync capabilities to the Server.
*
* WebDAV-sync is defined by rfc6578
*
* The sync capabilities only work with collections that implement
* Sabre\DAV\Sync\ISyncCollection.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Plugin extends DAV\ServerPlugin
{
/**
* Reference to server object.
*
* @var DAV\Server
*/
protected $server;
const SYNCTOKEN_PREFIX = 'http://sabre.io/ns/sync/';
/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return 'sync';
}
/**
* Initializes the plugin.
*
* This is when the plugin registers it's hooks.
*/
public function initialize(DAV\Server $server)
{
$this->server = $server;
$server->xml->elementMap['{DAV:}sync-collection'] = 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport';
$self = $this;
$server->on('report', function ($reportName, $dom, $uri) use ($self) {
if ('{DAV:}sync-collection' === $reportName) {
$this->server->transactionType = 'report-sync-collection';
$self->syncCollection($uri, $dom);
return false;
}
});
$server->on('propFind', [$this, 'propFind']);
$server->on('validateTokens', [$this, 'validateTokens']);
}
/**
* Returns a list of reports this plugin supports.
*
* This will be used in the {DAV:}supported-report-set property.
* Note that you still need to subscribe to the 'report' event to actually
* implement them
*
* @param string $uri
*
* @return array
*/
public function getSupportedReportSet($uri)
{
$node = $this->server->tree->getNodeForPath($uri);
if ($node instanceof ISyncCollection && $node->getSyncToken()) {
return [
'{DAV:}sync-collection',
];
}
return [];
}
/**
* This method handles the {DAV:}sync-collection HTTP REPORT.
*
* @param string $uri
*/
public function syncCollection($uri, SyncCollectionReport $report)
{
// Getting the data
$node = $this->server->tree->getNodeForPath($uri);
if (!$node instanceof ISyncCollection) {
throw new DAV\Exception\ReportNotSupported('The {DAV:}sync-collection REPORT is not supported on this url.');
}
$token = $node->getSyncToken();
if (!$token) {
throw new DAV\Exception\ReportNotSupported('No sync information is available at this node');
}
$syncToken = $report->syncToken;
if (!is_null($syncToken)) {
// Sync-token must start with our prefix
if (self::SYNCTOKEN_PREFIX !== substr($syncToken, 0, strlen(self::SYNCTOKEN_PREFIX))) {
throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token');
}
$syncToken = substr($syncToken, strlen(self::SYNCTOKEN_PREFIX));
}
$changeInfo = $node->getChanges($syncToken, $report->syncLevel, $report->limit);
if (is_null($changeInfo)) {
throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token');
}
// Encoding the response
$this->sendSyncCollectionResponse(
$changeInfo['syncToken'],
$uri,
$changeInfo['added'],
$changeInfo['modified'],
$changeInfo['deleted'],
$report->properties
);
}
/**
* Sends the response to a sync-collection request.
*
* @param string $syncToken
* @param string $collectionUrl
*/
protected function sendSyncCollectionResponse($syncToken, $collectionUrl, array $added, array $modified, array $deleted, array $properties)
{
$fullPaths = [];
// Pre-fetching children, if this is possible.
foreach (array_merge($added, $modified) as $item) {
$fullPath = $collectionUrl.'/'.$item;
$fullPaths[] = $fullPath;
}
$responses = [];
foreach ($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) {
// The 'Property_Response' class is responsible for generating a
// single {DAV:}response xml element.
$responses[] = new DAV\Xml\Element\Response($fullPath, $props);
}
// Deleted items also show up as 'responses'. They have no properties,
// and a single {DAV:}status element set as 'HTTP/1.1 404 Not Found'.
foreach ($deleted as $item) {
$fullPath = $collectionUrl.'/'.$item;
$responses[] = new DAV\Xml\Element\Response($fullPath, [], 404);
}
$multiStatus = new DAV\Xml\Response\MultiStatus($responses, self::SYNCTOKEN_PREFIX.$syncToken);
$this->server->httpResponse->setStatus(207);
$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
$this->server->httpResponse->setBody(
$this->server->xml->write('{DAV:}multistatus', $multiStatus, $this->server->getBaseUri())
);
}
/**
* This method is triggered whenever properties are requested for a node.
* We intercept this to see if we must return a {DAV:}sync-token.
*/
public function propFind(DAV\PropFind $propFind, DAV\INode $node)
{
$propFind->handle('{DAV:}sync-token', function () use ($node) {
if (!$node instanceof ISyncCollection || !$token = $node->getSyncToken()) {
return;
}
return self::SYNCTOKEN_PREFIX.$token;
});
}
/**
* The validateTokens event is triggered before every request.
*
* It's a moment where this plugin can check all the supplied lock tokens
* in the If: header, and check if they are valid.
*
* @param array $conditions
*/
public function validateTokens(RequestInterface $request, &$conditions)
{
foreach ($conditions as $kk => $condition) {
foreach ($condition['tokens'] as $ii => $token) {
// Sync-tokens must always start with our designated prefix.
if (self::SYNCTOKEN_PREFIX !== substr($token['token'], 0, strlen(self::SYNCTOKEN_PREFIX))) {
continue;
}
// Checking if the token is a match.
$node = $this->server->tree->getNodeForPath($condition['uri']);
if (
$node instanceof ISyncCollection &&
$node->getSyncToken() == substr($token['token'], strlen(self::SYNCTOKEN_PREFIX))
) {
$conditions[$kk]['tokens'][$ii]['validToken'] = true;
}
}
}
}
/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
public function getPluginInfo()
{
return [
'name' => $this->getPluginName(),
'description' => 'Adds support for WebDAV Collection Sync (rfc6578)',
'link' => 'http://sabre.io/dav/sync/',
];
}
}

View File

@ -0,0 +1,298 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\Uri;
/**
* Temporary File Filter Plugin.
*
* The purpose of this filter is to intercept some of the garbage files
* operation systems and applications tend to generate when mounting
* a WebDAV share as a disk.
*
* It will intercept these files and place them in a separate directory.
* these files are not deleted automatically, so it is advisable to
* delete these after they are not accessed for 24 hours.
*
* Currently it supports:
* * OS/X style resource forks and .DS_Store
* * desktop.ini and Thumbs.db (windows)
* * .*.swp (vim temporary files)
* * .dat.* (smultron temporary files)
*
* Additional patterns can be added, by adding on to the
* temporaryFilePatterns property.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class TemporaryFileFilterPlugin extends ServerPlugin
{
/**
* This is the list of patterns we intercept.
* If new patterns are added, they must be valid patterns for preg_match.
*
* @var array
*/
public $temporaryFilePatterns = [
'/^\._(.*)$/', // OS/X resource forks
'/^.DS_Store$/', // OS/X custom folder settings
'/^desktop.ini$/', // Windows custom folder settings
'/^Thumbs.db$/', // Windows thumbnail cache
'/^.(.*).swp$/', // ViM temporary files
'/^\.dat(.*)$/', // Smultron seems to create these
'/^~lock.(.*)#$/', // Windows 7 lockfiles
];
/**
* A reference to the main Server class.
*
* @var \Sabre\DAV\Server
*/
protected $server;
/**
* This is the directory where this plugin
* will store it's files.
*
* @var string
*/
private $dataDir;
/**
* Creates the plugin.
*
* Make sure you specify a directory for your files. If you don't, we
* will use PHP's directory for session-storage instead, and you might
* not want that.
*
* @param string|null $dataDir
*/
public function __construct($dataDir = null)
{
if (!$dataDir) {
$dataDir = ini_get('session.save_path').'/sabredav/';
}
if (!is_dir($dataDir)) {
mkdir($dataDir);
}
$this->dataDir = $dataDir;
}
/**
* Initialize the plugin.
*
* This is called automatically be the Server class after this plugin is
* added with Sabre\DAV\Server::addPlugin()
*/
public function initialize(Server $server)
{
$this->server = $server;
$server->on('beforeMethod:*', [$this, 'beforeMethod']);
$server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
}
/**
* This method is called before any HTTP method handler.
*
* This method intercepts any GET, DELETE, PUT and PROPFIND calls to
* filenames that are known to match the 'temporary file' regex.
*
* @return bool
*/
public function beforeMethod(RequestInterface $request, ResponseInterface $response)
{
if (!$tempLocation = $this->isTempFile($request->getPath())) {
return;
}
switch ($request->getMethod()) {
case 'GET':
return $this->httpGet($request, $response, $tempLocation);
case 'PUT':
return $this->httpPut($request, $response, $tempLocation);
case 'PROPFIND':
return $this->httpPropfind($request, $response, $tempLocation);
case 'DELETE':
return $this->httpDelete($request, $response, $tempLocation);
}
return;
}
/**
* This method is invoked if some subsystem creates a new file.
*
* This is used to deal with HTTP LOCK requests which create a new
* file.
*
* @param string $uri
* @param resource $data
* @param bool $modified should be set to true, if this event handler
* changed &$data
*
* @return bool
*/
public function beforeCreateFile($uri, $data, ICollection $parent, $modified)
{
if ($tempPath = $this->isTempFile($uri)) {
$hR = $this->server->httpResponse;
$hR->setHeader('X-Sabre-Temp', 'true');
file_put_contents($tempPath, $data);
return false;
}
return;
}
/**
* This method will check if the url matches the temporary file pattern
* if it does, it will return an path based on $this->dataDir for the
* temporary file storage.
*
* @param string $path
*
* @return bool|string
*/
protected function isTempFile($path)
{
// We're only interested in the basename.
list(, $tempPath) = Uri\split($path);
if (null === $tempPath) {
return false;
}
foreach ($this->temporaryFilePatterns as $tempFile) {
if (preg_match($tempFile, $tempPath)) {
return $this->getDataDir().'/sabredav_'.md5($path).'.tempfile';
}
}
return false;
}
/**
* This method handles the GET method for temporary files.
* If the file doesn't exist, it will return false which will kick in
* the regular system for the GET method.
*
* @param string $tempLocation
*
* @return bool
*/
public function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation)
{
if (!file_exists($tempLocation)) {
return;
}
$hR->setHeader('Content-Type', 'application/octet-stream');
$hR->setHeader('Content-Length', filesize($tempLocation));
$hR->setHeader('X-Sabre-Temp', 'true');
$hR->setStatus(200);
$hR->setBody(fopen($tempLocation, 'r'));
return false;
}
/**
* This method handles the PUT method.
*
* @param string $tempLocation
*
* @return bool
*/
public function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation)
{
$hR->setHeader('X-Sabre-Temp', 'true');
$newFile = !file_exists($tempLocation);
if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) {
throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied');
}
file_put_contents($tempLocation, $this->server->httpRequest->getBody());
$hR->setStatus($newFile ? 201 : 200);
return false;
}
/**
* This method handles the DELETE method.
*
* If the file didn't exist, it will return false, which will make the
* standard HTTP DELETE handler kick in.
*
* @param string $tempLocation
*
* @return bool
*/
public function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation)
{
if (!file_exists($tempLocation)) {
return;
}
unlink($tempLocation);
$hR->setHeader('X-Sabre-Temp', 'true');
$hR->setStatus(204);
return false;
}
/**
* This method handles the PROPFIND method.
*
* It's a very lazy method, it won't bother checking the request body
* for which properties were requested, and just sends back a default
* set of properties.
*
* @param string $tempLocation
*
* @return bool
*/
public function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation)
{
if (!file_exists($tempLocation)) {
return;
}
$hR->setHeader('X-Sabre-Temp', 'true');
$hR->setStatus(207);
$hR->setHeader('Content-Type', 'application/xml; charset=utf-8');
$properties = [
'href' => $request->getPath(),
200 => [
'{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)),
'{DAV:}getcontentlength' => filesize($tempLocation),
'{DAV:}resourcetype' => new Xml\Property\ResourceType(null),
'{'.Server::NS_SABREDAV.'}tempFile' => true,
],
];
$data = $this->server->generateMultiStatus([$properties]);
$hR->setBody($data);
return false;
}
/**
* This method returns the directory where the temporary files should be stored.
*
* @return string
*/
protected function getDataDir()
{
return $this->dataDir;
}
}

324
vendor/sabre/dav/lib/DAV/Tree.php vendored Normal file
View File

@ -0,0 +1,324 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
use Sabre\Uri;
/**
* The tree object is responsible for basic tree operations.
*
* It allows for fetching nodes by path, facilitates deleting, copying and
* moving.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Tree
{
/**
* The root node.
*
* @var ICollection
*/
protected $rootNode;
/**
* This is the node cache. Accessed nodes are stored here.
* Arrays keys are path names, values are the actual nodes.
*
* @var array
*/
protected $cache = [];
/**
* Creates the object.
*
* This method expects the rootObject to be passed as a parameter
*/
public function __construct(ICollection $rootNode)
{
$this->rootNode = $rootNode;
}
/**
* Returns the INode object for the requested path.
*
* @param string $path
*
* @return INode
*/
public function getNodeForPath($path)
{
$path = trim($path, '/');
if (isset($this->cache[$path])) {
return $this->cache[$path];
}
// Is it the root node?
if (!strlen($path)) {
return $this->rootNode;
}
// Attempting to fetch its parent
list($parentName, $baseName) = Uri\split($path);
// If there was no parent, we must simply ask it from the root node.
if ('' === $parentName) {
$node = $this->rootNode->getChild($baseName);
} else {
// Otherwise, we recursively grab the parent and ask him/her.
$parent = $this->getNodeForPath($parentName);
if (!($parent instanceof ICollection)) {
throw new Exception\NotFound('Could not find node at path: '.$path);
}
$node = $parent->getChild($baseName);
}
$this->cache[$path] = $node;
return $node;
}
/**
* This function allows you to check if a node exists.
*
* Implementors of this class should override this method to make
* it cheaper.
*
* @param string $path
*
* @return bool
*/
public function nodeExists($path)
{
try {
// The root always exists
if ('' === $path) {
return true;
}
list($parent, $base) = Uri\split($path);
$parentNode = $this->getNodeForPath($parent);
if (!$parentNode instanceof ICollection) {
return false;
}
return $parentNode->childExists($base);
} catch (Exception\NotFound $e) {
return false;
}
}
/**
* Copies a file from path to another.
*
* @param string $sourcePath The source location
* @param string $destinationPath The full destination path
*/
public function copy($sourcePath, $destinationPath)
{
$sourceNode = $this->getNodeForPath($sourcePath);
// grab the dirname and basename components
list($destinationDir, $destinationName) = Uri\split($destinationPath);
$destinationParent = $this->getNodeForPath($destinationDir);
// Check if the target can handle the copy itself. If not, we do it ourselves.
if (!$destinationParent instanceof ICopyTarget || !$destinationParent->copyInto($destinationName, $sourcePath, $sourceNode)) {
$this->copyNode($sourceNode, $destinationParent, $destinationName);
}
$this->markDirty($destinationDir);
}
/**
* Moves a file from one location to another.
*
* @param string $sourcePath The path to the file which should be moved
* @param string $destinationPath The full destination path, so not just the destination parent node
*
* @return int
*/
public function move($sourcePath, $destinationPath)
{
list($sourceDir) = Uri\split($sourcePath);
list($destinationDir, $destinationName) = Uri\split($destinationPath);
if ($sourceDir === $destinationDir) {
// If this is a 'local' rename, it means we can just trigger a rename.
$sourceNode = $this->getNodeForPath($sourcePath);
$sourceNode->setName($destinationName);
} else {
$newParentNode = $this->getNodeForPath($destinationDir);
$moveSuccess = false;
if ($newParentNode instanceof IMoveTarget) {
// The target collection may be able to handle the move
$sourceNode = $this->getNodeForPath($sourcePath);
$moveSuccess = $newParentNode->moveInto($destinationName, $sourcePath, $sourceNode);
}
if (!$moveSuccess) {
$this->copy($sourcePath, $destinationPath);
$this->getNodeForPath($sourcePath)->delete();
}
}
$this->markDirty($sourceDir);
$this->markDirty($destinationDir);
}
/**
* Deletes a node from the tree.
*
* @param string $path
*/
public function delete($path)
{
$node = $this->getNodeForPath($path);
$node->delete();
list($parent) = Uri\split($path);
$this->markDirty($parent);
}
/**
* Returns a list of childnodes for a given path.
*
* @param string $path
*
* @return \Traversable
*/
public function getChildren($path)
{
$node = $this->getNodeForPath($path);
$basePath = trim($path, '/');
if ('' !== $basePath) {
$basePath .= '/';
}
foreach ($node->getChildren() as $child) {
$this->cache[$basePath.$child->getName()] = $child;
yield $child;
}
}
/**
* This method is called with every tree update.
*
* Examples of tree updates are:
* * node deletions
* * node creations
* * copy
* * move
* * renaming nodes
*
* If Tree classes implement a form of caching, this will allow
* them to make sure caches will be expired.
*
* If a path is passed, it is assumed that the entire subtree is dirty
*
* @param string $path
*/
public function markDirty($path)
{
// We don't care enough about sub-paths
// flushing the entire cache
$path = trim($path, '/');
foreach ($this->cache as $nodePath => $node) {
if ('' === $path || $nodePath == $path || 0 === strpos($nodePath, $path.'/')) {
unset($this->cache[$nodePath]);
}
}
}
/**
* This method tells the tree system to pre-fetch and cache a list of
* children of a single parent.
*
* There are a bunch of operations in the WebDAV stack that request many
* children (based on uris), and sometimes fetching many at once can
* optimize this.
*
* This method returns an array with the found nodes. It's keys are the
* original paths. The result may be out of order.
*
* @param array $paths list of nodes that must be fetched
*
* @return array
*/
public function getMultipleNodes($paths)
{
// Finding common parents
$parents = [];
foreach ($paths as $path) {
list($parent, $node) = Uri\split($path);
if (!isset($parents[$parent])) {
$parents[$parent] = [$node];
} else {
$parents[$parent][] = $node;
}
}
$result = [];
foreach ($parents as $parent => $children) {
$parentNode = $this->getNodeForPath($parent);
if ($parentNode instanceof IMultiGet) {
foreach ($parentNode->getMultipleChildren($children) as $childNode) {
$fullPath = $parent.'/'.$childNode->getName();
$result[$fullPath] = $childNode;
$this->cache[$fullPath] = $childNode;
}
} else {
foreach ($children as $child) {
$fullPath = $parent.'/'.$child;
$result[$fullPath] = $this->getNodeForPath($fullPath);
}
}
}
return $result;
}
/**
* copyNode.
*
* @param string $destinationName
*/
protected function copyNode(INode $source, ICollection $destinationParent, $destinationName = null)
{
if ('' === (string) $destinationName) {
$destinationName = $source->getName();
}
if ($source instanceof IFile) {
$data = $source->get();
// If the body was a string, we need to convert it to a stream
if (is_string($data)) {
$stream = fopen('php://temp', 'r+');
fwrite($stream, $data);
rewind($stream);
$data = $stream;
}
$destinationParent->createFile($destinationName, $data);
$destination = $destinationParent->getChild($destinationName);
} elseif ($source instanceof ICollection) {
$destinationParent->createDirectory($destinationName);
$destination = $destinationParent->getChild($destinationName);
foreach ($source->getChildren() as $child) {
$this->copyNode($child, $destination);
}
}
if ($source instanceof IProperties && $destination instanceof IProperties) {
$props = $source->getProperties([]);
$propPatch = new PropPatch($props);
$destination->propPatch($propPatch);
$propPatch->commit();
}
}
}

66
vendor/sabre/dav/lib/DAV/UUIDUtil.php vendored Normal file
View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* UUID Utility.
*
* This class has static methods to generate and validate UUID's.
* UUIDs are used a decent amount within various *DAV standards, so it made
* sense to include it.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class UUIDUtil
{
/**
* Returns a pseudo-random v4 UUID.
*
* This function is based on a comment by Andrew Moore on php.net
*
* @see http://www.php.net/manual/en/function.uniqid.php#94959
*
* @return string
*/
public static function getUUID()
{
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
// 16 bits for "time_mid"
mt_rand(0, 0xffff),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand(0, 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand(0, 0x3fff) | 0x8000,
// 48 bits for "node"
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
/**
* Checks if a string is a valid UUID.
*
* @param string $uuid
*
* @return bool
*/
public static function validateUUID($uuid)
{
return 0 !== preg_match(
'/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
$uuid
);
}
}

20
vendor/sabre/dav/lib/DAV/Version.php vendored Normal file
View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV;
/**
* This class contains the SabreDAV version constants.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Version
{
/**
* Full version number.
*/
public const VERSION = '4.1.0';
}

View File

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Xml\Element;
use Sabre\DAV\Xml\Property\Complex;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;
/**
* This class is responsible for decoding the {DAV:}prop element as it appears
* in {DAV:}property-update.
*
* This class doesn't return an instance of itself. It just returns a
* key->value array.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Prop implements XmlDeserializable
{
/**
* The deserialize method is called during xml parsing.
*
* This method is called statically, this is because in theory this method
* may be used as a type of constructor, or factory method.
*
* Often you want to return an instance of the current class, but you are
* free to return other data as well.
*
* You are responsible for advancing the reader to the next element. Not
* doing anything will result in a never-ending loop.
*
* If you just want to skip parsing for this element altogether, you can
* just call $reader->next();
*
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
* the next element.
*
* @return mixed
*/
public static function xmlDeserialize(Reader $reader)
{
// If there's no children, we don't do anything.
if ($reader->isEmptyElement) {
$reader->next();
return [];
}
$values = [];
$reader->read();
do {
if (Reader::ELEMENT === $reader->nodeType) {
$clark = $reader->getClark();
$values[$clark] = self::parseCurrentElement($reader)['value'];
} else {
$reader->read();
}
} while (Reader::END_ELEMENT !== $reader->nodeType);
$reader->read();
return $values;
}
/**
* This function behaves similar to Sabre\Xml\Reader::parseCurrentElement,
* but instead of creating deep xml array structures, it will turn any
* top-level element it doesn't recognize into either a string, or an
* XmlFragment class.
*
* This method returns arn array with 2 properties:
* * name - A clark-notation XML element name.
* * value - The parsed value.
*
* @return array
*/
private static function parseCurrentElement(Reader $reader)
{
$name = $reader->getClark();
if (array_key_exists($name, $reader->elementMap)) {
$deserializer = $reader->elementMap[$name];
if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) {
$value = call_user_func([$deserializer, 'xmlDeserialize'], $reader);
} elseif (is_callable($deserializer)) {
$value = call_user_func($deserializer, $reader);
} else {
$type = gettype($deserializer);
if ('string' === $type) {
$type .= ' ('.$deserializer.')';
} elseif ('object' === $type) {
$type .= ' ('.get_class($deserializer).')';
}
throw new \LogicException('Could not use this type as a deserializer: '.$type);
}
} else {
$value = Complex::xmlDeserialize($reader);
}
return [
'name' => $name,
'value' => $value,
];
}
}

View File

@ -0,0 +1,237 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Xml\Element;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
/**
* WebDAV {DAV:}response parser.
*
* This class parses the {DAV:}response element, as defined in:
*
* https://tools.ietf.org/html/rfc4918#section-14.24
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Response implements Element
{
/**
* Url for the response.
*
* @var string
*/
protected $href;
/**
* Propertylist, ordered by HTTP status code.
*
* @var array
*/
protected $responseProperties;
/**
* The HTTP status for an entire response.
*
* This is currently only used in WebDAV-Sync
*
* @var string
*/
protected $httpStatus;
/**
* The href argument is a url relative to the root of the server. This
* class will calculate the full path.
*
* The responseProperties argument is a list of properties
* within an array with keys representing HTTP status codes
*
* Besides specific properties, the entire {DAV:}response element may also
* have a http status code.
* In most cases you don't need it.
*
* This is currently used by the Sync extension to indicate that a node is
* deleted.
*
* @param string $href
* @param string $httpStatus
*/
public function __construct($href, array $responseProperties, $httpStatus = null)
{
$this->href = $href;
$this->responseProperties = $responseProperties;
$this->httpStatus = $httpStatus;
}
/**
* Returns the url.
*
* @return string
*/
public function getHref()
{
return $this->href;
}
/**
* Returns the httpStatus value.
*
* @return string
*/
public function getHttpStatus()
{
return $this->httpStatus;
}
/**
* Returns the property list.
*
* @return array
*/
public function getResponseProperties()
{
return $this->responseProperties;
}
/**
* The serialize method is called during xml writing.
*
* It should use the $writer argument to encode this object into XML.
*
* Important note: it is not needed to create the parent element. The
* parent element is already created, and we only have to worry about
* attributes, child elements and text (if any).
*
* Important note 2: If you are writing any new elements, you are also
* responsible for closing them.
*/
public function xmlSerialize(Writer $writer)
{
if ($status = $this->getHTTPStatus()) {
$writer->writeElement('{DAV:}status', 'HTTP/1.1 '.$status.' '.\Sabre\HTTP\Response::$statusCodes[$status]);
}
$writer->writeElement('{DAV:}href', $writer->contextUri.\Sabre\HTTP\encodePath($this->getHref()));
$empty = true;
foreach ($this->getResponseProperties() as $status => $properties) {
// Skipping empty lists
if (!$properties || (!ctype_digit($status) && !is_int($status))) {
continue;
}
$empty = false;
$writer->startElement('{DAV:}propstat');
$writer->writeElement('{DAV:}prop', $properties);
$writer->writeElement('{DAV:}status', 'HTTP/1.1 '.$status.' '.\Sabre\HTTP\Response::$statusCodes[$status]);
$writer->endElement(); // {DAV:}propstat
}
if ($empty) {
/*
* The WebDAV spec _requires_ at least one DAV:propstat to appear for
* every DAV:response. In some circumstances however, there are no
* properties to encode.
*
* In those cases we MUST specify at least one DAV:propstat anyway, with
* no properties.
*/
$writer->writeElement('{DAV:}propstat', [
'{DAV:}prop' => [],
'{DAV:}status' => 'HTTP/1.1 418 '.\Sabre\HTTP\Response::$statusCodes[418],
]);
}
}
/**
* The deserialize method is called during xml parsing.
*
* This method is called statically, this is because in theory this method
* may be used as a type of constructor, or factory method.
*
* Often you want to return an instance of the current class, but you are
* free to return other data as well.
*
* You are responsible for advancing the reader to the next element. Not
* doing anything will result in a never-ending loop.
*
* If you just want to skip parsing for this element altogether, you can
* just call $reader->next();
*
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
* the next element.
*
* @return mixed
*/
public static function xmlDeserialize(Reader $reader)
{
$reader->pushContext();
$reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue';
// We are overriding the parser for {DAV:}prop. This deserializer is
// almost identical to the one for Sabre\Xml\Element\KeyValue.
//
// The difference is that if there are any child-elements inside of
// {DAV:}prop, that have no value, normally any deserializers are
// called. But we don't want this, because a singular element without
// child-elements implies 'no value' in {DAV:}prop, so we want to skip
// deserializers and just set null for those.
$reader->elementMap['{DAV:}prop'] = function (Reader $reader) {
if ($reader->isEmptyElement) {
$reader->next();
return [];
}
$values = [];
$reader->read();
do {
if (Reader::ELEMENT === $reader->nodeType) {
$clark = $reader->getClark();
if ($reader->isEmptyElement) {
$values[$clark] = null;
$reader->next();
} else {
$values[$clark] = $reader->parseCurrentElement()['value'];
}
} else {
$reader->read();
}
} while (Reader::END_ELEMENT !== $reader->nodeType);
$reader->read();
return $values;
};
$elems = $reader->parseInnerTree();
$reader->popContext();
$href = null;
$propertyLists = [];
$statusCode = null;
foreach ($elems as $elem) {
switch ($elem['name']) {
case '{DAV:}href':
$href = $elem['value'];
break;
case '{DAV:}propstat':
$status = $elem['value']['{DAV:}status'];
list(, $status) = explode(' ', $status, 3);
$properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : [];
if ($properties) {
$propertyLists[$status] = $properties;
}
break;
case '{DAV:}status':
list(, $statusCode) = explode(' ', $elem['value'], 3);
break;
}
}
return new self($href, $propertyLists, $statusCode);
}
}

View File

@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Xml\Element;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Sharing\Plugin;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\Xml\Property\ShareAccess;
use Sabre\Xml\Deserializer;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
/**
* This class represents the {DAV:}sharee element.
*
* @copyright Copyright (C) fruux GmbH. (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Sharee implements Element
{
/**
* A URL. Usually a mailto: address, could also be a principal url.
* This uniquely identifies the sharee.
*
* @var string
*/
public $href;
/**
* A local principal path. The server will do its best to locate the
* principal uri based on the given uri. If we could find a local matching
* principal uri, this property will contain the value.
*
* @var string|null
*/
public $principal;
/**
* A list of WebDAV properties that describe the sharee. This might for
* example contain a {DAV:}displayname with the real name of the user.
*
* @var array
*/
public $properties = [];
/**
* Share access level. One of the Sabre\DAV\Sharing\Plugin::ACCESS
* constants.
*
* Can be one of:
*
* ACCESS_READ
* ACCESS_READWRITE
* ACCESS_SHAREDOWNER
* ACCESS_NOACCESS
*
* depending on context.
*
* @var int
*/
public $access;
/**
* When a sharee is originally invited to a share, the sharer may add
* a comment. This will be placed in this property.
*
* @var string
*/
public $comment;
/**
* The status of the invite, should be one of the
* Sabre\DAV\Sharing\Plugin::INVITE constants.
*
* @var int
*/
public $inviteStatus;
/**
* Creates the object.
*
* $properties will be used to populate all internal properties.
*/
public function __construct(array $properties = [])
{
foreach ($properties as $k => $v) {
if (property_exists($this, $k)) {
$this->$k = $v;
} else {
throw new \InvalidArgumentException('Unknown property: '.$k);
}
}
}
/**
* The xmlSerialize method is called during xml writing.
*
* Use the $writer argument to write its own xml serialization.
*
* An important note: do _not_ create a parent element. Any element
* implementing XmlSerializable should only ever write what's considered
* its 'inner xml'.
*
* The parent of the current element is responsible for writing a
* containing element.
*
* This allows serializers to be re-used for different element names.
*
* If you are opening new elements, you must also close them again.
*/
public function xmlSerialize(Writer $writer)
{
$writer->write([
new Href($this->href),
'{DAV:}prop' => $this->properties,
'{DAV:}share-access' => new ShareAccess($this->access),
]);
switch ($this->inviteStatus) {
case Plugin::INVITE_NORESPONSE:
$writer->writeElement('{DAV:}invite-noresponse');
break;
case Plugin::INVITE_ACCEPTED:
$writer->writeElement('{DAV:}invite-accepted');
break;
case Plugin::INVITE_DECLINED:
$writer->writeElement('{DAV:}invite-declined');
break;
case Plugin::INVITE_INVALID:
$writer->writeElement('{DAV:}invite-invalid');
break;
}
}
/**
* The deserialize method is called during xml parsing.
*
* This method is called statically, this is because in theory this method
* may be used as a type of constructor, or factory method.
*
* Often you want to return an instance of the current class, but you are
* free to return other data as well.
*
* You are responsible for advancing the reader to the next element. Not
* doing anything will result in a never-ending loop.
*
* If you just want to skip parsing for this element altogether, you can
* just call $reader->next();
*
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
* the next element.
*
* @return mixed
*/
public static function xmlDeserialize(Reader $reader)
{
// Temporarily override configuration
$reader->pushContext();
$reader->elementMap['{DAV:}share-access'] = 'Sabre\DAV\Xml\Property\ShareAccess';
$reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\keyValue';
$elems = Deserializer\keyValue($reader, 'DAV:');
// Restore previous configuration
$reader->popContext();
$sharee = new self();
if (!isset($elems['href'])) {
throw new BadRequest('Every {DAV:}sharee must have a {DAV:}href child-element');
}
$sharee->href = $elems['href'];
if (isset($elems['prop'])) {
$sharee->properties = $elems['prop'];
}
if (isset($elems['comment'])) {
$sharee->comment = $elems['comment'];
}
if (!isset($elems['share-access'])) {
throw new BadRequest('Every {DAV:}sharee must have a {DAV:}share-access child element');
}
$sharee->access = $elems['share-access']->getValue();
return $sharee;
}
}

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Xml\Property;
use Sabre\Xml\Element\XmlFragment;
use Sabre\Xml\Reader;
/**
* This class represents a 'complex' property that didn't have a default
* decoder.
*
* It's basically a container for an xml snippet.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class Complex extends XmlFragment
{
/**
* The deserialize method is called during xml parsing.
*
* This method is called statically, this is because in theory this method
* may be used as a type of constructor, or factory method.
*
* Often you want to return an instance of the current class, but you are
* free to return other data as well.
*
* You are responsible for advancing the reader to the next element. Not
* doing anything will result in a never-ending loop.
*
* If you just want to skip parsing for this element altogether, you can
* just call $reader->next();
*
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
* the next element.
*
* @return mixed
*/
public static function xmlDeserialize(Reader $reader)
{
$xml = $reader->readInnerXml();
if (Reader::ELEMENT === $reader->nodeType && $reader->isEmptyElement) {
// Easy!
$reader->next();
return null;
}
// Now we have a copy of the inner xml, we need to traverse it to get
// all the strings. If there's no non-string data, we just return the
// string, otherwise we return an instance of this class.
$reader->read();
$nonText = false;
$text = '';
while (true) {
switch ($reader->nodeType) {
case Reader::ELEMENT:
$nonText = true;
$reader->next();
continue 2;
case Reader::TEXT:
case Reader::CDATA:
$text .= $reader->value;
break;
case Reader::END_ELEMENT:
break 2;
}
$reader->read();
}
// Make sure we advance the cursor one step further.
$reader->read();
if ($nonText) {
$new = new self($xml);
return $new;
} else {
return $text;
}
}
}

View File

@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace Sabre\DAV\Xml\Property;
use DateTime;
use DateTimeZone;
use Sabre\HTTP;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
/**
* This property represents the {DAV:}getlastmodified property.
*
* Defined in:
* http://tools.ietf.org/html/rfc4918#section-15.7
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://sabre.io/license/ Modified BSD License
*/
class GetLastModified implements Element
{
/**
* time.
*
* @var DateTime
*/
public $time;
/**
* Constructor.
*
* @param int|DateTime $time
*/
public function __construct($time)
{
if ($time instanceof DateTime) {
$this->time = clone $time;
} else {
$this->time = new DateTime('@'.$time);
}
// Setting timezone to UTC
$this->time->setTimezone(new DateTimeZone('UTC'));
}
/**
* getTime.
*
* @return DateTime
*/
public function getTime()
{
return $this->time;
}
/**
* The serialize method is called during xml writing.
*
* It should use the $writer argument to encode this object into XML.
*
* Important note: it is not needed to create the parent element. The
* parent element is already created, and we only have to worry about
* attributes, child elements and text (if any).
*
* Important note 2: If you are writing any new elements, you are also
* responsible for closing them.
*/
public function xmlSerialize(Writer $writer)
{
$writer->write(
HTTP\toDate($this->time)
);
}
/**
* The deserialize method is called during xml parsing.
*
* This method is called statically, this is because in theory this method
* may be used as a type of constructor, or factory method.
*
* Often you want to return an instance of the current class, but you are
* free to return other data as well.
*
* Important note 2: You are responsible for advancing the reader to the
* next element. Not doing anything will result in a never-ending loop.
*
* If you just want to skip parsing for this element altogether, you can
* just call $reader->next();
*
* $reader->parseInnerTree() will parse the entire sub-tree, and advance to
* the next element.
*
* @return mixed
*/
public static function xmlDeserialize(Reader $reader)
{
return
new self(new DateTime($reader->parseInnerTree()));
}
}

Some files were not shown because too many files have changed in this diff Show More