Test Core::getNoCacheHeaders() with a frozen clock

This avoids the tests randomly failing when current time differs.

Also changes the format from RFC 1123 to RFC 7231.

Signed-off-by: Maurício Meneghini Fauth <mauricio@mfauth.net>
This commit is contained in:
Maurício Meneghini Fauth 2026-01-18 22:16:52 -03:00
parent 0ae02437ae
commit d152e71161
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
4 changed files with 56 additions and 37 deletions

View File

@ -4,6 +4,9 @@ declare(strict_types=1);
namespace PhpMyAdmin;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use PhpMyAdmin\Error\ErrorHandler;
use PhpMyAdmin\Exceptions\MissingExtensionException;
use PhpMyAdmin\Http\ServerRequest;
@ -18,7 +21,6 @@ use function explode;
use function filter_var;
use function function_exists;
use function getenv;
use function gmdate;
use function hash_equals;
use function hash_hmac;
use function header;
@ -45,7 +47,6 @@ use function substr;
use function unserialize;
use function urldecode;
use const DATE_RFC1123;
use const FILTER_VALIDATE_IP;
/**
@ -209,10 +210,10 @@ class Core
*
* @return array<string, string>
*/
public static function headerJSON(): array
public static function headerJSON(string $currentDateTime = 'now'): array
{
// No caching
$headers = self::getNoCacheHeaders();
$headers = self::getNoCacheHeaders($currentDateTime);
// Media type
$headers['Content-Type'] = 'application/json; charset=UTF-8';
@ -227,13 +228,15 @@ class Core
}
/** @return array<string, string> */
public static function getNoCacheHeaders(): array
public static function getNoCacheHeaders(string $currentDateTime = 'now'): array
{
$headers = [];
$date = gmdate(DATE_RFC1123);
$formattedDateTime = (new DateTimeImmutable($currentDateTime))
->setTimezone(new DateTimeZone('UTC'))
->format(DateTimeInterface::RFC7231);
// rfc2616 - Section 14.21
$headers['Expires'] = $date;
$headers['Expires'] = $formattedDateTime;
// HTTP/1.1
$headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0';
@ -244,7 +247,7 @@ class Core
// test case: exporting a database into a .gz file with Safari
// would produce files not having the current time
// (added this header for Safari but should not harm other browsers)
$headers['Last-Modified'] = $date;
$headers['Last-Modified'] = $formattedDateTime;
return $headers;
}
@ -253,7 +256,6 @@ class Core
* @param string $filename Filename to include in headers if empty, none Content-Disposition header will be sent.
* @param string $mimetype MIME type to include in headers.
* @param int $length Length of content (optional)
* @param bool $noCache Whether to include no-caching headers.
*
* @return array<string, string>
*/
@ -261,13 +263,9 @@ class Core
string $filename,
string $mimetype,
int $length = 0,
bool $noCache = true,
string $currentDateTime = 'now',
): array {
$headers = [];
if ($noCache) {
$headers = self::getNoCacheHeaders();
}
$headers = self::getNoCacheHeaders($currentDateTime);
/* Replace all possibly dangerous chars in filename */
$filename = Sanitize::sanitizeFilename($filename);
@ -293,15 +291,10 @@ class Core
* @param string $filename Filename to include in headers if empty, none Content-Disposition header will be sent.
* @param string $mimetype MIME type to include in headers.
* @param int $length Length of content (optional)
* @param bool $noCache Whether to include no-caching headers.
*/
public static function downloadHeader(
string $filename,
string $mimetype,
int $length = 0,
bool $noCache = true,
): void {
$headers = self::getDownloadHeaders($filename, $mimetype, $length, $noCache);
public static function downloadHeader(string $filename, string $mimetype, int $length = 0): void
{
$headers = self::getDownloadHeaders($filename, $mimetype, $length);
// The default output in PMA uses gzip,
// so if we want to output uncompressed file, we should reset the encoding.

View File

@ -335,7 +335,7 @@ class Header
}
/** @return array<string, string> */
public function getHttpHeaders(): array
public function getHttpHeaders(string $currentDateTime = 'now'): array
{
$headers = [];
@ -388,7 +388,7 @@ class Header
*/
$headers['Permissions-Policy'] = 'fullscreen=(self), interest-cohort=()';
$headers = array_merge($headers, Core::getNoCacheHeaders());
$headers = array_merge($headers, Core::getNoCacheHeaders($currentDateTime));
/**
* A different Content-Type is set in {@see \PhpMyAdmin\Controllers\Transformation\WrapperController}.

View File

@ -658,9 +658,13 @@ class CoreTest extends AbstractTestCase
public function testGetDownloadHeaders(): void
{
$headersList = Core::getDownloadHeaders('test.sql', 'text/x-sql', 100, false);
$headersList = Core::getDownloadHeaders('test.sql', 'text/x-sql', 100, '2015-10-21T05:28:00-02:00');
$expected = [
'Expires' => 'Wed, 21 Oct 2015 07:28:00 GMT',
'Cache-Control' => 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
'Pragma' => 'no-cache',
'Last-Modified' => 'Wed, 21 Oct 2015 07:28:00 GMT',
'Content-Description' => 'File Transfer',
'Content-Disposition' => 'attachment; filename="test.sql"',
'Content-Type' => 'text/x-sql',
@ -672,9 +676,13 @@ class CoreTest extends AbstractTestCase
public function testGetDownloadHeaders2(): void
{
$headersList = Core::getDownloadHeaders('test.sql.gz', 'application/x-gzip', 0, false);
$headersList = Core::getDownloadHeaders('test.sql.gz', 'application/x-gzip', 0, '2015-10-21T05:28:00-02:00');
$expected = [
'Expires' => 'Wed, 21 Oct 2015 07:28:00 GMT',
'Cache-Control' => 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
'Pragma' => 'no-cache',
'Last-Modified' => 'Wed, 21 Oct 2015 07:28:00 GMT',
'Content-Description' => 'File Transfer',
'Content-Disposition' => 'attachment; filename="test.sql.gz"',
'Content-Type' => 'application/x-gzip',
@ -702,4 +710,28 @@ class CoreTest extends AbstractTestCase
self::assertSame('', Core::getEnv('PHPMYADMIN_GET_ENV_TEST'));
}
public function testGetNoCacheHeaders(): void
{
$expected = [
'Expires' => 'Wed, 21 Oct 2015 07:28:00 GMT',
'Cache-Control' => 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
'Pragma' => 'no-cache',
'Last-Modified' => 'Wed, 21 Oct 2015 07:28:00 GMT',
];
self::assertSame($expected, Core::getNoCacheHeaders('2015-10-21T05:28:00-02:00'));
}
public function testHeaderJSON(): void
{
$expected = [
'Expires' => 'Wed, 21 Oct 2015 07:28:00 GMT',
'Cache-Control' => 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
'Pragma' => 'no-cache',
'Last-Modified' => 'Wed, 21 Oct 2015 07:28:00 GMT',
'Content-Type' => 'application/json; charset=UTF-8',
'X-Content-Type-Options' => 'nosniff',
];
self::assertSame($expected, Core::headerJSON('2015-10-21T05:28:00-02:00'));
}
}

View File

@ -23,10 +23,6 @@ use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Medium;
use ReflectionProperty;
use function gmdate;
use const DATE_RFC1123;
#[CoversClass(Header::class)]
#[Medium]
class HeaderTest extends AbstractTestCase
@ -193,7 +189,6 @@ class HeaderTest extends AbstractTestCase
string $expectedWebKitCsp,
): void {
$header = $this->getNewHeaderInstance();
$date = gmdate(DATE_RFC1123);
$config = Config::getInstance();
$config->set('AllowThirdPartyFraming', $frameOptions);
@ -203,7 +198,7 @@ class HeaderTest extends AbstractTestCase
$config->set('CaptchaCsp', $captchaCsp);
$expected = [
'X-Frame-Options' => $expectedFrameOptions,
'X-Frame-Options' => $expectedFrameOptions ?? '',
'Referrer-Policy' => 'same-origin',
'Content-Security-Policy' => $expectedCsp,
'X-Content-Security-Policy' => $expectedXCsp,
@ -213,18 +208,17 @@ class HeaderTest extends AbstractTestCase
'X-Permitted-Cross-Domain-Policies' => 'none',
'X-Robots-Tag' => 'noindex, nofollow',
'Permissions-Policy' => 'fullscreen=(self), interest-cohort=()',
'Expires' => $date,
'Expires' => 'Wed, 21 Oct 2015 07:28:00 GMT',
'Cache-Control' => 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
'Pragma' => 'no-cache',
'Last-Modified' => $date,
'Last-Modified' => 'Wed, 21 Oct 2015 07:28:00 GMT',
'Content-Type' => 'text/html; charset=utf-8',
];
if ($expectedFrameOptions === null) {
unset($expected['X-Frame-Options']);
}
$headers = $this->callFunction($header, Header::class, 'getHttpHeaders', []);
self::assertSame($expected, $headers);
self::assertSame($expected, $header->getHttpHeaders('2015-10-21T05:28:00-02:00'));
}
/** @return mixed[][] */