Completed
Pull Request — master (#100)
by Maxime
03:53
created

Request::setPort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * This file is a part of a nekland library
5
 *
6
 * (c) Nekland <[email protected]>
7
 *
8
 * For the full license, take a look to the LICENSE file
9
 * on the root directory of this project
10
 */
11
12
namespace Nekland\Woketo\Http;
13
14
use Nekland\Woketo\Exception\Http\HttpException;
15
use Nekland\Woketo\Meta;
16
17
/**
18
 * Class Request
19
 *
20
 * @internal
21
 */
22
class Request extends AbstractHttpMessage
23
{
24
    /**
25
     * @var string
26
     */
27
    private $method;
28
29
    /**
30
     * @var string
31
     */
32
    private $uri;
33
34
    /**
35
     * @var string
36
     */
37
    private $host;
38
39
    /**
40
     * @var int
41
     */
42
    private $port;
43
44
    private function __construct() {}
45
46
    /**
47
     * @return Request
48
     */
49
    private function setMethod($method) : Request
50
    {
51
        $this->method = $method;
52
53
        return $this;
54
    }
55
56
    /**
57
     * @param string $uri
58
     * @return Request
59
     */
60
    private function setUri(string $uri) : Request
61
    {
62
        $this->uri = $uri;
63
64
        return $this;
65
    }
66
67
    /**
68
     * @return string
69
     */
70
    public function getUri() : string
71
    {
72
        return $this->uri;
73
    }
74
75
    /**
76
     * @return string
77
     */
78
    public function getMethod() : string
79
    {
80
        return $this->method;
81
    }
82
83
    /**
84
     * @return int
85
     */
86
    public function getVersion() : int
87
    {
88
        return (int) $this->getHeaders()->get('Sec-WebSocket-Version');
89
    }
90
91
    /**
92
     * @param int $version
93
     * @return Request
94
     */
95
    public function setVersion(int $version) : Request
96
    {
97
        $this->addHeader('Sec-WebSocket-Version', $version);
98
99
        return $this;
100
    }
101
102
    /**
103
     * @param string $key
104
     * @return Request
105
     */
106
    public function setKey(string $key) : Request
107
    {
108
        $this->addHeader('Sec-WebSocket-Key', $key);
109
110
        return $this;
111
    }
112
113
    /**
114
     * @return string|null
115
     */
116
    public function getKey()
117
    {
118
        return $this->getHeader('Sec-WebSocket-Key');
119
    }
120
121
    /**
122
     * @param string $host
123
     * @return self
124
     */
125
    private function setHost(string $host) : Request
126
    {
127
        $this->host = $host;
128
129
        return $this;
130
    }
131
132
    /**
133
     * @param int $port
134
     * @return Request
135
     */
136
    public function setPort($port)
137
    {
138
        $this->port = $port;
139
140
        return $this;
141
    }
142
143
    /**
144
     * @return array
145
     */
146
    public function getExtensions() : array
147
    {
148
        $originalHeaders = $this->getHeaders()->get('Sec-WebSocket-Extensions');
149
        if (!\is_array($originalHeaders)) {
150
            $originalHeaders = [$originalHeaders];
151
        }
152
153
        $extensionHeaders = [];
154
        $extensions = [];
155
156
        foreach ($originalHeaders as $extensionHeader) {
157
            $extensionHeaders = \array_merge($extensionHeaders, \array_map('trim', \explode(',', $extensionHeader)));
158
        }
159
160
        foreach ($extensionHeaders as $extension) {
161
            $explodingHeader = \explode(';', $extension);
162
            $extensionName = \trim($explodingHeader[0]);
163
            $extensions[$extensionName] = [];
164
165
            if (\count($explodingHeader)) {
166
                unset($explodingHeader[0]); // removing the name of the extension
167
                foreach($explodingHeader as $variable) {
168
                    $explodeVariable = \explode('=', $variable);
169
170
                    // The value can be with or without quote. We need to remove extra quotes.
171
                    $value = \preg_replace('/^"(.+)"$/', '$1', trim($explodeVariable[1]));
172
                    $value = \str_replace('\\"', '"', $value);
173
174
                    $extensions[$extensionName][\trim($explodeVariable[0])] = $value;
175
                }
176
            }
177
        }
178
179
        return $extensions;
180
    }
181
182
    /**
183
     * @return string
184
     */
185
    public function getRequestAsString() : string
186
    {
187
        $request = mb_strtoupper($this->method) . ' ' . $this->uri . " HTTP/1.1\r\n";
188
        $request .= 'Host: ' . $this->host . ($this->port ? ':' . $this->port : '') . "\r\n";
189
        $request .= 'User-Agent: Woketo/' . Meta::VERSION . "\r\n";
190
        $request .= "Upgrade: websocket\r\n";
191
        $request .= "Connection: Upgrade\r\n";
192
193
        foreach ($this->getHeaders() as $key => $header) {
194
            $request .= $key . ': ' . $header . "\r\n";
195
        }
196
197
        $request .= "\r\n";
198
199
        return $request;
200
    }
201
202
    /**
203
     * @return string
204
     */
205
    public function __toString()
206
    {
207
        return $this->getRequestAsString();
208
    }
209
210
    /**
211
     * @param string $requestString
212
     * @return Request
213
     * @throws HttpException
214
     */
215
    public static function create(string $requestString)
216
    {
217
        $request = new Request;
218
219
        $lines = \explode("\r\n", $requestString);
220
        Request::initRequest($lines[0], $request);
221
222
        unset($lines[0]);
223
        Request::initHeaders($lines, $request);
224
225
        if (empty($request->getHeaders()->get('Sec-WebSocket-Key')) || empty($request->getHeaders()->get('Upgrade')) || \strtolower($request->getHeaders()->get('Upgrade')) !== 'websocket') {
226
            throw new HttpException(sprintf("The request is not a websocket upgrade request, received:\n%s", $requestString));
227
        }
228
229
        return $request;
230
    }
231
232
    /**
233
     * @param string   $uri
234
     * @param string   $host
235
     * @param int|null $port
236
     * @return Request
237
     */
238
    public static function createClientRequest(string $uri, string $host, int $port = null)
239
    {
240
        $request = new Request();
241
242
        $request
243
            ->setMethod('GET')
244
            ->setUri($uri)
245
            ->setHttpVersion('1.1')
246
            ->setHost($host)
247
        ;
248
        
249
        if ($port) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $port of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
250
            $request->setPort($port);
251
        }
252
253
        return $request;
254
    }
255
256
    /**
257
     * @param string  $firstLine
258
     * @param Request $request
259
     * @throws HttpException
260
     */
261
    protected static function initRequest(string $firstLine, Request $request)
262
    {
263
        $httpElements = \explode(' ', $firstLine);
264
265
        if (\count($httpElements) < 3) {
266
            throw Request::createNotHttpException($firstLine);
267
        }
268
269
        $httpElements[2] = \trim($httpElements[2]);
270
        if (!\preg_match('/HTTP\/.+/', $httpElements[2])) {
271
            throw Request::createNotHttpException($firstLine);
272
        }
273
        $request->setHttpVersion($httpElements[2]);
274
275
        if (!\in_array($httpElements[0], ['POST', 'GET', 'PUT', 'DELETE'])) {
276
            throw new HttpException(
277
                \sprintf('Request not supported, only POST, GET, PUT, and DELETE are supported. "%s" received.', $httpElements[0])
278
            );
279
        }
280
281
        $request->setMethod($httpElements[0]);
282
        $request->setUri($httpElements[1]);
283
    }
284
}
285