Rework IspDb to use IClient
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
This commit is contained in:
Родитель
0ad54d85d7
Коммит
05320c75a0
|
@ -26,10 +26,18 @@ declare(strict_types=1);
|
|||
namespace OCA\Mail\Service\AutoConfig;
|
||||
|
||||
use Exception;
|
||||
use OCP\Http\Client\IClient;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class IspDb {
|
||||
|
||||
/** @var string[] */
|
||||
private const SUPPORTED_TYPES = ['imap', 'smtp'];
|
||||
|
||||
/** @var IClient */
|
||||
private $client;
|
||||
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
|
@ -42,62 +50,67 @@ class IspDb {
|
|||
];
|
||||
}
|
||||
|
||||
public function __construct(LoggerInterface $logger) {
|
||||
public function __construct(IClientService $clientService, LoggerInterface $logger) {
|
||||
$this->client = $clientService->newClient();
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* Query IspDb for the given url
|
||||
*/
|
||||
private function queryUrl(string $url): array {
|
||||
try {
|
||||
$content = @file_get_contents($url, false, stream_context_create([
|
||||
'http' => [
|
||||
'timeout' => 7
|
||||
]
|
||||
]));
|
||||
if ($content !== false) {
|
||||
$xml = @simplexml_load_string($content);
|
||||
} else {
|
||||
$this->logger->debug("IsbDb: <$url> request timed out");
|
||||
return [];
|
||||
}
|
||||
|
||||
if (libxml_get_last_error() !== false || !is_object($xml) || !$xml->emailProvider) {
|
||||
libxml_clear_errors();
|
||||
return [];
|
||||
}
|
||||
$provider = [
|
||||
'displayName' => (string)$xml->emailProvider->displayName,
|
||||
];
|
||||
foreach ($xml->emailProvider->children() as $tag => $server) {
|
||||
if (!in_array($tag, ['incomingServer', 'outgoingServer'])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($server->attributes() as $name => $value) {
|
||||
if ($name === 'type') {
|
||||
$type = (string)$value;
|
||||
}
|
||||
}
|
||||
$data = [];
|
||||
foreach ($server as $name => $value) {
|
||||
foreach ($value->children() as $tag => $val) {
|
||||
$data[$name][$tag] = (string)$val;
|
||||
}
|
||||
if (!isset($data[$name])) {
|
||||
$data[$name] = (string)$value;
|
||||
}
|
||||
}
|
||||
$provider[$type][] = $data;
|
||||
}
|
||||
$xml = $this->client->get($url, [
|
||||
'timeout' => 7,
|
||||
])->getBody();
|
||||
} catch (Exception $e) {
|
||||
// ignore own not-found exception or xml parsing exceptions
|
||||
unset($e);
|
||||
$provider = [];
|
||||
$this->logger->debug('IsbDb: <' . $url . '> failed with "' . $e->getMessage() . '"', [
|
||||
'exception' => $e,
|
||||
]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = simplexml_load_string($xml);
|
||||
if ($data === false || !isset($data->emailProvider)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$provider = [
|
||||
'displayName' => (string)$data->emailProvider->displayName,
|
||||
'imap' => [],
|
||||
'smtp' => [],
|
||||
];
|
||||
|
||||
foreach ($data->emailProvider->incomingServer as $server) {
|
||||
$type = (string)$server['type'];
|
||||
if (in_array($type, self::SUPPORTED_TYPES)) {
|
||||
$provider[$type][] = $this->convertServerElement($server);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data->emailProvider->outgoingServer as $server) {
|
||||
$type = (string)$server['type'];
|
||||
if (in_array($type, self::SUPPORTED_TYPES)) {
|
||||
$provider[$type][] = $this->convertServerElement($server);
|
||||
}
|
||||
}
|
||||
|
||||
return $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an incomingServer and outgoingServer xml element to array.
|
||||
*/
|
||||
private function convertServerElement(\SimpleXMLElement $server): array {
|
||||
return [
|
||||
'hostname' => (string)$server->hostname,
|
||||
'port' => (int)$server->port,
|
||||
'socketType' => (string)$server->socketType,
|
||||
'username' => (string)$server->username,
|
||||
'authentication' => (string)$server->authentication,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $domain
|
||||
* @param bool $tryMx
|
||||
|
@ -112,11 +125,10 @@ class IspDb {
|
|||
|
||||
$provider = [];
|
||||
foreach ($this->getUrls() as $url) {
|
||||
$url = str_replace("{DOMAIN}", $domain, $url);
|
||||
$url = str_replace("{EMAIL}", $email, $url);
|
||||
if (strpos($url, "{SCHEME}") !== false) {
|
||||
foreach (['https', 'http'] as $scheme) {
|
||||
$completeurl = str_replace("{SCHEME}", $scheme, $url);
|
||||
$url = str_replace(['{DOMAIN}', '{EMAIL}'], [$domain, $email], $url);
|
||||
if (strpos($url, '{SCHEME}') !== false) {
|
||||
foreach (['https', 'http'] as $scheme) {
|
||||
$completeurl = str_replace('{SCHEME}', $scheme, $url);
|
||||
$this->logger->debug("IsbDb: querying <$domain> via <$completeurl>");
|
||||
$provider = $this->queryUrl($completeurl);
|
||||
if (!empty($provider)) {
|
||||
|
|
|
@ -25,25 +25,33 @@ declare(strict_types=1);
|
|||
namespace OCA\Mail\Tests\Unit\Service\Autoconfig;
|
||||
|
||||
use ChristophWurst\Nextcloud\Testing\TestCase;
|
||||
use OCA\Mail\Service\AutoConfig\IspDb;
|
||||
use OCP\Http\Client\IClient;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\Http\Client\IResponse;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class IspDbTest extends TestCase {
|
||||
|
||||
/** @var MockObject|LoggerInterface */
|
||||
/** @var IClientService|MockObject */
|
||||
private $clientService;
|
||||
|
||||
/** @var IClient|MockObject */
|
||||
private $client;
|
||||
|
||||
/** @var LoggerInterface|MockObject */
|
||||
private $logger;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->clientService = $this->createMock(IClientService::class);
|
||||
$this->client = $this->createMock(IClient::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
}
|
||||
|
||||
public function queryData() {
|
||||
return [
|
||||
['gmail.com', 'user@gmail.com',],
|
||||
['outlook.com', 'user@outlook.com',],
|
||||
];
|
||||
$this->clientService->method('newClient')
|
||||
->willReturn($this->client);
|
||||
}
|
||||
|
||||
public function fakeAutoconfigData() {
|
||||
|
@ -53,42 +61,72 @@ class IspDbTest extends TestCase {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider fakeAutoconfigData
|
||||
*/
|
||||
public function testQueryFakeAutoconfig(string $domain, string $email, bool $shouldSucceed) {
|
||||
$urls = [
|
||||
dirname(__FILE__) . '/../../../resources/autoconfig-freenet.xml',
|
||||
];
|
||||
$ispDb = $this->getIspDbMock($urls);
|
||||
public function testQueryGmx(): void {
|
||||
$this->client->method('get')
|
||||
->willReturnCallback(function ($url) {
|
||||
switch ($url) {
|
||||
case 'https://autoconfig.gmx.com/mail/config-v1.1.xml?emailaddress=test@gmx.com':
|
||||
case 'http://autoconfig.gmx.com/mail/config-v1.1.xml?emailaddress=test@gmx.com':
|
||||
throw new \Exception('cURL error 6: Could not resolve host: autoconfig.gmx.com');
|
||||
case 'https://gmx.com/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=test@gmx.com':
|
||||
case 'http://gmx.com/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=test@gmx.com':
|
||||
throw new \Exception('Client error: `GET https://gmx.com/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=test@gmx.com` resulted in a `404 Not Found` response');
|
||||
case 'https://autoconfig.thunderbird.net/v1.1/gmx.com':
|
||||
$response = $this->createMock(IResponse::class);
|
||||
$response->method('getBody')->willReturn(file_get_contents(__DIR__ . '/../../../resources/autoconfig-gmx.xml'));
|
||||
return $response;
|
||||
}
|
||||
});
|
||||
|
||||
$result = $ispDb->query($domain, $email);
|
||||
$ispDb = new IspDb($this->clientService, $this->logger);
|
||||
|
||||
if ($shouldSucceed) {
|
||||
$this->assertContainsIspData($result);
|
||||
} else {
|
||||
$this->assertEmpty($result);
|
||||
}
|
||||
$providers = $ispDb->query('gmx.com', 'test@gmx.com');
|
||||
|
||||
$this->assertEquals('GMX Freemail', $providers['displayName']);
|
||||
$this->assertCount(2, $providers['imap']);
|
||||
$this->assertCount(2, $providers['smtp']);
|
||||
}
|
||||
|
||||
private function getIspDbMock($urls) {
|
||||
$mock = $this->getMockBuilder('\OCA\Mail\Service\AutoConfig\IspDb')
|
||||
->setMethods(['getUrls'])
|
||||
->setConstructorArgs([$this->logger])
|
||||
->getMock();
|
||||
$mock->expects($this->once())
|
||||
->method('getUrls')
|
||||
->will($this->returnValue($urls));
|
||||
return $mock;
|
||||
public function testQueryOutlook(): void {
|
||||
$this->client->method('get')
|
||||
->willReturnCallback(function ($url) {
|
||||
switch ($url) {
|
||||
case 'https://autoconfig.outlook.com/mail/config-v1.1.xml?emailaddress=test@outlook.com':
|
||||
case 'http://autoconfig.outlook.com/mail/config-v1.1.xml?emailaddress=test@outlook.com':
|
||||
throw new \Exception('cURL error 6: Could not resolve host: autoconfig.outlook.com');
|
||||
case 'https://outlook.com/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=test@outlook.com':
|
||||
case 'http://outlook.com/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=test@outlook.com':
|
||||
throw new \Exception('Client error: `GET https://outlook.com/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=test@outlook.com` resulted in a `404 Not Found` response');
|
||||
case 'https://autoconfig.thunderbird.net/v1.1/outlook.com':
|
||||
$response = $this->createMock(IResponse::class);
|
||||
$response->method('getBody')->willReturn(file_get_contents(__DIR__ . '/../../../resources/autoconfig-outlook.xml'));
|
||||
return $response;
|
||||
}
|
||||
});
|
||||
|
||||
$ispDb = new IspDb($this->clientService, $this->logger);
|
||||
|
||||
$providers = $ispDb->query('outlook.com', 'test@outlook.com');
|
||||
|
||||
$this->assertEquals('Microsoft', $providers['displayName']);
|
||||
$this->assertCount(1, $providers['imap']);
|
||||
$this->assertCount(1, $providers['smtp']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo check actual values
|
||||
*/
|
||||
private function assertContainsIspData($data) {
|
||||
$this->assertArrayHasKey('imap', $data);
|
||||
$this->assertTrue(count($data['imap']) >= 1, 'no isp imap data returned');
|
||||
$this->assertArrayHasKey('smtp', $data);
|
||||
$this->assertTrue(count($data['smtp']) >= 1, 'no isp smtp data returned');
|
||||
public function testQueryPosteo(): void {
|
||||
$this->client->method('get')
|
||||
->willReturnCallback(function () {
|
||||
$response = $this->createMock(IResponse::class);
|
||||
$response->method('getBody')->willReturn(file_get_contents(__DIR__ . '/../../../resources/autoconfig-posteo.xml'));
|
||||
return $response;
|
||||
});
|
||||
|
||||
$ispDb = new IspDb($this->clientService, $this->logger);
|
||||
|
||||
$providers = $ispDb->query('posteo.org', 'test@postdeo.org');
|
||||
|
||||
$this->assertEquals('Posteo', $providers['displayName']);
|
||||
$this->assertCount(1, $providers['imap']);
|
||||
$this->assertCount(1, $providers['smtp']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<clientConfig version="1.1">
|
||||
<emailProvider id="gmx.com">
|
||||
<domain>gmx.com</domain>
|
||||
<domain>gmx.tm</domain>
|
||||
<domain>gmx.us</domain>
|
||||
<domain>gmx.co.uk</domain>
|
||||
<domain>gmx.es</domain>
|
||||
<domain>gmx.fr</domain>
|
||||
<domain>gmx.ca</domain>
|
||||
<domain>gmx.cn</domain>
|
||||
<domain>gmx.co.in</domain>
|
||||
<domain>gmx.com.br</domain>
|
||||
<domain>gmx.com.my</domain>
|
||||
<domain>gmx.hk</domain>
|
||||
<domain>gmx.ie</domain>
|
||||
<domain>gmx.ph</domain>
|
||||
<domain>gmx.pt</domain>
|
||||
<domain>gmx.ru</domain>
|
||||
<domain>gmx.se</domain>
|
||||
<domain>gmx.sg</domain>
|
||||
<domain>gmx.tw</domain>
|
||||
<domain>gmx.com.tr</domain>
|
||||
<domain>gmx.it</domain>
|
||||
<domain>gmx.li</domain>
|
||||
<!-- gmx.net is same company, but different access servers -->
|
||||
<displayName>GMX Freemail</displayName>
|
||||
<displayShortName>GMX</displayShortName>
|
||||
<incomingServer type="imap">
|
||||
<hostname>imap.gmx.com</hostname>
|
||||
<port>993</port>
|
||||
<socketType>SSL</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<authentication>password-cleartext</authentication>
|
||||
</incomingServer>
|
||||
<incomingServer type="imap">
|
||||
<hostname>imap.gmx.com</hostname>
|
||||
<port>143</port>
|
||||
<socketType>STARTTLS</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<authentication>password-cleartext</authentication>
|
||||
</incomingServer>
|
||||
<incomingServer type="pop3">
|
||||
<hostname>pop.gmx.com</hostname>
|
||||
<port>995</port>
|
||||
<socketType>SSL</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<authentication>password-cleartext</authentication>
|
||||
</incomingServer>
|
||||
<incomingServer type="pop3">
|
||||
<hostname>pop.gmx.com</hostname>
|
||||
<port>110</port>
|
||||
<socketType>STARTTLS</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<authentication>password-cleartext</authentication>
|
||||
</incomingServer>
|
||||
<outgoingServer type="smtp">
|
||||
<hostname>mail.gmx.com</hostname>
|
||||
<port>465</port>
|
||||
<socketType>SSL</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<authentication>password-cleartext</authentication>
|
||||
</outgoingServer>
|
||||
<outgoingServer type="smtp">
|
||||
<hostname>mail.gmx.com</hostname>
|
||||
<port>587</port>
|
||||
<socketType>STARTTLS</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<authentication>password-cleartext</authentication>
|
||||
</outgoingServer>
|
||||
</emailProvider>
|
||||
</clientConfig>
|
|
@ -0,0 +1,53 @@
|
|||
<clientConfig version="1.1">
|
||||
<emailProvider id="outlook.com">
|
||||
<!-- This is a temporary config until bug 1185366 is fixed.
|
||||
Without the fix, this config is fetched both for
|
||||
for all (!) Office365 users, due to the MX setup.
|
||||
So, this config here is mostly adjusted to Office365 users.
|
||||
It is unfortunately also used for foo@outlook.com email addresses
|
||||
(and only this exact domain, not outlook.de, not hotmail.com etc.),
|
||||
Bug 1185366 fixes this and means that Office365 will
|
||||
fetch the separate office365.com config.
|
||||
Once that fix is deployed to 95+% of end users,
|
||||
remove this config and add the outlook.com domain
|
||||
to the hotmail.com config. -->
|
||||
<domain>outlook.com</domain>
|
||||
<displayName>Microsoft</displayName>
|
||||
<displayShortName>Microsoft</displayShortName>
|
||||
<incomingServer type="imap">
|
||||
<hostname>outlook.office365.com</hostname>
|
||||
<port>993</port>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
</incomingServer>
|
||||
<incomingServer type="pop3">
|
||||
<hostname>outlook.office365.com</hostname>
|
||||
<port>995</port>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<pop3>
|
||||
<leaveMessagesOnServer>true</leaveMessagesOnServer>
|
||||
<!-- Outlook.com docs specifically mention that POP3 deletes have effect on the main inbox on webmail and IMAP -->
|
||||
</pop3>
|
||||
</incomingServer>
|
||||
<incomingServer type="exchange">
|
||||
<hostname>outlook.office365.com</hostname>
|
||||
<port>443</port>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>OAuth2</authentication>
|
||||
<owaURL>https://outlook.office365.com/owa/</owaURL>
|
||||
<ewsURL>https://outlook.office365.com/ews/exchange.asmx</ewsURL>
|
||||
<useGlobalPreferredServer>true</useGlobalPreferredServer>
|
||||
</incomingServer>
|
||||
<outgoingServer type="smtp">
|
||||
<hostname>smtp.office365.com</hostname>
|
||||
<port>587</port>
|
||||
<socketType>STARTTLS</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
</outgoingServer>
|
||||
</emailProvider>
|
||||
</clientConfig>
|
|
@ -0,0 +1,33 @@
|
|||
<clientConfig version="1.1">
|
||||
<emailProvider id="posteo.de">
|
||||
<domain>posteo.de</domain>
|
||||
<domain>posteo.at</domain>
|
||||
<domain>posteo.ch</domain>
|
||||
<domain>posteo.org</domain>
|
||||
<domain>posteo.eu</domain>
|
||||
<domain>posteo.ie</domain>
|
||||
<displayName>Posteo</displayName>
|
||||
<displayShortName>Posteo</displayShortName>
|
||||
<incomingServer type="imap">
|
||||
<hostname>posteo.de</hostname>
|
||||
<port>993</port>
|
||||
<socketType>SSL</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<authentication>password-cleartext</authentication>
|
||||
</incomingServer>
|
||||
<incomingServer type="pop3">
|
||||
<hostname>posteo.de</hostname>
|
||||
<port>995</port>
|
||||
<socketType>SSL</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<authentication>password-cleartext</authentication>
|
||||
</incomingServer>
|
||||
<outgoingServer type="smtp">
|
||||
<hostname>posteo.de</hostname>
|
||||
<port>465</port>
|
||||
<socketType>SSL</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<authentication>password-cleartext</authentication>
|
||||
</outgoingServer>
|
||||
</emailProvider>
|
||||
</clientConfig>
|
Загрузка…
Ссылка в новой задаче