Completed
Pull Request — master (#100)
by Maxime
02:21
created

Request::createNotHttpException()   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 6
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
class Request extends AbstractHttpMessage
18
{
19
    /**
20
     * @var string
21
     */
22
    private $method;
23
24
    /**
25
     * @var string
26
     */
27
    private $uri;
28
29
    /**
30
     * @var string
31
     */
32
    private $host;
33
34
    /**
35
     * @var int
36
     */
37
    private $port;
38
39
    private function __construct() {}
40
41
    /**
42
     * @return Request
43
     */
44
    private function setMethod($method) : Request
45
    {
46
        $this->method = $method;
47
48
        return $this;
49
    }
50
51
    /**
52
     * @param string $uri
53
     * @return Request
54
     */
55
    private function setUri(string $uri) : Request
56
    {
57
        $this->uri = $uri;
58
59
        return $this;
60
    }
61
62
    /**
63
     * @return string
64
     */
65
    public function getUri() : string
66
    {
67
        return $this->uri;
68
    }
69
70
    /**
71
     * @return string
72
     */
73
    public function getMethod() : string
74
    {
75
        return $this->method;
76
    }
77
78
    /**
79
     * @return int
80
     */
81
    public function getVersion() : int
82
    {
83
        return (int) $this->getHeaders()->get('Sec-WebSocket-Version');
84
    }
85
86
    /**
87
     * @param int $version
88
     * @return Request
89
     */
90
    public function setVersion(int $version) : Request
91
    {
92
        $this->addHeader('Sec-WebSocket-Version', $version);
93
94
        return $this;
95
    }
96
97
    /**
98
     * @param string $key
99
     * @return Request
100
     */
101
    public function setKey(string $key) : Request
102
    {
103
        $this->addHeader('Sec-WebSocket-Key', $key);
104
105
        return $this;
106
    }
107
108
    /**
109
     * @return string|null
110
     */
111
    public function getKey()
112
    {
113
        return $this->getHeader('Sec-WebSocket-Key');
114
    }
115
116
    /**
117
     * @param string $host
118
     * @return self
119
     */
120
    private function setHost(string $host) : Request
121
    {
122
        $this->host = $host;
123
124
        return $this;
125
    }
126
127
    /**
128
     * @param int $port
129
     * @return Request
130
     */
131
    public function setPort($port)
132
    {
133
        $this->port = $port;
134
135
        return $this;
136
    }
137
138
    /**
139
     * @return array
140
     */
141
    public function getExtensions() : array
142
    {
143
        $originalHeaders = $this->getHeaders()->get('Sec-WebSocket-Extensions');
144
        if (!\is_array($originalHeaders)) {
145
            $originalHeaders = [$originalHeaders];
146
        }
147
148
        $extensionHeaders = [];
149
        $extensions = [];
150
151
        foreach ($originalHeaders as $extensionHeader) {
152
            $extensionHeaders = \array_merge($extensionHeaders, \array_map('trim', \explode(',', $extensionHeader)));
153
        }
154
155
        foreach ($extensionHeaders as $extension) {
156
            $explodingHeader = \explode(';', $extension);
157
            $extensionName = \trim($explodingHeader[0]);
158
            $extensions[$extensionName] = [];
159
160
            if (\count($explodingHeader)) {
161
                unset($explodingHeader[0]); // removing the name of the extension
162
                foreach($explodingHeader as $variable) {
163
                    $explodeVariable = \explode('=', $variable);
164
165
                    // The value can be with or without quote. We need to remove extra quotes.
166
                    $value = \preg_replace('/^"(.+)"$/', '$1', trim($explodeVariable[1]));
167
                    $value = \str_replace('\\"', '"', $value);
168
169
                    $extensions[$extensionName][\trim($explodeVariable[0])] = $value;
170
                }
171
            }
172
        }
173
174
        return $extensions;
175
    }
176
177
    /**
178
     * @return string
179
     */
180
    public function getRequestAsString() : string
181
    {
182
        $request = mb_strtoupper($this->method) . ' ' . $this->uri . " HTTP/1.1\r\n";
183
        $request .= 'Host: ' . $this->host . ($this->port ? ':' . $this->port : '') . "\r\n";
184
        $request .= 'User-Agent: Woketo/' . Meta::VERSION . "\r\n";
185
        $request .= "Upgrade: websocket\r\n";
186
        $request .= "Connection: Upgrade\r\n";
187
188
        foreach ($this->getHeaders() as $key => $header) {
189
            $request .= $key . ': ' . $header . "\r\n";
190
        }
191
192
        $request .= "\r\n";
193
194
        return $request;
195
    }
196
197
    /**
198
     * @return string
199
     */
200
    public function __toString()
201
    {
202
        return $this->getRequestAsString();
203
    }
204
205
    /**
206
     * @param string $requestString
207
     * @return Request
208
     * @throws HttpException
209
     */
210
    public static function create(string $requestString)
211
    {
212
        $request = new Request;
213
214
        $lines = \explode("\r\n", $requestString);
215
        Request::initRequest($lines[0], $request);
216
217
        unset($lines[0]);
218
        Request::initHeaders($lines, $request);
219
220
        if (empty($request->getHeaders()->get('Sec-WebSocket-Key')) || empty($request->getHeaders()->get('Upgrade')) || \strtolower($request->getHeaders()->get('Upgrade')) !== 'websocket') {
221
            throw new HttpException(sprintf("The request is not a websocket upgrade request, received:\n%s", $requestString));
222
        }
223
224
        return $request;
225
    }
226
227
    /**
228
     * @param string   $uri
229
     * @param string   $host
230
     * @param int|null $port
231
     * @return Request
232
     */
233
    public static function createClientRequest(string $uri, string $host, int $port = null)
234
    {
235
        $request = new Request();
236
237
        $request->setMethod('GET');
238
        $request->setUri($uri);
239
        $request->setHttpVersion('1.1');
240
        $request->setHost($host);
241
        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...
242
            $request->setPort($port);
243
        }
244
245
        return $request;
246
    }
247
248
    /**
249
     * @param string  $firstLine
250
     * @param Request $request
251
     * @throws HttpException
252
     */
253
    protected static function initRequest(string $firstLine, Request $request)
254
    {
255
        $httpElements = \explode(' ', $firstLine);
256
257
        if (\count($httpElements) < 3) {
258
            throw Request::createNotHttpException($firstLine);
259
        }
260
261
        $httpElements[2] = \trim($httpElements[2]);
262
        if (!\preg_match('/HTTP\/.+/', $httpElements[2])) {
263
            throw Request::createNotHttpException($firstLine);
264
        }
265
        $request->setHttpVersion($httpElements[2]);
266
267
        if (!\in_array($httpElements[0], ['POST', 'GET', 'PUT', 'DELETE'])) {
268
            throw new HttpException(
269
                \sprintf('Request not supported, only POST, GET, PUT, and DELETE are supported. "%s" received.', $httpElements[0])
270
            );
271
        }
272
273
        $request->setMethod($httpElements[0]);
274
        $request->setUri($httpElements[1]);
275
    }
276
}
277