Improvements to request validation
This commit is contained in:
parent
881befccfa
commit
fcac2b20ab
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue