TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
<?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\Core\Http\Client;
|
||||
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Promise\RejectedPromise;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
/**
|
||||
* Guzzle client middleware for filtering allowed request targets (SSRF prevention)
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class AllowedHostsMiddleware
|
||||
{
|
||||
public function __construct(
|
||||
private string $context,
|
||||
private array $allowedHosts,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param callable(RequestInterface, array): PromiseInterface $nextHandler
|
||||
* @return callable(RequestInterface $request, array $options): PromiseInterface
|
||||
*/
|
||||
public function __invoke(callable $nextHandler): callable
|
||||
{
|
||||
return fn(RequestInterface $request, array $options): PromiseInterface
|
||||
=> $this->matches($request->getUri()->getHost())
|
||||
? $nextHandler($request, $options)
|
||||
: new RejectedPromise(sprintf(
|
||||
'Requested host \'%s\' is not allowed in $GLOBALS[\'TYPO3_CONF_VARS\'][\'HTTP\'][\'allowed_hosts\'][\'%s\']',
|
||||
$request->getUri()->getHost(),
|
||||
$this->context
|
||||
));
|
||||
}
|
||||
|
||||
private function matches(string $host): bool
|
||||
{
|
||||
foreach ($this->allowedHosts as $allowedHost) {
|
||||
// Match wildcards
|
||||
if (str_contains($allowedHost, '*')) {
|
||||
$expr = implode(
|
||||
'.+',
|
||||
array_map(
|
||||
static fn(string $part): string => preg_quote($part, '/'),
|
||||
explode('*', $allowedHost)
|
||||
)
|
||||
);
|
||||
if (preg_match('/^' . $expr . '$/', $host)) {
|
||||
return true;
|
||||
}
|
||||
} elseif ($allowedHost === $host) {
|
||||
// Exact matches
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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\Core\Http\Client;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly class GuzzleClientFactory
|
||||
{
|
||||
/**
|
||||
* Creates the client to do requests
|
||||
*/
|
||||
public function getClient(?string $context = null): ClientInterface
|
||||
{
|
||||
$httpOptions = $GLOBALS['TYPO3_CONF_VARS']['HTTP'];
|
||||
$httpOptions['verify'] = filter_var($httpOptions['verify'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? $httpOptions['verify'];
|
||||
|
||||
// HEADS UP:
|
||||
// Passing a guzzle handler stack instead of an array of middlewares has never been documented,
|
||||
// but was theoretically possible since the introduction of handler middlewares.
|
||||
// This is not considered API (we can not control the ordering of the AllowedHosts Middleware)
|
||||
// but is preserved for maximum compatibility for now. It may vanish in a major release without notice.
|
||||
$stack = ($httpOptions['handler'] ?? null) instanceof HandlerStack ? $httpOptions['handler'] : HandlerStack::create();
|
||||
|
||||
if ($context !== null) {
|
||||
$allowedHosts = $httpOptions['allowed_hosts'][$context] ?? null;
|
||||
if (is_array($allowedHosts)) {
|
||||
$stack->push(
|
||||
new AllowedHostsMiddleware($context, array_filter($allowedHosts, is_string(...))),
|
||||
'typo3_allowed_hosts'
|
||||
);
|
||||
}
|
||||
}
|
||||
unset($httpOptions['allowed_hosts']);
|
||||
|
||||
if (is_array($httpOptions['handler'] ?? null)) {
|
||||
foreach ($httpOptions['handler'] as $name => $handler) {
|
||||
$stack->push($handler, (string)$name);
|
||||
}
|
||||
}
|
||||
|
||||
$httpOptions['handler'] = $stack;
|
||||
return new Client($httpOptions);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user