You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
4.1 KiB
158 lines
4.1 KiB
<?php |
|
error_reporting(E_ALL); |
|
set_time_limit(0); |
|
ob_implicit_flush(); |
|
|
|
// Based on: |
|
// gemini://glasgow.social/gemini-php |
|
|
|
class Gemini { |
|
function __construct($config) { |
|
if (empty($config['certificate_file'])) { |
|
die('Missing certificate file. Edit config.php\n'); |
|
} |
|
|
|
$this->ip = '0'; |
|
$this->port = '1965'; |
|
$this->data_dir = 'hosts/'; |
|
$this->default_host_dir = 'default/'; |
|
$this->default_index_file = 'index.gmi'; |
|
$this->logging = 1; |
|
$this->log_file = "logs/gemini-php.log"; |
|
$this->log_sep = "\t"; |
|
|
|
$settings = array( |
|
'ip', 'port', |
|
'data_dir', |
|
'default_host_dir', |
|
'default_index_file', |
|
'certificate_file', |
|
'certificate_passphrase' |
|
); |
|
|
|
foreach ($settings as $setting_key) { |
|
if (!empty($config[$setting_key])) { |
|
$this->$setting_key = $config[$setting_key]; |
|
} |
|
} |
|
|
|
// Append the required filepath slashes if they're missing |
|
if (substr($this->data_dir, -1) != "/") { |
|
$this->data_dir .= "/"; |
|
} |
|
if (substr($this->default_host_dir, -1) != "/") { |
|
$this->default_host_dir .= "/"; |
|
} |
|
if ($this->logging) { |
|
if (!file_exists($this->log_file)) { |
|
$this->log_to_file("Log created", null, null, null, null); |
|
} |
|
if (!is_writable($this->log_file)) { |
|
die("{$this->log_file} is not writable.\n"); |
|
} |
|
} |
|
if (!is_readable($this->certificate_file)) { |
|
die("Certificate file {$this->certificate_file} not readable.\n"); |
|
} |
|
} |
|
|
|
function parse_request($request) { |
|
$url = trim($request); // Strip <CR><LF> from the end |
|
return parse_url($url); |
|
} |
|
|
|
function get_valid_hosts() { |
|
$dirs = array_map('basename', glob($this->data_dir . '*', GLOB_ONLYDIR)); |
|
return $dirs; |
|
} |
|
|
|
function get_status_code($filepath) { |
|
if (is_file($filepath) and file_exists($filepath)) { |
|
return '20'; |
|
} |
|
if (!file_exists($filepath)) { |
|
//echo("File $filepath doesn't exist\n"); |
|
return '51'; |
|
} |
|
return '50'; |
|
} |
|
|
|
function get_mime_type($filepath) { |
|
$type = mime_content_type($filepath); |
|
// We need a way to detect gemini file types, which PHP doesn't |
|
// so.. if it ends with gemini (or if it has no extension), assume |
|
$path_parts = pathinfo($filepath); |
|
|
|
if (empty($path_parts['extension']) |
|
or $path_parts['extension'] === 'gemini' |
|
or $path_parts['extension'] === 'gmi' |
|
) { |
|
$type = 'text/gemini'; |
|
} |
|
return $type; |
|
} |
|
|
|
/** |
|
* Gets the full file path (assumes directory structure based on host) |
|
* |
|
* This function determines where the requested file is stored |
|
* based on the hostname supplied in the request from the client. |
|
* If no host is supplied, the default directory is assumed. |
|
* |
|
* @param array $url An array returned by the parse_request() method |
|
* |
|
* @return string |
|
*/ |
|
function get_filepath($url) { |
|
$hostname = ''; |
|
|
|
if (!is_array($url)) { |
|
return false; |
|
} |
|
if (!empty($url['host'])) { |
|
$hostname = $url['host']; |
|
} |
|
|
|
$valid_hosts = $this->get_valid_hosts(); |
|
|
|
if (!in_array($hostname, $valid_hosts)) { |
|
$hostname = 'default'; |
|
} |
|
|
|
// Kristall Browser is adding "__" to the end of the filenames |
|
// wtf am I missing? |
|
// also removing ".." to mitigate against directory traversal |
|
$url['path'] = str_replace(array('..', '__'), '', $url['path']); |
|
|
|
// Force an index file to be appended if a filename is missing |
|
if (empty($url['path'])) { |
|
$url['path'] = '/' . $this->default_index_file; |
|
} elseif(substr($url['path'], -1) === '/') { |
|
$url['path'] .= $this->default_index_file; |
|
} |
|
|
|
$valid_data_dir = dirname(__FILE__) . '/' . $this->data_dir; |
|
$return_path = $this->data_dir.$hostname.$url['path']; |
|
|
|
if (is_link($return_path)) { |
|
return $return_path; |
|
} |
|
|
|
// Check the real path is in the data_dir (path traversal sanity check) |
|
if (substr(realpath($return_path), 0, strlen($valid_data_dir)) === $valid_data_dir) { |
|
return $return_path; |
|
} |
|
return false; |
|
} |
|
|
|
function log_to_file($ip, $status_code, $meta, $filepath, $filesize) { |
|
$ts = date("Y-m-d H:i:s", strtotime('now')); |
|
$this->log_sep; |
|
|
|
$str = $ts.$this->log_sep . $ip . $this->log_sep |
|
. $status_code . $this->log_sep |
|
. $meta.$this->log_sep . $filepath . $this->log_sep |
|
. $filesize . "\n"; |
|
file_put_contents($this->log_file, $str, FILE_APPEND); |
|
} |
|
}
|
|
|