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