This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace NokitaKaze\TestHTTPServer; |
||
4 | |||
5 | /** |
||
6 | * Class Server |
||
7 | * @package NokitaKaze\TestHTTPServer |
||
8 | * |
||
9 | * @doc https://tools.ietf.org/html/rfc7230 |
||
10 | * @doc http://httpwg.org/specs/rfc7230.html |
||
11 | */ |
||
12 | class Server { |
||
13 | const CHUNK_SIZE = 4096; |
||
14 | |||
15 | /** |
||
16 | * @var ServerSettings |
||
17 | */ |
||
18 | protected $_settings = null; |
||
19 | |||
20 | /** |
||
21 | * @var resource |
||
22 | */ |
||
23 | protected $_server_socket = null; |
||
24 | |||
25 | /** |
||
26 | * @var resource |
||
27 | */ |
||
28 | protected $_stream_context = null; |
||
29 | |||
30 | /** |
||
31 | * @var ClientDatum[] |
||
32 | */ |
||
33 | protected $_client_connects = []; |
||
34 | |||
35 | /** |
||
36 | * Server constructor |
||
37 | * |
||
38 | * @param ServerSettings|object|array $settings |
||
39 | * |
||
40 | * @throws Exception |
||
41 | */ |
||
42 | 613 | function __construct($settings) { |
|
43 | 613 | $settings = (object) $settings; |
|
44 | 613 | self::fix_incoming_settings($settings); |
|
45 | 613 | $this->_settings = $settings; |
|
46 | 613 | $this->init_ssl_settings(); |
|
47 | 601 | } |
|
48 | |||
49 | 601 | function __destruct() { |
|
50 | 601 | $this->shutdown(); |
|
51 | 601 | } |
|
52 | |||
53 | 601 | function shutdown() { |
|
54 | 601 | foreach ($this->_client_connects as &$connect) { |
|
55 | 268 | $this->close_connection($connect); |
|
56 | 301 | } |
|
57 | 601 | $this->_client_connects = []; |
|
58 | 601 | $this->close_connection_socket($this->_server_socket); |
|
59 | 601 | $this->_server_socket = null; |
|
60 | 601 | } |
|
61 | |||
62 | /** |
||
63 | * @param resource $connection |
||
64 | */ |
||
65 | 601 | function close_connection_socket($connection) { |
|
66 | 601 | if (is_resource($connection)) { |
|
67 | 597 | stream_socket_shutdown($connection, STREAM_SHUT_RDWR); |
|
68 | 597 | fclose($connection); |
|
69 | 299 | } |
|
70 | 601 | } |
|
71 | |||
72 | /** |
||
73 | * @param object|ClientDatum $connection |
||
74 | */ |
||
75 | 573 | function close_connection(&$connection) { |
|
76 | 573 | if (is_null($connection)) { |
|
77 | 245 | return; |
|
78 | } |
||
79 | 573 | $this->close_connection_socket($connection->client); |
|
80 | 573 | $connection->client = null; |
|
81 | 573 | $connection = null; |
|
82 | 573 | } |
|
83 | |||
84 | /** |
||
85 | * @return array |
||
86 | */ |
||
87 | 613 | static function get_default_settings() { |
|
88 | return [ |
||
89 | 613 | 'interface' => '127.0.0.1', |
|
90 | 613 | 'port' => 58080, |
|
91 | 613 | 'server_connect_waiting_time' => 0.02, |
|
92 | 307 | 'is_ssl' => false, |
|
93 | 'filterIncomingConnect' => function () { return true; }, |
||
94 | 307 | 'server_maximum_chunk' => 300 * 1024, |
|
95 | 613 | 'server_backlog' => min(255, SOMAXCONN), |
|
96 | |||
97 | 613 | 'time_wait_until_first_byte' => 60, |
|
98 | 307 | ]; |
|
99 | } |
||
100 | |||
101 | 613 | function init_ssl_settings() { |
|
102 | 613 | $this->_stream_context = stream_context_create(); |
|
103 | /** @url http://php.net/manual/ru/context.socket.php */ |
||
104 | 613 | stream_context_set_option($this->_stream_context, 'socket', 'backlog', $this->_settings->server_backlog); |
|
105 | 613 | if (isset($this->_settings->ssl_server_certificate_file)) { |
|
106 | // @hint Здесь специально не задаётся _settings->is_ssl |
||
107 | 412 | $this->stream_set_ssl_option('local_cert', $this->_settings->ssl_server_certificate_file); |
|
108 | 412 | $this->stream_set_ssl_option('allow_self_signed', true); |
|
109 | 412 | $this->stream_set_ssl_option('verify_peer', false); |
|
110 | 412 | if (isset($this->_settings->ssl_server_key_file)) { |
|
111 | 412 | $this->stream_set_ssl_option('local_pk', $this->_settings->ssl_server_key_file); |
|
112 | 206 | } |
|
113 | 412 | $this->stream_set_ssl_option('passphrase', |
|
114 | 412 | isset($this->_settings->ssl_server_key_password) ? $this->_settings->ssl_server_key_password : ''); |
|
115 | |||
116 | 412 | if (isset($this->_settings->ssl_client_certificate_file)) { |
|
117 | 48 | $this->stream_set_ssl_option('verify_peer', true); |
|
118 | 48 | $this->stream_set_ssl_option('capture_peer_cert', true); |
|
119 | 48 | $this->stream_set_ssl_option('capture_peer_cert_chain', true); |
|
120 | 139 | $this->stream_set_ssl_option('cafile', $this->_settings->ssl_client_certificate_file); |
|
121 | 24 | } |
|
122 | 307 | } elseif (isset($this->_settings->ssl_server_key_file)) { |
|
123 | 4 | throw new Exception('ssl_server_key_file is set, but ssl_server_certificate_file is missing'); |
|
124 | 99 | } elseif (isset($this->_settings->ssl_server_key_password)) { |
|
125 | 4 | throw new Exception('ssl_server_key_password is set, but ssl_server_certificate_file is missing'); |
|
126 | 97 | } elseif (isset($this->_settings->ssl_client_certificate_file)) { |
|
127 | 4 | throw new Exception('ssl_client_certificate_file is set, but ssl_server_certificate_file is missing'); |
|
128 | } |
||
129 | 601 | } |
|
130 | |||
131 | /** |
||
132 | * Server constructor. |
||
133 | * |
||
134 | * @param ServerSettings|object $settings |
||
135 | */ |
||
136 | 613 | protected static function fix_incoming_settings($settings) { |
|
137 | 613 | $default_settings = self::get_default_settings(); |
|
138 | 613 | foreach ($default_settings as $key => $value) { |
|
139 | 613 | if (!isset($settings->{$key})) { |
|
140 | 613 | $settings->{$key} = $value; |
|
141 | 307 | } |
|
142 | 307 | } |
|
143 | 613 | foreach (['ListenStart', 'Connect', 'Request', 'Disconnect', 'ListenStop', 'AnyIncomingData', 'HeadIncomingData', |
|
144 | 307 | 'BodyIncomingData', 'HeadReceived', 'HeadInvalidReceived'] as $event) { |
|
145 | 613 | if (!isset($settings->{'on'.$event})) { |
|
146 | 613 | $settings->{'on'.$event} = null; |
|
147 | 307 | } |
|
148 | 307 | } |
|
149 | 613 | } |
|
150 | |||
151 | /** |
||
152 | * @param callable|double $param |
||
153 | * @param callable|null $tick |
||
154 | */ |
||
155 | 34 | function listen($param, $tick = null) { |
|
156 | 34 | $this->init_listening(); |
|
157 | 34 | if (is_callable($param)) { |
|
158 | 34 | $closure = $param; |
|
159 | 18 | } else { |
|
160 | $closure = function () use ($param) { return (microtime(true) < $param); }; |
||
161 | } |
||
162 | /** @noinspection PhpMethodParametersCountMismatchInspection */ |
||
163 | 34 | while ($closure($this)) { |
|
164 | 34 | $this->listen_tick(); |
|
165 | 34 | if (!is_null($tick)) { |
|
166 | $tick($this); |
||
167 | } |
||
168 | 18 | } |
|
169 | 34 | } |
|
170 | |||
171 | 597 | function init_listening() { |
|
172 | 597 | if (!is_null($this->_server_socket)) { |
|
173 | 34 | return; |
|
174 | } |
||
175 | 597 | $this->_server_socket = stream_socket_server( |
|
176 | 597 | sprintf('%s://%s:%d', |
|
177 | 597 | $this->_settings->is_ssl ? 'ssl' : 'tcp', |
|
178 | 597 | is_null($this->_settings->interface) ? '0.0.0.0' : $this->_settings->interface, |
|
179 | 597 | $this->_settings->port |
|
180 | 299 | ), |
|
181 | 597 | $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, |
|
182 | 597 | $this->_stream_context); |
|
183 | 597 | if (!isset($this->_server_socket)) { |
|
184 | // @codeCoverageIgnoreStart |
||
185 | throw new Exception('Can not create socket ['.$errstr.']', 100 + $errno); |
||
186 | // @codeCoverageIgnoreEnd |
||
187 | } |
||
188 | |||
189 | 597 | $this->event_raise('ListenStart'); |
|
190 | 597 | } |
|
191 | |||
192 | 597 | function listen_tick() { |
|
193 | 597 | $write = null; |
|
194 | 597 | $except = null; |
|
195 | 597 | $tv_sec = (int) $this->_settings->server_connect_waiting_time; |
|
196 | 597 | $tv_usec = ($this->_settings->server_connect_waiting_time - $tv_sec) * 1000000; |
|
197 | 597 | while (true) { |
|
198 | 597 | $read = [$this->_server_socket]; |
|
199 | 597 | if (stream_select($read, $write, $except, $tv_sec, $tv_usec) === false) { |
|
200 | // @codeCoverageIgnoreStart |
||
201 | throw new Exception('Error on stream select'); |
||
202 | // @codeCoverageIgnoreEnd |
||
203 | } |
||
204 | 597 | if (empty($read)) { |
|
205 | 597 | $this->process_all_connected_clients(); |
|
206 | |||
207 | 597 | return; |
|
208 | } |
||
209 | |||
210 | 589 | $client = @stream_socket_accept($this->_server_socket); |
|
211 | 589 | if ($client === false) { |
|
212 | 30 | $this->event_raise('InvalidConnection'); |
|
213 | 30 | continue; |
|
214 | } |
||
215 | |||
216 | 573 | if (stream_set_blocking($client, 0) === false) { |
|
217 | // @codeCoverageIgnoreStart |
||
218 | throw new Exception('Can not set socket as non blocking'); |
||
219 | // @codeCoverageIgnoreEnd |
||
220 | } |
||
221 | 573 | $connect_time = microtime(true); |
|
222 | /** |
||
223 | * @var ClientDatum $datum |
||
224 | */ |
||
225 | 573 | $datum = new \stdClass(); |
|
226 | 573 | $datum->status = 0; |
|
227 | 573 | $datum->client = $client; |
|
228 | 573 | $datum->connection_time = $connect_time; |
|
229 | 573 | $datum->blob_request = ''; |
|
230 | 573 | $datum->request_head_params = []; |
|
231 | 573 | $datum->server = $this; |
|
232 | 573 | $datum->accepted_hosts = null; |
|
233 | 573 | $this->event_raise('Connect', $datum); |
|
234 | 573 | $this->_client_connects[] = $datum; |
|
235 | 287 | } |
|
236 | } |
||
237 | |||
238 | /** |
||
239 | * @param string $key |
||
240 | * @param mixed $value |
||
241 | * |
||
242 | * @url http://php.net/manual/ru/context.ssl.php |
||
243 | */ |
||
244 | 426 | function stream_set_ssl_option($key, $value) { |
|
245 | 426 | stream_context_set_option($this->_stream_context, 'ssl', $key, $value); |
|
246 | 426 | } |
|
247 | |||
248 | 17 | function set_option($key, $value) { |
|
249 | 17 | $this->_settings->{$key} = $value; |
|
250 | 17 | } |
|
251 | |||
252 | 7 | function get_option($key) { |
|
253 | 7 | return isset($this->_settings->{$key}) ? $this->_settings->{$key} : null; |
|
254 | } |
||
255 | |||
256 | /** |
||
257 | * Первый байт не пришёл |
||
258 | * |
||
259 | * @param object|ClientDatum $connection |
||
260 | * |
||
261 | * @return boolean |
||
262 | */ |
||
263 | 573 | protected function check_timeouts_on_connection_first_byte($connection) { |
|
264 | 573 | return isset($this->_settings->time_wait_until_first_byte) and |
|
265 | 573 | !isset($connection->first_byte_received_time) and |
|
266 | 573 | ($connection->connection_time < microtime(true) - $this->_settings->time_wait_until_first_byte); |
|
267 | } |
||
268 | |||
269 | /** |
||
270 | * Голова не пришла |
||
271 | * |
||
272 | * @param object|ClientDatum $connection |
||
273 | * |
||
274 | * @return boolean |
||
275 | */ |
||
276 | 572 | protected function check_timeouts_on_connection_head_received($connection) { |
|
277 | 572 | return isset($this->_settings->time_wait_until_head_received) and |
|
278 | 431 | !isset($connection->head_received_time) and |
|
279 | 572 | ($connection->connection_time < microtime(true) - $this->_settings->time_wait_until_head_received); |
|
280 | } |
||
281 | |||
282 | /** |
||
283 | * Запрос не пришёл |
||
284 | * |
||
285 | * @param object|ClientDatum $connection |
||
286 | * |
||
287 | * @return boolean |
||
288 | */ |
||
289 | 572 | protected function check_timeouts_on_connection_request_received($connection) { |
|
290 | 572 | return isset($this->_settings->time_wait_until_request_received) and |
|
291 | 431 | !isset($connection->full_request_received_time) and |
|
292 | 572 | ($connection->connection_time < microtime(true) - $this->_settings->time_wait_until_request_received); |
|
293 | } |
||
294 | |||
295 | /** |
||
296 | * Тело не пришло (голова пришла) |
||
297 | * |
||
298 | * @param object|ClientDatum $connection |
||
299 | * |
||
300 | * @return boolean |
||
301 | */ |
||
302 | 572 | protected function check_timeouts_on_connection_body_received_without_head($connection) { |
|
303 | 572 | return isset($this->_settings->time_between_head_and_body_received, $connection->head_received_time) and |
|
304 | 431 | !isset($connection->full_request_received_time) and |
|
305 | 572 | ($connection->head_received_time < microtime(true) - $this->_settings->time_between_head_and_body_received); |
|
306 | } |
||
307 | |||
308 | /** |
||
309 | * Проверяем слишком старые подключения и убиваем их |
||
310 | * |
||
311 | * @param object|ClientDatum $connection |
||
312 | * |
||
313 | * @return bool |
||
314 | */ |
||
315 | 573 | protected function check_timeouts_on_connection(&$connection) { |
|
316 | 573 | if ($this->check_timeouts_on_connection_first_byte($connection) or |
|
317 | 572 | $this->check_timeouts_on_connection_head_received($connection) or |
|
318 | 572 | $this->check_timeouts_on_connection_request_received($connection) or |
|
319 | 572 | $this->check_timeouts_on_connection_body_received_without_head($connection) |
|
320 | 287 | ) { |
|
321 | 25 | $this->close_connection($connection); |
|
322 | |||
323 | 25 | return false; |
|
324 | } |
||
325 | |||
326 | // @hint No Slowloris ( https://en.wikipedia.org/wiki/Slowloris_%28computer_security%29 ) test. |
||
327 | // This is not a real server |
||
328 | |||
329 | 572 | return true; |
|
330 | } |
||
331 | |||
332 | /** |
||
333 | * Processing all connected clients |
||
334 | */ |
||
335 | 597 | protected function process_all_connected_clients() { |
|
336 | 597 | if (empty($this->_client_connects)) { |
|
337 | 369 | return; |
|
338 | } |
||
339 | |||
340 | /** |
||
341 | * @var resource[] $read |
||
342 | * @var resource[] $write |
||
343 | * @var resource[] $except |
||
344 | */ |
||
345 | 573 | $read = []; |
|
346 | 573 | foreach ($this->_client_connects as $connected) { |
|
347 | 573 | if (is_null($connected)) { |
|
348 | 220 | continue; |
|
349 | } |
||
350 | 573 | if (is_null($connected->client)) { |
|
351 | 60 | $connected = null; |
|
352 | 60 | continue; |
|
353 | } |
||
354 | |||
355 | 573 | if (!is_resource($connected->client)) { |
|
356 | // @codeCoverageIgnoreStart |
||
357 | throw new Exception(sprintf('Connection became non resource: %s', (string) $connected->client)); |
||
358 | // @codeCoverageIgnoreEnd |
||
359 | } |
||
360 | |||
361 | // Проверяем слишком старые подключения и убиваем их |
||
362 | 573 | if (!$this->check_timeouts_on_connection($connected)) { |
|
363 | 25 | continue; |
|
364 | } |
||
365 | |||
366 | 572 | $read[] = $connected->client; |
|
367 | 287 | } |
|
368 | 573 | if (empty($read)) { |
|
369 | 305 | $this->_client_connects = []; |
|
370 | |||
371 | 305 | return; |
|
372 | } |
||
373 | 572 | $write = null; |
|
374 | 572 | $except = null; |
|
375 | 572 | if (stream_select($read, $write, $except, 0) === false) { |
|
376 | // @codeCoverageIgnoreStart |
||
377 | throw new Exception('Error on stream select'); |
||
378 | // @codeCoverageIgnoreEnd |
||
379 | } |
||
380 | 572 | unset($connected); |
|
381 | |||
382 | 572 | foreach ($read as &$socket_resource) { |
|
383 | 556 | foreach ($this->_client_connects as &$connected) { |
|
384 | 556 | if (!is_null($connected) and ($connected->client == $socket_resource)) { |
|
385 | 556 | $this->process_connected_client($connected); |
|
386 | 556 | break; |
|
387 | } |
||
388 | 278 | } |
|
389 | 286 | } |
|
390 | 572 | } |
|
391 | |||
392 | /** |
||
393 | * @param ClientDatum $connect |
||
394 | * @param double $time |
||
395 | * @param string $buf |
||
396 | * |
||
397 | * @return boolean |
||
398 | */ |
||
399 | 556 | protected function receive_data_from_connected_client($connect, $time, &$buf) { |
|
400 | 556 | $buf = @fread($connect->client, self::CHUNK_SIZE); |
|
401 | 556 | if (empty($buf)) { |
|
402 | 130 | $this->close_connection($connect); |
|
403 | |||
404 | 130 | return false; |
|
405 | } |
||
406 | 448 | $connect->last_byte_received_time = $time; |
|
407 | 448 | if (!isset($connect->first_byte_received_time)) { |
|
408 | 448 | $connect->first_byte_received_time = $time; |
|
409 | 224 | } |
|
410 | 448 | if (strlen($buf) >= self::CHUNK_SIZE) { |
|
411 | do { |
||
412 | 32 | $sub_buf = @fread($connect->client, self::CHUNK_SIZE); |
|
413 | 32 | $buf .= $sub_buf; |
|
414 | 32 | if (isset($this->_settings->server_maximum_chunk) and |
|
415 | 32 | (strlen($buf) >= $this->_settings->server_maximum_chunk) |
|
416 | 16 | ) { |
|
417 | // Слишком много пришло за один раз |
||
418 | 8 | break; |
|
419 | } |
||
420 | 24 | } while (strlen($sub_buf) >= self::CHUNK_SIZE); |
|
421 | 16 | } |
|
422 | |||
423 | 448 | return true; |
|
424 | } |
||
425 | |||
426 | /** |
||
427 | * Processing all connected clients |
||
428 | * |
||
429 | * @param ClientDatum $connect |
||
430 | */ |
||
431 | 556 | protected function process_connected_client(&$connect) { |
|
432 | 556 | $time = microtime(true); |
|
433 | 556 | $connect->context_options = stream_context_get_options($connect->client); |
|
434 | 556 | $connect->context_params = stream_context_get_params($connect->client); |
|
435 | 556 | if (!$this->receive_data_from_connected_client($connect, $time, $buf)) { |
|
436 | 130 | $this->close_connection($connect); |
|
437 | |||
438 | 130 | return; |
|
439 | } |
||
440 | 448 | $this->event_raise('AnyIncomingData', $connect, $buf); |
|
441 | 448 | if ($connect->status == 0) { |
|
442 | 448 | $this->event_raise('HeadIncomingData', $connect, $buf); |
|
443 | 258 | } elseif ($connect->status == 1) { |
|
444 | 68 | $this->event_raise('BodyIncomingData', $connect, $buf); |
|
445 | 34 | } |
|
446 | 448 | $connect->blob_request .= $buf; |
|
447 | 448 | if (strpos($connect->blob_request, "\r\n\r\n") === false) { |
|
448 | // Head не дошёл |
||
449 | 8 | return; |
|
450 | } |
||
451 | |||
452 | // Проверяем на голову |
||
453 | // Голова только-только дошла |
||
454 | 440 | if (($connect->status == 0) and !$this->process_connected_client_head($connect, $time)) { |
|
455 | 56 | return; |
|
456 | } |
||
457 | |||
458 | // Проверяем на body |
||
459 | 384 | if ($connect->status == 1) { |
|
460 | 96 | $this->process_connected_client_body($connect, $buf, $time); |
|
461 | 48 | } |
|
462 | 384 | if (is_null($connect) or ($connect->status != 2)) { |
|
463 | 92 | return; |
|
464 | } |
||
465 | |||
466 | // Проверяем, что Host в списке обрабатываемых |
||
467 | 360 | if (!$this->check_requested_host_in_accepted_list($connect)) { |
|
468 | 72 | return; |
|
469 | } |
||
470 | |||
471 | // filterIncomingConnect |
||
472 | 288 | $closure = $this->_settings->filterIncomingConnect; |
|
473 | 288 | if (!$closure($this, $connect)) { |
|
474 | 8 | $this->close_connection($connect); |
|
475 | |||
476 | 8 | return; |
|
477 | } |
||
478 | 280 | unset($closure, $buf); |
|
479 | |||
480 | // Request |
||
481 | 280 | $this->event_raise('Request', $connect); |
|
482 | |||
483 | 280 | if ($connect->status == 3) { |
|
484 | 277 | $this->close_connection($connect); |
|
485 | 139 | } |
|
486 | 280 | } |
|
487 | |||
488 | 360 | function check_requested_host_in_accepted_list($connect) { |
|
489 | 360 | if (!isset($this->_settings->accepted_hosts)) { |
|
490 | 220 | return true; |
|
491 | } |
||
492 | |||
493 | 140 | list($host) = explode(':', $connect->request_head_params['Host']); |
|
494 | 140 | if (!in_array(strtolower($host), $this->_settings->accepted_hosts)) { |
|
495 | 72 | if ($this->event_raise('HostNotFound', $connect) === false) { |
|
496 | 12 | $this->close_connection($connect); |
|
497 | |||
498 | 12 | return false; |
|
499 | } |
||
500 | |||
501 | 60 | $this->answer($connect, 404, 'Not Found', 'Host not found'); |
|
502 | 60 | $this->close_connection($connect); |
|
503 | |||
504 | 60 | return false; |
|
505 | } |
||
506 | |||
507 | 68 | return true; |
|
508 | } |
||
509 | |||
510 | /** |
||
511 | * If returns "false" connection closed |
||
512 | * |
||
513 | * @param ClientDatum $connect |
||
514 | * @param double $time |
||
515 | * |
||
516 | * @return boolean |
||
517 | */ |
||
518 | 440 | protected function process_connected_client_head(&$connect, $time) { |
|
519 | 440 | $connect->head_received_time = $time; |
|
520 | 440 | list($connect->blob_head) = explode("\r\n\r\n", $connect->blob_request, 2); |
|
521 | 440 | $a = explode("\r\n", $connect->blob_head, 2); |
|
522 | 440 | list($first_line, $other_lines) = (count($a) == 2) ? $a : [$a[0], '']; |
|
523 | 440 | if (!preg_match('_^([A-Z]+)\\s+(.+)\\s+HTTP/([0-9.]+)$_', $first_line, $a)) { |
|
524 | 16 | if ($this->event_raise('HeadInvalidReceived', $connect, 0) === false) { |
|
525 | // Подключение сдохло |
||
526 | 8 | $this->close_connection($connect); |
|
527 | |||
528 | 8 | return false; |
|
529 | } |
||
530 | |||
531 | 8 | $this->answer($connect, 400, 'Bad Request', "This is not a HTTP request"); |
|
532 | 8 | $this->close_connection($connect); |
|
533 | |||
534 | 8 | return false; |
|
535 | } |
||
536 | |||
537 | 424 | $connect->request_type = $a[1]; |
|
538 | 424 | $connect->request_url = $a[2]; |
|
539 | 424 | $connect->request_http_version = $a[3]; |
|
540 | 424 | if ($connect->request_type == 'POST') { |
|
541 | 96 | $connect->status = 1; |
|
542 | 376 | } elseif ($connect->request_type == 'GET') { |
|
543 | 312 | $connect->status = 2; |
|
544 | 312 | $connect->full_request_received_time = $time; |
|
545 | 156 | } else { |
|
546 | 16 | if ($this->event_raise('HeadInvalidReceived', $connect, 1) === false) { |
|
547 | // Подключение сдохло |
||
548 | 8 | $this->close_connection($connect); |
|
549 | |||
550 | 8 | return false; |
|
551 | } |
||
552 | |||
553 | 8 | $this->answer($connect, 400, 'Bad Request', "Can not process this request type"); |
|
554 | 8 | $this->close_connection($connect); |
|
555 | |||
556 | 8 | return false; |
|
557 | } |
||
558 | |||
559 | 408 | $connect->request_head_params = []; |
|
560 | 408 | foreach (explode("\r\n", $other_lines) as $other_line) { |
|
561 | 408 | if (empty($other_line) or !preg_match('_^([A-Za-z0-9-]+):\\s*(.*)$_', $other_line, $a)) { |
|
562 | 16 | if ($this->event_raise('HeadInvalidReceived', $connect, 2) === false) { |
|
563 | // Подключение сдохло |
||
564 | 8 | $this->close_connection($connect); |
|
565 | |||
566 | 8 | return false; |
|
567 | } |
||
568 | |||
569 | 8 | $this->answer($connect, 400, 'Bad Request', "Malformed head"); |
|
570 | 8 | $this->close_connection($connect); |
|
571 | |||
572 | 8 | return false; |
|
573 | } |
||
574 | |||
575 | 408 | $connect->request_head_params[$a[1]] = $a[2]; |
|
576 | 204 | } |
|
577 | |||
578 | 392 | if (!isset($connect->request_head_params['Host'])) { |
|
579 | 8 | $this->answer($connect, 400, 'Bad Request', "Field 'host' missed"); |
|
580 | 8 | $this->close_connection($connect); |
|
581 | |||
582 | 8 | return false; |
|
583 | } |
||
584 | |||
585 | // event |
||
586 | 384 | $this->event_raise('HeadReceived', $connect); |
|
587 | |||
588 | 384 | return true; |
|
589 | } |
||
590 | |||
591 | /** |
||
592 | * If returns "false" connection closed |
||
593 | * |
||
594 | * @param ClientDatum $connect |
||
595 | * @param string $buf |
||
596 | * @param double $time |
||
597 | */ |
||
598 | 96 | protected function process_connected_client_body(&$connect, $buf, $time) { |
|
599 | 96 | $head_end = strpos($connect->blob_request, "\r\n\r\n"); |
|
600 | 96 | if (!isset($connect->request_head_params['Content-Length'])) { |
|
601 | 16 | if ($this->event_raise('HeadInvalidReceived', $connect, 3) === false) { |
|
602 | 8 | $this->close_connection($connect); |
|
603 | |||
604 | 8 | return; |
|
605 | } |
||
606 | |||
607 | 8 | $this->answer($connect, 400, 'malformed request', 'Malformed request'); |
|
608 | 8 | $this->close_connection($connect); |
|
609 | |||
610 | 8 | return; |
|
611 | } |
||
612 | 80 | $requested_body_length = (int) $connect->request_head_params['Content-Length']; |
|
613 | 80 | if (strlen($connect->blob_request) >= $head_end + 4 + $requested_body_length) { |
|
614 | 72 | $connect->blob_body = substr($connect->blob_request, $head_end + 4, $requested_body_length); |
|
615 | 72 | $connect->status = 2; |
|
616 | 72 | $connect->body_received_time = $time; |
|
617 | 72 | $connect->full_request_received_time = $time; |
|
618 | // @hint Request raised in process_connected_client |
||
619 | 36 | } |
|
620 | |||
621 | 80 | $this->event_raise('BodyIncomingData', $connect, $buf); |
|
622 | 80 | } |
|
623 | |||
624 | /** |
||
625 | * @param string $event |
||
626 | * |
||
627 | * @return mixed|null |
||
628 | */ |
||
629 | 597 | function event_raise($event) { |
|
630 | 597 | $method_name = 'on'.$event; |
|
631 | 597 | if (!isset($this->_settings->{$method_name}) or !is_callable($this->_settings->{$method_name})) { |
|
632 | 597 | return null; |
|
633 | } |
||
634 | |||
635 | 349 | $args = func_get_args(); |
|
636 | 349 | $args[0] = $this; |
|
637 | |||
638 | 349 | return call_user_func_array($this->_settings->{$method_name}, $args); |
|
639 | } |
||
640 | |||
641 | /** |
||
642 | * @param ClientDatum $connect |
||
643 | * @param integer $code |
||
644 | * @param string $code_text |
||
645 | * @param string $body |
||
646 | * @param array $headers |
||
647 | */ |
||
648 | 429 | function answer($connect, $code, $code_text, $body, array $headers = []) { |
|
649 | 429 | $buf = sprintf("HTTP/1.0 %d %s\r\n", $code, $code_text); |
|
650 | 429 | $headers['Content-Length'] = strlen($body); |
|
651 | 429 | foreach ($headers as $key => &$value) { |
|
652 | 429 | $buf .= sprintf("%s: %s\r\n", $key, $value); |
|
653 | 215 | } |
|
654 | 429 | @fwrite($connect->client, "{$buf}\r\n{$body}"); |
|
0 ignored issues
–
show
|
|||
655 | 429 | } |
|
656 | } |
||
657 | |||
658 | ?> |
If you suppress an error, we recommend checking for the error condition explicitly: