Passed
Push — master ( 8bd912...d93388 )
by Alan
06:58 queued 02:20
created

src/Util/RequestParser.php (1 issue)

1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace ApiPlatform\Core\Util;
15
16
use Symfony\Component\HttpFoundation\Request;
17
18
/**
19
 * Utility functions for working with Symfony's HttpFoundation request.
20
 *
21
 * @internal
22
 *
23
 * @author Teoh Han Hui <[email protected]>
24
 * @author Vincent Chalamon <[email protected]>
25
 */
26
final class RequestParser
27
{
28
    private function __construct()
29
    {
30
    }
31
32
    /**
33
     * Gets a fixed request.
34
     */
35
    public static function parseAndDuplicateRequest(Request $request): Request
36
    {
37
        $query = self::parseRequestParams(self::getQueryString($request) ?? '');
38
        $body = self::parseRequestParams($request->getContent());
39
40
        return $request->duplicate($query, $body);
41
    }
42
43
    /**
44
     * Parses request parameters from the specified source.
45
     *
46
     * @author Rok Kralj
47
     *
48
     * @see https://stackoverflow.com/a/18209799/1529493
49
     */
50
    public static function parseRequestParams(string $source): array
51
    {
52
        // '[' is urlencoded ('%5B') in the input, but we must urldecode it in order
53
        // to find it when replacing names with the regexp below.
54
        $source = str_replace('%5B', '[', $source);
55
56
        $source = preg_replace_callback(
57
            '/(^|(?<=&))[^=[&]+/',
58
            function ($key) {
59
                return bin2hex(urldecode($key[0]));
60
            },
61
            $source
62
        );
63
64
        // parse_str urldecodes both keys and values in resulting array.
65
        parse_str($source, $params);
66
67
        return array_combine(array_map('hex2bin', array_keys($params)), $params);
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_combine(arr...eys($params)), $params) could return the type false which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
68
    }
69
70
    /**
71
     * Generates the normalized query string for the Request.
72
     *
73
     * It builds a normalized query string, where keys/value pairs are alphabetized
74
     * and have consistent escaping.
75
     */
76
    public static function getQueryString(Request $request): ?string
77
    {
78
        $qs = $request->server->get('QUERY_STRING', '');
79
        if ('' === $qs) {
80
            return null;
81
        }
82
83
        $parts = [];
84
85
        foreach (explode('&', $qs) as $param) {
86
            if ('' === $param || '=' === $param[0]) {
87
                // Ignore useless delimiters, e.g. "x=y&".
88
                // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
89
                // PHP also does not include them when building _GET.
90
                continue;
91
            }
92
93
            $keyValuePair = explode('=', $param, 2);
94
95
            // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
96
            // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to
97
            // RFC 3986 with rawurlencode.
98
            $parts[] = isset($keyValuePair[1]) ?
99
                rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) :
100
                rawurlencode(urldecode($keyValuePair[0]));
101
        }
102
103
        return implode('&', $parts);
104
    }
105
}
106