|
11 | 11 |
|
12 | 12 | namespace Symfony\Component\HttpFoundation;
|
13 | 13 |
|
| 14 | +use Symfony\Component\HttpFoundation\File\UploadedFile; |
14 | 15 | use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
15 | 16 |
|
16 | 17 | /**
|
@@ -252,26 +253,126 @@ public function initialize(array $query = array(), array $request = array(), arr
|
252 | 253 | }
|
253 | 254 |
|
254 | 255 | /**
|
255 |
| - * Creates a new request with values from PHP's super globals. |
| 256 | + * Creates a new request with values from PHP's super globals, or by analyzing the request content if the |
| 257 | + * request method is PUT, DELETE or PATCH. |
256 | 258 | *
|
257 |
| - * @return Request A new request |
| 259 | + * @param Boolean $test Whether the test mode is active |
| 260 | + * @return Request A new request |
258 | 261 | *
|
259 | 262 | * @api
|
260 | 263 | */
|
261 | 264 | public static function createFromGlobals()
|
262 | 265 | {
|
263 | 266 | $request = new static($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);
|
264 | 267 |
|
265 |
| - if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') |
266 |
| - && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH')) |
267 |
| - ) { |
268 |
| - parse_str($request->getContent(), $data); |
269 |
| - $request->request = new ParameterBag($data); |
| 268 | + if (in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))) { |
| 269 | + $contentType = $request->headers->get('CONTENT_TYPE'); |
| 270 | + |
| 271 | + if (0 === strpos($contentType, 'application/x-www-form-urlencoded')) { |
| 272 | + parse_str($request->getContent(), $data); |
| 273 | + $request->request = new ParameterBag($data); |
| 274 | + } elseif (0 === strpos($contentType, 'multipart/form-data')) { |
| 275 | + $request->decodeMultiPartFormData(); |
| 276 | + } |
270 | 277 | }
|
271 | 278 |
|
272 | 279 | return $request;
|
273 | 280 | }
|
274 | 281 |
|
| 282 | + /** |
| 283 | + * Decodes a multipart/form-data request, and injects the data into the request object. This method is used |
| 284 | + * whenever a multipart/form-data request is submitted with a PUT, DELETE or PATCH method. |
| 285 | + * |
| 286 | + * This implementation is based on an example by netcoder at http://stackoverflow.com/a/9469615. |
| 287 | + * |
| 288 | + * @param Request $request The Request object whos content should be decoded and places into it's files |
| 289 | + * and request properties. |
| 290 | + */ |
| 291 | + protected function decodeMultiPartFormData() |
| 292 | + { |
| 293 | + /** |
| 294 | + * The files array is structured such that it mimics PHP's $_FILES superglobal, allowing it to be passed on |
| 295 | + * to the Request constructor. |
| 296 | + * @var array |
| 297 | + */ |
| 298 | + $files = array(); |
| 299 | + |
| 300 | + /** |
| 301 | + * Key/value pairs of decoded form-data |
| 302 | + * @var array |
| 303 | + */ |
| 304 | + $data = array(); |
| 305 | + |
| 306 | + // Fetch content and determine boundary |
| 307 | + $rawData = $this->getContent(); |
| 308 | + if ($rawData) { |
| 309 | + $boundary = substr($rawData, 0, strpos($rawData, "\r\n")); |
| 310 | + |
| 311 | + // Fetch and process each part |
| 312 | + $parts = array_slice(explode($boundary, $rawData), 1); |
| 313 | + foreach ($parts as $part) { |
| 314 | + // If this is the last part, break |
| 315 | + if ($part == "--\r\n") { |
| 316 | + break; |
| 317 | + } |
| 318 | + |
| 319 | + // Separate content from headers |
| 320 | + $part = ltrim($part, "\r\n"); |
| 321 | + list($rawHeaders, $content) = explode("\r\n\r\n", $part, 2); |
| 322 | + $content = substr($content, 0, strlen($content) - 2); |
| 323 | + |
| 324 | + // Parse the headers list |
| 325 | + $rawHeaders = explode("\r\n", $rawHeaders); |
| 326 | + $headers = array(); |
| 327 | + foreach ($rawHeaders as $header) { |
| 328 | + list($name, $value) = explode(':', $header, 2); |
| 329 | + $headers[strtolower($name)] = ltrim($value, ' '); |
| 330 | + } |
| 331 | + |
| 332 | + // Parse the Content-Disposition to get the field name, etc. |
| 333 | + if (isset($headers['content-disposition'])) { |
| 334 | + $filename = null; |
| 335 | + preg_match( |
| 336 | + '/^form-data; *name="([^"]+)"(?:; *filename="([^"]+)")?/', |
| 337 | + $headers['content-disposition'], |
| 338 | + $matches |
| 339 | + ); |
| 340 | + |
| 341 | + $fieldName = $matches[1]; |
| 342 | + $fileName = (isset($matches[2]) ? $matches[2] : null); |
| 343 | + |
| 344 | + // If we have no filename, save the data. Otherwise, save the file. |
| 345 | + if ($fileName === null) { |
| 346 | + $data[$fieldName] = $content; |
| 347 | + } else { |
| 348 | + $localFileName = tempnam(sys_get_temp_dir(), 'sfy'); |
| 349 | + file_put_contents($localFileName, $content); |
| 350 | + |
| 351 | + $files[$fieldName] = array( |
| 352 | + 'name' => $fileName, |
| 353 | + 'type' => $headers['content-type'], |
| 354 | + 'tmp_name' => $localFileName, |
| 355 | + 'error' => 0, |
| 356 | + 'size' => filesize($localFileName) |
| 357 | + ); |
| 358 | + |
| 359 | + // Record the file path so that it can be verified as an uploaded file later |
| 360 | + UploadedFile::$files[] = $localFileName; |
| 361 | + |
| 362 | + // If the uploaded file is not moved, we need to delete it. To do that, we |
| 363 | + // register a shutdown function to cleanup the temporary file |
| 364 | + register_shutdown_function(function () use ($localFileName) { |
| 365 | + @unlink($localFileName); |
| 366 | + }); |
| 367 | + } |
| 368 | + } |
| 369 | + } |
| 370 | + } |
| 371 | + |
| 372 | + $this->request = new ParameterBag($data); |
| 373 | + $this->files = new FileBag($files); |
| 374 | + } |
| 375 | + |
275 | 376 | /**
|
276 | 377 | * Creates a Request based on a given URI and configuration.
|
277 | 378 | *
|
|
0 commit comments