diff --git a/bin/orbit b/bin/orbit index d45794a..1150b70 100755 --- a/bin/orbit +++ b/bin/orbit @@ -13,6 +13,7 @@ $args = new \Qi_Console_ArgV( 'hostname:' => 'Set hostname of server', 'tls-cert:' => 'Set cert PEM file to use (default null)', 'tls-key:' => 'Set private key PEM file to use (default null)', + 'root-dir:' => 'Set the file root directory', 'log:' => 'Set log filename (default orbit.log)', 'help|h' => 'Show help', 'verbose|v' => 'Include more verbose output', diff --git a/src/Orbit/Config.php b/src/Orbit/Config.php index 9718983..78e1af2 100644 --- a/src/Orbit/Config.php +++ b/src/Orbit/Config.php @@ -15,4 +15,6 @@ class Config public $quiet = false; public $verbose = false; + + public $root_dir = "."; } diff --git a/src/Orbit/Console.php b/src/Orbit/Console.php index b261ba1..4bd2d0e 100644 --- a/src/Orbit/Console.php +++ b/src/Orbit/Console.php @@ -10,6 +10,10 @@ class Console extends \Qi_Console_Client { public function execute() { + if ($this->_args->get('no-color')) { + $this->_terminal->setIsatty(false); + } + if ($this->_args->version) { $this->showVersion(); return 0; @@ -59,6 +63,10 @@ class Console extends \Qi_Console_Client $config->verbose = $this->_args->verbose; } + if ($this->_args->get("root-dir")) { + $config->root_dir = $this->_args->get("root-dir"); + } + if ($this->_args->get("tls-cert")) { $config->tls_certfile = $this->_args->get("tls-cert"); } else { diff --git a/src/Orbit/Request.php b/src/Orbit/Request.php index c812b21..653f186 100644 --- a/src/Orbit/Request.php +++ b/src/Orbit/Request.php @@ -20,7 +20,20 @@ class Request $data = parse_url($request_input); foreach ($data as $key => $value) { - $this->{$key} = $value; + $this->{$key} = urldecode($value); } } + + public function getUrlAppendPath($text) + { + return $this->scheme . '://' + . ($this->user ? $this->user : '') + . ($this->pass ? ':' . $this->pass : '') + . ($this->user ? '@' : '') + . $this->host + . ($this->port ? ':' . $this->port : '') + . $this->path . $text + . ($this->query ? '?' . $this->query : '') + . ($this->fragment ? '#' . $this->fragment : ''); + } } diff --git a/src/Orbit/Response.php b/src/Orbit/Response.php new file mode 100644 index 0000000..499db05 --- /dev/null +++ b/src/Orbit/Response.php @@ -0,0 +1,94 @@ +status = $status; + $this->meta = $meta; + } + + public function getHeader() + { + return sprintf("%s %s\r\n", $this->status, $this->meta); + } + + public function send($client) + { + fwrite($client, $this->getHeader()); + + if ($this->filepath) { + $size = filesize($this->filepath); + $fp = fopen($this->filepath, "rb"); + if (false === $fp) { + throw new \Exception("Error reading file '{$this->filepath}'"); + } + while (!feof($fp)) { + fwrite($client, fread($fp, 8192)); + } + return $size; + } else { + $body = $this->getBody(); + fwrite($client, $body); + return mb_strlen($body); + } + } + + public function setBody($body = '') + { + $this->body = $body; + return $this; + } + + public function getBody() + { + if ($this->filepath) { + $this->body = file_get_contents($this->filepath); + } + return $this->body; + } + + public function setStaticFile($filepath) + { + $this->filepath = $filepath; + return $this; + } + + public function setStatus($status) + { + $this->status = $status; + return $this; + } + + public function setMeta($meta) + { + $this->meta = $meta; + return $this; + } +} diff --git a/src/Orbit/Server.php b/src/Orbit/Server.php index 72688b9..68d0084 100644 --- a/src/Orbit/Server.php +++ b/src/Orbit/Server.php @@ -62,8 +62,16 @@ class Server return $this->logger; } - public function listen() + public function listen($root_dir = ".") { + $path = realpath($root_dir); + + if (!is_dir($path)) { + throw new \Exception("Error: Root directory '$path' not a directory"); + } + + $this->log(Logger::DEBUG, "Root directory '$path'"); + $server = stream_socket_server( $this->getListenAddress(), $errno, $errstr, @@ -86,20 +94,68 @@ class Server restore_error_handler(); if ($client) { + $time = ['start' => microtime(true)]; + $this->log(Logger::DEBUG, "$client_name Accepted"); $request_buffer = stream_get_line($client, 1024, "\r\n"); $this->log(Logger::INFO, "REQ: $request_buffer", ["client" => $client_name]); $request = new Request($request_buffer); // Respond to client - $response = implode("\r\n", ['20 text/gemini', "Thanks for all the fish\n"]); - fwrite($client, $response); + $response = $this->handleResponse($request, $path); + $size = $response->send($client); + $time['end'] = microtime(true); + $this->log( + Logger::DEBUG, + "RSP: " . trim($response->getHeader()), + ['size' => $size, 'time' => $time['end'] - $time['start']] + ); + fclose($client); $this->log(Logger::DEBUG, "$client_name Closed"); } } } + public function handleResponse($request, $dir) + { + $resource_path = rtrim($dir, "/") . $request->path; + + $response = new Response(); + if (is_dir($resource_path)) { + // If missing the final slash, issue a redirect + if ($resource_path[-1] != "/") { + $response->setStatus(Response::STATUS_REDIRECT_PERMANENT); + $response->setMeta($request->getUrlAppendPath('/')); + return $response; + } + + // Check if index file exists + if (file_exists($resource_path . DIRECTORY_SEPARATOR . "index.gmi")) { + $resource_path = $resource_path . "/index.gmi"; + } + } + + if (file_exists($resource_path)) { + $response->setStatus(Response::STATUS_SUCCESS); + $pathinfo = pathinfo($resource_path); + if ($pathinfo['extension'] == 'gmi' || $pathinfo['extension'] == 'gemini') { + $response->setMeta('text/gemini'); + } elseif ($pathinfo['extension'] == 'md' || $pathinfo['extension'] == 'markdown') { + $response->setMeta('text/gemini'); + } else { + $response->setMeta(mime_content_type($resource_path)); + } + + $response->setStaticFile($resource_path); + } else { + $response->setStatus(Response::STATUS_NOT_FOUND); + $response->setMeta('Not found!'); + } + + return $response; + } + public function log($level, $message, $context = []) { $this->getLogger()->log($level, $message, $context);