orbit/src/Orbit/Module/Statics.php
2020-09-06 02:32:56 -05:00

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;
}
}