Commit e859ce49 authored by Oliver Hader's avatar Oliver Hader Committed by Oliver Hader
Browse files

[TASK] Streamline server response check

* replace string states with specific StatusMessage models
* combine file path and base URL in new FileLocation model
* streamline responsibilities of classes

Resolves: #92834
Releases: master, 10.4, 9.5
Change-Id: Ib1a24fb00d4362062e88f93f236b3fd385015c3c
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/66624


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
parent 3fa3dd9e
......@@ -26,16 +26,17 @@ use Psr\Http\Message\ResponseInterface;
*/
class FileDeclaration
{
public const MISMATCH_EXPECTED_CONTENT_TYPE = 'expectedContentType';
public const MISMATCH_UNEXPECTED_CONTENT_TYPE = 'unexpectedContentType';
public const MISMATCH_EXPECTED_CONTENT = 'expectedContent';
public const MISMATCH_UNEXPECTED_CONTENT = 'unexpectedContent';
public const FLAG_BUILD_HTML = 1;
public const FLAG_BUILD_PHP = 2;
public const FLAG_BUILD_SVG = 4;
public const FLAG_BUILD_HTML_DOCUMENT = 64;
public const FLAG_BUILD_SVG_DOCUMENT = 128;
/**
* @var FileLocation
*/
protected $fileLocation;
/**
* @var string
*/
......@@ -71,8 +72,9 @@ class FileDeclaration
*/
protected $buildFlags = self::FLAG_BUILD_HTML | self::FLAG_BUILD_HTML_DOCUMENT;
public function __construct(string $fileName, bool $fail = false)
public function __construct(FileLocation $fileLocation, string $fileName, bool $fail = false)
{
$this->fileLocation = $fileLocation;
$this->fileName = $fileName;
$this->fail = $fail;
}
......@@ -107,24 +109,44 @@ class FileDeclaration
return $this->getMismatches($response) === [];
}
/**
* @param ResponseInterface $response
* @return StatusMessage[]
*/
public function getMismatches(ResponseInterface $response): array
{
$mismatches = [];
$body = (string)$response->getBody();
$contentType = $response->getHeaderLine('content-type');
if ($this->expectedContent !== null && strpos($body, $this->expectedContent) === false) {
$mismatches[] = self::MISMATCH_EXPECTED_CONTENT;
$mismatches[] = new StatusMessage(
'content mismatch %s',
$this->expectedContent,
$body
);
}
if ($this->unexpectedContent !== null && strpos($body, $this->unexpectedContent) !== false) {
$mismatches[] = self::MISMATCH_UNEXPECTED_CONTENT;
$mismatches[] = new StatusMessage(
'unexpected content %s',
$this->unexpectedContent,
$body
);
}
if ($this->expectedContentType !== null
&& strpos($contentType . ';', $this->expectedContentType . ';') !== 0) {
$mismatches[] = self::MISMATCH_EXPECTED_CONTENT_TYPE;
$mismatches[] = new StatusMessage(
'content-type mismatch %s, got %s',
$this->expectedContentType,
$contentType
);
}
if ($this->unexpectedContentType !== null
&& strpos($contentType . ';', $this->unexpectedContentType . ';') === 0) {
$mismatches[] = self::MISMATCH_UNEXPECTED_CONTENT_TYPE;
$mismatches[] = new StatusMessage(
'unexpected content-type %s',
$this->unexpectedContentType,
$contentType
);
}
return $mismatches;
}
......@@ -164,6 +186,14 @@ class FileDeclaration
return $target;
}
/**
* @return FileLocation
*/
public function getFileLocation(): FileLocation
{
return $this->fileLocation;
}
/**
* @return string
*/
......@@ -172,6 +202,14 @@ class FileDeclaration
return $this->fileName;
}
/**
* @return string
*/
public function getUrl(): string
{
return $this->fileLocation->getBaseUrl() . $this->fileName;
}
/**
* @return bool
*/
......
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Install\SystemEnvironment\ServerResponse;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* References local file path and corresponding HTTP base URL
*
* @internal should only be used from within TYPO3 Core
*/
class FileLocation
{
/**
* @var string
*/
protected $path;
/**
* @var string
*/
protected $filePath;
/**
* @var string
*/
protected $baseUrl;
public function __construct(string $path)
{
$this->path = $path;
$this->filePath = Environment::getPublicPath() . $path;
$this->baseUrl = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST')
. PathUtility::getAbsoluteWebPath($this->filePath);
}
/**
* @return string
*/
public function getFilePath(): string
{
return $this->filePath;
}
/**
* @return string
*/
public function getBaseUrl(): string
{
return $this->baseUrl;
}
}
......@@ -21,11 +21,9 @@ use GuzzleHttp\Client;
use GuzzleHttp\Exception\BadResponseException;
use function GuzzleHttp\Promise\settle;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Install\SystemEnvironment\CheckInterface;
use TYPO3\CMS\Reports\Status;
......@@ -37,6 +35,9 @@ use TYPO3\CMS\Reports\Status;
*/
class ServerResponseCheck implements CheckInterface
{
protected const WRAP_FLAT = 1;
protected const WRAP_NESTED = 2;
/**
* @var bool
*/
......@@ -48,14 +49,9 @@ class ServerResponseCheck implements CheckInterface
protected $messageQueue;
/**
* @var string
*/
protected $filePath;
/**
* @var string
* @var FileLocation
*/
protected $baseUrl;
protected $assetLocation;
/**
* @var FileDeclaration[]
......@@ -68,10 +64,7 @@ class ServerResponseCheck implements CheckInterface
$fileName = bin2hex(random_bytes(4));
$folderName = bin2hex(random_bytes(4));
$this->filePath = Environment::getPublicPath()
. sprintf('/typo3temp/assets/%s.tmp/', $folderName);
$this->baseUrl = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST')
. PathUtility::getAbsoluteWebPath($this->filePath);
$this->assetLocation = new FileLocation(sprintf('/typo3temp/assets/%s.tmp/', $folderName));
$this->fileDeclarations = $this->initializeFileDeclarations($fileName);
}
......@@ -92,7 +85,7 @@ class ServerResponseCheck implements CheckInterface
return new Status(
'Server Response on static files',
$title ?? 'OK',
$this->wrapList($messages),
$this->wrapList($messages, '', self::WRAP_NESTED),
$severity ?? Status::OK
);
}
......@@ -123,31 +116,31 @@ class ServerResponseCheck implements CheckInterface
protected function initializeFileDeclarations(string $fileName): array
{
return [
(new FileDeclaration($fileName . '.html'))
(new FileDeclaration($this->assetLocation, $fileName . '.html'))
->withExpectedContentType('text/html')
->withExpectedContent('HTML content'),
(new FileDeclaration($fileName . '.wrong'))
(new FileDeclaration($this->assetLocation, $fileName . '.wrong'))
->withUnexpectedContentType('text/html')
->withExpectedContent('HTML content'),
(new FileDeclaration($fileName . '.html.wrong'))
(new FileDeclaration($this->assetLocation, $fileName . '.html.wrong'))
->withUnexpectedContentType('text/html')
->withExpectedContent('HTML content'),
(new FileDeclaration($fileName . '.1.svg.wrong'))
(new FileDeclaration($this->assetLocation, $fileName . '.1.svg.wrong'))
->withBuildFlags(FileDeclaration::FLAG_BUILD_SVG | FileDeclaration::FLAG_BUILD_SVG_DOCUMENT)
->withUnexpectedContentType('image/svg+xml')
->withExpectedContent('SVG content'),
(new FileDeclaration($fileName . '.2.svg.wrong'))
(new FileDeclaration($this->assetLocation, $fileName . '.2.svg.wrong'))
->withBuildFlags(FileDeclaration::FLAG_BUILD_SVG | FileDeclaration::FLAG_BUILD_SVG_DOCUMENT)
->withUnexpectedContentType('image/svg')
->withExpectedContent('SVG content'),
(new FileDeclaration($fileName . '.php.wrong', true))
(new FileDeclaration($this->assetLocation, $fileName . '.php.wrong', true))
->withBuildFlags(FileDeclaration::FLAG_BUILD_PHP | FileDeclaration::FLAG_BUILD_HTML_DOCUMENT)
->withUnexpectedContent('PHP content'),
(new FileDeclaration($fileName . '.html.txt'))
(new FileDeclaration($this->assetLocation, $fileName . '.html.txt'))
->withExpectedContentType('text/plain')
->withUnexpectedContentType('text/html')
->withExpectedContent('HTML content'),
(new FileDeclaration($fileName . '.php.txt', true))
(new FileDeclaration($this->assetLocation, $fileName . '.php.txt', true))
->withBuildFlags(FileDeclaration::FLAG_BUILD_PHP | FileDeclaration::FLAG_BUILD_HTML_DOCUMENT)
->withUnexpectedContent('PHP content'),
];
......@@ -155,12 +148,13 @@ class ServerResponseCheck implements CheckInterface
protected function buildFileDeclarations(): void
{
if (!is_dir($this->filePath)) {
GeneralUtility::mkdir_deep($this->filePath);
}
foreach ($this->fileDeclarations as $fileDeclaration) {
$filePath = $fileDeclaration->getFileLocation()->getFilePath();
if (!is_dir($filePath)) {
GeneralUtility::mkdir_deep($filePath);
}
file_put_contents(
$this->filePath . $fileDeclaration->getFileName(),
$filePath . $fileDeclaration->getFileName(),
$fileDeclaration->buildContent()
);
}
......@@ -168,15 +162,15 @@ class ServerResponseCheck implements CheckInterface
protected function purgeFileDeclarations(): void
{
GeneralUtility::rmdir($this->filePath, true);
GeneralUtility::rmdir($this->assetLocation->getFilePath(), true);
}
protected function processFileDeclarations(FlashMessageQueue $messageQueue): void
{
$promises = [];
$client = new Client(['base_uri' => $this->baseUrl]);
$client = new Client(['timeout' => 10]);
foreach ($this->fileDeclarations as $fileDeclaration) {
$promises[] = $client->requestAsync('GET', $fileDeclaration->getFileName());
$promises[] = $client->requestAsync('GET', $fileDeclaration->getUrl());
}
foreach (settle($promises)->wait() as $index => $response) {
$fileDeclaration = $this->fileDeclarations[$index];
......@@ -224,62 +218,28 @@ class ServerResponseCheck implements CheckInterface
protected function createMismatchMessage(FileDeclaration $fileDeclaration, ResponseInterface $response): string
{
$messageParts = [];
$mismatches = $fileDeclaration->getMismatches($response);
if (in_array(FileDeclaration::MISMATCH_UNEXPECTED_CONTENT_TYPE, $mismatches, true)) {
$messageParts[] = sprintf(
'unexpected content-type %s',
$this->wrapValue(
(string)$fileDeclaration->getUnexpectedContentType(),
'<code>',
'</code>'
)
);
}
if (in_array(FileDeclaration::MISMATCH_EXPECTED_CONTENT_TYPE, $mismatches, true)) {
$messageParts[] = sprintf(
'content-type mismatch %s, got %s',
$this->wrapValue(
(string)$fileDeclaration->getExpectedContent(),
'<code>',
'</code>'
),
$this->wrapValue(
$response->getHeaderLine('content-type'),
'<code>',
'</code>'
)
);
}
if (in_array(FileDeclaration::MISMATCH_UNEXPECTED_CONTENT, $mismatches, true)) {
$messageParts[] = sprintf(
'unexpected content %s',
$this->wrapValue(
(string)$fileDeclaration->getUnexpectedContent(),
'<code>',
'</code>'
)
);
}
if (in_array(FileDeclaration::MISMATCH_EXPECTED_CONTENT, $mismatches, true)) {
$messageParts[] = sprintf(
'content mismatch %s',
$this->wrapValue(
(string)$fileDeclaration->getExpectedContent(),
'<code>',
'</code>'
)
);
}
return $this->wrapList(
$messageParts,
$this->baseUrl . $fileDeclaration->getFileName()
$messageParts = array_map(
function (StatusMessage $mismatch): string {
return vsprintf(
$mismatch->getMessage(),
$this->wrapValues($mismatch->getValues(), '<code>', '</code>')
);
},
$fileDeclaration->getMismatches($response)
);
return $this->wrapList($messageParts, $fileDeclaration->getUrl(), self::WRAP_FLAT);
}
protected function wrapList(array $items, string $label = ''): string
protected function wrapList(array $items, string $label, int $style): string
{
if ($this->useMarkup) {
if (!$this->useMarkup) {
return sprintf(
'%s%s',
$label ? $label . ': ' : '',
implode(', ', $items)
);
}
if ($style === self::WRAP_NESTED) {
return sprintf(
'%s<ul>%s</ul>',
$label,
......@@ -287,9 +247,9 @@ class ServerResponseCheck implements CheckInterface
);
}
return sprintf(
'%s%s',
$label ? $label . ': ' : '',
implode(', ', $items)
'<p>%s%s</p>',
$label,
implode('', $this->wrapItems($items, '<br>', ''))
);
}
......@@ -303,6 +263,16 @@ class ServerResponseCheck implements CheckInterface
);
}
protected function wrapValues(array $values, string $before, string $after): array
{
return array_map(
function (string $value) use ($before, $after): string {
return $this->wrapValue($value, $before, $after);
},
array_filter($values)
);
}
protected function wrapValue(string $value, string $before, string $after): string
{
if ($this->useMarkup) {
......
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Install\SystemEnvironment\ServerResponse;
/**
* @internal should only be used from within TYPO3 Core
*/
class StatusMessage
{
/**
* @var string
*/
protected $message;
/**
* @var string[]
*/
protected $values;
public function __construct(string $message, string ...$values)
{
$this->message = $message;
$this->values = $values;
}
/**
* @return string
*/
public function getMessage(): string
{
return $this->message;
}
/**
* @return string[]
*/
public function getValues(): array
{
return $this->values;
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment