Completed
Push — 2.2 ( 45c537 )
by Kévin
13s
created

RequestParser::parseRequestParams()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 1
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
     * @param Request $request
36
     *
37
     * @return Request
38
     */
39
    public static function parseAndDuplicateRequest(Request $request): Request
40
    {
41
        $query = self::parseRequestParams(self::getQueryString($request) ?? '');
42
        $body = self::parseRequestParams($request->getContent());
43
44
        return $request->duplicate($query, $body);
45
    }
46
47
    /**
48
     * Parses request parameters from the specified source.
49
     *
50
     * @author Rok Kralj
51
     *
52
     * @see https://stackoverflow.com/a/18209799/1529493
53
     *
54
     * @param string $source
55
     *
56
     * @return array
57
     */
58
    public static function parseRequestParams(string $source): array
59
    {
60
        // '[' is urlencoded ('%5B') in the input, but we must urldecode it in order
61
        // to find it when replacing names with the regexp below.
62
        $source = str_replace('%5B', '[', $source);
63
64
        $source = preg_replace_callback(
65
            '/(^|(?<=&))[^=[&]+/',
66
            function ($key) {
67
                return bin2hex(urldecode($key[0]));
68
            },
69
            $source
70
        );
71
72
        // parse_str urldecodes both keys and values in resulting array.
73
        parse_str($source, $params);
74
75
        return array_combine(array_map('hex2bin', array_keys($params)), $params);
76
    }
77
78
    /**
79
     * Generates the normalized query string for the Request.
80
     *
81
     * It builds a normalized query string, where keys/value pairs are alphabetized
82
     * and have consistent escaping.
83
     *
84
     * @return string|null A normalized query string for the Request
85
     */
86
    public static function getQueryString(Request $request)
87
    {
88
        $qs = $request->server->get('QUERY_STRING', '');
89
        if ('' === $qs) {
90
            return null;
91
        }
92
93
        $parts = [];
94
95
        foreach (explode('&', $qs) as $param) {
96
            if ('' === $param || '=' === $param[0]) {
97
                // Ignore useless delimiters, e.g. "x=y&".
98
                // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
99
                // PHP also does not include them when building _GET.
100
                continue;
101
            }
102
103
            $keyValuePair = explode('=', $param, 2);
104
105
            // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
106
            // 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
107
            // RFC 3986 with rawurlencode.
108
            $parts[] = isset($keyValuePair[1]) ?
109
                rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) :
110
                rawurlencode(urldecode($keyValuePair[0]));
111
        }
112
113
        return implode('&', $parts);
114
    }
115
}
116