Passed
Push — master ( bfd836...2e261a )
by Pol
28:58 queued 26:53
created

UnalteredPsrHttpFactory::parseStr()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 17
c 2
b 0
f 0
dl 0
loc 30
ccs 15
cts 15
cp 1
rs 9.7
cc 3
nc 4
nop 1
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace loophp\UnalteredPsrHttpMessageBridgeBundle\Factory;
6
7
use Generator;
8
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
9
use Symfony\Component\HttpFoundation\Request;
10
use Symfony\Component\HttpFoundation\Response;
11
12
final class UnalteredPsrHttpFactory implements HttpMessageFactoryInterface
13
{
14
    /**
15
     * @var \Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface
16
     */
17
    private $httpMessageFactory;
18
19 5
    public function __construct(HttpMessageFactoryInterface $httpMessageFactory)
20
    {
21 5
        $this->httpMessageFactory = $httpMessageFactory;
22 5
    }
23
24
    /**
25
     * {@inheritdoc}
26
     */
27 3
    public function createRequest(Request $symfonyRequest)
28
    {
29
        // Call the original object to avoid duplicating code.
30 3
        $request = $this->httpMessageFactory->createRequest($symfonyRequest);
31
32
        // If a query string does not exist, return the original object.
33 3
        if ('' === $unalteredQueryString = $symfonyRequest->server->get('QUERY_STRING')) {
34 1
            return $request;
35
        }
36
37
        // This is where all is happening.
38
        // We do not rely on $symfonyRequest->query->all() because it relies
39
        // on parse_str() which is altering the query string parameters.
40
        // We rely on $symfonyRequest->server->get('QUERY_STRING') so we are
41
        // sure that the query string hasn't been altered.
42
        // Create a new request with the URI and Params updated.
43
        return $request
44 2
            ->withQueryParams(
45 2
                iterator_to_array($this->parseStr($unalteredQueryString))
46
            )
47 2
            ->withUri(
48
                $request
49 2
                    ->getUri()
50 2
                    ->withQuery($unalteredQueryString)
51
            );
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57 1
    public function createResponse(Response $symfonyResponse)
58
    {
59 1
        return $this->httpMessageFactory->createResponse($symfonyResponse);
60
    }
61
62
    /**
63
     * Custom parse_str() function that doesn't alter the parameters key value.
64
     *
65
     * @see: https://github.com/ecphp/cas-lib/issues/5.
66
     *
67
     * @param string $queryString
68
     *
69
     * @return Generator<string, string>
70
     */
71 2
    private function parseStr(string $queryString): Generator
72
    {
73 2
        $regex = '/^(?<key>[^&=]+?)(?:\[[^&=]*\])?=(?<value>[^&=]+)$/';
74 2
        $parameters = explode('&', $queryString);
75
76 2
        foreach (['urldecode', 'bin2hex'] as $callback) {
77 2
            $parameters = array_map(
78
                static function (string $keyValue) use ($regex, $callback): string {
79 2
                    return (string) preg_replace_callback(
80 2
                        $regex,
81
                        static function (array $match) use ($callback): string {
82 2
                            return str_replace($match['key'], $callback($match['key']), $match[0]);
83 2
                        },
84
                        $keyValue
85
                    );
86 2
                },
87
                $parameters
88
            );
89
        }
90
91 2
        parse_str(
92 2
            implode('&', $parameters),
93
            $parameters
94
        );
95
96 2
        foreach ($parameters as $key => $value) {
97 2
            yield (string) hex2bin($key) => $value;
98
        }
99
100 2
        return yield from [];
101
    }
102
}
103