diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php
index f8f1aed03..e1d0f391a 100644
--- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php
@@ -3,6 +3,7 @@
namespace RainLoop\Providers\AddressBook;
use \RainLoop\Providers\AddressBook\Enumerations\PropertyType;
+use \SnappyMail\DAV\Client as DAVClient;
class PdoAddressBook
extends \RainLoop\Common\PdoAbstract
@@ -104,7 +105,7 @@ class PdoAddressBook
return $aResult;
}
- private function prepearRemoteSyncData($oClient, string $sPath)
+ private function prepearRemoteSyncData(DAVClient $oClient, string $sPath)
{
$mResult = false;
$aResponse = null;
@@ -175,7 +176,7 @@ class PdoAddressBook
return $mResult;
}
- private function davClientRequest($oClient, string $sCmd, string $sUrl, $mData = null) : ?array
+ private function davClientRequest(DAVClient $oClient, string $sCmd, string $sUrl, $mData = null) : ?array
{
\MailSo\Base\Utils::ResetTimeLimit();
@@ -214,7 +215,7 @@ class PdoAddressBook
return $aResponse;
}
- private function detectionPropFind(\Sabre\DAV\Client $oClient, string $sPath) : ?array
+ private function detectionPropFind(DAVClient $oClient, string $sPath) : ?array
{
$aResponse = null;
@@ -239,7 +240,7 @@ class PdoAddressBook
return $aResponse;
}
- private function getContactsPaths(\Sabre\DAV\Client $oClient, string $sUser, string $sPassword, string $sProxy = '') : array
+ private function getContactsPaths(DAVClient $oClient, string $sUser, string $sPassword, string $sProxy = '') : array
{
$aContactsPaths = array();
@@ -249,11 +250,6 @@ class PdoAddressBook
// [{DAV:}current-user-principal] => /cloud/remote.php/carddav/principals/admin/
// [{urn:ietf:params:xml:ns:carddav}addressbook-home-set] => /cloud/remote.php/carddav/addressbooks/admin/
- if (!$oClient)
- {
- return $aContactsPaths;
- }
-
$aResponse = $this->detectionPropFind($oClient, '/.well-known/carddav');
$sNextPath = '';
@@ -425,7 +421,7 @@ class PdoAddressBook
return $aContactsPaths;
}
- private function checkContactsPath(\Sabre\DAV\Client $oClient, string $sPath) : bool
+ private function checkContactsPath(DAVClient $oClient, string $sPath) : bool
{
if (!$oClient)
{
@@ -475,7 +471,7 @@ class PdoAddressBook
return $bGood;
}
- public function getDavClientFromUrl(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : \Sabre\DAV\Client
+ public function getDavClientFromUrl(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : DAVClient
{
if (!\preg_match('/^http[s]?:\/\//i', $sUrl))
{
@@ -509,7 +505,7 @@ class PdoAddressBook
$aSettings['proxy'] = $sProxy;
}
- $oClient = new \Sabre\DAV\Client($aSettings);
+ $oClient = new DAVClient($aSettings);
$oClient->setVerifyPeer(false);
$oClient->__UrlPath__ = $aUrl['path'];
@@ -519,13 +515,8 @@ class PdoAddressBook
return $oClient;
}
- public function getDavClient(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : ?\Sabre\DAV\Client
+ public function getDavClient(string $sUrl, string $sUser, string $sPassword, string $sProxy = '') : ?DAVClient
{
- if (!\class_exists('Sabre\DAV\Client'))
- {
- return null;
- }
-
$aMatch = array();
$sUserAddressBookNameName = '';
@@ -538,10 +529,6 @@ class PdoAddressBook
}
$oClient = $this->getDavClientFromUrl($sUrl, $sUser, $sPassword, $sProxy);
- if (!$oClient)
- {
- return null;
- }
$sPath = $oClient->__UrlPath__;
diff --git a/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Client.php b/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Client.php
deleted file mode 100644
index 2d7ea4040..000000000
--- a/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Client.php
+++ /dev/null
@@ -1,656 +0,0 @@
-$validSetting = $settings[$validSetting];
- }
- }
-
- if (isset($settings['authType'])) {
- $this->authType = $settings['authType'];
- } else {
- $this->authType = self::AUTH_BASIC | self::AUTH_DIGEST;
- }
-
- $this->propertyMap['{DAV:}resourcetype'] = 'Sabre\\DAV\\Property\\ResourceType';
-
- }
-
- /**
- * Add trusted root certificates to the webdav client.
- *
- * The parameter certificates should be a absolute path to a file
- * which contains all trusted certificates
- *
- * @param string $certificates
- */
- public function addTrustedCertificates($certificates) {
- $this->trustedCertificates = $certificates;
- }
-
- /**
- * Enables/disables SSL peer verification
- *
- * @param boolean $value
- */
- public function setVerifyPeer($value) {
- $this->verifyPeer = $value;
- }
-
- /**
- * 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 array $properties
- * @param int $depth
- * @return array
- */
- public function propFind($url, array $properties, $depth = 0) {
-
- $body = '' . "\n";
- $body.= '' . "\n";
- $body.= ' ' . "\n";
-
- foreach($properties as $property) {
-
- list(
- $namespace,
- $elementName
- ) = XMLUtil::parseClarkNotation($property);
-
- if ($namespace === 'DAV:') {
- $body.=' ' . "\n";
- } else {
- $body.=" \n";
- }
-
- }
-
- $body.= ' ' . "\n";
- $body.= '';
-
- $response = $this->request('PROPFIND', $url, $body, array(
- 'Depth' => $depth,
- 'Content-Type' => 'application/xml'
- ));
-
- $result = $this->parseMultiStatus($response['body']);
-
- // If depth was 0, we only return the top item
- if ($depth===0) {
- reset($result);
- $result = current($result);
- return isset($result[200])?$result[200]:array();
- }
-
- $newResult = array();
- foreach($result as $href => $statusList) {
-
- $newResult[$href] = isset($statusList[200])?$statusList[200]:array();
-
- }
-
- 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.
- *
- * @todo Must be building the request using the DOM, and does not yet
- * support complex properties.
- * @param string $url
- * @param array $properties
- * @return void
- */
- public function propPatch($url, array $properties) {
-
- $body = '' . "\n";
- $body.= '' . "\n";
-
- foreach($properties as $propName => $propValue) {
-
- list(
- $namespace,
- $elementName
- ) = XMLUtil::parseClarkNotation($propName);
-
- if ($propValue === null) {
-
- $body.="\n";
-
- if ($namespace === 'DAV:') {
- $body.=' ' . "\n";
- } else {
- $body.=" \n";
- }
-
- $body.="\n";
-
- } else {
-
- $body.="\n";
- if ($namespace === 'DAV:') {
- $body.=' ';
- } else {
- $body.=" ";
- }
- // Shitty.. i know
- $body.=htmlspecialchars($propValue, ENT_NOQUOTES, 'UTF-8');
- if ($namespace === 'DAV:') {
- $body.='' . "\n";
- } else {
- $body.="\n";
- }
- $body.="\n";
-
- }
-
- }
-
- $body.= '';
-
- $this->request('PROPPATCH', $url, $body, array(
- 'Content-Type' => 'application/xml'
- ));
-
- }
-
- /**
- * 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() {
-
- $result = $this->request('OPTIONS');
- if (!isset($result['headers']['dav'])) {
- return array();
- }
-
- $features = explode(',', $result['headers']['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.
- *
- * @param string $method
- * @param string $url
- * @param string $body
- * @param array $headers
- * @return array
- */
- public function request($method, $url = '', $body = null, $headers = array()) {
-
- $url = $this->getAbsoluteUrl($url);
-
- $curlSettings = array(
- CURLOPT_RETURNTRANSFER => true,
- // Return headers as part of the response
- CURLOPT_HEADER => true,
-
- // For security we cast this to a string. If somehow an array could
- // be passed here, it would be possible for an attacker to use @ to
- // post local files.
- CURLOPT_POSTFIELDS => (string)$body,
- CURLOPT_USERAGENT => 'SnappyMail DAV Client', // TODO rainloop
- // Automatically follow redirects
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_MAXREDIRS => 5,
- CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
- CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
- );
-
- if($this->verifyPeer !== null) {
- $curlSettings[CURLOPT_SSL_VERIFYPEER] = $this->verifyPeer;
- // TODO rainloop
- if (!$this->verifyPeer) {
- $curlSettings[CURLOPT_SSL_VERIFYHOST] = 0;
- }
- // END rainloop
- }
-
- if($this->trustedCertificates) {
- $curlSettings[CURLOPT_CAINFO] = $this->trustedCertificates;
- }
-
- switch ($method) {
- case 'HEAD' :
-
- // do not read body with HEAD requests (this is necessary because cURL does not ignore the body with HEAD
- // requests when the Content-Length header is given - which in turn is perfectly valid according to HTTP
- // specs...) cURL does unfortunately return an error in this case ("transfer closed transfer closed with
- // ... bytes remaining to read") this can be circumvented by explicitly telling cURL to ignore the
- // response body
- $curlSettings[CURLOPT_NOBODY] = true;
- $curlSettings[CURLOPT_CUSTOMREQUEST] = 'HEAD';
- break;
-
- default:
- $curlSettings[CURLOPT_CUSTOMREQUEST] = $method;
- break;
-
- }
-
- // Adding HTTP headers
- $nHeaders = array();
- foreach($headers as $key=>$value) {
-
- $nHeaders[] = $key . ': ' . $value;
-
- }
- $curlSettings[CURLOPT_HTTPHEADER] = $nHeaders;
-
- if ($this->proxy) {
- $curlSettings[CURLOPT_PROXY] = $this->proxy;
- }
-
- if ($this->userName && $this->authType) {
- $curlType = 0;
- if ($this->authType & self::AUTH_BASIC) {
- $curlType |= CURLAUTH_BASIC;
- }
- if ($this->authType & self::AUTH_DIGEST) {
- $curlType |= CURLAUTH_DIGEST;
- }
- $curlSettings[CURLOPT_HTTPAUTH] = $curlType;
- $curlSettings[CURLOPT_USERPWD] = $this->userName . ':' . $this->password;
- }
-
- list(
- $response,
- $curlInfo,
- $curlErrNo,
- $curlError
- ) = $this->curlRequest($url, $curlSettings);
-
- $headerBlob = substr($response, 0, $curlInfo['header_size']);
- $response = substr($response, $curlInfo['header_size']);
-
- // In the case of 100 Continue, or redirects we'll have multiple lists
- // of headers for each separate HTTP response. We can easily split this
- // because they are separated by \r\n\r\n
- $headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n"));
-
- // We only care about the last set of headers
- $headerBlob = $headerBlob[count($headerBlob)-1];
-
- // Splitting headers
- $headerBlob = explode("\r\n", $headerBlob);
-
- $headers = array();
- foreach($headerBlob as $header) {
- $parts = explode(':', $header, 2);
- if (count($parts)==2) {
- $headers[strtolower(trim($parts[0]))] = trim($parts[1]);
- }
- }
-
- $response = array(
- 'body' => $response,
- 'statusCode' => $curlInfo['http_code'],
- 'headers' => $headers
- );
-
- if ($curlErrNo) {
- throw new Exception('[CURL] Error while making request: ' . $curlError . ' (error code: ' . $curlErrNo . ')');
- }
-
- if ($response['statusCode']>=400) {
- switch ($response['statusCode']) {
- case 400 :
- throw new Exception\BadRequest('Bad request');
- case 401 :
- throw new Exception\NotAuthenticated('Not authenticated');
- case 402 :
- throw new Exception\PaymentRequired('Payment required');
- case 403 :
- throw new Exception\Forbidden('Forbidden');
- case 404:
- throw new Exception\NotFound('Resource not found.');
- case 405 :
- throw new Exception\MethodNotAllowed('Method not allowed');
- case 409 :
- throw new Exception\Conflict('Conflict');
- case 412 :
- throw new Exception\PreconditionFailed('Precondition failed');
- case 416 :
- throw new Exception\RequestedRangeNotSatisfiable('Requested Range Not Satisfiable');
- case 500 :
- throw new Exception('Internal server error');
- case 501 :
- throw new Exception\NotImplemented('Not Implemented');
- case 507 :
- throw new Exception\InsufficientStorage('Insufficient storage');
- default:
- throw new Exception('HTTP error response. (errorcode ' . $response['statusCode'] . ')');
- }
- }
-
- return $response;
-
- }
-
- /**
- * Wrapper for all curl functions.
- *
- * The only reason this was split out in a separate method, is so it
- * becomes easier to unittest.
- *
- * @param string $url
- * @param array $settings
- * @return array
- */
- // @codeCoverageIgnoreStart
- protected function curlRequest($url, $settings) {
-
- // TODO rainloop
- $curl = curl_init($url);
-
- if (ini_get('open_basedir') === '')
- {
- curl_setopt_array($curl, $settings);
- $data = curl_exec($curl);
- }
- else
- {
- $settings[CURLOPT_FOLLOWLOCATION] = false;
- curl_setopt_array($curl, $settings);
-
- $max_redirects = isset($settings[CURLOPT_MAXREDIRS]) ? $settings[CURLOPT_MAXREDIRS] : 5;
- $mr = $max_redirects;
- if ($mr > 0)
- {
- $newurl = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
-
- $rcurl = curl_copy_handle($curl);
- curl_setopt($rcurl, CURLOPT_HEADER, true);
- curl_setopt($rcurl, CURLOPT_NOBODY, true);
- curl_setopt($rcurl, CURLOPT_FORBID_REUSE, false);
- curl_setopt($rcurl, CURLOPT_RETURNTRANSFER, true);
- do
- {
- curl_setopt($rcurl, CURLOPT_URL, $newurl);
- $header = curl_exec($rcurl);
- if (curl_errno($rcurl))
- {
- $code = 0;
- }
- else
- {
- $code = curl_getinfo($rcurl, CURLINFO_HTTP_CODE);
- if ($code == 301 || $code == 302)
- {
- $matches = array();
- preg_match('/Location:(.*?)\n/', $header, $matches);
- $newurl = trim(array_pop($matches));
- }
- else
- {
- $code = 0;
- }
- }
- } while ($code && --$mr);
-
- curl_close($rcurl);
- if ($mr > 0)
- {
- curl_setopt($curl, CURLOPT_URL, $newurl);
- }
- }
-
- if ($mr == 0 && $max_redirects > 0)
- {
- $data = false;
- }
- else
- {
- $data = curl_exec($curl);
- }
- }
-
- return array(
- $data,
- curl_getinfo($curl),
- curl_errno($curl),
- curl_error($curl)
- );
- // END rainloop
-
- $curl = curl_init($url);
- curl_setopt_array($curl, $settings);
-
- return array(
- curl_exec($curl),
- curl_getinfo($curl),
- curl_errno($curl),
- curl_error($curl)
- );
-
- }
- // @codeCoverageIgnoreEnd
-
- /**
- * 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
- */
- protected function getAbsoluteUrl($url) {
-
- // If the url starts with http:// or https://, the url is already absolute.
- if (preg_match('/^http(s?):\/\//', $url)) {
- return $url;
- }
-
- // If the url starts with a slash, we must calculate the url based off
- // the root of the base url.
- if (strpos($url,'/') === 0) {
- $parts = parse_url($this->baseUri);
- return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url;
- }
-
- // Otherwise...
- return $this->baseUri . $url;
-
- }
-
- /**
- * Parses a WebDAV multistatus response body
- *
- * This method returns an array with the following structure
- *
- * array(
- * 'url/to/resource' => array(
- * '200' => array(
- * '{DAV:}property1' => 'value1',
- * '{DAV:}property2' => 'value2',
- * ),
- * '404' => array(
- * '{DAV:}property1' => null,
- * '{DAV:}property2' => null,
- * ),
- * )
- * 'url/to/resource2' => array(
- * .. etc ..
- * )
- * )
- *
- *
- * @param string $body xml body
- * @return array
- */
- public function parseMultiStatus($body) {
-
- $body = XMLUtil::convertDAVNamespace($body);
-
- // Fixes an XXE vulnerability on PHP versions older than 5.3.23 or
- // 5.4.13.
- $previous = libxml_disable_entity_loader(true);
- $responseXML = simplexml_load_string($body, null, LIBXML_NOBLANKS | LIBXML_NOCDATA);
- libxml_disable_entity_loader($previous);
-
- if ($responseXML===false) {
- throw new \InvalidArgumentException('The passed data is not valid XML');
- }
-
- $responseXML->registerXPathNamespace('d', 'urn:DAV');
-
- $propResult = array();
-
- foreach($responseXML->xpath('d:response') as $response) {
- $response->registerXPathNamespace('d', 'urn:DAV');
- $href = $response->xpath('d:href');
- $href = (string)$href[0];
-
- $properties = array();
-
- foreach($response->xpath('d:propstat') as $propStat) {
-
- $propStat->registerXPathNamespace('d', 'urn:DAV');
- $status = $propStat->xpath('d:status');
- list($httpVersion, $statusCode, $message) = explode(' ', (string)$status[0],3);
-
- // Only using the propertymap for results with status 200.
- $propertyMap = $statusCode==='200' ? $this->propertyMap : array();
-
- $properties[$statusCode] = XMLUtil::parseProperties(dom_import_simplexml($propStat), $propertyMap);
-
- }
-
- $propResult[$href] = $properties;
-
- }
-
- return $propResult;
-
- }
-
-}
diff --git a/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Exception.php b/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Exception.php
deleted file mode 100644
index af47c36a2..000000000
--- a/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Exception.php
+++ /dev/null
@@ -1,64 +0,0 @@
-getAllowedMethods($server->getRequestUri());
-
- return array(
- 'Allow' => strtoupper(implode(', ',$methods)),
- );
-
- }
-
-}
diff --git a/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Exception/NotAuthenticated.php b/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Exception/NotAuthenticated.php
deleted file mode 100644
index 26eb61d3d..000000000
--- a/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Exception/NotAuthenticated.php
+++ /dev/null
@@ -1,30 +0,0 @@
-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
- *
- * @param DAV\Server $server
- * @param \DOMElement $errorNode
- * @return void
- */
- public function serialize(DAV\Server $server,\DOMElement $errorNode) {
-
- if ($this->header) {
- $prop = $errorNode->ownerDocument->createElement('s:header');
- $prop->nodeValue = $this->header;
- $errorNode->appendChild($prop);
- }
-
- }
-
-}
diff --git a/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php b/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php
deleted file mode 100644
index 2651b1d3e..000000000
--- a/snappymail/v/0.0.0/app/libraries/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php
+++ /dev/null
@@ -1,31 +0,0 @@
-
- * 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.
- *
- * This function will return null if a nodetype other than an Element is passed.
- *
- * @param \DOMNode $dom
- * @return string
- */
- static function toClarkNotation(\DOMNode $dom) {
-
- if ($dom->nodeType !== XML_ELEMENT_NODE) return null;
-
- // Mapping back to the real namespace, in case it was dav
- if ($dom->namespaceURI=='urn:DAV') $ns = 'DAV:'; else $ns = $dom->namespaceURI;
-
- // Mapping to clark notation
- return '{' . $ns . '}' . $dom->localName;
-
- }
-
- /**
- * Parses a clark-notation string, and returns the namespace and element
- * name components.
- *
- * If the string was invalid, it will throw an InvalidArgumentException.
- *
- * @param string $str
- * @throws InvalidArgumentException
- * @return array
- */
- static function parseClarkNotation($str) {
-
- if (!preg_match('/^{([^}]*)}(.*)$/',$str,$matches)) {
- throw new \InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string');
- }
-
- return array(
- $matches[1],
- $matches[2]
- );
-
- }
-
- /**
- * This method takes an XML document (as string) and converts 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
- *
- * @param string $xmlDocument
- * @return array|string|null
- */
- static function convertDAVNamespace($xmlDocument) {
-
- // 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
- return preg_replace("/xmlns(:[A-Za-z0-9_]*)?=(\"|\')DAV:(\\2)/","xmlns\\1=\\2urn:DAV\\2",$xmlDocument);
-
- }
-
- /**
- * This method provides a generic way to load a DOMDocument for WebDAV use.
- *
- * This method throws a Sabre\DAV\Exception\BadRequest exception for any xml errors.
- * It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV.
- *
- * @param string $xml
- * @throws Sabre\DAV\Exception\BadRequest
- * @return DOMDocument
- */
- static function loadDOMDocument($xml) {
-
- if (empty($xml))
- throw new Exception\BadRequest('Empty XML document sent');
-
- // The BitKinex client sends xml documents as UTF-16. PHP 5.3.1 (and presumably lower)
- // does not support this, so we must intercept this and convert to UTF-8.
- if (substr($xml,0,12) === "\x3c\x00\x3f\x00\x78\x00\x6d\x00\x6c\x00\x20\x00") {
-
- // Note: the preceeding byte sequence is "]*)encoding="UTF-16"([^>]*)>|u','',$xml);
-
- }
-
- // Retaining old error setting
- $oldErrorSetting = libxml_use_internal_errors(true);
- // Fixes an XXE vulnerability on PHP versions older than 5.3.23 or
- // 5.4.13.
- $oldEntityLoaderSetting = libxml_disable_entity_loader(true);
-
- // Clearing any previous errors
- libxml_clear_errors();
-
- $dom = new \DOMDocument();
-
- // We don't generally care about any whitespace
- $dom->preserveWhiteSpace = false;
-
- $dom->loadXML(self::convertDAVNamespace($xml),LIBXML_NOWARNING | LIBXML_NOERROR);
-
- if ($error = libxml_get_last_error()) {
- libxml_clear_errors();
- throw new Exception\BadRequest('The request body had an invalid XML body. (message: ' . $error->message . ', errorcode: ' . $error->code . ', line: ' . $error->line . ')');
- }
-
- // Restoring old mechanism for error handling
- if ($oldErrorSetting===false) libxml_use_internal_errors(false);
- if ($oldEntityLoaderSetting===false) libxml_disable_entity_loader(false);
-
- return $dom;
-
- }
-
- /**
- * Parses all WebDAV properties out of a DOM Element
- *
- * Generally WebDAV properties are enclosed in {DAV:}prop elements. This
- * method helps by going through all these and pulling out the actual
- * propertynames, making them array keys and making the property values,
- * well.. the array values.
- *
- * If no value was given (self-closing element) null will be used as the
- * value. This is used in for example PROPFIND requests.
- *
- * Complex values are supported through the propertyMap argument. The
- * propertyMap should have the clark-notation properties as it's keys, and
- * classnames as values.
- *
- * When any of these properties are found, the unserialize() method will be
- * (statically) called. The result of this method is used as the value.
- *
- * @param \DOMElement $parentNode
- * @param array $propertyMap
- * @return array
- */
- static function parseProperties(\DOMElement $parentNode, array $propertyMap = array()) {
-
- $propList = array();
- foreach($parentNode->childNodes as $propNode) {
-
- if (self::toClarkNotation($propNode)!=='{DAV:}prop') continue;
-
- foreach($propNode->childNodes as $propNodeData) {
-
- /* If there are no elements in here, we actually get 1 text node, this special case is dedicated to netdrive */
- if ($propNodeData->nodeType != XML_ELEMENT_NODE) continue;
-
- $propertyName = self::toClarkNotation($propNodeData);
- if (isset($propertyMap[$propertyName])) {
- $propList[$propertyName] = call_user_func(array($propertyMap[$propertyName],'unserialize'),$propNodeData);
- } else {
- $propList[$propertyName] = $propNodeData->textContent;
- }
- }
-
-
- }
- return $propList;
-
- }
-
-}
diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php b/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php
new file mode 100644
index 000000000..0c1e6c2ce
--- /dev/null
+++ b/snappymail/v/0.0.0/app/libraries/snappymail/dav/client.php
@@ -0,0 +1,237 @@
+ 'SnappyMail\\DAV\\Property\\ResourceType'
+ );
+
+ protected $baseUri;
+
+ /**
+ * 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 \InvalidArgumentException('A baseUri must be provided');
+ }
+ $this->baseUri = $settings['baseUri'];
+
+ $this->HTTP = \SnappyMail\HTTP\Request::factory('socket');
+ $this->HTTP->proxy = $settings['proxy'] ?? null;
+ $this->HTTP->setAuth(3, $settings['userName'] ?? '', $settings['password'] ?? '');
+ $this->HTTP->max_response_kb = 0;
+ $this->HTTP->timeout = 15; // timeout in seconds.
+ $this->HTTP->follow_location = false;
+ }
+
+ /**
+ * Enable/disable SSL peer verification
+ */
+ public function setVerifyPeer(bool $value) : void
+ {
+ $this->HTTP->verify_peer = $value;
+ }
+
+ /**
+ * 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 = '' . "\n";
+ $body.= '' . "\n";
+ $body.= ' ' . "\n";
+
+ foreach ($properties as $property) {
+ if (!\preg_match('/^{([^}]*)}(.*)$/', $property, $match)) {
+ throw new \InvalidArgumentException('\'' . $property . '\' is not a valid clark-notation formatted string');
+ }
+ if ('DAV:' === $match[1]) {
+ $body .= " \n";
+ } else {
+ $body .= " \n";
+ }
+ }
+
+ $body .= ' ' . "\n";
+ $body .= '';
+
+ if (!\preg_match('/^http(s?):\/\//', $url)) {
+ // If the url starts with a slash, we must calculate the url based off
+ // the root of the base url.
+ if (0 === \strpos($url, '/')) {
+ $parts = \parse_url($this->baseUri);
+ $url = $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url;
+ } else {
+ $url = $this->baseUri . $url;
+ }
+ }
+ $response = $this->HTTP->doRequest('PROPFIND', $url, $body, array(
+ "Depth: {$depth}",
+ 'Content-Type: application/xml'
+ ));
+ if (300 <= $response->status) {
+ throw new \SnappyMail\HTTP\Exception('', $response->status);
+ }
+
+ /**
+ * 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 \InvalidArgumentException('The passed data is not valid XML');
+ }
+
+ $responseXML->registerXPathNamespace('d', 'urn:DAV');
+
+ $result = array();
+
+ foreach ($responseXML->xpath('d:response') as $response) {
+ $response->registerXPathNamespace('d', 'urn:DAV');
+ $href = $response->xpath('d:href');
+ $href = (string) $href[0];
+
+ $properties = array();
+
+ foreach ($response->xpath('d:propstat') as $propStat) {
+ $propStat->registerXPathNamespace('d', 'urn:DAV');
+ $status = $propStat->xpath('d:status');
+ list($httpVersion, $statusCode, $message) = \explode(' ', (string)$status[0], 3);
+
+ // Only using the propertymap for results with status 200.
+ $propertyMap = $statusCode === '200' ? $this->propertyMap : array();
+
+ $properties[$statusCode] = static::parseProperties(\dom_import_simplexml($propStat), $propertyMap);
+ }
+
+ $result[$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:
+ *
+ * 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.
+ *
+ * This function will return null if a nodetype other than an Element is passed.
+ */
+ public static function toClarkNotation(\DOMNode $dom) : ?string
+ {
+ // Mapping back to the real namespace, in case it was dav
+ // Mapping to clark notation
+ return XML_ELEMENT_NODE === $dom->nodeType
+ ? '{' . ('urn:DAV' == $dom->namespaceURI ? 'DAV:' : $dom->namespaceURI) . '}' . $dom->localName
+ : null;
+ }
+
+ /**
+ * Parses all WebDAV properties out of a DOM Element
+ *
+ * Generally WebDAV properties are enclosed in {DAV:}prop elements. This
+ * method helps by going through all these and pulling out the actual
+ * propertynames, making them array keys and making the property values,
+ * well.. the array values.
+ *
+ * If no value was given (self-closing element) null will be used as the
+ * value. This is used in for example PROPFIND requests.
+ *
+ * Complex values are supported through the propertyMap argument. The
+ * propertyMap should have the clark-notation properties as it's keys, and
+ * classnames as values.
+ *
+ * When any of these properties are found, the fromDOMElement() method will be
+ * (statically) called. The result of this method is used as the value.
+ */
+ protected static function parseProperties(\DOMElement $parentNode, array $propertyMap = array()) : array
+ {
+ $propList = array();
+ foreach ($parentNode->childNodes as $propNode) {
+ if ('{DAV:}prop' === self::toClarkNotation($propNode)) {
+ foreach ($propNode->childNodes as $propNodeData) {
+ /* If there are no elements in here, we actually get 1 text node, this special case is dedicated to netdrive */
+ if (XML_ELEMENT_NODE == $propNodeData->nodeType) {
+ $propertyName = self::toClarkNotation($propNodeData);
+ if (isset($propertyMap[$propertyName])) {
+ $propList[$propertyName] = \call_user_func(array($propertyMap[$propertyName], 'fromDOMElement'), $propNodeData);
+ } else {
+ $propList[$propertyName] = $propNodeData->textContent;
+ }
+ }
+ }
+ }
+ }
+ return $propList;
+ }
+
+}
diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/http/exception.php b/snappymail/v/0.0.0/app/libraries/snappymail/http/exception.php
new file mode 100644
index 000000000..bdc6c3df8
--- /dev/null
+++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/exception.php
@@ -0,0 +1,84 @@
+ 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+// 306 => 'Switch Proxy', # obsolete
+ 307 => 'Temporary Redirect',
+
+ // Client Error 4xx
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required', # reserved for future use
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ // https://tools.ietf.org/html/rfc7540#section-9.1.2
+ 421 => 'Misdirected Request',
+ // https://tools.ietf.org/html/rfc4918
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ // http://tools.ietf.org/html/rfc2817
+ 426 => 'Upgrade Required',
+ // http://tools.ietf.org/html/rfc6585
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 451 => 'Unavailable For Legal Reasons',
+
+ // Server Error 5xx
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable', # may have Retry-After header
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+// 506 => 'Variant Also Negotiates',
+// 507 => 'Insufficient Storage',
+// 508 => 'Loop Detected',
+// 509 => 'Bandwidth Limit Exceeded',
+// 510 => 'Not Extended',
+ 511 => 'Network Authentication Required',
+ );
+
+ function __construct(string $message = "", int $code = 0, Response $response = null)
+ {
+ if ($response) {
+ if (\in_array($code, array(301, 302, 303, 307))) {
+ $message = $response->getRedirectLocation() . "\n" . $message;
+ } else if (405 === $code && ($allow = $this->getHeader('allow'))) {
+ $message = (\is_array($allow) ? $allow[0] : $allow) . "\n" . $message;
+ }
+ }
+ if (isset(static::CODES[$code])) {
+ $message = "{$code} " . static::CODES[$code] . ($message ? ": {$message}" : '');
+ }
+ parent::__construct($message, $code);
+ }
+
+}
diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php b/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php
new file mode 100644
index 000000000..fba6497b6
--- /dev/null
+++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php
@@ -0,0 +1,159 @@
+ 0,
+ 'user' => '',
+ 'pass' => ''
+ ],
+ $stream = null,
+ $headers = array(),
+ $ca_bundle = null;
+
+ protected static $scheme_ports = array(
+ 'http' => 80,
+ 'https' => 443
+ );
+
+ public static function factory(string $type = 'curl')
+ {
+ if ('curl' === $type && \function_exists('curl_init')) {
+ return new Request\CURL();
+ }
+ return new Request\Socket();
+ }
+
+ function __construct()
+ {
+ $this->user_agent = 'SnappyMail/' . APP_VERSION;
+ }
+
+ public function setAuth(int $type, string $user, string $pass) : void
+ {
+ $this->auth = [
+ 'type' => $type,
+ 'user' => $user,
+ 'pass' => $pass
+ ];
+ }
+
+ public function addHeader($header)
+ {
+ $this->headers[] = $header;
+ return $this;
+ }
+
+ public function streamBodyTo($stream)
+ {
+ if (!\is_resource($stream)) {
+ throw new \Exception('Invalid body target');
+ }
+ $this->stream = $stream;
+ }
+
+ public function setCABundleFile($file)
+ {
+ $this->ca_bundle = $file;
+ }
+
+ /**
+ * Return whether a URI can be fetched. Returns false if the URI scheme is not allowed
+ * or is not supported by this fetcher implementation; returns true otherwise.
+ *
+ * @return bool
+ */
+ public function canFetchURI($uri)
+ {
+ if ('https:' === \substr($uri, 0, 6) && !$this->supportsSSL()) {
+ \trigger_error('HTTPS URI unsupported fetching '.$uri, E_USER_WARNING);
+ return false;
+ }
+ if (!self::URIHasAllowedScheme($uri)) {
+ \trigger_error('URI fetching not allowed for '.$uri, E_USER_WARNING);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Does this fetcher implementation (and runtime) support fetching HTTPS URIs?
+ * May inspect the runtime environment.
+ *
+ * @return bool $support True if this fetcher supports HTTPS
+ * fetching; false if not.
+ */
+ abstract public function supportsSSL() : bool;
+
+ abstract protected function __doRequest(string &$method, string &$request_url, &$body, array $extra_headers) : Response;
+
+ public function doRequest($method, $request_url, $body = null, array $extra_headers = array()) : ?Response
+ {
+ $method = \strtoupper($method);
+ $url = $request_url;
+ $etime = \time() + $this->timeout;
+ if (\is_array($body)) { $body = \http_build_query($body, '', '&'); }
+ if ($body && 'GET' === $method) {
+ $url .= (\strpos($url, '?')?'&':'?').$body;
+ $body = null;
+ }
+ do
+ {
+ if (!$this->canFetchURI($url)) {
+ throw new \RuntimeException("Can't fetch URL: {$url}");
+ }
+
+ if (!self::URIHasAllowedScheme($url)) {
+ throw new \RuntimeException("Fetching URL not allowed: {$url}");
+ }
+
+ $result = $this->__doRequest($method, $url, $body, \array_merge($this->headers, $extra_headers));
+
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3
+ // In response to a request other than GET or HEAD, the user agent MUST NOT
+ // automatically redirect the request unless it can be confirmed by the user
+ if ($this->follow_location && \is_null($body) && \in_array($result->status, array(301, 302, 303, 307))) {
+ $url = $result->getRedirectLocation();
+ } else {
+ $result->final_uri = $url;
+ $result->request_uri = $request_url;
+ return $result;
+ }
+
+ } while ($etime-time() > 0);
+
+ return null;
+ }
+
+ /**
+ * Return whether a URI should be allowed. Override this method to conform to your local policy.
+ * By default, will attempt to fetch any http or https URI.
+ */
+ public static function URIHasAllowedScheme($uri) : bool
+ {
+ return (bool) \preg_match('#^https?://#i', $uri);
+ }
+
+ public static function getSchemePort($scheme) : int
+ {
+ return self::$scheme_ports[$scheme] ?? 0;
+ }
+}
diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/http/request/curl.php b/snappymail/v/0.0.0/app/libraries/snappymail/http/request/curl.php
new file mode 100644
index 000000000..313b83797
--- /dev/null
+++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/request/curl.php
@@ -0,0 +1,115 @@
+ $this->user_agent,
+ CURLOPT_CONNECTTIMEOUT => $this->timeout,
+ CURLOPT_TIMEOUT => $this->timeout,
+ CURLOPT_URL => $request_url,
+ CURLOPT_HEADERFUNCTION => array($this, 'fetchHeader'),
+ CURLOPT_WRITEFUNCTION => array($this, \is_resource($this->stream) ? 'streamData' : 'fetchData'),
+ CURLOPT_SSL_VERIFYPEER => ($this->verify_peer || $this->ca_bundle),
+// CURLOPT_SSL_VERIFYHOST => $this->verify_peer ? 2 : 0,
+// CURLOPT_FOLLOWLOCATION => false, // follow redirects
+// CURLOPT_MAXREDIRS => 0, // stop after 0 redirects
+ ));
+// \curl_setopt($c, CURLOPT_ENCODING , 'gzip');
+ if (\defined('CURLOPT_NOSIGNAL')) {
+ \curl_setopt($c, CURLOPT_NOSIGNAL, true);
+ }
+ if ($this->ca_bundle) {
+ \curl_setopt($c, CURLOPT_CAINFO, $this->ca_bundle);
+ }
+ if ($extra_headers) {
+ \curl_setopt($c, CURLOPT_HTTPHEADER, $extra_headers);
+ }
+ if ($this->auth['user'] && $this->auth['type']) {
+ $auth = 0;
+ if ($this->auth['type'] & self::AUTH_BASIC) {
+ $auth |= CURLAUTH_BASIC;
+ }
+ if ($this->auth['type'] & self::AUTH_DIGEST) {
+ $auth |= CURLAUTH_DIGEST;
+ }
+ \curl_setopt($c, CURLOPT_HTTPAUTH, $auth);
+ \curl_setopt($c, CURLOPT_USERPWD, $this->auth['user'] . ':' . $this->auth['pass']);
+ }
+ if ($this->proxy) {
+ \curl_setopt($c, CURLOPT_PROXY, $this->proxy);
+ }
+ if ('HEAD' === $method) {
+ \curl_setopt($c, CURLOPT_NOBODY, true);
+ } else if ('GET' !== $method) {
+ if ('POST' === $method) {
+ \curl_setopt($c, CURLOPT_POST, true);
+ } else {
+ \curl_setopt($c, CURLOPT_CUSTOMREQUEST, $method);
+ }
+ if (!\is_null($body)) {
+ \curl_setopt($c, CURLOPT_POSTFIELDS, $body);
+ }
+ }
+
+ \curl_exec($c);
+
+ try {
+ $code = \curl_getinfo($c, CURLINFO_RESPONSE_CODE);
+ if (!$code) {
+ throw new \RuntimeException("Error " . \curl_errno($c) . ": " . \curl_error($c) . " for {$request_url}");
+ }
+ return new Response($request_url, $code, $this->response_headers, $this->response_body);
+ } finally {
+ \curl_close($c);
+ $this->response_headers = array();
+ $this->response_body = '';
+ }
+ }
+
+ protected function fetchHeader($ch, $header)
+ {
+ $this->response_headers[] = \rtrim($header);
+ return \strlen($header);
+ }
+
+ protected function fetchData($ch, $data)
+ {
+ if ($this->max_response_kb) {
+ $data = \substr($data, 0, \min(\strlen($data), ($this->max_response_kb*1024) - \strlen($this->response_body)));
+ }
+ $this->response_body .= $data;
+ return \strlen($data);
+ }
+
+ protected function streamData($ch, $data)
+ {
+ return \fwrite($this->stream, $data);
+ }
+
+}
diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/http/request/socket.php b/snappymail/v/0.0.0/app/libraries/snappymail/http/request/socket.php
new file mode 100644
index 000000000..93e4e809c
--- /dev/null
+++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/request/socket.php
@@ -0,0 +1,177 @@
+user_agent}",
+ 'Connection: Close',
+ );
+
+ if ($extra_headers) {
+ $headers = \array_merge($headers, $extra_headers);
+ }
+ $headers = \implode("\r\n", $headers);
+ if (!\is_null($body)) {
+ if (!\stripos($headers,'Content-Type')) {
+ $headers .= "\r\nContent-Type: application/x-www-form-urlencoded";
+ }
+ $headers .= "\r\nContent-Length: ".\strlen($body);
+ }
+
+ $context = \stream_context_create();
+ if ('https' === $parts['scheme']) {
+ $parts['host'] = 'ssl://'.$parts['host'];
+ \stream_context_set_option($context, 'ssl', 'verify_peer_name', true);
+ if ($this->verify_peer || $this->ca_bundle) {
+ \stream_context_set_option($context, 'ssl', 'verify_peer', true);
+ if ($this->ca_bundle) {
+ if (\is_dir($this->ca_bundle) || (\is_link($this->ca_bundle) && \is_dir(\readlink($this->ca_bundle)))) {
+ \stream_context_set_option($context, 'ssl', 'capath', $this->ca_bundle);
+ } else {
+ \stream_context_set_option($context, 'ssl', 'cafile', $this->ca_bundle);
+ }
+ }
+ } else {
+ \stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
+ }
+ } else {
+ $parts['host'] = 'tcp://'.$parts['host'];
+ }
+
+ $errno = 0;
+ $errstr = '';
+
+ $sock = \stream_socket_client("{$parts['host']}:{$parts['port']}", $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $context);
+ if (false === $sock) {
+ throw new \RuntimeException($errstr);
+ }
+
+ \stream_set_timeout($sock, $this->timeout);
+
+ \fwrite($sock, $headers . "\r\n\r\n");
+ if (!\is_null($body)) {
+ \fwrite($sock, $body);
+ }
+
+ # Read all headers
+ $chunked = false;
+ $response_headers = array();
+ $data = \rtrim(\fgets($sock, 1024)); # read line
+ $code = \intval(\explode(' ', $data)[1]??0);
+ while (\strlen($data)) {
+ $response_headers[] = $data;
+ $chunked |= \preg_match('#Transfer-Encoding:.*chunked#i', $data);
+
+ if (401 === $code && $this->auth['user']) {
+ // Basic authentication
+ if ($this->auth['type'] & self::AUTH_BASIC && \preg_match("/WWW-Authenticate:\\s+Basic\\s+realm=([^\\r\\n]*)/i", $data, $match)) {
+ $extra_headers['Authorization'] = "Authorization: Basic " . \base64_encode($this->auth['user'] . ':' . $this->auth['pass']);
+ \fclose($sock);
+ return $this->__doRequest($method, $request_url, $body, $extra_headers);
+ }
+ // Digest authentication
+ else if ($this->auth['type'] & self::AUTH_DIGEST && \preg_match("/WWW-Authenticate:\\s+Digest\\s+([^\\r\\n]*)/i", $data, $match)) {
+ $challenge = [];
+ foreach (\split(',', $match[1]) as $i) {
+ $ii = \split('=', \trim($i), 2);
+ if (!empty($ii[1]) && !empty($ii[0])) {
+ $challenge[$ii[0]] = \preg_replace('/^"/','', \preg_replace('/"$/','', $ii[1]));
+ }
+ }
+ $a1 = \md5($this->auth['user'] . ':' . $challenge['realm'] . ':' . $this->auth['pass']);
+ $a2 = \md5($method . ':' . $request_url);
+ if (empty($challenge['qop'])) {
+ $digest = \md5($a1 . ':' . $challenge['nonce'] . ':' . $a2);
+ } else {
+ $challenge['cnonce'] = 'Req2.' . \random_int();
+ if (empty($challenge['nc'])) {
+ $challenge['nc'] = 1;
+ }
+ $nc = \sprintf('%08x', $challenge['nc']++);
+ $digest = \md5($a1 . ':' . $challenge['nonce'] . ':' . $nc . ':' . $challenge['cnonce'] . ':auth:' . $a2);
+ }
+ $extra_headers['Authorization'] = "Authorization: Digest "
+ . ' username="' . \str_replace(array('\\', '"'), array('\\\\', '\\"'), $this->auth['user']) . '",'
+ . ' realm="' . $challenge['realm'] . '",'
+ . ' nonce="' . $challenge['nonce'] . '",'
+ . ' uri="' . $request_url . '",'
+ . ' response="' . $digest . '"'
+ . (empty($challenge['opaque']) ? '' : ', opaque="' . $challenge['opaque'] . '"')
+ . (empty($challenge['qop']) ? '' : ', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"');
+
+ \fclose($sock);
+ return $this->__doRequest($method, $request_url, $body, $extra_headers);
+ }
+ }
+
+ $data = \rtrim(\fgets($sock, 1024)); # read next line
+ }
+
+ # Read body
+ $body = '';
+ if (\is_resource($this->stream)) {
+ while (!\feof($sock)) {
+ if ($chunked) {
+ $chunk = \hexdec(\trim(\fgets($sock, 8)));
+ if (!$chunk) { break; }
+ while ($chunk > 0) {
+ $tmp = \fread($sock, $chunk);
+ \fwrite($this->stream, $tmp);
+ $chunk -= \strlen($tmp);
+ }
+ } else {
+ \fwrite($this->stream, \fread($sock, 1024));
+ }
+ }
+ } else {
+ $max_bytes = $this->max_response_kb * 1024;
+ while (!\feof($sock) && (!$max_bytes || \strlen($body) < $max_bytes)) {
+ if ($chunked) {
+ $chunk = \hexdec(\trim(\fgets($sock, 8)));
+ if (!$chunk) { break; }
+ while ($chunk > 0) {
+ $tmp = \fread($sock, $chunk);
+ $body .= $tmp;
+ $chunk -= \strlen($tmp);
+ }
+ } else {
+ $body .= \fread($sock, 1024);
+ }
+ }
+ }
+
+ \fclose($sock);
+
+ return new Response($request_url, $code, $response_headers, $body);
+ }
+
+}
diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/http/response.php b/snappymail/v/0.0.0/app/libraries/snappymail/http/response.php
new file mode 100644
index 000000000..1e6cffff9
--- /dev/null
+++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/response.php
@@ -0,0 +1,88 @@
+headers = array();
+ foreach ($headers as $header) {
+ if (\strpos($header, ':')) {
+ list($name, $value) = \explode(':', $header, 2);
+ $name = \strtolower(\trim($name));
+ $value = \trim($value);
+ if (isset($this->headers[$name])) {
+ if (\is_array($this->headers[$name])) {
+ $this->headers[$name][] = $value;
+ } else {
+ $this->headers[$name] = array($this->headers[$name], $value);
+ }
+ } else {
+ $this->headers[$name] = $value;
+ }
+ } else if ($name) {
+// $this->headers[$name] .= \trim($header);
+ }
+ }
+ }
+ $this->request_uri = $request_uri;
+ $this->final_uri = $request_uri;
+ $this->status = (int) $status;
+ if (\function_exists('gzinflate') && isset($this->headers['content-encoding'])
+ && (false !== \stripos($this->headers['content-encoding'], 'gzip'))) {
+ $this->body = \gzinflate(\substr($body, 10, -4));
+ } else {
+ $this->body = $body;
+ }
+ }
+
+ function __get($k)
+ {
+ return \property_exists($this, $k) ? $this->$k : null;
+ }
+
+ public function getHeader($names)
+ {
+ $names = \is_array($names) ? $names : array($names);
+ foreach ($names as $n) {
+ $n = \strtolower($n);
+ if (isset($this->headers[$n])) {
+ return $this->headers[$n];
+ }
+ }
+ return null;
+ }
+
+ public function getRedirectLocation() : ?string
+ {
+ if ($location = $this->getHeader('location')) {
+ $uri = \is_array($location) ? $location[0] : $location;
+ if (!\preg_match('#^[a-z][a-z0-9\\+\\.\\-]+://[^/]+#i', $uri)) {
+ // no host
+ \preg_match('#^([a-z][a-z0-9\\+\\.\\-]+://[^/]+)(/[^\\?\\#]*)#i', $this->final_uri, $match);
+ if ('/' === $uri[0]) {
+ // absolute path
+ $uri = $match[1] . $uri;
+ } else {
+ // relative path
+ $rpos = \strrpos($match[2], '/');
+ $uri = $match[1] . \substr($match[2], 0, $rpos+1) . $uri;
+ }
+ }
+ return $uri;
+ }
+ return null;
+ }
+
+}