diff --git a/Log/FileLogger.php b/Log/FileLogger.php index 68cec9476..d6ad24e8f 100644 --- a/Log/FileLogger.php +++ b/Log/FileLogger.php @@ -214,7 +214,7 @@ class FileLogger implements LoggerInterface * Interpolate context * * @param string $message - * @param array $context + * @param array $context * @param string $level * * @return string @@ -292,7 +292,7 @@ class FileLogger implements LoggerInterface * System is unusable. * * @param string $message - * @param array $context + * @param array $context * * @return null * @@ -312,7 +312,7 @@ class FileLogger implements LoggerInterface * trigger the SMS alerts and wake you up. * * @param string $message - * @param array $context + * @param array $context * * @return void * @@ -331,7 +331,7 @@ class FileLogger implements LoggerInterface * Example: Application component unavailable, unexpected exception. * * @param string $message - * @param array $context + * @param array $context * * @return void * @@ -349,7 +349,7 @@ class FileLogger implements LoggerInterface * be logged and monitored. * * @param string $message - * @param array $context + * @param array $context * * @return void * @@ -369,7 +369,7 @@ class FileLogger implements LoggerInterface * that are not necessarily wrong. * * @param string $message - * @param array $context + * @param array $context * * @return void * @@ -386,7 +386,7 @@ class FileLogger implements LoggerInterface * Normal but significant events. * * @param string $message - * @param array $context + * @param array $context * * @return void * @@ -405,7 +405,7 @@ class FileLogger implements LoggerInterface * Example: User logs in, SQL logs. * * @param string $message - * @param array $context + * @param array $context * * @return void * @@ -422,7 +422,7 @@ class FileLogger implements LoggerInterface * Detailed debug information. * * @param string $message - * @param array $context + * @param array $context * * @return void * @@ -440,7 +440,7 @@ class FileLogger implements LoggerInterface * * @param string $level * @param string $message - * @param array $context + * @param array $context * * @return void * @@ -610,4 +610,13 @@ class FileLogger implements LoggerInterface return $log; } + + public function console(string $text, bool $verbose) + { + $text = date('[Y-m-d H:i:s] ') . $text . "\r\n"; + + if ($verbose) { + echo $text; + } + } } diff --git a/Module/ModuleManager.php b/Module/ModuleManager.php index 9b8112452..78027e0b8 100644 --- a/Module/ModuleManager.php +++ b/Module/ModuleManager.php @@ -17,6 +17,7 @@ namespace phpOMS\Module; use phpOMS\ApplicationAbstract; use phpOMS\DataStorage\Database\DatabaseType; +use phpOMS\Log\FileLogger; use phpOMS\Message\Http\Request; use phpOMS\System\FilePathException; use phpOMS\Utils\IO\Json\InvalidJsonException; @@ -429,7 +430,15 @@ class ModuleManager { if (is_array($module)) { foreach ($module as $m) { - $this->initModule($m); + try { + $this->initModule($m); + } catch (\InvalidArgumentException $e) { + $this->app->logger->warning(FileLogger::MSG_FULL, [ + 'message' => 'Trying to initialize ' . $m . ' without controller.', + 'line' => $e->getLine(), + 'file' => $e->getFile(), + ]); + } } } elseif (is_string($module) && realpath(self::MODULE_PATH . '/' . $module . '/Controller.php') !== false) { $this->running[$module] = ModuleFactory::getInstance($module, $this->app); diff --git a/Socket/Client/ClientConnection.php b/Socket/Client/ClientConnection.php new file mode 100644 index 000000000..69f7789eb --- /dev/null +++ b/Socket/Client/ClientConnection.php @@ -0,0 +1,82 @@ + + * @author Dennis Eichhorn + * @copyright 2013 Dennis Eichhorn + * @license OMS License 1.0 + * @version 1.0.0 + * @link http://orange-management.com + */ +namespace phpOMS\Socket\Client; + +use phpOMS\Socket\CommandManager; +use phpOMS\Socket\SocketAbstract; + +/** + * Client socket class. + * + * @category Framework + * @package phpOMS\Socket\Client + * @author OMS Development Team + * @author Dennis Eichhorn + * @license OMS License 1.0 + * @link http://orange-management.com + * @since 1.0.0 + */ +class ClientConnection +{ + private $id = 0; + private $socket = null; + private $handshake = false; + private $pid = null; + private $connected = true; + + public function __construct($id, $socket) + { + $this->id = $id; + $this->socket = $socket; + } + + public function getId() + { + return $this->id; + } + + public function getSocket() { + return $this->socket; + } + + public function getHandshake() { + return $this->handshake; + } + + public function getPid() { + return $this->pid; + } + + public function isConnected() { + return $this->connected; + } + + public function setSocket($socket) { + $this->socket = $socket; + } + + public function setHandshake($handshake) { + $this->handshake = $handshake; + } + + public function setPid($pid) { + $this->pid = $pid; + } + + public function setConnected(bool $connected) { + $this->connected = $connected; + } +} diff --git a/Socket/Client/NullClientConnection.php b/Socket/Client/NullClientConnection.php new file mode 100644 index 000000000..56b5eff84 --- /dev/null +++ b/Socket/Client/NullClientConnection.php @@ -0,0 +1,34 @@ + + * @author Dennis Eichhorn + * @copyright 2013 Dennis Eichhorn + * @license OMS License 1.0 + * @version 1.0.0 + * @link http://orange-management.com + */ +namespace phpOMS\Socket\Client; + +use phpOMS\Socket\CommandManager; +use phpOMS\Socket\SocketAbstract; + +/** + * Client socket class. + * + * @category Framework + * @package phpOMS\Socket\Client + * @author OMS Development Team + * @author Dennis Eichhorn + * @license OMS License 1.0 + * @link http://orange-management.com + * @since 1.0.0 + */ +class NullClientConnection extends ClientConnection +{ +} diff --git a/Socket/Packets/PacketManager.php b/Socket/Packets/PacketManager.php index ac1ee2465..65a79145b 100644 --- a/Socket/Packets/PacketManager.php +++ b/Socket/Packets/PacketManager.php @@ -69,21 +69,14 @@ class PacketManager * Handle package. * * @param string $data Package data - * @param mixed $key Client Id * * @return void * * @since 1.0.0 * @author Dennis Eichhorn */ - public function handle(string $data, $key) + public function handle(string $data, $client) { - /* - if (!empty($data)) { - $data = explode(' ', $data); - $this->commandManager->trigger($data[0], $key, $data); - } else { - $this->commandManager->trigger('empty', $key, $data); - }*/ + echo $data; } } diff --git a/Socket/Server/ClientManager.php b/Socket/Server/ClientManager.php index 002fe272f..6b2002e49 100644 --- a/Socket/Server/ClientManager.php +++ b/Socket/Server/ClientManager.php @@ -16,6 +16,40 @@ namespace phpOMS\Socket\Server; +use phpOMS\Socket\Client\ClientConnection; +use phpOMS\Socket\Client\NullClientConnection; + class ClientManager { + private $clients = []; + + public function add(ClientConnection $client) + { + $this->clients[$client->getId()] = $client; + } + + public function get($id) + { + return $this->clients[$id] ?? new NullClientConnection(uniqid(), null); + } + + public function getBySocket($socket) { + foreach($this->clients as $client) { + if($client->getSocket() === $socket) { + return $client; + } + } + + return new NullClientConnection(uniqid(), null); + } + + public function remove($id) { + if(isset($this->clients[$id])) { + unset($this->clients[$id]); + + return true; + } + + return false; + } } diff --git a/Socket/Server/Server.php b/Socket/Server/Server.php index d374dcf59..70df3580e 100644 --- a/Socket/Server/Server.php +++ b/Socket/Server/Server.php @@ -15,6 +15,7 @@ */ namespace phpOMS\Socket\Server; +use phpOMS\Socket\Client\ClientConnection; use phpOMS\Socket\CommandManager; use phpOMS\Socket\Packets\PacketManager; @@ -58,6 +59,10 @@ class Server extends SocketAbstract */ private $packetManager = null; + private $clientManager = null; + + private $verbose = true; + /** * Socket application. * @@ -72,6 +77,7 @@ class Server extends SocketAbstract if ($connected) { fclose($connected); + return true; } else { return false; @@ -89,6 +95,7 @@ class Server extends SocketAbstract public function __construct($app) { $this->app = $app; + $this->clientManager = new ClientManager(); $this->packetManager = new PacketManager(new CommandManager(), new ClientManager()); } @@ -97,7 +104,9 @@ class Server extends SocketAbstract */ public function create(string $ip, int $port) { + $this->app->logger->console('Creating socket...', $this->verbose); parent::create($ip, $port); + $this->app->logger->console('Binding socket...', $this->verbose); socket_bind($this->sock, $this->ip, $this->port); } @@ -116,15 +125,57 @@ class Server extends SocketAbstract $this->limit = $limit; } + public function handshake($client, $headers) + { + if (preg_match("/Sec-WebSocket-Version: (.*)\r\n/", $headers, $match)) { + $version = $match[1]; + } else { + return false; + } + + if ($version == 13) { + if (preg_match("/GET (.*) HTTP/", $headers, $match)) { + $root = $match[1]; + } + + if (preg_match("/Host: (.*)\r\n/", $headers, $match)) { + $host = $match[1]; + } + + if (preg_match("/Origin: (.*)\r\n/", $headers, $match)) { + $origin = $match[1]; + } + + if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $headers, $match)) { + $key = $match[1]; + } + + $acceptKey = $key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + $acceptKey = base64_encode(sha1($acceptKey, true)); + $upgrade = "HTTP/1.1 101 Switching Protocols\r\n" . + "Upgrade: websocket\r\n" . + "Connection: Upgrade\r\n" . + "Sec-WebSocket-Accept: $acceptKey" . + "\r\n\r\n"; + socket_write($client->getSocket(), $upgrade); + $client->setHandshake(true); + return true; + } else { + return false; + } + } + /** * {@inheritdoc} */ public function run() { + $this->app->logger->console('Start listening...', $this->verbose); socket_listen($this->sock); socket_set_nonblock($this->sock); $this->conn[] = $this->sock; + $this->app->logger->console('Start running...', $this->verbose); while ($this->run) { $read = $this->conn; @@ -138,55 +189,54 @@ class Server extends SocketAbstract // socket_clear_error(); } - if (in_array($this->sock, $read)) { - $this->conn[] = $newc = socket_accept($this->sock); + foreach ($read as $key => $socket) { + if ($this->sock === $socket) { + $newc = socket_accept($this->sock); + $this->connectClient($newc); + } else { + $client = $this->clientManager->getBySocket($socket); + $data = socket_read($socket, 1024); - // TODO: init account - // This is only the initial connection no auth happens here - // Here the server should request an authentication - - $welcome = "Welcome to the server.\n"; - socket_write($newc, $welcome, strlen($welcome)); - socket_getpeername($newc, $ip); - - unset($read[0]); - } - - foreach ($read as $key => $client) { - $data = socket_read($client, 1024); - - if ($this->clientReadError($key)) { - continue; + if (!$client->getHandshake()) { + $this->app->logger->console('Doing handshake...', $this->verbose); + if ($this->handshake($client, $data)) { + $this->app->logger->console('Handshake succeeded.', $this->verbose); + } else { + $this->app->logger->console('Handshake failed.', $this->verbose); + $this->disconnectClient($client); + } + } else { + $this->packetManager->handle($this->unmask($data), $client); + } } - - $this->packetManager->handle(trim($data), $key); } } $this->close(); } - /** - * Handle client read error. - * - * @param mixed $key Client key - * - * @return bool - * - * @since 1.0.0 - * @author Dennis Eichhorn - */ - private function clientReadError($key) : bool + public function connectClient($socket) { - if (socket_last_error() === 10054) { - socket_clear_error(); - unset($this->conn[$key]); - $this->conn = array_values($this->conn); + $this->app->logger->console('Connecting client...', $this->verbose); + $this->clientManager->add($client = new ClientConnection(uniqid(), $socket)); + $this->conn[$client->getId()] = $socket; + $this->app->logger->console('Connected client.', $this->verbose); + } - return true; + public function disconnectClient($client) + { + $this->app->logger->console('Disconnecting client...', $this->verbose); + $client->setConnected(false); + $client->setHandshake(false); + socket_shutdown($client->getSocket(), 2); + socket_close($client->getSocket()); + + if (isset($this->conn[$client->getId()])) { + unset($this->conn[$client->getId()]); } - return false; + $this->clientManager->remove($client->getId()); + $this->app->logger->console('Disconnected client.', $this->verbose); } /** @@ -205,20 +255,24 @@ class Server extends SocketAbstract parent::__destruct(); } - /** - * Disconnect client. - * - * @param mixed $key Client key - * - * @return void - * - * @since 1.0.0 - * @author Dennis Eichhorn - */ - private function disconnect($key) + private function unmask($payload) { - if (isset($this->conn[$key])) { - unset($this->conn[$key]); + $length = ord($payload[1]) & 127; + if ($length == 126) { + $masks = substr($payload, 4, 4); + $data = substr($payload, 8); + } elseif ($length == 127) { + $masks = substr($payload, 10, 4); + $data = substr($payload, 14); + } else { + $masks = substr($payload, 2, 4); + $data = substr($payload, 6); } + $text = ''; + for ($i = 0; $i < strlen($data); ++$i) { + $text .= $data[$i] ^ $masks[$i % 4]; + } + + return $text; } } diff --git a/Socket/SocketAbstract.php b/Socket/SocketAbstract.php index 160fcfeb0..fc20447b3 100644 --- a/Socket/SocketAbstract.php +++ b/Socket/SocketAbstract.php @@ -89,6 +89,7 @@ abstract class SocketAbstract implements SocketInterface public function close() { if ($this->sock !== null) { + socket_shutdown($this->sock, 2); socket_close($this->sock); $this->sock = null; }