mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-11 00:14:50 +03:00
Bugfix: DAV getContactsPaths() failed due to failures of (string) SimpleXMLElement
Added better logging of DAV connection for #653 and #680
This commit is contained in:
parent
28a70ccc8e
commit
28a7377f97
3 changed files with 87 additions and 102 deletions
|
|
@ -35,13 +35,13 @@ trait Contacts
|
|||
$oAccount = $this->getAccountFromToken();
|
||||
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
||||
if (!$oAddressBookProvider) {
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ContactsSyncError);
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ContactsSyncError, null, 'No AddressBookProvider');
|
||||
}
|
||||
\ignore_user_abort(true);
|
||||
\SnappyMail\HTTP\Stream::start(/*$binary = false*/);
|
||||
\SnappyMail\HTTP\Stream::JSON(['messsage'=>'start']);
|
||||
if (!$oAddressBookProvider->Sync()) {
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ContactsSyncError);
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ContactsSyncError, null, 'AddressBookProvider->Sync() failed');
|
||||
}
|
||||
return $this->TrueResponse(__FUNCTION__);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,15 +28,11 @@ trait CardDAV
|
|||
$aResponse = null;
|
||||
try
|
||||
{
|
||||
$this->oLogger->Write('PROPFIND '.$sPath, \LOG_INFO, 'DAV');
|
||||
|
||||
$aResponse = $oClient->propFind($sPath, array(
|
||||
'{DAV:}getlastmodified',
|
||||
'{DAV:}resourcetype',
|
||||
'{DAV:}getetag'
|
||||
), 1);
|
||||
|
||||
// $this->oLogger->WriteDump($aResponse);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
|
|
@ -139,16 +135,12 @@ trait CardDAV
|
|||
|
||||
try
|
||||
{
|
||||
$this->oLogger->Write('PROPFIND '.$sPath, \LOG_INFO, 'DAV');
|
||||
|
||||
$aResponse = $oClient->propFind($sPath, array(
|
||||
'{DAV:}current-user-principal',
|
||||
'{DAV:}resourcetype',
|
||||
'{DAV:}displayname',
|
||||
'{urn:ietf:params:xml:ns:carddav}addressbook-home-set'
|
||||
), 1);
|
||||
|
||||
// $this->oLogger->WriteDump($aResponse);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
|
|
@ -158,49 +150,47 @@ trait CardDAV
|
|||
return $aResponse;
|
||||
}
|
||||
|
||||
private function getContactsPaths(DAVClient $oClient, string $sUser, string $sPassword, string $sProxy = '') : array
|
||||
private function getContactsPaths(DAVClient $oClient, string $sPath, string $sUser, string $sPassword, string $sProxy = '') : array
|
||||
{
|
||||
$aContactsPaths = array();
|
||||
|
||||
$sCurrentUserPrincipal = '';
|
||||
$sAddressbookHomeSet = '';
|
||||
|
||||
// [{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/
|
||||
|
||||
$aResponse = $this->detectionPropFind($oClient, '/.well-known/carddav');
|
||||
$aResponse = $this->detectionPropFind($oClient, '/.well-known/carddav')
|
||||
?: $this->detectionPropFind($oClient, $sPath);
|
||||
|
||||
$sNextPath = '';
|
||||
$sFirstNextPath = '';
|
||||
if (\is_array($aResponse))
|
||||
{
|
||||
foreach ($aResponse as $sKey => $aItem)
|
||||
foreach ($aResponse as $sPropPath => $aItem)
|
||||
{
|
||||
if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) &&
|
||||
false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy'))
|
||||
if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']['{DAV:}href'])
|
||||
&& false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']['{DAV:}href'], '/calendar-proxy'))
|
||||
{
|
||||
$sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'];
|
||||
$sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']['{DAV:}href'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($sCurrentUserPrincipal) && !empty($aItem['{DAV:}current-user-principal']))
|
||||
if (empty($sCurrentUserPrincipal) && !empty($aItem['{DAV:}current-user-principal']['{DAV:}href']))
|
||||
{
|
||||
$sCurrentUserPrincipal = $aItem['{DAV:}current-user-principal'];
|
||||
$sCurrentUserPrincipal = $aItem['{DAV:}current-user-principal']['{DAV:}href'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($sKey))
|
||||
if (!empty($sPropPath))
|
||||
{
|
||||
if (empty($sFirstNextPath))
|
||||
{
|
||||
$sFirstNextPath = $sKey;
|
||||
$sFirstNextPath = $sPropPath;
|
||||
}
|
||||
|
||||
if (empty($sNextPath))
|
||||
{
|
||||
if (static::hasDAVCollection($aItem))
|
||||
{
|
||||
$sNextPath = $sKey;
|
||||
$sNextPath = $sPropPath;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -219,36 +209,32 @@ trait CardDAV
|
|||
{
|
||||
return $aContactsPaths;
|
||||
}
|
||||
else
|
||||
if (\preg_match('/^http[s]?:\/\//i', $sNextPath))
|
||||
{
|
||||
if (\preg_match('/^http[s]?:\/\//i', $sNextPath))
|
||||
$oClient = $this->getDavClientFromUrl($sNextPath, $sUser, $sPassword, $sProxy);
|
||||
if (!$oClient)
|
||||
{
|
||||
$oClient = $this->getDavClientFromUrl($sNextPath, $sUser, $sPassword, $sProxy);
|
||||
if ($oClient)
|
||||
{
|
||||
$sNextPath = $oClient->__UrlPath__;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $aContactsPaths;
|
||||
}
|
||||
return $aContactsPaths;
|
||||
}
|
||||
$sNextPath = $oClient->__UrlPath__;
|
||||
}
|
||||
|
||||
if ($sPath != $sNextPath) {
|
||||
$aResponse = $this->detectionPropFind($oClient, $sNextPath);
|
||||
if (\is_array($aResponse))
|
||||
{
|
||||
foreach ($aResponse as $sKey => $aItem)
|
||||
foreach ($aResponse as $aItem)
|
||||
{
|
||||
if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) &&
|
||||
false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy'))
|
||||
if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']['{DAV:}href']) &&
|
||||
false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']['{DAV:}href'], '/calendar-proxy'))
|
||||
{
|
||||
$sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'];
|
||||
$sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']['{DAV:}href'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($sCurrentUserPrincipal) && !empty($aItem['{DAV:}current-user-principal']))
|
||||
if (empty($sCurrentUserPrincipal) && !empty($aItem['{DAV:}current-user-principal']['{DAV:}href']))
|
||||
{
|
||||
$sCurrentUserPrincipal = $aItem['{DAV:}current-user-principal'];
|
||||
$sCurrentUserPrincipal = $aItem['{DAV:}current-user-principal']['{DAV:}href'];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -262,32 +248,29 @@ trait CardDAV
|
|||
{
|
||||
return $aContactsPaths;
|
||||
}
|
||||
else
|
||||
if (\preg_match('/^http[s]?:\/\//i', $sCurrentUserPrincipal))
|
||||
{
|
||||
if (\preg_match('/^http[s]?:\/\//i', $sCurrentUserPrincipal))
|
||||
$oClient = $this->getDavClientFromUrl($sCurrentUserPrincipal, $sUser, $sPassword, $sProxy);
|
||||
if ($oClient)
|
||||
{
|
||||
$oClient = $this->getDavClientFromUrl($sCurrentUserPrincipal, $sUser, $sPassword, $sProxy);
|
||||
if ($oClient)
|
||||
{
|
||||
$sCurrentUserPrincipal = $oClient->__UrlPath__;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $aContactsPaths;
|
||||
}
|
||||
$sCurrentUserPrincipal = $oClient->__UrlPath__;
|
||||
}
|
||||
|
||||
$aResponse = $this->detectionPropFind($oClient, $sCurrentUserPrincipal);
|
||||
if (\is_array($aResponse))
|
||||
else
|
||||
{
|
||||
foreach ($aResponse as $sKey => $aItem)
|
||||
return $aContactsPaths;
|
||||
}
|
||||
}
|
||||
|
||||
$aResponse = $this->detectionPropFind($oClient, $sCurrentUserPrincipal);
|
||||
if (\is_array($aResponse))
|
||||
{
|
||||
foreach ($aResponse as $aItem)
|
||||
{
|
||||
if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']['{DAV:}href']) &&
|
||||
false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']['{DAV:}href'], '/calendar-proxy'))
|
||||
{
|
||||
if (empty($sAddressbookHomeSet) && !empty($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']) &&
|
||||
false === \strpos($aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'], '/calendar-proxy'))
|
||||
{
|
||||
$sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set'];
|
||||
continue;
|
||||
}
|
||||
$sAddressbookHomeSet = $aItem['{urn:ietf:params:xml:ns:carddav}addressbook-home-set']['{DAV:}href'];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -297,31 +280,28 @@ trait CardDAV
|
|||
{
|
||||
return $aContactsPaths;
|
||||
}
|
||||
else
|
||||
if (\preg_match('/^http[s]?:\/\//i', $sAddressbookHomeSet))
|
||||
{
|
||||
if (\preg_match('/^http[s]?:\/\//i', $sAddressbookHomeSet))
|
||||
$oClient = $this->getDavClientFromUrl($sAddressbookHomeSet, $sUser, $sPassword, $sProxy);
|
||||
if ($oClient)
|
||||
{
|
||||
$oClient = $this->getDavClientFromUrl($sAddressbookHomeSet, $sUser, $sPassword, $sProxy);
|
||||
if ($oClient)
|
||||
{
|
||||
$sAddressbookHomeSet = $oClient->__UrlPath__;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $aContactsPaths;
|
||||
}
|
||||
$sAddressbookHomeSet = $oClient->__UrlPath__;
|
||||
}
|
||||
|
||||
$aResponse = $this->detectionPropFind($oClient, $sAddressbookHomeSet);
|
||||
if (\is_array($aResponse))
|
||||
else
|
||||
{
|
||||
foreach ($aResponse as $sKey => $aItem)
|
||||
return $aContactsPaths;
|
||||
}
|
||||
}
|
||||
|
||||
$aResponse = $this->detectionPropFind($oClient, $sAddressbookHomeSet);
|
||||
if (\is_array($aResponse))
|
||||
{
|
||||
foreach ($aResponse as $sPropPath => $aItem)
|
||||
{
|
||||
if (!empty($sPropPath) && static::hasDAVCollection($aItem)
|
||||
&& \in_array('{urn:ietf:params:xml:ns:carddav}addressbook', $aItem['{DAV:}resourcetype']))
|
||||
{
|
||||
if (!empty($sKey) && static::hasDAVCollection($aItem)
|
||||
&& \in_array('{urn:ietf:params:xml:ns:carddav}addressbook', $aItem['{DAV:}resourcetype']))
|
||||
{
|
||||
$aContactsPaths[$sKey] = isset($aItem['{DAV:}displayname']) ? \trim($aItem['{DAV:}displayname']) : '';
|
||||
}
|
||||
$aContactsPaths[$sPropPath] = isset($aItem['{DAV:}displayname']) ? \trim($aItem['{DAV:}displayname']) : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -329,6 +309,9 @@ trait CardDAV
|
|||
return $aContactsPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if remote path resourcetype is an addressbook
|
||||
*/
|
||||
private function checkContactsPath(DAVClient $oClient, string $sPath) : bool
|
||||
{
|
||||
if (!$oClient)
|
||||
|
|
@ -336,16 +319,12 @@ trait CardDAV
|
|||
return false;
|
||||
}
|
||||
|
||||
$this->oLogger->Write('PROPFIND '.$sPath, \LOG_INFO, 'DAV');
|
||||
|
||||
$aResponse = null;
|
||||
try
|
||||
{
|
||||
$aResponse = $oClient->propFind($sPath, array(
|
||||
'{DAV:}resourcetype'
|
||||
), 1);
|
||||
|
||||
// $this->oLogger->WriteDump($aResponse);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
|
|
@ -445,11 +424,13 @@ trait CardDAV
|
|||
$bGood = true;
|
||||
if ('' === $sPath || '/' === $sPath || !$this->checkContactsPath($oClient, $sPath))
|
||||
{
|
||||
$sNewPath = '';
|
||||
|
||||
$aPaths = $this->getContactsPaths($oClient, $sUser, $sPassword, $sProxy);
|
||||
/**
|
||||
* Path is not an addressbook, try to find it
|
||||
*/
|
||||
$aPaths = $this->getContactsPaths($oClient, $sPath, $sUser, $sPassword, $sProxy);
|
||||
$this->oLogger->WriteDump($aPaths);
|
||||
|
||||
$sNewPath = '';
|
||||
if (\is_array($aPaths))
|
||||
{
|
||||
if (1 < \count($aPaths))
|
||||
|
|
@ -493,7 +474,7 @@ trait CardDAV
|
|||
|
||||
$sPath = $sNewPath;
|
||||
|
||||
$bGood = $this->checkContactsPath($oClient, $sPath);
|
||||
$bGood = $sPath && $this->checkContactsPath($oClient, $sPath);
|
||||
}
|
||||
|
||||
return $bGood ? $oClient : null;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class Client
|
|||
$this->HTTP->setAuth(3, $settings['userName'] ?? '', $settings['password'] ?? '');
|
||||
$this->HTTP->max_response_kb = 0;
|
||||
$this->HTTP->timeout = 15; // timeout in seconds.
|
||||
// $this->HTTP->max_redirects = 0;
|
||||
$this->HTTP->max_redirects = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -69,11 +69,12 @@ class Client
|
|||
$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();
|
||||
\trigger_error("Redirect {$url} to {$location}");
|
||||
\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;
|
||||
|
|
@ -82,6 +83,7 @@ class Client
|
|||
if (300 <= $response->status) {
|
||||
throw new \SnappyMail\HTTP\Exception("{$method} {$url}", $response->status, $response);
|
||||
}
|
||||
\SnappyMail\Log::debug('DAV', "{$response->status}: {$response->body}");
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
|
@ -104,23 +106,20 @@ class Client
|
|||
*/
|
||||
public function propFind(string $url, array $properties, int $depth = 0) : array
|
||||
{
|
||||
$body = '<?xml version="1.0"?>' . "\n";
|
||||
$body.= '<d:propfind xmlns:d="DAV:">' . "\n";
|
||||
$body.= ' <d:prop>' . "\n";
|
||||
$body = '<?xml version="1.0"?>' . "\n" . '<d:propfind xmlns:d="DAV:"><d:prop>';
|
||||
|
||||
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 .= " <d:{$match[2]} />\n";
|
||||
$body .= "<d:{$match[2]}/>";
|
||||
} else {
|
||||
$body .= " <x:{$match[2]} xmlns:x=\"{$match[1]}\"/>\n";
|
||||
$body .= "<x:{$match[2]} xmlns:x=\"{$match[1]}\"/>";
|
||||
}
|
||||
}
|
||||
|
||||
$body .= ' </d:prop>' . "\n";
|
||||
$body .= '</d:propfind>';
|
||||
$body .= '</d:prop></d:propfind>';
|
||||
|
||||
$response = $this->request('PROPFIND', $url, $body, array(
|
||||
"Depth: {$depth}",
|
||||
|
|
@ -144,7 +143,7 @@ class Client
|
|||
null, LIBXML_NOBLANKS | LIBXML_NOCDATA);
|
||||
|
||||
if (false === $responseXML) {
|
||||
throw new \InvalidArgumentException('The passed data is not valid XML');
|
||||
throw new \UnexpectedValueException("The passed data is not valid XML\n{$response->body}");
|
||||
}
|
||||
|
||||
$ns = \array_search('urn:DAV', $responseXML->getNamespaces(true)) ?: 'd';
|
||||
|
|
@ -163,13 +162,18 @@ class Client
|
|||
foreach ($propStat->xpath("{$ns}:prop") as $prop) {
|
||||
foreach ($prop->xpath("*") as $element) {
|
||||
$propertyName = self::toClarkNotation($element);
|
||||
$propList[$propertyName] = [];
|
||||
if ('{DAV:}resourcetype' === $propertyName) {
|
||||
$propList[$propertyName] = [];
|
||||
foreach ($element->xpath("*") as $resourcetype) {
|
||||
$propList[$propertyName][] = self::toClarkNotation($resourcetype);
|
||||
}
|
||||
} else {
|
||||
$propList[$propertyName] = (string) $element;
|
||||
foreach ($element->xpath("*") as $child) {
|
||||
$propList[$propertyName][self::toClarkNotation($child)] = (string) $child;
|
||||
}
|
||||
if (!$propList[$propertyName]) {
|
||||
$propList[$propertyName] = (string) $element;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue