summaryrefslogtreecommitdiffstats
path: root/vendor/textalk/websocket/lib/Server.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/textalk/websocket/lib/Server.php')
-rw-r--r--vendor/textalk/websocket/lib/Server.php176
1 files changed, 176 insertions, 0 deletions
diff --git a/vendor/textalk/websocket/lib/Server.php b/vendor/textalk/websocket/lib/Server.php
new file mode 100644
index 0000000..ae9325f
--- /dev/null
+++ b/vendor/textalk/websocket/lib/Server.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * Copyright (C) 2014-2020 Textalk/Abicart and contributors.
+ *
+ * This file is part of Websocket PHP and is free software under the ISC License.
+ * License text: https://raw.githubusercontent.com/Textalk/websocket-php/master/COPYING
+ */
+
+namespace WebSocket;
+
+class Server extends Base
+{
+ // Default options
+ protected static $default_options = [
+ 'filter' => ['text', 'binary'],
+ 'fragment_size' => 4096,
+ 'logger' => null,
+ 'port' => 8000,
+ 'return_obj' => false,
+ 'timeout' => null,
+ ];
+
+ protected $addr;
+ protected $port;
+ protected $listening;
+ protected $request;
+ protected $request_path;
+
+ /**
+ * @param array $options
+ * Associative array containing:
+ * - timeout: Set the socket timeout in seconds.
+ * - fragment_size: Set framgemnt size. Default: 4096
+ * - port: Chose port for listening. Default 8000.
+ */
+ public function __construct(array $options = [])
+ {
+ $this->options = array_merge(self::$default_options, $options);
+ $this->port = $this->options['port'];
+ $this->setLogger($this->options['logger']);
+
+ $error = $errno = $errstr = null;
+ set_error_handler(function (int $severity, string $message, string $file, int $line) use (&$error) {
+ $this->logger->warning($message, ['severity' => $severity]);
+ $error = $message;
+ }, E_ALL);
+
+ do {
+ $this->listening = stream_socket_server("tcp://0.0.0.0:$this->port", $errno, $errstr);
+ } while ($this->listening === false && $this->port++ < 10000);
+
+ restore_error_handler();
+
+ if (!$this->listening) {
+ $error = "Could not open listening socket: {$errstr} ({$errno}) {$error}";
+ $this->logger->error($error);
+ throw new ConnectionException($error, (int)$errno);
+ }
+
+ $this->logger->info("Server listening to port {$this->port}");
+ }
+
+ public function __destruct()
+ {
+ if ($this->isConnected()) {
+ fclose($this->socket);
+ }
+ $this->socket = null;
+ }
+
+ public function getPort(): int
+ {
+ return $this->port;
+ }
+
+ public function getPath(): string
+ {
+ return $this->request_path;
+ }
+
+ public function getRequest(): array
+ {
+ return $this->request;
+ }
+
+ public function getHeader($header): ?string
+ {
+ foreach ($this->request as $row) {
+ if (stripos($row, $header) !== false) {
+ list($headername, $headervalue) = explode(":", $row);
+ return trim($headervalue);
+ }
+ }
+ return null;
+ }
+
+ public function accept(): bool
+ {
+ $this->socket = null;
+ return (bool)$this->listening;
+ }
+
+ protected function connect(): void
+ {
+
+ $error = null;
+ set_error_handler(function (int $severity, string $message, string $file, int $line) use (&$error) {
+ $this->logger->warning($message, ['severity' => $severity]);
+ $error = $message;
+ }, E_ALL);
+
+ if (isset($this->options['timeout'])) {
+ $this->socket = stream_socket_accept($this->listening, $this->options['timeout']);
+ } else {
+ $this->socket = stream_socket_accept($this->listening);
+ }
+
+ restore_error_handler();
+
+ if (!$this->socket) {
+ $this->throwException("Server failed to connect. {$error}");
+ }
+ if (isset($this->options['timeout'])) {
+ stream_set_timeout($this->socket, $this->options['timeout']);
+ }
+
+ $this->logger->info("Client has connected to port {port}", [
+ 'port' => $this->port,
+ 'pier' => stream_socket_get_name($this->socket, true),
+ ]);
+ $this->performHandshake();
+ }
+
+ protected function performHandshake(): void
+ {
+ $request = '';
+ do {
+ $buffer = stream_get_line($this->socket, 1024, "\r\n");
+ $request .= $buffer . "\n";
+ $metadata = stream_get_meta_data($this->socket);
+ } while (!feof($this->socket) && $metadata['unread_bytes'] > 0);
+
+ if (!preg_match('/GET (.*) HTTP\//mUi', $request, $matches)) {
+ $error = "No GET in request: {$request}";
+ $this->logger->error($error);
+ throw new ConnectionException($error);
+ }
+ $get_uri = trim($matches[1]);
+ $uri_parts = parse_url($get_uri);
+
+ $this->request = explode("\n", $request);
+ $this->request_path = $uri_parts['path'];
+ /// @todo Get query and fragment as well.
+
+ if (!preg_match('#Sec-WebSocket-Key:\s(.*)$#mUi', $request, $matches)) {
+ $error = "Client had no Key in upgrade request: {$request}";
+ $this->logger->error($error);
+ throw new ConnectionException($error);
+ }
+
+ $key = trim($matches[1]);
+
+ /// @todo Validate key length and base 64...
+ $response_key = base64_encode(pack('H*', sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
+
+ $header = "HTTP/1.1 101 Switching Protocols\r\n"
+ . "Upgrade: websocket\r\n"
+ . "Connection: Upgrade\r\n"
+ . "Sec-WebSocket-Accept: $response_key\r\n"
+ . "\r\n";
+
+ $this->write($header);
+ $this->logger->debug("Handshake on {$get_uri}");
+ }
+}