153 lines
4.8 KiB
PHP
153 lines
4.8 KiB
PHP
|
<?php declare(strict_types=1);
|
||
|
|
||
|
namespace Orbit\Module;
|
||
|
|
||
|
use Orbit\Module;
|
||
|
use Orbit\Request;
|
||
|
use Orbit\Response;
|
||
|
|
||
|
/**
|
||
|
* Static files server module
|
||
|
*
|
||
|
* @uses Module
|
||
|
* @package Orbit
|
||
|
*/
|
||
|
class Statics extends Module
|
||
|
{
|
||
|
/**
|
||
|
* Handle a request and generate a proper response
|
||
|
*
|
||
|
* @param Request $request The request object
|
||
|
* @param Response $response The already created response object
|
||
|
* @param string $real_root_dir The real path to root on disk
|
||
|
*/
|
||
|
public function handleResponse(Request $request, Response $response, $real_root_dir): array
|
||
|
{
|
||
|
$resource_path = rtrim($real_root_dir, "/") . $request->path;
|
||
|
|
||
|
// Check if within the server root
|
||
|
// Realpath will translate any '..' in the path
|
||
|
$realpath = realpath($resource_path);
|
||
|
if ($realpath && strpos($realpath, $real_root_dir) !== 0) {
|
||
|
$response->setStatus(Response::STATUS_PERMANENT_FAILURE);
|
||
|
$response->setMeta("Invalid location");
|
||
|
return [true, $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 [true, $response];
|
||
|
}
|
||
|
|
||
|
// Check if index file exists
|
||
|
if (file_exists($resource_path . DIRECTORY_SEPARATOR . $this->config->index_file)) {
|
||
|
$resource_path = $resource_path . DIRECTORY_SEPARATOR . $this->config->index_file;
|
||
|
} else {
|
||
|
if (!$this->config->enable_directory_index) {
|
||
|
$response->setStatus(Response::STATUS_BAD_REQUEST);
|
||
|
$response->setMeta('Path not available');
|
||
|
return [true, $response];
|
||
|
} else {
|
||
|
$response->setStatus(Response::STATUS_SUCCESS);
|
||
|
$response->setMeta('text/gemini');
|
||
|
$response->setBody($this->makeDirectoryIndex($resource_path, $real_root_dir));
|
||
|
return [true, $response];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// File exists and is world readable
|
||
|
if (file_exists($resource_path) && self::isWorldReadble($resource_path)) {
|
||
|
$response->setStatus(Response::STATUS_SUCCESS);
|
||
|
|
||
|
$pathinfo = pathinfo($resource_path);
|
||
|
|
||
|
// TODO : handle files without extensions
|
||
|
if (!isset($pathinfo['extension'])) {
|
||
|
$response->setStatus(Response::STATUS_TEMPORARY_FAILURE);
|
||
|
$response->setMeta('Error reading resource');
|
||
|
return [true, $response];
|
||
|
}
|
||
|
|
||
|
$meta = $this->getCustomMimeFromFileExtension($pathinfo['extension']);
|
||
|
if (!$meta) {
|
||
|
$meta = mime_content_type($resource_path);
|
||
|
}
|
||
|
$response->setMeta($meta);
|
||
|
$response->setStaticFile($resource_path);
|
||
|
} else {
|
||
|
$response->setStatus(Response::STATUS_NOT_FOUND);
|
||
|
$response->setMeta('Not found!');
|
||
|
}
|
||
|
|
||
|
return [true, $response];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get mime type from file extension for custom types
|
||
|
*
|
||
|
* @param string $extension
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getCustomMimeFromFileExtension($extension): string
|
||
|
{
|
||
|
switch ($extension) {
|
||
|
case 'gmi':
|
||
|
case 'gemini':
|
||
|
return 'text/gemini';
|
||
|
break;
|
||
|
case 'md':
|
||
|
case 'markdown':
|
||
|
return 'text/gemini';
|
||
|
break;
|
||
|
case 'ans':
|
||
|
case 'ansi':
|
||
|
return 'text/x-ansi';
|
||
|
break;
|
||
|
default:
|
||
|
return '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Make a directory index suitable as response content
|
||
|
*
|
||
|
* @param string $path Current path
|
||
|
* @param string $root Root path on disk of the server
|
||
|
* @return string
|
||
|
*/
|
||
|
public function makeDirectoryIndex($path, $root): string
|
||
|
{
|
||
|
$files = glob($path . "*");
|
||
|
|
||
|
$body = "# Directory listing " . str_replace($root, '', $path) . "\n\n";
|
||
|
$body .= "=> " . str_replace($root, '', dirname($path)) . " ..\n";
|
||
|
|
||
|
foreach ($files as $file) {
|
||
|
$relative_path = str_replace($path, '', $file);
|
||
|
|
||
|
$is_dir = false;
|
||
|
if (is_dir($file)) {
|
||
|
$is_dir = true;
|
||
|
$size = '';
|
||
|
} else {
|
||
|
$size = filesize($file);
|
||
|
}
|
||
|
|
||
|
$body .= sprintf(
|
||
|
"=> %s%s %s%s%s\n",
|
||
|
urlencode($relative_path),
|
||
|
($is_dir ? '/' : ''),
|
||
|
$relative_path,
|
||
|
($is_dir ? '/' : ''),
|
||
|
($size ? " ($size)" : '')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $body;
|
||
|
}
|
||
|
}
|