mirror of
https://github.com/roundcube/roundcubemail.git
synced 2026-03-03 06:44:03 +01:00
This includes a new "message loading" notice without meta refresh (which requires unsafe-inline in a CSP, which we want to avoid)
160 lines
6.1 KiB
PHP
160 lines
6.1 KiB
PHP
<?php
|
||
|
||
namespace Tests\MessageRendering;
|
||
|
||
use Masterminds\HTML5;
|
||
use Roundcube\Tests\ActionTestCase;
|
||
use Roundcube\Tests\ExitException;
|
||
|
||
/**
|
||
* Class to base actual test classes on, which test specific message rendering.
|
||
*/
|
||
class MessageRenderingTestCase extends ActionTestCase
|
||
{
|
||
/**
|
||
* Get the body from the document, trimmed from surrounding whitespace.
|
||
*/
|
||
protected function getBody(\DOMXPath $domxpath): string
|
||
{
|
||
$bodyElem = $domxpath->query('//div[@id="messagebody"]');
|
||
$this->assertCount(1, $bodyElem, 'Message body');
|
||
return trim($bodyElem[0]->textContent);
|
||
}
|
||
|
||
/**
|
||
* Get the subject from the document, stripped by the prefix "Subject: ",
|
||
* the suffix "Open in new window", and trimmed from surrounding whitespace.
|
||
*/
|
||
protected function getScrubbedSubject(\DOMXPath $domxpath): string
|
||
{
|
||
$subjectElem = $domxpath->query('//h2[@class="subject"][1]');
|
||
$subject = preg_replace('/^\s*Subject:\s*(.*)\s*Open in new window$/', '$1', trim($subjectElem[0]->textContent));
|
||
return trim($subject);
|
||
}
|
||
|
||
/**
|
||
* Execute run() to render the message with the given $msgId.
|
||
*
|
||
* This is useful to check how rcmail_action_mail_show() renders messages.
|
||
* It requires a running dovecot to fetch the messages from. Messages need
|
||
* to be placed as individual files in
|
||
* `tests/src/emails/test@example/Mail/cur/`.
|
||
*/
|
||
protected function renderIframedBodyContent(array $params): \DOMXPath
|
||
{
|
||
return $this->runActionAndGetHtmlOutputDomxpath('get', function ($storage) use ($params) {
|
||
// rcmail_action_mail_get calls rcmail_attachment_handler, which
|
||
// closes one layer of output buffering, probably because index.php
|
||
// always starts one and rcmail_attachment_handler wants to close
|
||
// that. In order to avoid our tests being "risky" and thus marked
|
||
// as problematic, we need to simulate the opened output buffer
|
||
// layer.
|
||
ob_start();
|
||
$action = new \rcmail_action_mail_get();
|
||
$this->assertTrue($action->checks());
|
||
$_GET = $params;
|
||
$action->run();
|
||
});
|
||
}
|
||
|
||
protected function renderMessage(string $msgId): \DOMXPath
|
||
{
|
||
return $this->runActionAndGetHtmlOutputDomxpath('preview', function ($storage) use ($msgId) {
|
||
$action = new \rcmail_action_mail_show();
|
||
$this->assertTrue($action->checks());
|
||
|
||
$messagesList = $storage->list_messages();
|
||
|
||
// Find the UID of the wanted message.
|
||
$messageUid = null;
|
||
// TODO: find a method that scales better.
|
||
foreach ($messagesList as $messageHeaders) {
|
||
if ($messageHeaders->get('message-id') === "<{$msgId}>") {
|
||
$messageUid = $messageHeaders->uid;
|
||
break;
|
||
}
|
||
}
|
||
if ($messageUid === null) {
|
||
throw new \Exception("No message found in messages list with Message-Id '{$msgId}'");
|
||
}
|
||
|
||
$_GET = ['_uid' => $messageUid];
|
||
$action->run();
|
||
});
|
||
}
|
||
|
||
protected function runActionAndGetHtmlOutputDomxpath(string $urlActionParam, \Closure $callback): \DOMXPath
|
||
{
|
||
$imap_host = getenv('RC_CONFIG_IMAP_HOST') ?: 'tls://localhost:143';
|
||
$rcmail = \rcmail::get_instance();
|
||
// We need to overwrite the storage object, else storage_init() just
|
||
// returns the cached one (which might be a StorageMock instance).
|
||
$mockStorage = $rcmail->storage;
|
||
$rcmail->storage = null;
|
||
$rcmail->storage_init();
|
||
// Login our test user so we can fetch messages from the imap server.
|
||
$rcmail->login('test-message-rendering@localhost', 'pass', $imap_host);
|
||
$storage = $rcmail->get_storage();
|
||
$storage->set_options(['all_headers' => true]);
|
||
// We need to set the folder, else no message can be fetched.
|
||
$storage->set_folder('INBOX');
|
||
|
||
$this->initOutput(\rcmail_action::MODE_HTTP, 'mail', $urlActionParam);
|
||
// TODO: Why do we need to set the skin manually?
|
||
$rcmail->output->set_skin('elastic');
|
||
|
||
// Prepare and trigger the rendering.
|
||
$html = '';
|
||
try {
|
||
$callback($storage);
|
||
} catch (ExitException $e) {
|
||
// phpstan complains that the output classes of the production code
|
||
// don't provide that method – which is true, but not relevant
|
||
// here.
|
||
// @phpstan-ignore-next-line
|
||
$html = $rcmail->output->getOutput();
|
||
}
|
||
|
||
// Reset the storage to the mocked one most other tests expect.
|
||
$rcmail->storage = $mockStorage;
|
||
|
||
// disabled_html_ns=true is a workaround for the performance issue
|
||
// https://github.com/Masterminds/html5-php/issues/181
|
||
$html5 = new HTML5(['disable_html_ns' => true]);
|
||
return new \DOMXPath($html5->loadHTML($html));
|
||
}
|
||
|
||
protected function getSrcParams(\DOMNode $elem): array
|
||
{
|
||
$src = $elem->attributes->getNamedItem('data-src')->textContent;
|
||
$url = parse_url($src, \PHP_URL_QUERY);
|
||
parse_str($url, $params);
|
||
return $params;
|
||
}
|
||
|
||
protected function getIframedContent(array $params): string
|
||
{
|
||
$domxpath_body = $this->renderIframedBodyContent($params);
|
||
$bodyElem = $domxpath_body->query('//body');
|
||
$this->assertCount(1, $bodyElem, 'Message body');
|
||
return $bodyElem[0]->textContent;
|
||
}
|
||
|
||
protected function assertIframedContent(\DOMNode $elem, string $partId, string $content): void
|
||
{
|
||
$params = $this->getSrcParams($elem);
|
||
$this->assertSrcUrlParams($params, $partId);
|
||
$body = $this->getIframedContent($params);
|
||
$this->assertSame($content, trim($body));
|
||
}
|
||
|
||
protected function assertSrcUrlParams(array $params, string $partId): void
|
||
{
|
||
$this->assertSame('mail', $params['_task']);
|
||
$this->assertSame('get', $params['_action']);
|
||
$this->assertSame('INBOX', $params['_mbox']);
|
||
$this->assertMatchesRegularExpression('/^\d+$/', $params['_uid']);
|
||
$this->assertSame($partId, $params['_part']);
|
||
}
|
||
}
|