From fcbf3ce37ca8f90a3e36d524a3274ffc063a40e3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 15:26:02 +0200 Subject: Adding upstream version 0.10.2+dfsg1. Signed-off-by: Daniel Baumann --- vendor/textalk/websocket/lib/Client.php | 216 ++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 vendor/textalk/websocket/lib/Client.php (limited to 'vendor/textalk/websocket/lib/Client.php') diff --git a/vendor/textalk/websocket/lib/Client.php b/vendor/textalk/websocket/lib/Client.php new file mode 100644 index 0000000..8cefaaa --- /dev/null +++ b/vendor/textalk/websocket/lib/Client.php @@ -0,0 +1,216 @@ + null, + 'filter' => ['text', 'binary'], + 'fragment_size' => 4096, + 'headers' => null, + 'logger' => null, + 'origin' => null, // @deprecated + 'persistent' => false, + 'return_obj' => false, + 'timeout' => 5, + ]; + + protected $socket_uri; + + /** + * @param string $uri A ws/wss-URI + * @param array $options + * Associative array containing: + * - context: Set the stream context. Default: empty context + * - timeout: Set the socket timeout in seconds. Default: 5 + * - fragment_size: Set framgemnt size. Default: 4096 + * - headers: Associative array of headers to set/override. + */ + public function __construct(string $uri, array $options = []) + { + $this->options = array_merge(self::$default_options, $options); + $this->socket_uri = $uri; + $this->setLogger($this->options['logger']); + } + + public function __destruct() + { + if ($this->isConnected() && get_resource_type($this->socket) !== 'persistent stream') { + fclose($this->socket); + } + $this->socket = null; + } + + /** + * Perform WebSocket handshake + */ + protected function connect(): void + { + $url_parts = parse_url($this->socket_uri); + if (empty($url_parts) || empty($url_parts['scheme']) || empty($url_parts['host'])) { + $error = "Invalid url '{$this->socket_uri}' provided."; + $this->logger->error($error); + throw new BadUriException($error); + } + $scheme = $url_parts['scheme']; + $host = $url_parts['host']; + $user = isset($url_parts['user']) ? $url_parts['user'] : ''; + $pass = isset($url_parts['pass']) ? $url_parts['pass'] : ''; + $port = isset($url_parts['port']) ? $url_parts['port'] : ($scheme === 'wss' ? 443 : 80); + $path = isset($url_parts['path']) ? $url_parts['path'] : '/'; + $query = isset($url_parts['query']) ? $url_parts['query'] : ''; + $fragment = isset($url_parts['fragment']) ? $url_parts['fragment'] : ''; + + $path_with_query = $path; + if (!empty($query)) { + $path_with_query .= '?' . $query; + } + if (!empty($fragment)) { + $path_with_query .= '#' . $fragment; + } + + if (!in_array($scheme, ['ws', 'wss'])) { + $error = "Url should have scheme ws or wss, not '{$scheme}' from URI '{$this->socket_uri}'."; + $this->logger->error($error); + throw new BadUriException($error); + } + + $host_uri = ($scheme === 'wss' ? 'ssl' : 'tcp') . '://' . $host; + + // Set the stream context options if they're already set in the config + if (isset($this->options['context'])) { + // Suppress the error since we'll catch it below + if (@get_resource_type($this->options['context']) === 'stream-context') { + $context = $this->options['context']; + } else { + $error = "Stream context in \$options['context'] isn't a valid context."; + $this->logger->error($error); + throw new \InvalidArgumentException($error); + } + } else { + $context = stream_context_create(); + } + + $persistent = $this->options['persistent'] === true; + $flags = STREAM_CLIENT_CONNECT; + $flags = $persistent ? $flags | STREAM_CLIENT_PERSISTENT : $flags; + + $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); + + // Open the socket. + $this->socket = stream_socket_client( + "{$host_uri}:{$port}", + $errno, + $errstr, + $this->options['timeout'], + $flags, + $context + ); + + restore_error_handler(); + + if (!$this->isConnected()) { + $error = "Could not open socket to \"{$host}:{$port}\": {$errstr} ({$errno}) {$error}."; + $this->logger->error($error); + throw new ConnectionException($error); + } + + $address = "{$scheme}://{$host}{$path_with_query}"; + + if (!$persistent || ftell($this->socket) == 0) { + // Set timeout on the stream as well. + stream_set_timeout($this->socket, $this->options['timeout']); + + // Generate the WebSocket key. + $key = self::generateKey(); + + // Default headers + $headers = [ + 'Host' => $host . ":" . $port, + 'User-Agent' => 'websocket-client-php', + 'Connection' => 'Upgrade', + 'Upgrade' => 'websocket', + 'Sec-WebSocket-Key' => $key, + 'Sec-WebSocket-Version' => '13', + ]; + + // Handle basic authentication. + if ($user || $pass) { + $headers['authorization'] = 'Basic ' . base64_encode($user . ':' . $pass); + } + + // Deprecated way of adding origin (use headers instead). + if (isset($this->options['origin'])) { + $headers['origin'] = $this->options['origin']; + } + + // Add and override with headers from options. + if (isset($this->options['headers'])) { + $headers = array_merge($headers, $this->options['headers']); + } + + $header = "GET " . $path_with_query . " HTTP/1.1\r\n" . implode( + "\r\n", + array_map( + function ($key, $value) { + return "$key: $value"; + }, + array_keys($headers), + $headers + ) + ) . "\r\n\r\n"; + + // Send headers. + $this->write($header); + + // Get server response header (terminated with double CR+LF). + $response = stream_get_line($this->socket, 1024, "\r\n\r\n"); + + // Validate response. + if (!preg_match('#Sec-WebSocket-Accept:\s(.*)$#mUi', $response, $matches)) { + $error = "Connection to '{$address}' failed: Server sent invalid upgrade response: {$response}"; + $this->logger->error($error); + throw new ConnectionException($error); + } + + $keyAccept = trim($matches[1]); + $expectedResonse + = base64_encode(pack('H*', sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); + + if ($keyAccept !== $expectedResonse) { + $error = 'Server sent bad upgrade response.'; + $this->logger->error($error); + throw new ConnectionException($error); + } + } + + $this->logger->info("Client connected to {$address}"); + } + + /** + * Generate a random string for WebSocket key. + * + * @return string Random string + */ + protected static function generateKey(): string + { + $key = ''; + for ($i = 0; $i < 16; $i++) { + $key .= chr(rand(33, 126)); + } + return base64_encode($key); + } +} -- cgit v1.2.3