Improvements to request validation
This commit is contained in:
parent
881befccfa
commit
fcac2b20ab
|
@ -22,6 +22,11 @@ class Request
|
||||||
foreach ($data as $key => $value) {
|
foreach ($data as $key => $value) {
|
||||||
$this->{$key} = urldecode($value);
|
$this->{$key} = urldecode($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If scheme is missing, infer as default scheme
|
||||||
|
if (!$this->scheme) {
|
||||||
|
$this->scheme = Server::SCHEME;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUrlAppendPath($text)
|
public function getUrlAppendPath($text)
|
||||||
|
|
|
@ -8,6 +8,8 @@ use ForceUTF8\Encoding;
|
||||||
|
|
||||||
class Server
|
class Server
|
||||||
{
|
{
|
||||||
|
const SCHEME = "gemini";
|
||||||
|
|
||||||
public static $version = "0.3";
|
public static $version = "0.3";
|
||||||
|
|
||||||
public $config;
|
public $config;
|
||||||
|
@ -71,7 +73,6 @@ class Server
|
||||||
|
|
||||||
$this->logger->debug("Root directory '$path'");
|
$this->logger->debug("Root directory '$path'");
|
||||||
|
|
||||||
var_dump($this->getListenAddress());
|
|
||||||
$server = stream_socket_server(
|
$server = stream_socket_server(
|
||||||
$this->getListenAddress(),
|
$this->getListenAddress(),
|
||||||
$errno, $errstr,
|
$errno, $errstr,
|
||||||
|
@ -83,12 +84,11 @@ class Server
|
||||||
throw new \Exception("Error " . $errno . ": " . $errstr);
|
throw new \Exception("Error " . $errno . ": " . $errstr);
|
||||||
}
|
}
|
||||||
|
|
||||||
$protocol = "gemini";
|
|
||||||
$name = stream_socket_get_name($server, false);
|
$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) {
|
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']);
|
set_error_handler([$this, 'onWarning']);
|
||||||
$client = stream_socket_accept($server, $this->timeout, $client_name);
|
$client = stream_socket_accept($server, $this->timeout, $client_name);
|
||||||
//stream_socket_enable_crypto($server, true, STREAM_CRYPTO_METHOD_TLSv1_2_SERVER);
|
//stream_socket_enable_crypto($server, true, STREAM_CRYPTO_METHOD_TLSv1_2_SERVER);
|
||||||
|
@ -96,14 +96,14 @@ class Server
|
||||||
|
|
||||||
if ($client) {
|
if ($client) {
|
||||||
$time = ['start' => microtime(true)];
|
$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");
|
$request_buffer = stream_get_line($client, 1026, "\r\n");
|
||||||
print($this->hexView($request_buffer));
|
//print($this->hexView($request_buffer));
|
||||||
print("Length: " . mb_strlen($request_buffer) . "\n");
|
//print("Length: " . mb_strlen($request_buffer) . "\n");
|
||||||
$this->logger->info("REQ: $request_buffer", ["client" => $client_name]);
|
$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
|
// Respond to client
|
||||||
|
@ -114,7 +114,6 @@ class Server
|
||||||
"RSP: " . trim($response->getHeader()),
|
"RSP: " . trim($response->getHeader()),
|
||||||
['size' => $size, 'time' => $time['end'] - $time['start']]
|
['size' => $size, 'time' => $time['end'] - $time['start']]
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
fclose($client);
|
fclose($client);
|
||||||
$this->logger->debug("$client_name Closed");
|
$this->logger->debug("$client_name Closed");
|
||||||
|
@ -124,38 +123,23 @@ class Server
|
||||||
|
|
||||||
public function handleResponse($request, $dir)
|
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 ($is_valid === false) {
|
||||||
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");
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
$resource_path = rtrim($dir, "/") . $request->path;
|
$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 (is_dir($resource_path)) {
|
||||||
// If missing the final slash, issue a redirect
|
// If missing the final slash, issue a redirect
|
||||||
if ($resource_path[-1] != "/") {
|
if ($resource_path[-1] != "/") {
|
||||||
|
@ -209,6 +193,56 @@ class Server
|
||||||
return $response;
|
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 = [])
|
public function log($level, $message, $context = [])
|
||||||
{
|
{
|
||||||
$this->getLogger()->log($level, $message, $context);
|
$this->getLogger()->log($level, $message, $context);
|
||||||
|
|
Loading…
Reference in a new issue