@@ -1488,8 +1488,10 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
14881488 /**
14891489 * @author Tobias Nyholm <[email protected] > 14901490 * @author Martijn van der Ven <[email protected] > 1491+ *
1492+ * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
14911493 */
1492- final class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
1494+ class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
14931495 {
14941496 public function createRequest (string $ method , $ uri ): RequestInterface
14951497 {
@@ -1603,12 +1605,12 @@ public function getHeaders(): array
16031605
16041606 public function hasHeader ($ header ): bool
16051607 {
1606- return isset ($ this ->headerNames [\strtolower ($ header )]);
1608+ return isset ($ this ->headerNames [\strtr ($ header, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ ' , ' abcdefghijklmnopqrstuvwxyz ' )]);
16071609 }
16081610
16091611 public function getHeader ($ header ): array
16101612 {
1611- $ header = \strtolower ($ header );
1613+ $ header = \strtr ($ header, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ ' , ' abcdefghijklmnopqrstuvwxyz ' );
16121614 if (!isset ($ this ->headerNames [$ header ])) {
16131615 return [];
16141616 }
@@ -1626,7 +1628,7 @@ public function getHeaderLine($header): string
16261628 public function withHeader ($ header , $ value ): self
16271629 {
16281630 $ value = $ this ->validateAndTrimHeader ($ header , $ value );
1629- $ normalized = \strtolower ($ header );
1631+ $ normalized = \strtr ($ header, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ ' , ' abcdefghijklmnopqrstuvwxyz ' );
16301632
16311633 $ new = clone $ this ;
16321634 if (isset ($ new ->headerNames [$ normalized ])) {
@@ -1652,7 +1654,7 @@ public function withAddedHeader($header, $value): self
16521654
16531655 public function withoutHeader ($ header ): self
16541656 {
1655- $ normalized = \strtolower ($ header );
1657+ $ normalized = \strtr ($ header, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ ' , ' abcdefghijklmnopqrstuvwxyz ' );
16561658 if (!isset ($ this ->headerNames [$ normalized ])) {
16571659 return $ this ;
16581660 }
@@ -1688,8 +1690,13 @@ public function withBody(StreamInterface $body): self
16881690 private function setHeaders (array $ headers ) /*:void*/
16891691 {
16901692 foreach ($ headers as $ header => $ value ) {
1693+ if (\is_int ($ header )) {
1694+ // If a header name was set to a numeric string, PHP will cast the key to an int.
1695+ // We must cast it back to a string in order to comply with validation.
1696+ $ header = (string ) $ header ;
1697+ }
16911698 $ value = $ this ->validateAndTrimHeader ($ header , $ value );
1692- $ normalized = \strtolower ($ header );
1699+ $ normalized = \strtr ($ header, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ ' , ' abcdefghijklmnopqrstuvwxyz ' );
16931700 if (isset ($ this ->headerNames [$ normalized ])) {
16941701 $ header = $ this ->headerNames [$ normalized ];
16951702 $ this ->headers [$ header ] = \array_merge ($ this ->headers [$ header ], $ value );
@@ -1760,8 +1767,10 @@ private function validateAndTrimHeader($header, $values): array
17601767 /**
17611768 * @author Tobias Nyholm <[email protected] > 17621769 * @author Martijn van der Ven <[email protected] > 1770+ *
1771+ * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
17631772 */
1764- final class Request implements RequestInterface
1773+ class Request implements RequestInterface
17651774 {
17661775 use MessageTrait;
17671776 use RequestTrait;
@@ -1917,8 +1926,10 @@ private function updateHostFromUri() /*:void*/
19171926 * @author Michael Dowling and contributors to guzzlehttp/psr7
19181927 * @author Tobias Nyholm <[email protected] > 19191928 * @author Martijn van der Ven <[email protected] > 1929+ *
1930+ * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
19201931 */
1921- final class Response implements ResponseInterface
1932+ class Response implements ResponseInterface
19221933 {
19231934 use MessageTrait;
19241935
@@ -1980,7 +1991,7 @@ public function withStatus($code, $reasonPhrase = ''): self
19801991
19811992 $ code = (int ) $ code ;
19821993 if ($ code < 100 || $ code > 599 ) {
1983- throw new \InvalidArgumentException ('Status code has to be an integer between 100 and 599 ' );
1994+ throw new \InvalidArgumentException (\sprintf ( 'Status code has to be an integer between 100 and 599. A status code of %d was given ' , $ code ) );
19841995 }
19851996
19861997 $ new = clone $ this ;
@@ -2004,8 +2015,10 @@ public function withStatus($code, $reasonPhrase = ''): self
20042015 * @author Michael Dowling and contributors to guzzlehttp/psr7
20052016 * @author Tobias Nyholm <[email protected] > 20062017 * @author Martijn van der Ven <[email protected] > 2018+ *
2019+ * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
20072020 */
2008- final class ServerRequest implements ServerRequestInterface
2021+ class ServerRequest implements ServerRequestInterface
20092022 {
20102023 use MessageTrait;
20112024 use RequestTrait;
@@ -2160,13 +2173,17 @@ public function withoutAttribute($attribute): self
21602173namespace Nyholm \Psr7 {
21612174
21622175 use Psr \Http \Message \StreamInterface ;
2176+ use Symfony \Component \Debug \ErrorHandler as SymfonyLegacyErrorHandler ;
2177+ use Symfony \Component \ErrorHandler \ErrorHandler as SymfonyErrorHandler ;
21632178
21642179 /**
21652180 * @author Michael Dowling and contributors to guzzlehttp/psr7
21662181 * @author Tobias Nyholm <[email protected] > 21672182 * @author Martijn van der Ven <[email protected] > 2183+ *
2184+ * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
21682185 */
2169- final class Stream implements StreamInterface
2186+ class Stream implements StreamInterface
21702187 {
21712188 /** @var resource|null A resource reference */
21722189 private $ stream ;
@@ -2180,7 +2197,7 @@ final class Stream implements StreamInterface
21802197 /** @var bool */
21812198 private $ writable ;
21822199
2183- /** @var array|mixed|void|null */
2200+ /** @var array|mixed|void|bool| null */
21842201 private $ uri ;
21852202
21862203 /** @var int|null */
@@ -2211,8 +2228,6 @@ private function __construct()
22112228 *
22122229 * @param string|resource|StreamInterface $body
22132230 *
2214- * @return StreamInterface
2215- *
22162231 * @throws \InvalidArgumentException
22172232 */
22182233 public static function create ($ body = '' ): StreamInterface
@@ -2234,7 +2249,6 @@ public static function create($body = ''): StreamInterface
22342249 $ new ->seekable = $ meta ['seekable ' ] && 0 === \fseek ($ new ->stream , 0 , \SEEK_CUR );
22352250 $ new ->readable = isset (self ::READ_WRITE_HASH ['read ' ][$ meta ['mode ' ]]);
22362251 $ new ->writable = isset (self ::READ_WRITE_HASH ['write ' ][$ meta ['mode ' ]]);
2237- $ new ->uri = $ new ->getMetadata ('uri ' );
22382252
22392253 return $ new ;
22402254 }
@@ -2250,15 +2264,31 @@ public function __destruct()
22502264 $ this ->close ();
22512265 }
22522266
2253- public function __toString (): string
2267+ /**
2268+ * @return string
2269+ */
2270+ public function __toString ()
22542271 {
22552272 try {
22562273 if ($ this ->isSeekable ()) {
22572274 $ this ->seek (0 );
22582275 }
22592276
22602277 return $ this ->getContents ();
2261- } catch (\Exception $ e ) {
2278+ } catch (\Throwable $ e ) {
2279+ if (\PHP_VERSION_ID >= 70400 ) {
2280+ throw $ e ;
2281+ }
2282+
2283+ if (\is_array ($ errorHandler = \set_error_handler ('var_dump ' ))) {
2284+ $ errorHandler = $ errorHandler [0 ] ?? null ;
2285+ }
2286+ \restore_error_handler ();
2287+
2288+ if ($ e instanceof \Error || $ errorHandler instanceof SymfonyErrorHandler || $ errorHandler instanceof SymfonyLegacyErrorHandler) {
2289+ return \trigger_error ((string ) $ e , \E_USER_ERROR );
2290+ }
2291+
22622292 return '' ;
22632293 }
22642294 }
@@ -2287,6 +2317,15 @@ public function detach()
22872317 return $ result ;
22882318 }
22892319
2320+ private function getUri ()
2321+ {
2322+ if (false !== $ this ->uri ) {
2323+ $ this ->uri = $ this ->getMetadata ('uri ' ) ?? false ;
2324+ }
2325+
2326+ return $ this ->uri ;
2327+ }
2328+
22902329 public function getSize () /*:?int*/
22912330 {
22922331 if (null !== $ this ->size ) {
@@ -2298,8 +2337,8 @@ public function getSize() /*:?int*/
22982337 }
22992338
23002339 // Clear the stat cache if the stream has a URI
2301- if ($ this ->uri ) {
2302- \clearstatcache (true , $ this -> uri );
2340+ if ($ uri = $ this ->getUri () ) {
2341+ \clearstatcache (true , $ uri );
23032342 }
23042343
23052344 $ stats = \fstat ($ this ->stream );
@@ -2379,7 +2418,11 @@ public function read($length): string
23792418 throw new \RuntimeException ('Cannot read from non-readable stream ' );
23802419 }
23812420
2382- return \fread ($ this ->stream , $ length );
2421+ if (false === $ result = \fread ($ this ->stream , $ length )) {
2422+ throw new \RuntimeException ('Unable to read from stream ' );
2423+ }
2424+
2425+ return $ result ;
23832426 }
23842427
23852428 public function getContents (): string
@@ -2421,8 +2464,10 @@ public function getMetadata($key = null)
24212464 * @author Michael Dowling and contributors to guzzlehttp/psr7
24222465 * @author Tobias Nyholm <[email protected] > 24232466 * @author Martijn van der Ven <[email protected] > 2467+ *
2468+ * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
24242469 */
2425- final class UploadedFile implements UploadedFileInterface
2470+ class UploadedFile implements UploadedFileInterface
24262471 {
24272472 /** @var array */
24282473 /*private*/ const ERRORS = [
@@ -2595,8 +2640,10 @@ public function getClientMediaType() /*:?string*/
25952640 * @author Matthew Weier O'Phinney
25962641 * @author Tobias Nyholm <[email protected] > 25972642 * @author Martijn van der Ven <[email protected] > 2643+ *
2644+ * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md
25982645 */
2599- final class Uri implements UriInterface
2646+ class Uri implements UriInterface
26002647 {
26012648 /*private*/ const SCHEMES = ['http ' => 80 , 'https ' => 443 ];
26022649
@@ -2633,9 +2680,9 @@ public function __construct(string $uri = '')
26332680 }
26342681
26352682 // Apply parse_url parts to a URI.
2636- $ this ->scheme = isset ($ parts ['scheme ' ]) ? \strtolower ($ parts ['scheme ' ]) : '' ;
2683+ $ this ->scheme = isset ($ parts ['scheme ' ]) ? \strtr ($ parts ['scheme ' ], ' ABCDEFGHIJKLMNOPQRSTUVWXYZ ' , ' abcdefghijklmnopqrstuvwxyz ' ) : '' ;
26372684 $ this ->userInfo = $ parts ['user ' ] ?? '' ;
2638- $ this ->host = isset ($ parts ['host ' ]) ? \strtolower ($ parts ['host ' ]) : '' ;
2685+ $ this ->host = isset ($ parts ['host ' ]) ? \strtr ($ parts ['host ' ], ' ABCDEFGHIJKLMNOPQRSTUVWXYZ ' , ' abcdefghijklmnopqrstuvwxyz ' ) : '' ;
26392686 $ this ->port = isset ($ parts ['port ' ]) ? $ this ->filterPort ($ parts ['port ' ]) : null ;
26402687 $ this ->path = isset ($ parts ['path ' ]) ? $ this ->filterPath ($ parts ['path ' ]) : '' ;
26412688 $ this ->query = isset ($ parts ['query ' ]) ? $ this ->filterQueryAndFragment ($ parts ['query ' ]) : '' ;
@@ -2710,7 +2757,7 @@ public function withScheme($scheme): self
27102757 throw new \InvalidArgumentException ('Scheme must be a string ' );
27112758 }
27122759
2713- if ($ this ->scheme === $ scheme = \strtolower ($ scheme )) {
2760+ if ($ this ->scheme === $ scheme = \strtr ($ scheme, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ ' , ' abcdefghijklmnopqrstuvwxyz ' )) {
27142761 return $ this ;
27152762 }
27162763
@@ -2744,7 +2791,7 @@ public function withHost($host): self
27442791 throw new \InvalidArgumentException ('Host must be a string ' );
27452792 }
27462793
2747- if ($ this ->host === $ host = \strtolower ($ host )) {
2794+ if ($ this ->host === $ host = \strtr ($ host, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ ' , ' abcdefghijklmnopqrstuvwxyz ' )) {
27482795 return $ this ;
27492796 }
27502797
@@ -2941,20 +2988,42 @@ public function fromGlobals(): ServerRequestInterface
29412988
29422989 $ headers = \function_exists ('getallheaders ' ) ? getallheaders () : static ::getHeadersFromServer ($ _SERVER );
29432990
2944- return $ this ->fromArrays ($ server , $ headers , $ _COOKIE , $ _GET , $ _POST , $ _FILES , \fopen ('php://input ' , 'r ' ) ?: null );
2991+ $ post = null ;
2992+ if ('POST ' === $ this ->getMethodFromEnv ($ server )) {
2993+ foreach ($ headers as $ headerName => $ headerValue ) {
2994+ if (true === \is_int ($ headerName ) || 'content-type ' !== \strtolower ($ headerName )) {
2995+ continue ;
2996+ }
2997+ if (\in_array (
2998+ \strtolower (\trim (\explode ('; ' , $ headerValue , 2 )[0 ])),
2999+ ['application/x-www-form-urlencoded ' , 'multipart/form-data ' ]
3000+ )) {
3001+ $ post = $ _POST ;
3002+
3003+ break ;
3004+ }
3005+ }
3006+ }
3007+
3008+ return $ this ->fromArrays ($ server , $ headers , $ _COOKIE , $ _GET , $ post , $ _FILES , \fopen ('php://input ' , 'r ' ) ?: null );
29453009 }
29463010
29473011 /**
29483012 * {@inheritdoc}
29493013 */
2950- public function fromArrays (array $ server , array $ headers = [], array $ cookie = [], array $ get = [], array $ post = [] , array $ files = [], $ body = null ): ServerRequestInterface
3014+ public function fromArrays (array $ server , array $ headers = [], array $ cookie = [], array $ get = [], /*? array*/ $ post = null , array $ files = [], $ body = null ): ServerRequestInterface
29513015 {
29523016 $ method = $ this ->getMethodFromEnv ($ server );
29533017 $ uri = $ this ->getUriFromEnvWithHTTP ($ server );
29543018 $ protocol = isset ($ server ['SERVER_PROTOCOL ' ]) ? \str_replace ('HTTP/ ' , '' , $ server ['SERVER_PROTOCOL ' ]) : '1.1 ' ;
29553019
29563020 $ serverRequest = $ this ->serverRequestFactory ->createServerRequest ($ method , $ uri , $ server );
29573021 foreach ($ headers as $ name => $ value ) {
3022+ // Because PHP automatically casts array keys set with numeric strings to integers, we have to make sure
3023+ // that numeric headers will not be sent along as integers, as withAddedHeader can only accept strings.
3024+ if (\is_int ($ name )) {
3025+ $ name = (string ) $ name ;
3026+ }
29583027 $ serverRequest = $ serverRequest ->withAddedHeader ($ name , $ value );
29593028 }
29603029
@@ -2981,7 +3050,7 @@ public function fromArrays(array $server, array $headers = [], array $cookie = [
29813050 }
29823051
29833052 /**
2984- * Implementation from Zend \Diactoros\marshalHeadersFromSapi().
3053+ * Implementation from Laminas \Diactoros\marshalHeadersFromSapi().
29853054 */
29863055 public static function getHeadersFromServer (array $ server ): array
29873056 {
@@ -3080,10 +3149,14 @@ private function createUploadedFileFromSpec(array $value)
30803149 return $ this ->normalizeNestedFileSpec ($ value );
30813150 }
30823151
3083- try {
3084- $ stream = $ this ->streamFactory ->createStreamFromFile ($ value ['tmp_name ' ]);
3085- } catch (\RuntimeException $ e ) {
3152+ if (UPLOAD_ERR_OK !== $ value ['error ' ]) {
30863153 $ stream = $ this ->streamFactory ->createStream ();
3154+ } else {
3155+ try {
3156+ $ stream = $ this ->streamFactory ->createStreamFromFile ($ value ['tmp_name ' ]);
3157+ } catch (\RuntimeException $ e ) {
3158+ $ stream = $ this ->streamFactory ->createStream ();
3159+ }
30873160 }
30883161
30893162 return $ this ->uploadedFileFactory ->createUploadedFile (
@@ -3101,8 +3174,6 @@ private function createUploadedFileFromSpec(array $value)
31013174 * Loops through all nested files and returns a normalized array of
31023175 * UploadedFileInterface instances.
31033176 *
3104- * @param array $files
3105- *
31063177 * @return UploadedFileInterface[]
31073178 */
31083179 private function normalizeNestedFileSpec (array $ files = []): array
@@ -3199,7 +3270,7 @@ public function fromGlobals(): ServerRequestInterface;
31993270 * @param array $headers typically the output of getallheaders() or similar structure
32003271 * @param array $cookie typically $_COOKIE or similar structure
32013272 * @param array $get typically $_GET or similar structure
3202- * @param array $post typically $_POST or similar structure
3273+ * @param array|null $post typically $_POST or similar structure, represents parsed request body
32033274 * @param array $files typically $_FILES or similar structure
32043275 * @param StreamInterface|resource|string|null $body Typically stdIn
32053276 *
@@ -3209,8 +3280,7 @@ public function fromArrays(
32093280 array $ server ,
32103281 array $ headers = [],
32113282 array $ cookie = [],
3212- array $ get = [],
3213- array $ post = [],
3283+ array $ get = [], /*?array*/ $ post = null ,
32143284 array $ files = [],
32153285 $ body = null
32163286 ): ServerRequestInterface ;
@@ -3219,8 +3289,6 @@ public function fromArrays(
32193289 * Get parsed headers from ($_SERVER) array.
32203290 *
32213291 * @param array $server typically $_SERVER or similar structure
3222- *
3223- * @return array
32243292 */
32253293 public static function getHeadersFromServer (array $ server ): array ;
32263294 }
0 commit comments