Improvements to request validation

This commit is contained in:
Jansen Price 2020-09-03 15:59:02 -05:00
parent 881befccfa
commit fcac2b20ab
2 changed files with 83 additions and 44 deletions

View File

@ -22,6 +22,11 @@ class Request
foreach ($data as $key => $value) {
$this->{$key} = urldecode($value);
}
// If scheme is missing, infer as default scheme
if (!$this->scheme) {
$this->scheme = Server::SCHEME;
}
}
public function getUrlAppendPath($text)

View File

@ -8,6 +8,8 @@ use ForceUTF8\Encoding;
class Server
{
const SCHEME = "gemini";
public static $version = "0.3";
public $config;
@ -71,7 +73,6 @@ class Server
$this->logger->debug("Root directory '$path'");
var_dump($this->getListenAddress());
$server = stream_socket_server(
$this->getListenAddress(),
$errno, $errstr,
@ -83,12 +84,11 @@ class Server
throw new \Exception("Error " . $errno . ": " . $errstr);
}
$protocol = "gemini";
$name = stream_socket_get_name($server, false);
$this->logger->info("Listening on $protocol://$name ...");
$this->logger->info(sprintf("Listening on %s://%s...", self::SCHEME, $name));
while (true) {
# This is to swallow up the `timeout` warning
# onWarning is added here to swallow up the `timeout` warning
set_error_handler([$this, 'onWarning']);
$client = stream_socket_accept($server, $this->timeout, $client_name);
//stream_socket_enable_crypto($server, true, STREAM_CRYPTO_METHOD_TLSv1_2_SERVER);
@ -96,25 +96,24 @@ class Server
if ($client) {
$time = ['start' => microtime(true)];
$meta = stream_get_meta_data($client);
$this->logger->debug("$client_name Accepted");
$this->logger->debug("$client_name Accepted", $meta);
$request_buffer = stream_get_line($client, 1026, "\r\n");
print($this->hexView($request_buffer));
print("Length: " . mb_strlen($request_buffer) . "\n");
//print($this->hexView($request_buffer));
//print("Length: " . mb_strlen($request_buffer) . "\n");
$this->logger->info("REQ: $request_buffer", ["client" => $client_name]);
if (trim($request_buffer)) {
$request = new Request($request_buffer);
$request = new Request($request_buffer);
// Respond to client
$response = $this->handleResponse($request, $path);
$size = $response->send($client);
$time['end'] = microtime(true);
$this->logger->debug(
"RSP: " . trim($response->getHeader()),
['size' => $size, 'time' => $time['end'] - $time['start']]
);
}
// Respond to client
$response = $this->handleResponse($request, $path);
$size = $response->send($client);
$time['end'] = microtime(true);
$this->logger->debug(
"RSP: " . trim($response->getHeader()),
['size' => $size, 'time' => $time['end'] - $time['start']]
);
fclose($client);
$this->logger->debug("$client_name Closed");
@ -124,38 +123,23 @@ class Server
public function handleResponse($request, $dir)
{
$response = new Response();
list($is_valid, $response) = $this->validateRequest($request);
// Valid URL must not be more than 1024 chars
if (mb_strlen($request->url) > 1024) {
$response->setStatus(Response::STATUS_BAD_REQUEST);
$response->setMeta("Bad request - too long");
return $response;
}
if ($request->host != "127.0.0.1" && $request->host != $this->config->hostname) {
$response->setStatus(Response::STATUS_PROXY_REQUEST_REFUSED);
$response->setMeta("Proxy error - invalid host");
return $response;
}
// Valid URL must use correct port
if ($request->port != "" && $request->port != $this->config->port) {
$response->setStatus(Response::STATUS_PROXY_REQUEST_REFUSED);
$response->setMeta("Proxy error - invalid port");
return $response;
}
// Valid URL must not contain non-UTF-8 bytes
$conv = Encoding::fixUTF8($request->url);
if ($conv != $request->url) {
$response->setStatus(Response::STATUS_BAD_REQUEST);
$response->setMeta("Bad request - non-UTF8");
if ($is_valid === false) {
return $response;
}
$resource_path = rtrim($dir, "/") . $request->path;
// Check if within the server root
// Realpath will translate any '..' in the path
$realpath = realpath($resource_path);
if ($realpath && strpos($realpath, $dir) !== 0) {
$response->setStatus(Response::STATUS_PERMANENT_FAILURE);
$response->setMeta("Invalid location");
return $response;
}
if (is_dir($resource_path)) {
// If missing the final slash, issue a redirect
if ($resource_path[-1] != "/") {
@ -209,6 +193,56 @@ class Server
return $response;
}
public function validateRequest($request)
{
$response = new Response();
// Valid URL must contain a host
if (!$request->host) {
$response->setStatus(Response::STATUS_BAD_REQUEST);
$response->setMeta("Bad request - url is empty");
return [false, $response];
}
// Valid URL must be the target scheme
if ($request->scheme != self::SCHEME) {
$response->setStatus(Response::STATUS_PROXY_REQUEST_REFUSED);
$response->setMeta("Proxy error - unsupported scheme");
return [false, $response];
}
// Valid URL must use targeted hostname
if ($request->host != "127.0.0.1" && $request->host != "localhost" && $request->host != $this->config->hostname) {
$response->setStatus(Response::STATUS_PROXY_REQUEST_REFUSED);
$response->setMeta("Proxy error - invalid host");
return [false, $response];
}
// Valid URL must use correct port
if ($request->port != "" && $request->port != $this->config->port) {
$response->setStatus(Response::STATUS_PROXY_REQUEST_REFUSED);
$response->setMeta("Proxy error - invalid port");
return [false, $response];
}
// Valid URL must not be more than 1024 chars
if (mb_strlen($request->url) > 1024) {
$response->setStatus(Response::STATUS_BAD_REQUEST);
$response->setMeta("Bad request - too long");
return [false, $response];
}
// Valid URL must not contain non-UTF-8 bytes
$conv = Encoding::fixUTF8($request->url);
if ($conv != $request->url) {
$response->setStatus(Response::STATUS_BAD_REQUEST);
$response->setMeta("Bad request - non-UTF8");
return [false, $response];
}
return [true, $response];
}
public function log($level, $message, $context = [])
{
$this->getLogger()->log($level, $message, $context);