Passed
Pull Request — master (#125)
by
unknown
11:26
created

BasicNetworkResolver::process()   B

Complexity

Conditions 11
Paths 14

Size

Total Lines 33
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 11
eloc 24
nc 14
nop 2
dl 0
loc 33
rs 7.3166
c 1
b 0
f 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
4
namespace Yiisoft\Yii\Web\Middleware;
5
6
7
use Psr\Http\Message\ResponseInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Psr\Http\Server\MiddlewareInterface;
10
use Psr\Http\Server\RequestHandlerInterface;
11
12
/**
13
 * Basic network resolver
14
 *
15
 * It can be used in the following cases:
16
 * - not required IP resolve to access the user's IP
17
 * - user's IP is already resolved (eg `ngx_http_realip_module` or similar)
18
 *
19
 * @package Yiisoft\Yii\Web\Middleware
20
 */
21
class BasicNetworkResolver implements MiddlewareInterface
22
{
23
    private const DEFAULT_PROTOCOL_AND_ACCEPTABLE_VALUES = [
24
        'http' => ['http'],
25
        'https' => ['https', 'on'],
26
    ];
27
28
    private $protocolHeaders = [];
29
30
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
31
    {
32
        $newScheme = null;
33
        foreach ($this->protocolHeaders as $header => $data) {
34
            if (!$request->hasHeader($header)) {
35
                continue;
36
            }
37
            $headerValues = $request->getHeader($header);
38
            if (is_callable($data)) {
39
                $newScheme = call_user_func($data, $headerValues, $header, $request);
40
                if ($newScheme === null) {
41
                    continue;
42
                } elseif (!is_string($newScheme)) {
43
                    throw new \RuntimeException('The scheme is neither string nor null!');
44
                } elseif (strlen($newScheme) === 0) {
45
                    throw new \RuntimeException('The scheme cannot be an empty string!');
46
                }
47
                break;
48
            }
49
            $headerValue = strtolower($headerValues[0]);
50
            foreach ($data as $protocol => $acceptedValues) {
51
                if (!in_array($headerValue, $acceptedValues)) {
52
                    continue;
53
                }
54
                $newScheme = $protocol;
55
                break 2;
56
            }
57
        }
58
        $uri = $request->getUri();
59
        if ($newScheme !== null && $newScheme !== $uri->getScheme()) {
60
            $request = $request->withUri($uri->withScheme($newScheme));
61
        }
62
        return $handler->handle($request);
63
    }
64
65
    /**
66
     * With header to check for determining whether the connection is made via HTTP or HTTPS (or any protocol).
67
     *
68
     * The match of header names and values is case-insensitive.
69
     * It's not advisable to put insecure/untrusted headers here.
70
     *
71
     * Accepted types of values:
72
     * - NULL (default): {{DEFAULT_PROTOCOL_AND_ACCEPTABLE_VALUES}}
73
     * - callable: custom function for getting the protocol
74
     * ```php
75
     * ->withProtocolHeader('x-forwarded-proto', function(array $values, string $header, ServerRequestInterface $request) {
76
     *   return $values[0] === 'https' ? 'https' : 'http';
77
     *   return null;     // If it doesn't make sense.
78
     * });
79
     * ```
80
     * - array: The array keys are protocol string and the array value is a list of header values that indicate the protocol.
81
     * ```php
82
     * ->withProtocolHeader('x-forwarded-proto', [
83
     *   'http' => ['http'],
84
     *   'https' => ['https']
85
     * ]);
86
     * ```
87
     *
88
     * @param callable|array|null $protocolAndAcceptedValues
89
     * @return static
90
     * @see DEFAULT_PROTOCOL_AND_ACCEPTABLE_VALUES
91
     */
92
    public function withProtocolHeader(string $header, $protocolAndAcceptedValues = null)
93
    {
94
        $new = clone $this;
95
        $header = strtolower($header);
96
        if ($protocolAndAcceptedValues === null) {
97
            $new->protocolHeaders[$header] = self::DEFAULT_PROTOCOL_AND_ACCEPTABLE_VALUES;
98
        } elseif (is_callable($protocolAndAcceptedValues)) {
99
            $new->protocolHeaders[$header] = $protocolAndAcceptedValues;
100
        } elseif (!is_array($protocolAndAcceptedValues)) {
101
            throw new \RuntimeException('$protocolAndAcceptedValues is not array nor callable!');
102
        } elseif (is_array($protocolAndAcceptedValues) && count($protocolAndAcceptedValues) === 0) {
103
            throw new \RuntimeException('$protocolAndAcceptedValues cannot be an empty array!');
104
        } else {
105
            $new->protocolHeaders[$header] = [];
106
            foreach ($protocolAndAcceptedValues as $protocol => $acceptedValues) {
107
                if (!is_string($protocol)) {
108
                    throw new \RuntimeException('The protocol must be type of string!');
109
                }
110
                $new->protocolHeaders[$header][$protocol] = array_map('strtolower', (array)$acceptedValues);
111
            }
112
        }
113
        return $new;
114
    }
115
116
    /**
117
     * @return static
118
     */
119
    public function withoutProtocolHeader(string $header)
120
    {
121
        $new = clone $this;
122
        unset($new->protocolHeaders[strtolower($header)]);
123
        return $new;
124
    }
125
126
    /**
127
     * @return static
128
     */
129
    public function withoutProtocolHeaders(?array $headers = null)
130
    {
131
        $new = clone $this;
132
        if ($headers === null) {
133
            $new->protocolHeaders = [];
134
        } else {
135
            foreach ($headers as $header) {
136
                $new = $new->withoutProtocolHeader($header);
137
            }
138
        }
139
        return $new;
140
    }
141
}
142