1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Bridge\PsrHttpMessage\Factory; 13 14use Psr\Http\Message\ServerRequestInterface; 15use Psr\Http\Message\ResponseInterface; 16use Psr\Http\Message\UploadedFileInterface; 17use Psr\Http\Message\UriInterface; 18use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; 19use Symfony\Component\HttpFoundation\Cookie; 20use Symfony\Component\HttpFoundation\File\UploadedFile; 21use Symfony\Component\HttpFoundation\Request; 22use Symfony\Component\HttpFoundation\Response; 23 24/** 25 * {@inheritdoc} 26 * 27 * @author Kévin Dunglas <dunglas@gmail.com> 28 */ 29class HttpFoundationFactory implements HttpFoundationFactoryInterface 30{ 31 /** 32 * {@inheritdoc} 33 */ 34 public function createRequest(ServerRequestInterface $psrRequest) 35 { 36 $server = array(); 37 $uri = $psrRequest->getUri(); 38 39 if ($uri instanceof UriInterface) { 40 $server['SERVER_NAME'] = $uri->getHost(); 41 $server['SERVER_PORT'] = $uri->getPort(); 42 $server['REQUEST_URI'] = $uri->getPath(); 43 $server['QUERY_STRING'] = $uri->getQuery(); 44 } 45 46 $server['REQUEST_METHOD'] = $psrRequest->getMethod(); 47 48 $server = array_replace($server, $psrRequest->getServerParams()); 49 50 $parsedBody = $psrRequest->getParsedBody(); 51 $parsedBody = is_array($parsedBody) ? $parsedBody : array(); 52 53 $request = new Request( 54 $psrRequest->getQueryParams(), 55 $parsedBody, 56 $psrRequest->getAttributes(), 57 $psrRequest->getCookieParams(), 58 $this->getFiles($psrRequest->getUploadedFiles()), 59 $server, 60 $psrRequest->getBody()->__toString() 61 ); 62 $request->headers->replace($psrRequest->getHeaders()); 63 64 return $request; 65 } 66 67 /** 68 * Converts to the input array to $_FILES structure. 69 * 70 * @param array $uploadedFiles 71 * 72 * @return array 73 */ 74 private function getFiles(array $uploadedFiles) 75 { 76 $files = array(); 77 78 foreach ($uploadedFiles as $key => $value) { 79 if ($value instanceof UploadedFileInterface) { 80 $files[$key] = $this->createUploadedFile($value); 81 } else { 82 $files[$key] = $this->getFiles($value); 83 } 84 } 85 86 return $files; 87 } 88 89 /** 90 * Creates Symfony UploadedFile instance from PSR-7 ones. 91 * 92 * @param UploadedFileInterface $psrUploadedFile 93 * 94 * @return UploadedFile 95 */ 96 private function createUploadedFile(UploadedFileInterface $psrUploadedFile) 97 { 98 $temporaryPath = ''; 99 $clientFileName = ''; 100 if (UPLOAD_ERR_NO_FILE !== $psrUploadedFile->getError()) { 101 $temporaryPath = $this->getTemporaryPath(); 102 $psrUploadedFile->moveTo($temporaryPath); 103 104 $clientFileName = $psrUploadedFile->getClientFilename(); 105 } 106 107 if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { 108 // Symfony 4.1+ 109 return new UploadedFile( 110 $temporaryPath, 111 null === $clientFileName ? '' : $clientFileName, 112 $psrUploadedFile->getClientMediaType(), 113 $psrUploadedFile->getError(), 114 true 115 ); 116 } 117 118 return new UploadedFile( 119 $temporaryPath, 120 null === $clientFileName ? '' : $clientFileName, 121 $psrUploadedFile->getClientMediaType(), 122 $psrUploadedFile->getSize(), 123 $psrUploadedFile->getError(), 124 true 125 ); 126 } 127 128 /** 129 * Gets a temporary file path. 130 * 131 * @return string 132 */ 133 protected function getTemporaryPath() 134 { 135 return tempnam(sys_get_temp_dir(), uniqid('symfony', true)); 136 } 137 138 /** 139 * {@inheritdoc} 140 */ 141 public function createResponse(ResponseInterface $psrResponse) 142 { 143 $cookies = $psrResponse->getHeader('Set-Cookie'); 144 $psrResponse = $psrResponse->withoutHeader('Set-Cookie'); 145 146 $response = new Response( 147 $psrResponse->getBody()->__toString(), 148 $psrResponse->getStatusCode(), 149 $psrResponse->getHeaders() 150 ); 151 $response->setProtocolVersion($psrResponse->getProtocolVersion()); 152 153 foreach ($cookies as $cookie) { 154 $response->headers->setCookie($this->createCookie($cookie)); 155 } 156 157 return $response; 158 } 159 160 /** 161 * Creates a Cookie instance from a cookie string. 162 * 163 * Some snippets have been taken from the Guzzle project: https://github.com/guzzle/guzzle/blob/5.3/src/Cookie/SetCookie.php#L34 164 * 165 * @param string $cookie 166 * 167 * @return Cookie 168 * 169 * @throws \InvalidArgumentException 170 */ 171 private function createCookie($cookie) 172 { 173 foreach (explode(';', $cookie) as $part) { 174 $part = trim($part); 175 176 $data = explode('=', $part, 2); 177 $name = $data[0]; 178 $value = isset($data[1]) ? trim($data[1], " \n\r\t\0\x0B\"") : null; 179 180 if (!isset($cookieName)) { 181 $cookieName = $name; 182 $cookieValue = $value; 183 184 continue; 185 } 186 187 if ('expires' === strtolower($name) && null !== $value) { 188 $cookieExpire = new \DateTime($value); 189 190 continue; 191 } 192 193 if ('path' === strtolower($name) && null !== $value) { 194 $cookiePath = $value; 195 196 continue; 197 } 198 199 if ('domain' === strtolower($name) && null !== $value) { 200 $cookieDomain = $value; 201 202 continue; 203 } 204 205 if ('secure' === strtolower($name)) { 206 $cookieSecure = true; 207 208 continue; 209 } 210 211 if ('httponly' === strtolower($name)) { 212 $cookieHttpOnly = true; 213 214 continue; 215 } 216 217 if ('samesite' === strtolower($name) && null !== $value) { 218 $samesite = $value; 219 220 continue; 221 } 222 } 223 224 if (!isset($cookieName)) { 225 throw new \InvalidArgumentException('The value of the Set-Cookie header is malformed.'); 226 } 227 228 return new Cookie( 229 $cookieName, 230 $cookieValue, 231 isset($cookieExpire) ? $cookieExpire : 0, 232 isset($cookiePath) ? $cookiePath : '/', 233 isset($cookieDomain) ? $cookieDomain : null, 234 isset($cookieSecure), 235 isset($cookieHttpOnly), 236 false, 237 isset($samesite) ? $samesite : null 238 ); 239 } 240} 241