This commit is contained in:
Roland Gruber 2019-11-30 14:23:49 +01:00
parent 0a30964011
commit 0f13e3c8ba
35 changed files with 2490 additions and 23 deletions

View File

@ -4,6 +4,7 @@
},
"require" : {
"web-auth/webauthn-lib" : "2.1.7",
"symfony/http-foundation" : "5.0.0"
"symfony/http-foundation" : "5.0.0",
"symfony/psr-http-message-bridge" : "1.3.0"
}
}

75
lam/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "ba2488b4997bb9328901b5be3ed4bed0",
"content-hash": "ef5f7241f5ed768a1c63843aadbb54aa",
"packages": [
{
"name": "beberlei/assert",
@ -764,7 +764,7 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.13.0",
"version": "v1.13.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@ -822,7 +822,7 @@
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.13.0",
"version": "v1.13.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
@ -884,7 +884,7 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.13.0",
"version": "v1.13.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
@ -943,7 +943,7 @@
},
{
"name": "symfony/polyfill-php72",
"version": "v1.13.0",
"version": "v1.13.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
@ -996,6 +996,71 @@
],
"time": "2019-11-27T13:56:44+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
"version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/psr-http-message-bridge.git",
"reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/9d3e80d54d9ae747ad573cad796e8e247df7b796",
"reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796",
"shasum": ""
},
"require": {
"php": "^7.1",
"psr/http-message": "^1.0",
"symfony/http-foundation": "^4.4 || ^5.0"
},
"require-dev": {
"nyholm/psr7": "^1.1",
"symfony/phpunit-bridge": "^4.4 || ^5.0",
"zendframework/zend-diactoros": "^1.4.1 || ^2.0"
},
"suggest": {
"nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
},
"type": "symfony-bridge",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Bridge\\PsrHttpMessage\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "PSR HTTP message bridge",
"homepage": "http://symfony.com",
"keywords": [
"http",
"http-message",
"psr-17",
"psr-7"
],
"time": "2019-11-25T19:33:50+00:00"
},
{
"name": "web-auth/cose-lib",
"version": "v2.1.7",

View File

@ -10,6 +10,7 @@ use \htmlJavaScript;
use \htmlStatusMessage;
use \htmlDiv;
use \LAMException;
use Webauthn\PublicKeyCredentialCreationOptions;
use function LAM\LOGIN\WEBAUTHN\storeNewRegistration;
/*
@ -543,9 +544,8 @@ class WebauthnProvider extends BaseProvider {
public function verify2ndFactor($user, $password, $serial, $twoFactorInput) {
logNewMessage(LOG_DEBUG, 'WebauthnProvider: Checking 2nd factor for ' . $user);
$response = base64_decode($_POST['sig_response']);
include_once __DIR__ . '/3rdParty/composer/autoload.php';
include_once __DIR__ . '/webauthn.inc';
$registrationObject = $_SESSION['webauthn_registration'];
$registrationObject = PublicKeyCredentialCreationOptions::createFromString($_SESSION['webauthn_registration']);
if (storeNewRegistration($registrationObject, $response)) {
return true;
}

View File

@ -9,6 +9,6 @@ return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'a4ecaeafb8cfb009ad0e052c90355e98' => $vendorDir . '/beberlei/assert/lib/Assert/functions.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
);

View File

@ -14,6 +14,7 @@ return array(
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'),
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
'Symfony\\Bridge\\PsrHttpMessage\\' => array($vendorDir . '/symfony/psr-http-message-bridge'),
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),

View File

@ -10,8 +10,8 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'a4ecaeafb8cfb009ad0e052c90355e98' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
);
public static $prefixLengthsPsr4 = array (
@ -28,6 +28,7 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Component\\Mime\\' => 23,
'Symfony\\Component\\HttpFoundation\\' => 33,
'Symfony\\Bridge\\PsrHttpMessage\\' => 30,
),
'R' =>
array (
@ -98,6 +99,10 @@ class ComposerStaticInited73ceb9c1bdec18b7c6d09764d1bce5
array (
0 => __DIR__ . '/..' . '/symfony/http-foundation',
),
'Symfony\\Bridge\\PsrHttpMessage\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/psr-http-message-bridge',
),
'Ramsey\\Uuid\\' =>
array (
0 => __DIR__ . '/..' . '/ramsey/uuid/src',

View File

@ -783,8 +783,8 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.13.0",
"version_normalized": "1.13.0.0",
"version": "v1.13.1",
"version_normalized": "1.13.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@ -843,8 +843,8 @@
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.13.0",
"version_normalized": "1.13.0.0",
"version": "v1.13.1",
"version_normalized": "1.13.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
@ -907,8 +907,8 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.13.0",
"version_normalized": "1.13.0.0",
"version": "v1.13.1",
"version_normalized": "1.13.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
@ -968,8 +968,8 @@
},
{
"name": "symfony/polyfill-php72",
"version": "v1.13.0",
"version_normalized": "1.13.0.0",
"version": "v1.13.1",
"version_normalized": "1.13.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
@ -1023,6 +1023,73 @@
"shim"
]
},
{
"name": "symfony/psr-http-message-bridge",
"version": "v1.3.0",
"version_normalized": "1.3.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/psr-http-message-bridge.git",
"reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/9d3e80d54d9ae747ad573cad796e8e247df7b796",
"reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796",
"shasum": ""
},
"require": {
"php": "^7.1",
"psr/http-message": "^1.0",
"symfony/http-foundation": "^4.4 || ^5.0"
},
"require-dev": {
"nyholm/psr7": "^1.1",
"symfony/phpunit-bridge": "^4.4 || ^5.0",
"zendframework/zend-diactoros": "^1.4.1 || ^2.0"
},
"suggest": {
"nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
},
"time": "2019-11-25T19:33:50+00:00",
"type": "symfony-bridge",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Bridge\\PsrHttpMessage\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "PSR HTTP message bridge",
"homepage": "http://symfony.com",
"keywords": [
"http",
"http-message",
"psr-17",
"psr-7"
]
},
{
"name": "web-auth/cose-lib",
"version": "v2.1.7",

View File

@ -0,0 +1,5 @@
vendor/
composer.lock
phpunit.xml
.php_cs.cache
.phpunit.result.cache

View File

@ -0,0 +1,24 @@
<?php
return PhpCsFixer\Config::create()
->setRules([
'@Symfony' => true,
'@Symfony:risky' => true,
'@PHPUnit48Migration:risky' => true,
'php_unit_no_expectation_annotation' => false, // part of `PHPUnitXYMigration:risky` ruleset, to be enabled when PHPUnit 4.x support will be dropped, as we don't want to rewrite exceptions handling twice
'array_syntax' => ['syntax' => 'short'],
'fopen_flags' => false,
'ordered_imports' => true,
'protected_to_private' => false,
// Part of @Symfony:risky in PHP-CS-Fixer 2.13.0. To be removed from the config file once upgrading
'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced'],
// Part of future @Symfony ruleset in PHP-CS-Fixer To be removed from the config file once upgrading
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
])
->setRiskyAllowed(true)
->setFinder(
PhpCsFixer\Finder::create()
->in(__DIR__)
->name('*.php')
)
;

View File

@ -0,0 +1,48 @@
language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
- $HOME/symfony-bridge/.phpunit
env:
global:
- PHPUNIT_FLAGS="-v"
- SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit"
matrix:
fast_finish: true
include:
# Minimum supported dependencies with the latest and oldest PHP version
- php: 7.3
env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors"
- php: 7.1
- php: 7.2
- php: 7.3
env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text"
# Latest commit to master
- php: 7.3
env: STABILITY="dev"
allow_failures:
# Dev-master is allowed to fail.
- env: STABILITY="dev"
before_install:
- if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi
- if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi;
- if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi;
install:
# To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355
- if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi
- composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction
- ./vendor/bin/simple-phpunit install
script:
- composer validate --strict --no-check-lock
# simple-phpunit is the PHPUnit wrapper provided by the PHPUnit Bridge component and
# it helps with testing legacy code and deprecations (composer require symfony/phpunit-bridge)
- ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS

View File

@ -0,0 +1,40 @@
CHANGELOG
=========
* 1.3.0 (2019-11-25)
* Added support for streamed requests
* Added support for Symfony 5.0+
* Fixed bridging UploadedFile objects
* Bumped minimum version of Symfony to 4.4
* 1.2.0 (2019-03-11)
* Added new documentation links
* Bumped minimum version of PHP to 7.1
* Added support for streamed responses
* 1.1.2 (2019-04-03)
* Fixed createResponse
* 1.1.1 (2019-03-11)
* Deprecated DiactorosFactory, use PsrHttpFactory instead
* Removed triggering of deprecation
* 1.1.0 (2018-08-30)
* Added support for creating PSR-7 messages using PSR-17 factories
* 1.0.2 (2017-12-19)
* Fixed request target in PSR7 Request (mtibben)
* 1.0.1 (2017-12-04)
* Added support for Symfony 4 (dunglas)
* 1.0.0 (2016-09-14)
* Initial release

View File

@ -0,0 +1,171 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PsrHttpMessage\Factory;
@trigger_error(sprintf('The "%s" class is deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead.', DiactorosFactory::class), E_USER_DEPRECATED);
use Psr\Http\Message\UploadedFileInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Zend\Diactoros\Response as DiactorosResponse;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\ServerRequestFactory as DiactorosRequestFactory;
use Zend\Diactoros\Stream as DiactorosStream;
use Zend\Diactoros\UploadedFile as DiactorosUploadedFile;
/**
* Builds Psr\HttpMessage instances using the Zend Diactoros implementation.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead
*/
class DiactorosFactory implements HttpMessageFactoryInterface
{
public function __construct()
{
if (!class_exists('Zend\Diactoros\ServerRequestFactory')) {
throw new \RuntimeException('Zend Diactoros must be installed to use the DiactorosFactory.');
}
}
/**
* {@inheritdoc}
*/
public function createRequest(Request $symfonyRequest)
{
$server = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeServer')
? DiactorosRequestFactory::normalizeServer($symfonyRequest->server->all())
: \Zend\Diactoros\normalizeServer($symfonyRequest->server->all());
$headers = $symfonyRequest->headers->all();
$body = new DiactorosStream($symfonyRequest->getContent(true));
$files = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeFiles')
? DiactorosRequestFactory::normalizeFiles($this->getFiles($symfonyRequest->files->all()))
: \Zend\Diactoros\normalizeUploadedFiles($this->getFiles($symfonyRequest->files->all()));
$request = new ServerRequest(
$server,
$files,
$symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getRequestUri(),
$symfonyRequest->getMethod(),
$body,
$headers
);
$request = $request
->withCookieParams($symfonyRequest->cookies->all())
->withQueryParams($symfonyRequest->query->all())
->withParsedBody($symfonyRequest->request->all())
->withRequestTarget($symfonyRequest->getRequestUri())
;
foreach ($symfonyRequest->attributes->all() as $key => $value) {
$request = $request->withAttribute($key, $value);
}
return $request;
}
/**
* Converts Symfony uploaded files array to the PSR one.
*
* @return array
*/
private function getFiles(array $uploadedFiles)
{
$files = [];
foreach ($uploadedFiles as $key => $value) {
if (null === $value) {
$files[$key] = new DiactorosUploadedFile(null, 0, UPLOAD_ERR_NO_FILE, null, null);
continue;
}
if ($value instanceof UploadedFile) {
$files[$key] = $this->createUploadedFile($value);
} else {
$files[$key] = $this->getFiles($value);
}
}
return $files;
}
/**
* Creates a PSR-7 UploadedFile instance from a Symfony one.
*
* @return UploadedFileInterface
*/
private function createUploadedFile(UploadedFile $symfonyUploadedFile)
{
return new DiactorosUploadedFile(
$symfonyUploadedFile->getRealPath(),
(int) $symfonyUploadedFile->getSize(),
$symfonyUploadedFile->getError(),
$symfonyUploadedFile->getClientOriginalName(),
$symfonyUploadedFile->getClientMimeType()
);
}
/**
* {@inheritdoc}
*/
public function createResponse(Response $symfonyResponse)
{
if ($symfonyResponse instanceof BinaryFileResponse) {
$stream = new DiactorosStream($symfonyResponse->getFile()->getPathname(), 'r');
} else {
$stream = new DiactorosStream('php://temp', 'wb+');
if ($symfonyResponse instanceof StreamedResponse) {
ob_start(function ($buffer) use ($stream) {
$stream->write($buffer);
return '';
});
$symfonyResponse->sendContent();
ob_end_clean();
} else {
$stream->write($symfonyResponse->getContent());
}
}
$headers = $symfonyResponse->headers->all();
if (!isset($headers['Set-Cookie']) && !isset($headers['set-cookie'])) {
$cookies = $symfonyResponse->headers->getCookies();
if (!empty($cookies)) {
$headers['Set-Cookie'] = [];
foreach ($cookies as $cookie) {
$headers['Set-Cookie'][] = $cookie->__toString();
}
}
}
$response = new DiactorosResponse(
$stream,
$symfonyResponse->getStatusCode(),
$headers
);
$protocolVersion = $symfonyResponse->getProtocolVersion();
if ('1.1' !== $protocolVersion) {
$response = $response->withProtocolVersion($protocolVersion);
}
return $response;
}
}

View File

@ -0,0 +1,240 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PsrHttpMessage\Factory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* {@inheritdoc}
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class HttpFoundationFactory implements HttpFoundationFactoryInterface
{
/**
* @var int The maximum output buffering size for each iteration when sending the response
*/
private $responseBufferMaxLength;
public function __construct(int $responseBufferMaxLength = 16372)
{
$this->responseBufferMaxLength = $responseBufferMaxLength;
}
/**
* {@inheritdoc}
*/
public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false)
{
$server = [];
$uri = $psrRequest->getUri();
if ($uri instanceof UriInterface) {
$server['SERVER_NAME'] = $uri->getHost();
$server['SERVER_PORT'] = $uri->getPort();
$server['REQUEST_URI'] = $uri->getPath();
$server['QUERY_STRING'] = $uri->getQuery();
}
$server['REQUEST_METHOD'] = $psrRequest->getMethod();
$server = array_replace($server, $psrRequest->getServerParams());
$parsedBody = $psrRequest->getParsedBody();
$parsedBody = \is_array($parsedBody) ? $parsedBody : [];
$request = new Request(
$psrRequest->getQueryParams(),
$parsedBody,
$psrRequest->getAttributes(),
$psrRequest->getCookieParams(),
$this->getFiles($psrRequest->getUploadedFiles()),
$server,
$streamed ? $psrRequest->getBody()->detach() : $psrRequest->getBody()->__toString()
);
$request->headers->replace($psrRequest->getHeaders());
return $request;
}
/**
* Converts to the input array to $_FILES structure.
*/
private function getFiles(array $uploadedFiles): array
{
$files = [];
foreach ($uploadedFiles as $key => $value) {
if ($value instanceof UploadedFileInterface) {
$files[$key] = $this->createUploadedFile($value);
} else {
$files[$key] = $this->getFiles($value);
}
}
return $files;
}
/**
* Creates Symfony UploadedFile instance from PSR-7 ones.
*/
private function createUploadedFile(UploadedFileInterface $psrUploadedFile): UploadedFile
{
return new UploadedFile($psrUploadedFile, function () { return $this->getTemporaryPath(); });
}
/**
* Gets a temporary file path.
*
* @return string
*/
protected function getTemporaryPath()
{
return tempnam(sys_get_temp_dir(), uniqid('symfony', true));
}
/**
* {@inheritdoc}
*/
public function createResponse(ResponseInterface $psrResponse, bool $streamed = false)
{
$cookies = $psrResponse->getHeader('Set-Cookie');
$psrResponse = $psrResponse->withoutHeader('Set-Cookie');
if ($streamed) {
$response = new StreamedResponse(
$this->createStreamedResponseCallback($psrResponse->getBody()),
$psrResponse->getStatusCode(),
$psrResponse->getHeaders()
);
} else {
$response = new Response(
$psrResponse->getBody()->__toString(),
$psrResponse->getStatusCode(),
$psrResponse->getHeaders()
);
}
$response->setProtocolVersion($psrResponse->getProtocolVersion());
foreach ($cookies as $cookie) {
$response->headers->setCookie($this->createCookie($cookie));
}
return $response;
}
/**
* Creates a Cookie instance from a cookie string.
*
* Some snippets have been taken from the Guzzle project: https://github.com/guzzle/guzzle/blob/5.3/src/Cookie/SetCookie.php#L34
*
* @throws \InvalidArgumentException
*/
private function createCookie(string $cookie): Cookie
{
foreach (explode(';', $cookie) as $part) {
$part = trim($part);
$data = explode('=', $part, 2);
$name = $data[0];
$value = isset($data[1]) ? trim($data[1], " \n\r\t\0\x0B\"") : null;
if (!isset($cookieName)) {
$cookieName = $name;
$cookieValue = $value;
continue;
}
if ('expires' === strtolower($name) && null !== $value) {
$cookieExpire = new \DateTime($value);
continue;
}
if ('path' === strtolower($name) && null !== $value) {
$cookiePath = $value;
continue;
}
if ('domain' === strtolower($name) && null !== $value) {
$cookieDomain = $value;
continue;
}
if ('secure' === strtolower($name)) {
$cookieSecure = true;
continue;
}
if ('httponly' === strtolower($name)) {
$cookieHttpOnly = true;
continue;
}
if ('samesite' === strtolower($name) && null !== $value) {
$samesite = $value;
continue;
}
}
if (!isset($cookieName)) {
throw new \InvalidArgumentException('The value of the Set-Cookie header is malformed.');
}
return new Cookie(
$cookieName,
$cookieValue,
isset($cookieExpire) ? $cookieExpire : 0,
isset($cookiePath) ? $cookiePath : '/',
isset($cookieDomain) ? $cookieDomain : null,
isset($cookieSecure),
isset($cookieHttpOnly),
false,
isset($samesite) ? $samesite : null
);
}
private function createStreamedResponseCallback(StreamInterface $body): callable
{
return function () use ($body) {
if ($body->isSeekable()) {
$body->rewind();
}
if (!$body->isReadable()) {
echo $body;
return;
}
while (!$body->eof()) {
echo $body->read($this->responseBufferMaxLength);
}
};
}
}

View File

@ -0,0 +1,168 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PsrHttpMessage\Factory;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UploadedFileInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* Builds Psr\HttpMessage instances using a PSR-17 implementation.
*
* @author Antonio J. García Lagar <aj@garcialagar.es>
*/
class PsrHttpFactory implements HttpMessageFactoryInterface
{
private $serverRequestFactory;
private $streamFactory;
private $uploadedFileFactory;
private $responseFactory;
public function __construct(ServerRequestFactoryInterface $serverRequestFactory, StreamFactoryInterface $streamFactory, UploadedFileFactoryInterface $uploadedFileFactory, ResponseFactoryInterface $responseFactory)
{
$this->serverRequestFactory = $serverRequestFactory;
$this->streamFactory = $streamFactory;
$this->uploadedFileFactory = $uploadedFileFactory;
$this->responseFactory = $responseFactory;
}
/**
* {@inheritdoc}
*/
public function createRequest(Request $symfonyRequest)
{
$request = $this->serverRequestFactory->createServerRequest(
$symfonyRequest->getMethod(),
$symfonyRequest->getUri(),
$symfonyRequest->server->all()
);
foreach ($symfonyRequest->headers->all() as $name => $value) {
$request = $request->withHeader($name, $value);
}
$body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true));
$request = $request
->withBody($body)
->withUploadedFiles($this->getFiles($symfonyRequest->files->all()))
->withCookieParams($symfonyRequest->cookies->all())
->withQueryParams($symfonyRequest->query->all())
->withParsedBody($symfonyRequest->request->all())
;
foreach ($symfonyRequest->attributes->all() as $key => $value) {
$request = $request->withAttribute($key, $value);
}
return $request;
}
/**
* Converts Symfony uploaded files array to the PSR one.
*
* @return array
*/
private function getFiles(array $uploadedFiles)
{
$files = [];
foreach ($uploadedFiles as $key => $value) {
if (null === $value) {
$files[$key] = $this->uploadedFileFactory->createUploadedFile($this->streamFactory->createStream(), 0, UPLOAD_ERR_NO_FILE);
continue;
}
if ($value instanceof UploadedFile) {
$files[$key] = $this->createUploadedFile($value);
} else {
$files[$key] = $this->getFiles($value);
}
}
return $files;
}
/**
* Creates a PSR-7 UploadedFile instance from a Symfony one.
*
* @return UploadedFileInterface
*/
private function createUploadedFile(UploadedFile $symfonyUploadedFile)
{
return $this->uploadedFileFactory->createUploadedFile(
$this->streamFactory->createStreamFromFile(
$symfonyUploadedFile->getRealPath()
),
(int) $symfonyUploadedFile->getSize(),
$symfonyUploadedFile->getError(),
$symfonyUploadedFile->getClientOriginalName(),
$symfonyUploadedFile->getClientMimeType()
);
}
/**
* {@inheritdoc}
*/
public function createResponse(Response $symfonyResponse)
{
$response = $this->responseFactory->createResponse($symfonyResponse->getStatusCode(), Response::$statusTexts[$symfonyResponse->getStatusCode()] ?? '');
if ($symfonyResponse instanceof BinaryFileResponse) {
$stream = $this->streamFactory->createStreamFromFile(
$symfonyResponse->getFile()->getPathname()
);
} else {
$stream = $this->streamFactory->createStreamFromFile('php://temp', 'wb+');
if ($symfonyResponse instanceof StreamedResponse) {
ob_start(function ($buffer) use ($stream) {
$stream->write($buffer);
return '';
});
$symfonyResponse->sendContent();
ob_end_clean();
} else {
$stream->write($symfonyResponse->getContent());
}
}
$response = $response->withBody($stream);
$headers = $symfonyResponse->headers->all();
$cookies = $symfonyResponse->headers->getCookies();
if (!empty($cookies)) {
$headers['Set-Cookie'] = [];
foreach ($cookies as $cookie) {
$headers['Set-Cookie'][] = $cookie->__toString();
}
}
foreach ($headers as $name => $value) {
$response = $response->withHeader($name, $value);
}
$protocolVersion = $symfonyResponse->getProtocolVersion();
$response = $response->withProtocolVersion($protocolVersion);
return $response;
}
}

View File

@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PsrHttpMessage\Factory;
use Psr\Http\Message\UploadedFileInterface;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile as BaseUploadedFile;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class UploadedFile extends BaseUploadedFile
{
private $psrUploadedFile;
private $test = false;
public function __construct(UploadedFileInterface $psrUploadedFile, callable $getTemporaryPath)
{
$error = $psrUploadedFile->getError();
$path = '';
if (UPLOAD_ERR_NO_FILE !== $error) {
$path = $psrUploadedFile->getStream()->getMetadata('uri') ?? '';
if ($this->test = !\is_string($path) || !is_uploaded_file($path)) {
$path = $getTemporaryPath();
$psrUploadedFile->moveTo($path);
}
}
parent::__construct(
$path,
(string) $psrUploadedFile->getClientFilename(),
$psrUploadedFile->getClientMediaType(),
$psrUploadedFile->getError(),
$this->test
);
$this->psrUploadedFile = $psrUploadedFile;
}
/**
* {@inheritdoc}
*/
public function move($directory, $name = null): File
{
if (!$this->isValid() || $this->test) {
return parent::move($directory, $name);
}
$target = $this->getTargetFile($directory, $name);
try {
$this->psrUploadedFile->moveTo($target);
} catch (\RuntimeException $e) {
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, $e->getMessage()), 0, $e);
}
@chmod($target, 0666 & ~umask());
return $target;
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PsrHttpMessage;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Creates Symfony Request and Response instances from PSR-7 ones.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface HttpFoundationFactoryInterface
{
/**
* Creates a Symfony Request instance from a PSR-7 one.
*
* @return Request
*/
public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false);
/**
* Creates a Symfony Response instance from a PSR-7 one.
*
* @return Response
*/
public function createResponse(ResponseInterface $psrResponse, bool $streamed = false);
}

View File

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PsrHttpMessage;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Creates PSR HTTP Request and Response instances from Symfony ones.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface HttpMessageFactoryInterface
{
/**
* Creates a PSR-7 Request instance from a Symfony one.
*
* @return ServerRequestInterface
*/
public function createRequest(Request $symfonyRequest);
/**
* Creates a PSR-7 Response instance from a Symfony one.
*
* @return ResponseInterface
*/
public function createResponse(Response $symfonyResponse);
}

View File

@ -0,0 +1,19 @@
Copyright (c) 2004-2016 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,20 @@
PSR-7 Bridge
============
Provides integration for PSR7.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/psr7.html)
* [SensioFrameworkExtraBundle](https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html#psr-7-support)
Running the tests
-----------------
If you want to run the unit tests, install dev dependencies before
running PHPUnit:
$ cd path/to/Symfony/Bridge/PsrHttpMessage/
$ composer.phar install
$ phpunit

View File

@ -0,0 +1,217 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Antonio J. García Lagar <aj@garcialagar.es>
*/
abstract class AbstractHttpMessageFactoryTest extends TestCase
{
private $factory;
private $tmpDir;
abstract protected function buildHttpMessageFactory(): HttpMessageFactoryInterface;
public function setUp(): void
{
$this->factory = $this->buildHttpMessageFactory();
$this->tmpDir = sys_get_temp_dir();
}
public function testCreateRequest()
{
$stdClass = new \stdClass();
$request = new Request(
[
'bar' => ['baz' => '42'],
'foo' => '1',
],
[
'twitter' => [
'@dunglas' => 'Kévin Dunglas',
'@coopTilleuls' => 'Les-Tilleuls.coop',
],
'baz' => '2',
],
[
'a1' => $stdClass,
'a2' => ['foo' => 'bar'],
],
[
'c1' => 'foo',
'c2' => ['c3' => 'bar'],
],
[
'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK),
'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)],
],
[
'REQUEST_METHOD' => 'POST',
'HTTP_HOST' => 'dunglas.fr',
'HTTP_X_SYMFONY' => '2.8',
'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1',
'QUERY_STRING' => 'bar[baz]=42&foo=1',
],
'Content'
);
$psrRequest = $this->factory->createRequest($request);
$this->assertEquals('Content', $psrRequest->getBody()->__toString());
$queryParams = $psrRequest->getQueryParams();
$this->assertEquals('1', $queryParams['foo']);
$this->assertEquals('42', $queryParams['bar']['baz']);
$requestTarget = $psrRequest->getRequestTarget();
$this->assertEquals('/testCreateRequest?bar[baz]=42&foo=1', urldecode($requestTarget));
$parsedBody = $psrRequest->getParsedBody();
$this->assertEquals('Kévin Dunglas', $parsedBody['twitter']['@dunglas']);
$this->assertEquals('Les-Tilleuls.coop', $parsedBody['twitter']['@coopTilleuls']);
$this->assertEquals('2', $parsedBody['baz']);
$attributes = $psrRequest->getAttributes();
$this->assertEquals($stdClass, $attributes['a1']);
$this->assertEquals('bar', $attributes['a2']['foo']);
$cookies = $psrRequest->getCookieParams();
$this->assertEquals('foo', $cookies['c1']);
$this->assertEquals('bar', $cookies['c2']['c3']);
$uploadedFiles = $psrRequest->getUploadedFiles();
$this->assertEquals('F1', $uploadedFiles['f1']->getStream()->__toString());
$this->assertEquals('f1.txt', $uploadedFiles['f1']->getClientFilename());
$this->assertEquals('text/plain', $uploadedFiles['f1']->getClientMediaType());
$this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['f1']->getError());
$this->assertEquals('F2', $uploadedFiles['foo']['f2']->getStream()->__toString());
$this->assertEquals('f2.txt', $uploadedFiles['foo']['f2']->getClientFilename());
$this->assertEquals('text/plain', $uploadedFiles['foo']['f2']->getClientMediaType());
$this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError());
$serverParams = $psrRequest->getServerParams();
$this->assertEquals('POST', $serverParams['REQUEST_METHOD']);
$this->assertEquals('2.8', $serverParams['HTTP_X_SYMFONY']);
$this->assertEquals('POST', $psrRequest->getMethod());
$this->assertEquals(['2.8'], $psrRequest->getHeader('X-Symfony'));
}
public function testGetContentCanBeCalledAfterRequestCreation()
{
$header = ['HTTP_HOST' => 'dunglas.fr'];
$request = new Request([], [], [], [], [], $header, 'Content');
$psrRequest = $this->factory->createRequest($request);
$this->assertEquals('Content', $psrRequest->getBody()->__toString());
$this->assertEquals('Content', $request->getContent());
}
private function createUploadedFile($content, $originalName, $mimeType, $error)
{
$path = tempnam($this->tmpDir, uniqid());
file_put_contents($path, $content);
return new UploadedFile($path, $originalName, $mimeType, $error, true);
}
public function testCreateResponse()
{
$response = new Response(
'Response content.',
202,
['X-Symfony' => ['3.4']]
);
$response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, 'lax'));
$psrResponse = $this->factory->createResponse($response);
$this->assertEquals('Response content.', $psrResponse->getBody()->__toString());
$this->assertEquals(202, $psrResponse->getStatusCode());
$this->assertEquals(['3.4'], $psrResponse->getHeader('X-Symfony'));
$cookieHeader = $psrResponse->getHeader('Set-Cookie');
$this->assertIsArray($cookieHeader);
$this->assertCount(1, $cookieHeader);
$this->assertRegExp('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]);
}
public function testCreateResponseFromStreamed()
{
$response = new StreamedResponse(function () {
echo "Line 1\n";
flush();
echo "Line 2\n";
flush();
});
$psrResponse = $this->factory->createResponse($response);
$this->assertEquals("Line 1\nLine 2\n", $psrResponse->getBody()->__toString());
}
public function testCreateResponseFromBinaryFile()
{
$path = tempnam($this->tmpDir, uniqid());
file_put_contents($path, 'Binary');
$response = new BinaryFileResponse($path);
$psrResponse = $this->factory->createResponse($response);
$this->assertEquals('Binary', $psrResponse->getBody()->__toString());
}
public function testUploadErrNoFile()
{
$file = new UploadedFile('', '', null, UPLOAD_ERR_NO_FILE, true);
$this->assertEquals(0, $file->getSize());
$this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError());
$this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error');
$request = new Request(
[],
[],
[],
[],
[
'f1' => $file,
'f2' => ['name' => null, 'type' => null, 'tmp_name' => null, 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0],
],
[
'REQUEST_METHOD' => 'POST',
'HTTP_HOST' => 'dunglas.fr',
'HTTP_X_SYMFONY' => '2.8',
],
'Content'
);
$psrRequest = $this->factory->createRequest($request);
$uploadedFiles = $psrRequest->getUploadedFiles();
$this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError());
$this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError());
}
}

View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Antonio J. García Lagar <aj@garcialagar.es>
*
* @group legacy
*/
class DiactorosFactoryTest extends AbstractHttpMessageFactoryTest
{
protected function buildHttpMessageFactory(): HttpMessageFactoryInterface
{
if (!class_exists('Zend\Diactoros\ServerRequestFactory')) {
$this->markTestSkipped(