Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
This commit is contained in:
Daniel Kesselberg 2021-10-12 18:33:40 +02:00
Родитель 0ad54d85d7
Коммит 05320c75a0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 36E3664E099D0614
5 изменённых файлов: 294 добавлений и 87 удалений

Просмотреть файл

@ -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>