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

Request::createNotHttpException()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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