File manager - Edit - /usr/local/cpanel/base/3rdparty/roundcube/vendor/mstilkerich/carddavclient/src/Services/Discovery.php
Back
<?php /* * CardDAV client library for PHP ("PHP-CardDavClient"). * * Copyright (c) 2020-2021 Michael Stilkerich <ms@mike2k.de> * Licensed under the MIT license. See COPYING file in the project root for details. */ declare(strict_types=1); namespace MStilkerich\CardDavClient\Services; use MStilkerich\CardDavClient\{Account, AddressbookCollection, CardDavClient, Config, WebDavCollection}; /** * Provides a service to discover the addressbooks for a CardDAV account. * * It implements the discovery using the mechanisms specified in RFC 6764, which is based on DNS SRV/TXT records and/or * well-known URI redirection on the server. * * @psalm-type Server = array{ * host: string, * port: string, * scheme: string, * dnsrr?: string, * userinput?: bool * } * * @psalm-type SrvRecord = array{pri: int, weight: int, target: string, port: int} * @psalm-type TxtRecord = array{txt: string} * * @package Public\Services */ class Discovery { /** * Some builtins for public providers that don't have discovery properly set up. * * It maps a domain name that is part of the typically used usernames to a working discovery URI. This allows * discovery from data as typically provided by a user without the application having to care about it. * * @var array<string,string> */ private const KNOWN_SERVERS = [ "gmail.com" => "www.googleapis.com", "googlemail.com" => "www.googleapis.com", ]; /** * Discover the addressbooks for a CardDAV account. * * @param Account $account The CardDAV account providing credentials and initial discovery URI. * @psalm-return list<AddressbookCollection> * @return array<int,AddressbookCollection> The discovered addressbooks. * * @throws \Exception * In case of error, sub-classes of \Exception are thrown, with an error message contained within the \Exception * object. * * @api */ public function discoverAddressbooks(Account $account): array { $uri = $account->getDiscoveryUri(); Config::$logger->debug("Starting discovery with input $uri"); if (!preg_match(';^(([^:]+)://)?(([^/:]+)(:([0-9]+))?)(/?.*)$;', $uri, $match)) { throw new \InvalidArgumentException("The account's discovery URI must contain a hostname (got: $uri)"); } $protocol = $match[2]; // optional $host = $match[4]; // mandatory $port = $match[6]; // optional $path = $match[7] ?: '/'; // optional // plain is only used if http was explicitly given $force_ssl = ($protocol !== "http"); // setup default values if no user values given if (strlen($protocol) == 0) { $protocol = $force_ssl ? 'https' : 'http'; } if (strlen($port) == 0) { $port = $force_ssl ? '443' : '80'; } // First try the discovery URI that is stored in the Account $account->setUrl("$protocol://$host:$port"); $addressbooks = $this->tryContextPath($account, $path); if ($addressbooks !== null) { return $addressbooks; } // (1) Discover the hostname and port (may be multiple results for failover setups) // $servers is array of: // [host => "contacts.abc.com", port => "443", scheme => "https", dnsrr => '_carddavs._tcp.abc.com'] // servers in the array are ordered by precedence, highest first // dnsrr is only set when the server was discovered by lookup of DNS SRV record $servers = $this->discoverServers($host, $force_ssl); // some builtins for providers that have discovery for the domains known to users not properly set up if (key_exists($host, self::KNOWN_SERVERS)) { $servers[] = [ "host" => self::KNOWN_SERVERS[$host], "port" => $port, "scheme" => $protocol]; } // include the server that is part of the Account URI for discovery via /well-known/ URI $servers[] = [ "host" => $host, "port" => $port, "scheme" => $protocol ]; // (2) Discover the "initial context path" for each servers (until first success) foreach ($servers as $server) { $baseurl = $server["scheme"] . "://" . $server["host"] . ":" . $server["port"]; $account->setUrl($baseurl); $contextpaths = $this->discoverContextPath($server); foreach ($contextpaths as $contextpath) { $addressbooks = $this->tryContextPath($account, $contextpath); if ($addressbooks !== null) { return $addressbooks; } } } throw new \Exception("Could not determine the addressbook home"); } /** * Attempts to discover the addressbook home and addressbooks for a given context path. * * @param Account $account The CardDAV account * @param string $contextpath The context URI to try as starting point of the discovery * @psalm-return null|list<AddressbookCollection> * @return null|array<int,AddressbookCollection> The discovered addressbooks. */ private function tryContextPath(Account $account, string $contextpath): ?array { Config::$logger->debug("Try context path $contextpath"); // (3) Attempt a PROPFIND asking for the DAV:current-user-principal property $principalUri = $account->findCurrentUserPrincipal($contextpath); if (isset($principalUri)) { // (4) Attempt a PROPFIND asking for the addressbook home of the user on the principal URI $addressbookHomeUris = $account->findAddressbookHomes($principalUri); try { $addressbooks = []; foreach ($addressbookHomeUris ?? [] as $addressbookHomeUri) { // (5) Attempt PROPFIND (Depth 1) to discover all addressbooks of the user $addressbookHome = new WebDavCollection($addressbookHomeUri, $account); foreach ($addressbookHome->getChildren() as $abookCandidate) { if ($abookCandidate instanceof AddressbookCollection) { $addressbooks[] = $abookCandidate; } } } // We found valid addressbook homes. If they contain no addressbooks, this is fine and the // result of the discovery is an empty set. return $addressbooks; } catch (\Exception $e) { Config::$logger->info("Exception while querying addressbooks: " . $e->getMessage()); } } return null; } /** * Discovers the CardDAV service for the given domain using DNS SRV lookups. * * @param string $host A domain name to discover the service for * @param bool $force_ssl If true, only services with transport encryption (carddavs) will be discovered, * otherwise the function will try to discover unencrypted (carddav) services after failing * to discover encrypted ones. * @psalm-return list<Server> * @return array * Returns an array of associative arrays of services discovered via DNS. If nothing was found, the returned array * is empty. */ private function discoverServers(string $host, bool $force_ssl): array { $servers = []; $rrnamesAndSchemes = [ ["_carddavs._tcp.$host", 'https'] ]; if ($force_ssl === false) { $rrnamesAndSchemes[] = ["_carddav._tcp.$host", 'http']; } foreach ($rrnamesAndSchemes as $rrnameAndScheme) { list($rrname, $scheme) = $rrnameAndScheme; // query SRV records /** @psalm-var list<SrvRecord> | false */ $dnsresults = dns_get_record($rrname, DNS_SRV); if (is_array($dnsresults)) { break; } } if (is_array($dnsresults)) { usort( $dnsresults, /** * @psalm-param SrvRecord $a * @psalm-param SrvRecord $b */ function (array $a, array $b): int { if ($a['pri'] != $b['pri']) { return $b['pri'] - $a['pri']; } return $a['weight'] - $b['weight']; } ); // build results foreach ($dnsresults as $dnsres) { if (isset($dnsres['target']) && isset($dnsres['port'])) { $servers[] = [ "host" => $dnsres['target'], "port" => (string) $dnsres['port'], "scheme" => $scheme, "dnsrr" => $rrname ]; Config::$logger->info("Found server per DNS SRV $rrname: {$dnsres['target']}: {$dnsres['port']}"); } } } return $servers; } /** * Provides a list of URIs to check for discovering the location of the CardDAV service. * * The provided context paths comprise both well-known URIs as well as paths discovered via DNS TXT records. DNS TXT * lookup is only performed for servers that have themselves been discovery using DNS SRV lookups, using the same * service resource record. * * @psalm-param Server $server * @param array $server A server record (associative array) as returned by discoverServers() * @psalm-return list<string> * @return string[] The context paths that should be tried for discovery in the provided order. * @see Discovery::discoverServers() */ private function discoverContextPath(array $server): array { $contextpaths = []; if (isset($server["dnsrr"])) { /** @psalm-var list<TxtRecord> | false */ $dnsresults = dns_get_record($server["dnsrr"], DNS_TXT); if (is_array($dnsresults)) { foreach ($dnsresults as $dnsresult) { if (preg_match('/^path=(.+)/', $dnsresult['txt'] ?? "", $match)) { $contextpaths[] = $match[1]; Config::$logger->info("Discovered context path $match[1] per DNS TXT record\n"); } } } } $contextpaths[] = '/.well-known/carddav'; $contextpaths[] = '/'; $contextpaths[] = '/co'; // workaround for iCloud return $contextpaths; } } // vim: ts=4:sw=4:expandtab:fenc=utf8:ff=unix:tw=120
| ver. 1.4 |
Github
|
.
| PHP 8.1.34 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings