File: //usr/local/CyberCP/public/snappymail/snappymail/v/2.38.2/app/libraries/snappymail/dav/client.php
<?php
/**
* Based on Sabre\DAV\Client
* @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
namespace SnappyMail\DAV;
class Client
{
public string $urlPath;
const
NS_DAV = 'urn:DAV',
NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav';
protected string $baseUri;
protected $HTTP;
/**
* Constructor
*
* Settings are provided through the 'settings' argument. The following
* settings are supported:
*
* * baseUri
* * userName (optional)
* * password (optional)
* * proxy (optional)
*/
function __construct(array $settings)
{
if (!isset($settings['baseUri'])) {
throw new \ValueError('A baseUri must be provided');
}
$this->baseUri = $settings['baseUri'];
$this->HTTP = \SnappyMail\HTTP\Request::factory(/*'socket'*/);
$this->HTTP->proxy = $settings['proxy'] ?? null;
if (!empty($settings['userName']) && !empty($settings['password'])) {
$this->HTTP->setAuth(3, $settings['userName'], $settings['password']);
} else {
\SnappyMail\Log::warning('DAV', 'No user credentials set');
}
$this->HTTP->max_response_kb = 0;
$this->HTTP->timeout = 15; // timeout in seconds.
$this->HTTP->max_redirects = 0;
}
/**
* Enable/disable SSL peer verification
*/
public function setVerifyPeer(bool $value) : void
{
$this->HTTP->verify_peer = $value;
}
/**
* Performs an actual HTTP request, and returns the result.
*
* If the specified url is relative, it will be expanded based on the base url.
*/
public function request(string $method, string $url = '', ?string $body = null, array $headers = array()) : \SnappyMail\HTTP\Response
{
if (!\preg_match('@^(https?:)?//@', $url)) {
// If the url starts with a slash, we must calculate the url based off
// the root of the base url.
if (\str_starts_with($url, '/')) {
$parts = \parse_url($this->baseUri);
$url = $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url;
} else {
$url = $this->baseUri . $url;
}
}
\SnappyMail\Log::debug('DAV', "{$method} {$url}" . ($body ? "\n\t" . \str_replace("\n", "\n\t", $body) : ''));
$response = $this->HTTP->doRequest($method, $url, $body, $headers);
if (301 == $response->status) {
// Like: RewriteRule ^\.well-known/carddav /nextcloud/remote.php/dav [R=301,L]
$location = $response->getRedirectLocation();
\SnappyMail\Log::info('DAV', "301 Redirect {$url} to {$location}");
$url = \preg_replace('@^(https?:)?//[^/]+[/$]@', '/', $location);
$parts = \parse_url($this->baseUri);
$url = $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url;
$response = $this->HTTP->doRequest($method, $url, $body, $headers);
}
if (300 <= $response->status) {
throw new \SnappyMail\HTTP\Exception("{$method} {$url}", $response->status, $response);
}
\SnappyMail\Log::debug('DAV', "{$response->status}: {$response->body}");
return $response;
}
/**
* 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.
*/
public function propFind(string $url, array $properties, int $depth = 0) : array
{
$body = '<?xml version="1.0"?>' . "\n" . '<d:propfind xmlns:d="DAV:"><d:prop>';
foreach ($properties as $property) {
if (!\preg_match('/^{([^}]*)}(.*)$/', $property, $match)) {
throw new \ValueError('\'' . $property . '\' is not a valid clark-notation formatted string');
}
if ('DAV:' === $match[1]) {
$body .= "<d:{$match[2]}/>";
} else {
$body .= "<x:{$match[2]} xmlns:x=\"{$match[1]}\"/>";
}
}
$body .= '</d:prop></d:propfind>';
$response = $this->request('PROPFIND', $url, $body, array(
"Depth: {$depth}",
'Content-Type: application/xml'
));
/**
* Parse the WebDAV multistatus response body
*/
$responseXML = \simplexml_load_string(
/**
* Convert all instances of the DAV: namespace to urn:DAV
*
* This is unfortunately needed, because the DAV: namespace violates the xml namespaces
* spec, and causes the DOM to throw errors
*
* This is used to map the DAV: namespace to urn:DAV. This is needed, because the DAV:
* namespace is actually a violation of the XML namespaces specification, and will cause errors
*/
\preg_replace("/xmlns(:[A-Za-z0-9_]*)?=(\"|\')DAV:(\\2)/", "xmlns\\1=\\2urn:DAV\\2", $response->body),
null, LIBXML_NOBLANKS | LIBXML_NOCDATA);
if (false === $responseXML) {
throw new \UnexpectedValueException("The passed data is not valid XML\n{$response->body}");
}
$ns = \array_search('urn:DAV', $responseXML->getNamespaces(true)) ?: 'd';
// $ns_card = \array_search('urn:ietf:params:xml:ns:carddav', $responseXML->getNamespaces(true));
$result = array();
$responseXML->registerXPathNamespace($ns, 'urn:DAV');
foreach ($responseXML->xpath("{$ns}:response") as $response) {
$properties = array();
$response->registerXPathNamespace($ns, 'urn:DAV');
foreach ($response->xpath("{$ns}:propstat") as $propStat) {
// Parse all WebDAV properties
$propList = array();
$propStat->registerXPathNamespace($ns, 'urn:DAV');
foreach ($propStat->xpath("{$ns}:prop") as $prop) {
foreach ($prop->xpath("*") as $element) {
$propertyName = self::toClarkNotation($element);
$propList[$propertyName] = [];
if ('{DAV:}resourcetype' === $propertyName) {
foreach ($element->xpath("*") as $resourcetype) {
$propList[$propertyName][] = self::toClarkNotation($resourcetype);
}
} else {
foreach ($element->xpath("*") as $child) {
$propList[$propertyName][self::toClarkNotation($child)] = (string) $child;
}
if (!$propList[$propertyName]) {
$propList[$propertyName] = (string) $element;
}
}
}
}
list($httpVersion, $statusCode, $message) = \explode(' ', $propStat->children('urn:DAV')->status, 3);
$properties[$statusCode] = $propList;
}
$result[(string) $response->children('urn:DAV')->href] = $properties;
}
if (0 === $depth) {
\reset($result);
return \current($result)[200] ?? array();
}
return \array_map(function($statusList){
return $statusList[200] ?? array();
}, $result);
}
/**
* Returns the 'clark notation' for an element.
*
* For example, and element encoded as:
* <b:myelem xmlns:b="http://www.example.org/" />
* will be returned as:
* {http://www.example.org}myelem
*
* This format is used throughout the SabreDAV sourcecode.
* Elements encoded with the urn:DAV namespace will
* be returned as if they were in the DAV: namespace. This is to avoid
* compatibility problems.
*/
public static function toClarkNotation(\SimpleXMLElement $element) : string
{
// Mapping back to the real namespace, in case it was dav
// Mapping to clark notation
$ns = \array_values($element->getNamespaces())[0];
return '{' . ('urn:DAV' == $ns ? 'DAV:' : $ns) . '}' . $element->getName();
}
}