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

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