scriptDir = $scriptDir; $this->webPath = $webPath; $this->baseDir = $baseDir; } public function parseCleanURL() { $requestUri = $_SERVER['REQUEST_URI']; $scriptName = $_SERVER['SCRIPT_NAME']; $scriptPath = dirname($scriptName); if ($scriptPath !== '/' && !empty($scriptPath)) { $requestUri = str_replace($scriptPath, '', $requestUri); } $requestUri = ltrim($requestUri, '/'); $parts = explode('?', $requestUri, 2); $cleanPath = $parts[0]; if (isset($parts[1])) { parse_str($parts[1], $queryParams); $_GET = array_merge($_GET, $queryParams); } if (!empty($cleanPath) && substr($cleanPath, -1) === '/') { $cleanPath = rtrim($cleanPath, '/'); } $decodedPath = $this->decodePathFromURL($cleanPath); if ($decodedPath === false) { http_response_code(400); exit('Invalid path'); } return $decodedPath; } public function generateFileURL($path, $filename, $action = 'view', $viewType = 'default') { $encodedPath = $this->encodePathForURL($path); $encodedFilename = rawurlencode($filename); $fullPath = $encodedPath ? $encodedPath . '/' . $encodedFilename : $encodedFilename; switch ($action) { case 'download': return $this->webPath . '/' . $fullPath . '/?download=file'; case 'view': default: return $this->webPath . '/' . $fullPath . '/?view=' . $viewType; } } public function generateFolderURL($path, $foldername = '', $action = 'view') { $encodedPath = $this->encodePathForURL($path); $encodedFoldername = $foldername ? rawurlencode($foldername) : ''; if ($action === 'download') { $fullPath = $encodedPath ? $encodedPath . '/' . $encodedFoldername : $encodedFoldername; return $this->webPath . '/' . $fullPath . '/?download=archive'; } else { $fullPath = $encodedPath; if ($encodedFoldername) { $fullPath = $fullPath ? $fullPath . '/' . $encodedFoldername : $encodedFoldername; } $url = $this->webPath . '/' . $fullPath . '/'; $url = preg_replace('#/+#', '/', $url); $url = str_replace(':/', '://', $url); return $url; } } public function generateSortURL($sortBy, $currentSort, $currentDir, $currentPath) { $newDir = ($sortBy === $currentSort && $currentDir === 'asc') ? 'desc' : 'asc'; $params = http_build_query([ 'sort' => $sortBy, 'dir' => $newDir ]); if (empty($currentPath)) { $baseUrl = $this->webPath . '/'; } else { $encodedPath = $this->encodePathForURL($currentPath); $baseUrl = $this->webPath . '/' . $encodedPath . '/'; } return $baseUrl . '?' . $params; } public function generateBreadcrumbs($currentPath) { $parts = array_filter(explode('/', $currentPath)); $breadcrumbs = 'files'; $path = ''; foreach ($parts as $part) { $path .= '/' . $part; $encodedPath = $this->encodePathForURL($path); $breadcrumbs .= ' / ' . htmlspecialchars($part) . ''; } return $breadcrumbs; } public function isFileRequest($currentPath) { $fullPath = $this->baseDir . '/' . $currentPath; return is_file($fullPath); } public function isFolderRequest($currentPath) { $fullPath = $this->baseDir . '/' . $currentPath; return is_dir($fullPath); } private function encodePathForURL($path) { if (empty($path)) return ''; $segments = explode('/', $path); $encodedSegments = array_map('rawurlencode', $segments); return implode('/', $encodedSegments); } private function decodePathFromURL($encodedPath) { if (empty($encodedPath)) return ''; $decodedPath = rawurldecode($encodedPath); if (strpos($decodedPath, '../') !== false || strpos($decodedPath, '..\\') !== false) { return false; } $decodedPath = str_replace("\0", '', $decodedPath); return $decodedPath; } public function handleFileRequest($currentPath) { $fullPath = $this->baseDir . '/' . $currentPath; if (strpos(realpath($fullPath), realpath($this->baseDir)) !== 0) { http_response_code(403); exit('Access denied'); } if (!is_file($fullPath)) { http_response_code(404); exit('File not found'); } $filename = basename($currentPath); $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); $parentPath = dirname($currentPath); if ($parentPath === '.') $parentPath = ''; if (!isFileAccessible($fullPath, $parentPath, $extension)) { http_response_code(403); exit('File not accessible'); } if (isset($_GET['download'])) { $this->handleFileDownload($fullPath, $filename); } $viewType = isset($_GET['view']) ? $_GET['view'] : 'raw'; switch ($viewType) { case 'default': $this->handleFileView($fullPath, $filename, $extension, 'default'); break; case 'code': $this->handleFileView($fullPath, $filename, $extension, 'code'); break; case 'markdown': if (in_array($extension, ['md', 'markdown'])) { $this->handleFileView($fullPath, $filename, $extension, 'markdown'); } else { $newUrl = $_SERVER['REQUEST_URI']; $newUrl = preg_replace('/[?&]view=markdown/', '?view=default', $newUrl); if (!strpos($newUrl, '?')) { $newUrl .= '?view=default'; } header('Location: ' . $newUrl); exit; } break; case 'raw': default: $this->handleRawFileView($fullPath, $filename, $extension); break; } } private function handleRawFileView($fullPath, $filename, $extension) { $mimeTypes = [ 'txt' => 'text/plain', 'md' => 'text/plain', 'markdown' => 'text/plain', 'js' => 'text/plain', 'css' => 'text/plain', 'html' => 'text/plain', 'htm' => 'text/plain', 'json' => 'application/json', 'xml' => 'text/xml', 'php' => 'text/plain', 'py' => 'text/plain', 'sql' => 'text/plain', 'log' => 'text/plain', 'yml' => 'text/plain', 'yaml' => 'text/plain', 'conf' => 'text/plain', 'config' => 'text/plain', 'ini' => 'text/plain', 'env' => 'text/plain', 'sh' => 'text/plain', 'bat' => 'text/plain', 'ps1' => 'text/plain', ]; $directServeExtensions = [ 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'jfif', 'avif', 'ico', 'cur', 'tiff', 'bmp', 'heic', 'svg', 'mp4', 'mkv', 'mp3', 'aac', 'flac', 'm4a', 'ogg', 'opus', 'wma', 'mov', 'webm', 'wmv', '3gp', 'flv', 'm4v', 'docx', 'xlsx' ]; if (in_array($extension, $directServeExtensions)) { $this->serveFileDirect($fullPath, $extension, $filename); } else { $mimeType = isset($mimeTypes[$extension]) ? $mimeTypes[$extension] : 'text/plain'; header('Content-Type: ' . $mimeType . '; charset=utf-8'); header('Content-Disposition: inline; filename="' . $filename . '"'); readfile($fullPath); exit; } } public function handleFolderRequest($currentPath) { $fullPath = $this->baseDir . '/' . $currentPath; if (strpos(realpath($fullPath), realpath($this->baseDir)) !== 0) { http_response_code(403); exit('Access denied'); } if (!is_dir($fullPath)) { http_response_code(404); exit('Directory not found'); } if (!isFolderAccessible($currentPath)) { http_response_code(403); exit('Folder not accessible'); } if (isset($_GET['download']) && $_GET['download'] === 'archive') { $folderName = basename($currentPath); if (empty($folderName)) { $folderName = 'files'; } $this->handleFolderDownload($fullPath, $folderName); exit; } return true; } private function handleFileDownload($fullPath, $filename) { global $disableFileDownloads; if ($disableFileDownloads) { http_response_code(403); exit('File downloads disabled'); } $fileSize = filesize($fullPath); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Length: ' . $fileSize); header('Cache-Control: no-cache'); header('Accept-Ranges: bytes'); if (ob_get_level()) { ob_end_clean(); } $handle = fopen($fullPath, 'rb'); if ($handle === false) { http_response_code(500); exit('Cannot read file'); } $chunkSize = 8192; while (!feof($handle)) { $chunk = fread($handle, $chunkSize); if ($chunk === false) { break; } echo $chunk; flush(); if (connection_aborted()) { break; } } fclose($handle); exit; } private function handleFileView($fullPath, $filename, $extension, $viewMode = 'default') { $fileContent = file_get_contents($fullPath); $fileName = htmlspecialchars($filename); $directServeExtensions = [ 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'jfif', 'avif', 'ico', 'cur', 'tiff', 'bmp', 'heic', 'svg', 'mp4', 'mkv', 'mp3', 'aac', 'flac', 'm4a', 'ogg', 'opus', 'wma', 'mov', 'webm', 'wmv', '3gp', 'flv', 'm4v', 'docx', 'xlsx' ]; if (in_array($extension, $directServeExtensions)) { $this->serveFileDirect($fullPath, $extension, $filename); } else { $this->serveFileAsText($fileContent, $filename, $extension, $viewMode); } } private function serveFileDirect($fullPath, $extension, $filename) { $mimeTypes = [ 'pdf' => 'application/pdf', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif', 'webp' => 'image/webp', 'jfif' => 'image/jpeg', 'avif' => 'image/avif', 'ico' => 'image/vnd.microsoft.icon', 'cur' => 'image/vnd.microsoft.icon', 'tiff' => 'image/tiff', 'bmp' => 'image/bmp', 'heic' => 'image/heic', 'svg' => 'image/svg+xml', 'mp4' => 'video/mp4', 'mkv' => 'video/webm', 'mp3' => 'audio/mpeg', 'aac' => 'audio/aac', 'flac' => 'audio/flac', 'm4a' => 'audio/mp4', 'ogg' => 'audio/ogg', 'opus' => 'audio/ogg', 'wma' => 'audio/x-ms-wma', 'mov' => 'video/quicktime', 'webm' => 'video/webm', 'wmv' => 'video/x-ms-wmv', '3gp' => 'video/3gpp', 'flv' => 'video/x-flv', 'm4v' => 'video/mp4', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ]; $mimeType = $mimeTypes[$extension] ?? 'application/octet-stream'; header('Content-Type: ' . $mimeType); header('Content-Disposition: inline; filename="' . $filename . '"'); header('Content-Length: ' . filesize($fullPath)); readfile($fullPath); exit; } private function serveFileAsText($fileContent, $filename, $extension, $viewMode = 'default') { $markdownExtensions = ['md', 'markdown']; $isMarkdown = in_array($extension, $markdownExtensions); $showMarkdown = false; if ($viewMode === 'default' && $isMarkdown) { $showMarkdown = true; } elseif ($viewMode === 'markdown' && $isMarkdown) { $showMarkdown = true; } $showRaw = isset($_GET['raw']) && $_GET['raw'] === '1'; if ($showRaw) { $showMarkdown = false; } ?> <?php echo $filename; ?> View Markdown
'raw']); unset($rawParams['raw']); $viewButtons .= 'View Raw'; if ($viewMode !== 'code') { $codeParams = array_merge($currentParams, ['view' => 'code']); unset($codeParams['raw']); $viewButtons .= 'View Code'; } echo $viewButtons; ?>
'raw']); unset($rawParams['raw']); $viewButtons .= 'View Raw'; $markdownParams = array_merge($currentParams, ['view' => 'default']); unset($markdownParams['raw']); $viewButtons .= 'View Markdown'; echo $viewButtons; ?>
'raw']); unset($rawParams['raw']); echo 'View Raw'; ?>
open($zipPath, ZipArchive::CREATE) === TRUE) { addDirectoryToZip($zip, $tempDir, $folderName); $zip->close(); header('Content-Type: application/zip'); header('Content-Disposition: attachment; filename="' . $zipName . '"'); header('Content-Length: ' . filesize($zipPath)); if (ob_get_level()) { ob_end_clean(); } readfile($zipPath); deleteDirectory($tempDir); unlink($zipPath); exit; } else { deleteDirectory($tempDir); http_response_code(500); exit('Cannot create ZIP file'); } } else { http_response_code(500); exit('Cannot copy directory'); } } } $scriptDir = dirname(__FILE__); $baseDir = $scriptDir . '/files'; $isDockerEnvironment = getenv('DOCKER_ENV') === 'true' || file_exists('/.dockerenv'); $scriptDir = dirname(__FILE__); if ($isDockerEnvironment) { $baseDir = '/files'; $indexerFilesDir = '/config'; } else { $baseDir = $scriptDir . '/files'; $indexerFilesDir = $scriptDir . '/.indexer_files'; } $documentRoot = $_SERVER['DOCUMENT_ROOT']; $scriptPath = str_replace($documentRoot, '', $scriptDir); $webPath = rtrim($scriptPath, '/'); if ($webPath === '' || $webPath === '/') { $webPath = ''; } else { $webPath = '/' . ltrim($webPath, '/'); } $router = new URLRouter($scriptDir, $webPath, $baseDir); $currentPath = $router->parseCleanURL(); $currentPath = ltrim($currentPath, '/'); $currentPath = str_replace(['../', './'], '', $currentPath); $fullPath = $baseDir . '/' . $currentPath; $webCurrentPath = '/files' . ($currentPath ? '/' . $currentPath : ''); $indexerFilesDir = $scriptDir . '/.indexer_files'; $zipCacheDir = $indexerFilesDir . '/zip_cache'; $indexCacheDir = $indexerFilesDir . '/index_cache'; $iconsDir = $indexerFilesDir . '/icons'; $localApiDir = $indexerFilesDir . '/local_api'; $localStyleDir = $localApiDir . '/style'; $configFile = $indexerFilesDir . '/config.json'; $localExtensionMapFile = $localApiDir . '/extensionMap.json'; $localIconsFile = $localApiDir . '/icons.json'; $config = loadConfiguration(); $cacheType = isset($config['main']['cache_type']) ? $config['main']['cache_type'] : 'sqlite'; $disableFileDownloads = isset($config['main']['disable_file_downloads']) ? $config['main']['disable_file_downloads'] : false; $disableFolderDownloads = isset($config['main']['disable_folder_downloads']) ? $config['main']['disable_folder_downloads'] : false; $iconType = isset($config['main']['icon_type']) ? $config['main']['icon_type'] : 'default'; $denyList = parseDenyAllowList(isset($config['main']['deny_list']) ? $config['main']['deny_list'] : ''); $allowList = parseDenyAllowList(isset($config['main']['allow_list']) ? $config['main']['allow_list'] : ''); $conflictingRules = findConflictingRules($denyList, $allowList); $cacheInstance = initializeCache(); runCacheCleanup(); if (!empty($currentPath)) { if ($router->isFileRequest($currentPath)) { $router->handleFileRequest($currentPath); exit; } if ($router->isFolderRequest($currentPath)) { if (isset($_GET['download']) && $_GET['download'] === 'archive') { $router->handleFolderRequest($currentPath); exit; } $router->handleFolderRequest($currentPath); } else { http_response_code(404); exit('Path not found'); } } function parseDenyAllowList($listString) { if (empty(trim($listString))) { return []; } $items = array_map('trim', explode(',', $listString)); $parsedList = []; foreach ($items as $item) { if (empty($item)) continue; $rule = [ 'original' => $item, 'path' => '', 'type' => 'exact', 'target' => 'both' ]; if (substr($item, -2) === '/*') { $rule['type'] = 'folder_recursive'; $rule['path'] = substr($item, 0, -2); $rule['target'] = 'folder'; } elseif (substr($item, -1) === '*' && substr($item, -2) !== '/*') { if (strpos($item, '.') !== false && strrpos($item, '.') > strrpos($item, '/')) { $rule['type'] = 'wildcard'; $rule['path'] = $item; $rule['target'] = 'file'; } else { $rule['type'] = 'wildcard'; $rule['path'] = substr($item, 0, -1); $rule['target'] = 'folder'; } } elseif (strpos($item, '*') !== false) { $rule['type'] = 'wildcard'; $rule['path'] = $item; $rule['target'] = 'file'; } else { $rule['type'] = 'exact'; $rule['path'] = $item; if (strpos(basename($item), '.') !== false) { $rule['target'] = 'file'; } else { $rule['target'] = 'folder'; } } $parsedList[] = $rule; } return $parsedList; } function findConflictingRules($denyList, $allowList) { $conflicts = []; foreach ($denyList as $denyRule) { foreach ($allowList as $allowRule) { if ($denyRule['path'] === $allowRule['path'] && $denyRule['type'] === $allowRule['type']) { $conflicts[] = [ 'deny' => $denyRule['original'], 'allow' => $allowRule['original'] ]; } } } return $conflicts; } function pathMatchesRule($relativePath, $rule, $isFolder = false) { $rulePath = $rule['path']; if ($rule['type'] === 'exact') { return $relativePath === $rulePath; } if ($rule['type'] === 'wildcard') { if ($rule['target'] === 'file' && !$isFolder) { if (strpos($rulePath, '/') !== false) { $lastSlashPos = strrpos($rulePath, '/'); $directory = substr($rulePath, 0, $lastSlashPos); $pattern = substr($rulePath, $lastSlashPos + 1); $fileDir = dirname($relativePath); $fileName = basename($relativePath); $directory = rtrim($directory, '/'); $fileDir = rtrim($fileDir, '/'); if ($fileDir === $directory) { if (strpos($pattern, '.') === 0) { $extension = substr($pattern, 1, -1); $fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); return $fileExtension === $extension; } else { $regexPattern = str_replace('*', '.*', preg_quote($pattern, '/')); return preg_match('/^' . $regexPattern . '$/i', $fileName); } } } } elseif ($rule['target'] === 'folder') { if ($isFolder) { $pathParts = explode('/', $relativePath); $topLevelFolder = $pathParts[0]; if (count($pathParts) === 1 && strpos($topLevelFolder, $rulePath) === 0) { return true; } return false; } else { $fileDir = dirname($relativePath); if ($fileDir === '.') $fileDir = ''; if (strpos($fileDir, '/') === false) { return strpos($fileDir, $rulePath) === 0; } return false; } } } if ($rule['type'] === 'folder_recursive') { if ($relativePath === $rulePath) { return true; } return strpos($relativePath, $rulePath . '/') === 0; } return false; } function isPathDenied($relativePath, $isFolder = false) { global $denyList, $conflictingRules; foreach ($denyList as $rule) { $isConflicting = false; foreach ($conflictingRules as $conflict) { if ($conflict['deny'] === $rule['original']) { $isConflicting = true; break; } } if ($isConflicting) continue; if (pathMatchesRule($relativePath, $rule, $isFolder)) { return true; } } return false; } function isPathAllowed($relativePath, $isFolder = false) { global $allowList, $conflictingRules; foreach ($allowList as $rule) { $isConflicting = false; foreach ($conflictingRules as $conflict) { if ($conflict['allow'] === $rule['original']) { $isConflicting = true; break; } } if ($isConflicting) continue; if (pathMatchesRule($relativePath, $rule, $isFolder)) { return true; } } return false; } function isContentAllowedByWildcard($relativePath, $isFolder = false) { global $allowList, $conflictingRules; foreach ($allowList as $rule) { $isConflicting = false; foreach ($conflictingRules as $conflict) { if ($conflict['allow'] === $rule['original']) { $isConflicting = true; break; } } if ($isConflicting) continue; if ($rule['type'] === 'wildcard' && $rule['target'] === 'folder') { if (strpos($relativePath, $rule['path']) === 0) { return true; } } } return false; } function isFolderAccessible($currentPath) { global $denyList, $allowList, $conflictingRules, $config; if (isset($config['main']['index_all']) && $config['main']['index_all']) { return true; } foreach ($allowList as $rule) { $isConflicting = false; foreach ($conflictingRules as $conflict) { if ($conflict['allow'] === $rule['original']) { $isConflicting = true; break; } } if ($isConflicting) continue; if (pathMatchesRule($currentPath, $rule, true)) { return true; } } foreach ($denyList as $rule) { $isConflicting = false; foreach ($conflictingRules as $conflict) { if ($conflict['deny'] === $rule['original']) { $isConflicting = true; break; } } if ($isConflicting) continue; if ($rule['type'] === 'folder_recursive') { if ($currentPath === $rule['path'] || strpos($currentPath, $rule['path'] . '/') === 0) { return false; } } elseif ($rule['type'] === 'wildcard' && $rule['target'] === 'folder') { $pathParts = explode('/', $currentPath); $topLevelFolder = $pathParts[0]; if (strpos($topLevelFolder, $rule['path']) === 0) { if ($currentPath === $topLevelFolder) { return false; } } } elseif ($rule['type'] === 'exact') { if ($currentPath === $rule['path']) { return false; } } } return true; } function shouldIndexFile($filename, $extension) { global $config, $currentPath; $relativePath = $currentPath ? $currentPath . '/' . basename($filename) : basename($filename); if (empty($extension) || trim($extension) === '') { $indexNonDescript = isset($config['exclusions']['index_non-descript-files']) ? $config['exclusions']['index_non-descript-files'] : true; if (!$indexNonDescript) { return false; } if (isPathDenied($relativePath, false)) { if (isPathAllowed($relativePath, false)) { return true; } return false; } if (isContentAllowedByWildcard($relativePath, false)) { return true; } if (isPathAllowed($relativePath, false)) { return true; } if (isset($config['main']['index_all']) && $config['main']['index_all']) { return true; } if (strpos(basename($filename), '.') === 0) { if (!isset($config['main']['index_hidden']) || !$config['main']['index_hidden']) { return false; } } return true; } if (isPathDenied($relativePath, false)) { if (isPathAllowed($relativePath, false)) { return true; } return false; } if (isContentAllowedByWildcard($relativePath, false)) { return true; } if (isPathAllowed($relativePath, false)) { return true; } if (isset($config['main']['index_all']) && $config['main']['index_all']) { return true; } if (strpos(basename($filename), '.') === 0) { if (!isset($config['main']['index_hidden']) || !$config['main']['index_hidden']) { return false; } } $settingKey = getExtensionSetting($extension, 'indexing'); if ($settingKey !== null) { return isset($config['exclusions'][$settingKey]) ? $config['exclusions'][$settingKey] : true; } return true; } function shouldIndexFolder($foldername) { global $config, $currentPath; $relativePath = $currentPath ? $currentPath . '/' . basename($foldername) : basename($foldername); if (basename($foldername) === '.indexer_files') { if (isset($config['main']['index_all']) && $config['main']['index_all']) { return true; } return false; } if (isPathDenied($relativePath, true)) { if (isPathAllowed($relativePath, true)) { return true; } return false; } if (isContentAllowedByWildcard($relativePath, true)) { return true; } if (isPathAllowed($relativePath, true)) { return true; } if (isset($config['main']['index_all']) && $config['main']['index_all']) { return true; } if (strpos(basename($foldername), '.') === 0) { if (!isset($config['main']['index_hidden']) || !$config['main']['index_hidden']) { return false; } } return isset($config['exclusions']['index_folders']) ? $config['exclusions']['index_folders'] : true; } function isFileAccessible($filePath, $currentPath, $extension) { global $config, $disableFileDownloads; if (!isFolderAccessible($currentPath)) { return false; } $fileName = basename($filePath); $relativePath = $currentPath ? $currentPath . '/' . $fileName : $fileName; if (isPathDenied($relativePath, false)) { if (!isPathAllowed($relativePath, false)) { return false; } } if (!shouldIndexFile($filePath, $extension)) { return false; } return true; } function loadConfiguration() { global $configFile; if (file_exists($configFile)) { $configData = json_decode(file_get_contents($configFile), true); if ($configData !== null) { return $configData; } } return []; } function getExtensionSetting($extension, $type = 'indexing') { if (empty($extension) || trim($extension) === '') { if ($type === 'indexing') { return 'index_non-descript-files'; } elseif ($type === 'viewing') { return 'view_non-descript-files'; } return null; } $extensionMappings = loadLocalExtensionMappings(); if ($extensionMappings === null) { return null; } $extension = strtolower($extension); if ($type === 'indexing' && isset($extensionMappings['indexing'][$extension])) { return $extensionMappings['indexing'][$extension]; } elseif ($type === 'viewing' && isset($extensionMappings['viewing'][$extension])) { return $extensionMappings['viewing'][$extension]; } return null; } function loadLocalExtensionMappings() { global $localExtensionMapFile; if (!file_exists($localExtensionMapFile)) { return null; } $mappingData = file_get_contents($localExtensionMapFile); $mappings = json_decode($mappingData, true); return $mappings ?: null; } function loadLocalIconMappings() { global $localIconsFile; if (!file_exists($localIconsFile)) { return []; } $mappingData = file_get_contents($localIconsFile); $mappings = json_decode($mappingData, true); return $mappings ?: []; } function getIconPath($type, $extension = '') { global $webPath, $iconsDir, $iconType, $scriptDir; if ($iconType === 'disabled') { return null; } if ($iconType === 'emoji') { return null; } if ($iconType === 'minimal') { $iconFilename = ($type === 'folder') ? 'folder.png' : 'non-descript-default-file.png'; $iconPath = $iconsDir . '/' . $iconFilename; if (file_exists($iconPath)) { $relativePath = str_replace($scriptDir, '', $iconPath); return $webPath . $relativePath; } return null; } if ($iconType === 'default') { if ($type === 'folder') { $iconInfo = getIconFromLocal($type, $extension); if ($iconInfo !== null) { $relativePath = str_replace($scriptDir, '', $iconInfo['path']); return $webPath . $relativePath; } } if ($type === 'file') { if (empty($extension) || trim($extension) === '') { $iconFilename = 'non-descript-default-file.png'; $iconPath = $iconsDir . '/' . $iconFilename; if (file_exists($iconPath)) { $relativePath = str_replace($scriptDir, '', $iconPath); return $webPath . $relativePath; } return null; } $iconMappings = loadLocalIconMappings(); if ($iconMappings && is_array($iconMappings)) { $extension = strtolower($extension); if (!isset($iconMappings[$extension])) { $iconFilename = 'non-descript-default-file.png'; $iconPath = $iconsDir . '/' . $iconFilename; if (file_exists($iconPath)) { $relativePath = str_replace($scriptDir, '', $iconPath); return $webPath . $relativePath; } return null; } } $iconInfo = getIconFromLocal($type, $extension); if ($iconInfo !== null) { $relativePath = str_replace($scriptDir, '', $iconInfo['path']); return $webPath . $relativePath; } } } return null; } function getIconFromLocal($type, $extension = '') { global $iconsDir; $iconMappings = loadLocalIconMappings(); if ($type === 'folder') { $iconFile = isset($iconMappings['folder']) ? $iconMappings['folder'] : 'folder.png'; } else { $extension = strtolower($extension); $iconFile = isset($iconMappings[$extension]) ? $iconMappings[$extension] : 'non-descript-default-file.png'; } $iconPath = $iconsDir . '/' . $iconFile; if (!file_exists($iconPath)) { if ($type === 'folder') { $iconFile = 'folder.png'; } else { $iconFile = 'non-descript-default-file.png'; } $iconPath = $iconsDir . '/' . $iconFile; if (!file_exists($iconPath)) { return null; } } return [ 'filename' => $iconFile, 'path' => $iconPath, 'size' => filesize($iconPath), 'last_modified' => filemtime($iconPath) ]; } function isFileViewable($extension) { global $config; if (empty($extension) || trim($extension) === '') { $viewNonDescript = isset($config['viewable_files']['view_non-descript-files']) ? $config['viewable_files']['view_non-descript-files'] : false; return $viewNonDescript; } $settingKey = getExtensionSetting($extension, 'viewing'); if ($settingKey !== null) { return isset($config['viewable_files'][$settingKey]) ? $config['viewable_files'][$settingKey] : false; } return false; } function cleanupOldTempFiles() { global $zipCacheDir; if (!is_dir($zipCacheDir)) return; $files = glob($zipCacheDir . '/*'); $fiveMinutesAgo = time() - 300; foreach ($files as $file) { if (filemtime($file) < $fiveMinutesAgo) { if (is_dir($file)) { deleteDirectory($file); } else { unlink($file); } } } } function deleteDirectory($dir) { if (!is_dir($dir)) return; $files = glob($dir . '/*'); foreach ($files as $file) { if (is_dir($file)) { deleteDirectory($file); } else { unlink($file); } } rmdir($dir); } function getDirectorySize($path) { $size = 0; if (is_dir($path)) { $files = scandir($path); foreach ($files as $file) { if ($file == '.' || $file == '..') continue; $filePath = $path . '/' . $file; if (is_dir($filePath)) { $size += getDirectorySize($filePath); } else { $size += filesize($filePath); } } } return $size; } class IndexerCache { private $cacheType; private $indexCacheDir; private $pdo = null; public function __construct($cacheType, $indexCacheDir) { $this->cacheType = $cacheType; $this->indexCacheDir = $indexCacheDir; if ($this->cacheType === 'sqlite') { $this->initializeSQLite(); } } private function initializeSQLite() { if (!is_dir($this->indexCacheDir)) { mkdir($this->indexCacheDir, 0755, true); } $dbPath = $this->indexCacheDir . '/cache.sqlite'; try { $this->pdo = new PDO('sqlite:' . $dbPath); $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->pdo->exec("CREATE TABLE IF NOT EXISTS unified_cache ( cache_key TEXT PRIMARY KEY, cache_type TEXT NOT NULL, data TEXT NOT NULL, last_modified INTEGER NOT NULL, expires_at INTEGER DEFAULT NULL )"); $this->pdo->exec("CREATE INDEX IF NOT EXISTS idx_cache_type ON unified_cache(cache_type)"); $this->pdo->exec("CREATE INDEX IF NOT EXISTS idx_expires_at ON unified_cache(expires_at)"); } catch (Exception $e) { $this->cacheType = 'json'; } } public function get($key, $type = 'directory') { if ($this->cacheType === 'sqlite') { return $this->getSQLiteCache($key, $type); } else { return $this->getJSONCache($key, $type); } } public function set($key, $type, $data, $ttl = null) { if ($this->cacheType === 'sqlite') { $this->setSQLiteCache($key, $type, $data, $ttl); } else { $this->setJSONCache($key, $type, $data, $ttl); } } public function cleanup() { if ($this->cacheType === 'sqlite') { $this->cleanupSQLite(); } else { $this->cleanupJSON(); } } private function getSQLiteCache($key, $type) { if (!$this->pdo) return null; try { $stmt = $this->pdo->prepare("SELECT data, last_modified, expires_at FROM unified_cache WHERE cache_key = ? AND cache_type = ?"); $stmt->execute([$key, $type]); $result = $stmt->fetch(PDO::FETCH_ASSOC); if ($result) { if ($result['expires_at'] && time() > $result['expires_at']) { $this->pdo->prepare("DELETE FROM unified_cache WHERE cache_key = ? AND cache_type = ?")->execute([$key, $type]); return null; } if ($type === 'directory') { $actualPath = preg_replace('/_sort_.*$/', '', $key); $currentModified = $this->getPathLastModified($actualPath); if ($result['last_modified'] < $currentModified) { $this->pdo->prepare("DELETE FROM unified_cache WHERE cache_key = ? AND cache_type = ?")->execute([$key, $type]); return null; } } return json_decode($result['data'], true); } } catch (Exception $e) { error_log("Cache error: " . $e->getMessage()); } return null; } private function setSQLiteCache($key, $type, $data, $ttl = null) { if (!$this->pdo) return; try { $expiresAt = $ttl ? time() + $ttl : null; if ($type === 'directory') { $actualPath = preg_replace('/_sort_.*$/', '', $key); $lastModified = $this->getPathLastModified($actualPath); } else { $lastModified = time(); } $stmt = $this->pdo->prepare("INSERT OR REPLACE INTO unified_cache (cache_key, cache_type, data, last_modified, expires_at) VALUES (?, ?, ?, ?, ?)"); $stmt->execute([$key, $type, json_encode($data), $lastModified, $expiresAt]); } catch (Exception $e) { error_log("Cache set error: " . $e->getMessage()); } } private function getJSONCache($key, $type) { if (!is_dir($this->indexCacheDir)) { mkdir($this->indexCacheDir, 0755, true); } $cacheFile = $this->indexCacheDir . '/cache.json'; if (!file_exists($cacheFile)) { return null; } $cacheData = json_decode(file_get_contents($cacheFile), true) ?: []; $cacheKey = $type . ':' . $key; if (isset($cacheData[$cacheKey])) { $item = $cacheData[$cacheKey]; if (isset($item['expires_at']) && $item['expires_at'] && time() > $item['expires_at']) { unset($cacheData[$cacheKey]); file_put_contents($cacheFile, json_encode($cacheData, JSON_PRETTY_PRINT)); return null; } if ($type === 'directory') { $actualPath = preg_replace('/_sort_.*$/', '', $key); $currentModified = $this->getPathLastModified($actualPath); if ($item['last_modified'] < $currentModified) { unset($cacheData[$cacheKey]); file_put_contents($cacheFile, json_encode($cacheData, JSON_PRETTY_PRINT)); return null; } } return $item['data']; } return null; } private function setJSONCache($key, $type, $data, $ttl = null) { if (!is_dir($this->indexCacheDir)) { mkdir($this->indexCacheDir, 0755, true); } $cacheFile = $this->indexCacheDir . '/cache.json'; $cacheData = []; if (file_exists($cacheFile)) { $cacheData = json_decode(file_get_contents($cacheFile), true) ?: []; } $cacheKey = $type . ':' . $key; $expiresAt = $ttl ? time() + $ttl : null; if ($type === 'directory') { $actualPath = preg_replace('/_sort_.*$/', '', $key); $lastModified = $this->getPathLastModified($actualPath); } else { $lastModified = time(); } $cacheData[$cacheKey] = [ 'cache_type' => $type, 'data' => $data, 'last_modified' => $lastModified, 'expires_at' => $expiresAt ]; file_put_contents($cacheFile, json_encode($cacheData, JSON_PRETTY_PRINT)); } private function cleanupSQLite() { if (!$this->pdo) return; try { $this->pdo->prepare("DELETE FROM unified_cache WHERE expires_at IS NOT NULL AND expires_at < ?")->execute([time()]); } catch (Exception $e) { } } private function cleanupJSON() { $cacheFile = $this->indexCacheDir . '/cache.json'; if (!file_exists($cacheFile)) { return; } $cacheData = json_decode(file_get_contents($cacheFile), true) ?: []; $modified = false; foreach ($cacheData as $cacheKey => $item) { if (isset($item['expires_at']) && $item['expires_at'] && time() > $item['expires_at']) { unset($cacheData[$cacheKey]); $modified = true; } } if ($modified) { file_put_contents($cacheFile, json_encode($cacheData, JSON_PRETTY_PRINT)); } } private function getPathLastModified($path) { global $baseDir, $configFile; $fullPath = $baseDir . '/' . ltrim($path, '/'); if (!is_dir($fullPath)) return 0; $lastModified = filemtime($fullPath); $items = scandir($fullPath); foreach ($items as $item) { if ($item == '.' || $item == '..') continue; $itemPath = $fullPath . '/' . $item; if (is_readable($itemPath)) { $itemModified = filemtime($itemPath); if ($itemModified > $lastModified) { $lastModified = $itemModified; } if (is_dir($itemPath)) { $subItems = @scandir($itemPath); if ($subItems) { foreach ($subItems as $subItem) { if ($subItem == '.' || $subItem == '..') continue; $subItemPath = $itemPath . '/' . $subItem; if (is_readable($subItemPath)) { $subItemModified = filemtime($subItemPath); if ($subItemModified > $lastModified) { $lastModified = $subItemModified; } } } } } } } if (file_exists($configFile)) { $configModified = filemtime($configFile); if ($configModified > $lastModified) { $lastModified = $configModified; } } return $lastModified; } } $cacheInstance = null; function initializeCache() { global $cacheInstance, $cacheType, $indexCacheDir; if ($cacheInstance === null) { $cacheInstance = new IndexerCache($cacheType, $indexCacheDir); } return $cacheInstance; } function getCacheData($path) { $cache = initializeCache(); return $cache->get($path, 'directory'); } function setCacheData($path, $data) { $cache = initializeCache(); $cache->set($path, 'directory', $data); } function runCacheCleanup() { $cache = initializeCache(); $cache->cleanup(); } function copyDirectoryExcludePhp($source, $destination) { global $currentPath; if (!is_dir($source)) return false; if (!is_dir($destination)) { mkdir($destination, 0755, true); } $files = scandir($source); foreach ($files as $file) { if ($file == '.' || $file == '..') continue; $sourcePath = $source . '/' . $file; $destPath = $destination . '/' . $file; if (is_dir($sourcePath)) { if (shouldIndexFolder($sourcePath)) { copyDirectoryExcludePhp($sourcePath, $destPath); } } else { $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); if (shouldIndexFile($sourcePath, $extension)) { copy($sourcePath, $destPath); } } } return true; } function addDirectoryToZip($zip, $dir, $zipPath = '') { $files = scandir($dir); foreach ($files as $file) { if ($file == '.' || $file == '..') continue; $filePath = $dir . '/' . $file; $zipFilePath = $zipPath ? $zipPath . '/' . $file : $file; if (is_dir($filePath)) { $zip->addEmptyDir($zipFilePath); addDirectoryToZip($zip, $filePath, $zipFilePath); } else { $zip->addFile($filePath, $zipFilePath); } } } function getFileUrl($path, $filename) { global $router, $disableFileDownloads; $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (isFileViewable($extension)) { return $router->generateFileURL($path, $filename, 'view', 'default'); } else { if ($disableFileDownloads) { return '#'; } return $router->generateFileURL($path, $filename, 'download'); } } function getSortUrl($sortBy, $currentSort, $currentDir, $currentPath) { global $router; return $router->generateSortURL($sortBy, $currentSort, $currentDir, $currentPath); } function getBreadcrumbs($currentPath) { global $router; return $router->generateBreadcrumbs($currentPath); } function getBaseUrl($currentPath, $webPath) { if (empty($currentPath)) { return $webPath . '/'; } else { return $webPath . '/' . $currentPath . '/'; } } function getFileActionMenu($file, $currentPath) { global $disableFileDownloads, $router, $webPath; $extension = $file['extension']; $fileName = $file['name']; $isViewable = isFileViewable($extension); $showActions = isset($_GET['action']) && $_GET['action'] === $fileName; $menu = '
'; if ($showActions) { $closeParams = $_GET; unset($closeParams['action']); unset($closeParams['options']); if (empty($currentPath)) { $baseUrl = $webPath . '/'; } else { $encodedPath = rawurlencode($currentPath); $baseUrl = $webPath . '/' . $encodedPath . '/'; } $closeUrl = $baseUrl . ($closeParams ? '?' . http_build_query($closeParams) : ''); $menu .= '×'; $menu .= '
'; if ($isViewable) { $openUrl = $router->generateFileURL($currentPath, $fileName, 'view', 'default'); $menu .= 'Open'; $menu .= 'Open in new tab'; } if (!$disableFileDownloads) { $downloadUrl = $router->generateFileURL($currentPath, $fileName, 'download'); $menu .= 'Download'; } $shareParams = array_merge($_GET, ['share_popup' => 'view', 'share_file' => $fileName]); $shareUrl = $baseUrl . '?' . http_build_query($shareParams); $menu .= 'Share'; if (!$disableFileDownloads) { $shareParams = array_merge($_GET, ['share_popup' => 'download', 'share_file' => $fileName]); $shareUrl = $baseUrl . '?' . http_build_query($shareParams); $menu .= 'Share Download'; } $menu .= '
'; } else { $actionParams = $_GET; unset($actionParams['options']); if (isset($actionParams['action'])) { unset($actionParams['action']); } $actionParams['action'] = $fileName; if (empty($currentPath)) { $baseUrl = $webPath . '/'; } else { $encodedPath = rawurlencode($currentPath); $baseUrl = $webPath . '/' . $encodedPath . '/'; } $actionUrl = $baseUrl . '?' . http_build_query($actionParams); $menu .= ''; } $menu .= '
'; return $menu; } function getFolderActionMenu($folder, $currentPath) { global $disableFolderDownloads, $router, $webPath; $folderName = $folder['name']; $showActions = isset($_GET['action']) && $_GET['action'] === $folderName; $menu = '
'; if ($showActions) { $closeParams = $_GET; unset($closeParams['action']); unset($closeParams['options']); $baseUrl = getBaseUrl($currentPath, $webPath); $closeUrl = $baseUrl . ($closeParams ? '?' . http_build_query($closeParams) : ''); $menu .= '×'; $menu .= '
'; $openUrl = $router->generateFolderURL($currentPath, $folderName, 'view'); $menu .= 'Open'; $menu .= 'Open in new tab'; if (!$disableFolderDownloads) { $downloadUrl = $router->generateFolderURL($currentPath, $folderName, 'download'); $menu .= 'Download'; } $shareParams = array_merge($_GET, ['share_popup' => 'view', 'share_file' => $folderName]); $shareUrl = $baseUrl . '?' . http_build_query($shareParams); $menu .= 'Share'; if (!$disableFolderDownloads) { $shareParams = array_merge($_GET, ['share_popup' => 'download', 'share_file' => $folderName]); $shareUrl = $baseUrl . '?' . http_build_query($shareParams); $menu .= 'Share Download'; } $menu .= '
'; } else { $actionParams = $_GET; unset($actionParams['options']); if (isset($actionParams['action'])) { unset($actionParams['action']); } $actionParams['action'] = $folderName; $baseUrl = getBaseUrl($currentPath, $webPath); $actionUrl = $baseUrl . '?' . http_build_query($actionParams); $menu .= ''; } $menu .= '
'; return $menu; } function generateSharePopup($shareType, $shareFile, $currentPath) { global $router, $webPath; if ($shareType === 'view') { if (is_dir($GLOBALS['fullPath'] . '/' . $shareFile)) { $shareUrl = $router->generateFolderURL($currentPath, $shareFile, 'view'); $popupTitle = 'Share Folder Link'; $popupText = 'Copy the following URL to share folder'; } else { $shareUrl = $router->generateFileURL($currentPath, $shareFile, 'view', 'default'); $popupTitle = 'Share File Link'; $popupText = 'Copy the following URL to view file'; } } elseif ($shareType === 'folder') { $shareUrl = $router->generateFolderURL($currentPath, '', 'view'); $popupTitle = 'Share Folder Location'; $popupText = 'Copy the following URL to share the folder containing'; } elseif ($shareType === 'download') { $isFolder = is_dir($GLOBALS['fullPath'] . '/' . $shareFile); if ($isFolder) { $shareUrl = $router->generateFolderURL($currentPath, $shareFile, 'download'); } else { $shareUrl = $router->generateFileURL($currentPath, $shareFile, 'download'); } $popupTitle = 'Share Download Link'; $popupText = 'Copy the following URL to download'; } $absoluteShareUrl = getAbsoluteUrl($shareUrl); $closeParams = $_GET; unset($closeParams['share_popup']); unset($closeParams['share_file']); $baseUrl = getBaseUrl($currentPath, $webPath); $closeUrl = $baseUrl . ($closeParams ? '?' . http_build_query($closeParams) : ''); return '

' . htmlspecialchars($popupTitle) . '

' . htmlspecialchars($popupText) . ' "' . htmlspecialchars($shareFile) . '":

Select the URL above and copy it (Ctrl+C / Cmd+C)

'; } function formatBytes($size) { if ($size === null) return '-'; $units = ['B', 'KB', 'MB', 'GB']; for ($i = 0; $size > 1024 && $i < count($units) - 1; $i++) { $size /= 1024; } return round($size, 1) . ' ' . $units[$i]; } function getSortParams() { $sortBy = $_GET['sort'] ?? 'name'; $sortDir = $_GET['dir'] ?? 'asc'; $validSorts = ['name', 'size', 'modified', 'type']; $validDirs = ['asc', 'desc']; if (!in_array($sortBy, $validSorts)) $sortBy = 'name'; if (!in_array($sortDir, $validDirs)) $sortDir = 'asc'; return ['sort' => $sortBy, 'dir' => $sortDir]; } function sortItems($items, $sortBy, $sortDir) { usort($items, function($a, $b) use ($sortBy, $sortDir) { $result = 0; switch ($sortBy) { case 'name': $result = strcasecmp($a['name'], $b['name']); break; case 'size': $result = $a['size'] <=> $b['size']; break; case 'modified': $result = $a['modified'] <=> $b['modified']; break; case 'type': $aExt = isset($a['extension']) ? $a['extension'] : ($a['is_dir'] ? 'folder' : ''); $bExt = isset($b['extension']) ? $b['extension'] : ($b['is_dir'] ? 'folder' : ''); $result = strcasecmp($aExt, $bExt); break; } return $sortDir === 'desc' ? -$result : $result; }); return $items; } function getSortIndicator($column, $currentSort, $currentDir) { if ($column !== $currentSort) { return ''; } return $currentDir === 'asc' ? ' ↑' : ' ↓'; } function parseMarkdown($text) { $text = str_replace(["\r\n", "\r"], "\n", $text); $codeBlocks = []; $inlineCodes = []; $text = preg_replace_callback('/```([a-zA-Z0-9\-_]*)\n?(.*?)\n?```/s', function($matches) use (&$codeBlocks) { $id = count($codeBlocks); $placeholder = "XCODEBLOCKREPLACEX" . $id . "XCODEBLOCKREPLACEX"; $codeBlocks[$placeholder] = trim($matches[2]); return "\n" . $placeholder . "\n"; }, $text); $text = preg_replace_callback('/`([^`\n]+?)`/', function($matches) use (&$inlineCodes) { $id = count($inlineCodes); $placeholder = "XINLINECODEREPLACEX" . $id . "XINLINECODEREPLACEX"; $inlineCodes[$placeholder] = $matches[1]; return $placeholder; }, $text); $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); $text = preg_replace('/^###### (.+?)$/m', '
$1
', $text); $text = preg_replace('/^##### (.+?)$/m', '
$1
', $text); $text = preg_replace('/^#### (.+?)$/m', '

$1

', $text); $text = preg_replace('/^### (.+?)$/m', '

$1

', $text); $text = preg_replace('/^## (.+?)$/m', '

$1

', $text); $text = preg_replace('/^# (.+?)$/m', '

$1

', $text); $text = preg_replace('/!\[([^\]]*?)\]\(([^)]+?)\)/', '$1', $text); $text = preg_replace('/\[([^\]]+?)\]\(([^)]+?)\)/', '$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/~~([^~\n]+?)~~/', '$1', $text); $text = preg_replace('/^\s*---\s*$/m', '
', $text); $text = preg_replace('/^\s*\*\*\*\s*$/m', '
', $text); $text = preg_replace('/^> (.+?)$/m', '
$1
', $text); $text = preg_replace('/^(\s*)[\*\-\+] (.+?)$/m', '$1
  • $2
  • ', $text); $text = preg_replace('/^(\s*)\d+\. (.+?)$/m', '$1
  • $2
  • ', $text); $lines = explode("\n", $text); $result = []; $inList = false; $listType = ''; $lastWasListItem = false; foreach ($lines as $line) { if (preg_match('/^(\s*)
  • "; $listType = $newListType; $inList = true; } elseif ($listType !== $newListType) { if (!($listType === 'ol' && $newListType === 'ol')) { $result[] = ""; $result[] = "<$newListType class=\"markdown-list\">"; $listType = $newListType; } } $result[] = $line; $lastWasListItem = true; } else { if ($inList && trim($line) === '' && $lastWasListItem) { $lastWasListItem = false; continue; } if ($inList && trim($line) !== '') { $result[] = ""; $inList = false; } if (trim($line) !== '') { $result[] = $line; $lastWasListItem = false; } } } if ($inList) { $result[] = ""; } $text = implode("\n", $result); $text = preg_replace_callback('/(?:^\|.+\|\s*$\n?)+/m', function($matches) { $table = trim($matches[0]); $rows = explode("\n", $table); $html = ''; $isHeader = true; foreach ($rows as $row) { if (empty(trim($row))) continue; if (preg_match('/^\|[\s\-\|:]+\|$/', $row)) { $isHeader = false; continue; } $cells = explode('|', trim($row, '|')); $cells = array_map('trim', $cells); $tag = $isHeader ? 'th' : 'td'; $class = $isHeader ? 'markdown-th' : 'markdown-td'; $html .= ''; foreach ($cells as $cell) { $html .= "<$tag class=\"$class\">$cell"; } $html .= ''; if ($isHeader) $isHeader = false; } $html .= '
    '; return $html; }, $text); $paragraphs = preg_split('/\n\s*\n/', $text); $result = []; foreach ($paragraphs as $paragraph) { $paragraph = trim($paragraph); if (empty($paragraph)) continue; if (preg_match('/^XCODEBLOCKREPLACEX\d+XCODEBLOCKREPLACEX$/', $paragraph)) { $result[] = $paragraph; } elseif (preg_match('/^<(h[1-6]|ul|ol|blockquote|pre|hr|table|div)/i', $paragraph)) { $result[] = $paragraph; } else { $paragraph = preg_replace('/\n(?!<)/', '
    ', $paragraph); $result[] = '

    ' . $paragraph . '

    '; } } $text = implode("\n\n", $result); foreach ($codeBlocks as $placeholder => $content) { $escapedContent = htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); $codeHtml = '
    ' . $escapedContent . '
    '; $text = str_replace($placeholder, $codeHtml, $text); } foreach ($inlineCodes as $placeholder => $content) { $escapedContent = htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); $codeHtml = '' . $escapedContent . ''; $text = str_replace($placeholder, $codeHtml, $text); } return $text; } function getAbsoluteUrl($relativeUrl) { global $config; if (isset($config['main']['access_url']) && !empty($config['main']['access_url'])) { $baseUrl = rtrim($config['main']['access_url'], '/'); if (strpos($relativeUrl, '/') === 0) { return $baseUrl . $relativeUrl; } else { $currentDir = dirname($_SERVER['REQUEST_URI']); if ($currentDir === '/' || $currentDir === '\\') { return $baseUrl . '/' . $relativeUrl; } else { return $baseUrl . rtrim($currentDir, '/') . '/' . $relativeUrl; } } } $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST']; if (strpos($relativeUrl, '/') === 0) { return $protocol . '://' . $host . $relativeUrl; } else { $currentDir = dirname($_SERVER['REQUEST_URI']); return $protocol . '://' . $host . rtrim($currentDir, '/') . '/' . $relativeUrl; } } if (!is_dir($fullPath)) { http_response_code(404); die('Directory not found'); } if (!isFolderAccessible($currentPath)) { http_response_code(403); header('Location: ' . $_SERVER['SCRIPT_NAME']); exit; } $sortParams = getSortParams(); $sortBy = $sortParams['sort']; $sortDir = $sortParams['dir']; $cacheKey = $currentPath . '_sort_' . $sortBy . '_' . $sortDir; $cachedData = getCacheData($cacheKey); if ($cachedData !== null) { $directories = $cachedData['directories']; $files = $cachedData['files']; } else { $items = scandir($fullPath); $directories = []; $files = []; foreach ($items as $item) { if ($item == '.' || $item == '..') continue; $itemPath = $fullPath . '/' . $item; $itemInfo = [ 'name' => $item, 'modified' => filemtime($itemPath), 'is_dir' => is_dir($itemPath) ]; if ($itemInfo['is_dir']) { if (shouldIndexFolder(($currentPath ? $currentPath . '/' : '') . $item)) { $itemInfo['size'] = getDirectorySize($itemPath); $directories[] = $itemInfo; } } else { $extension = strtolower(pathinfo($item, PATHINFO_EXTENSION)); if (shouldIndexFile(($currentPath ? $currentPath . '/' : '') . $item, $extension)) { $itemInfo['size'] = filesize($itemPath); $itemInfo['extension'] = $extension; $files[] = $itemInfo; } } } $directories = sortItems($directories, $sortBy, $sortDir); $files = sortItems($files, $sortBy, $sortDir); setCacheData($cacheKey, [ 'directories' => $directories, 'files' => $files ]); } ?> Index of <?php echo htmlspecialchars($webCurrentPath); ?> >

    Index of

    ×
    'name', 'dir' => 'asc', 'label' => 'Name (A-Z)'], ['sort' => 'name', 'dir' => 'desc', 'label' => 'Name (Z-A)'], ['sort' => 'size', 'dir' => 'asc', 'label' => 'Size (Small to Large)'], ['sort' => 'size', 'dir' => 'desc', 'label' => 'Size (Large to Small)'], ['sort' => 'modified', 'dir' => 'asc', 'label' => 'Date Modified (Oldest First)'], ['sort' => 'modified', 'dir' => 'desc', 'label' => 'Date Modified (Newest First)'], ['sort' => 'type', 'dir' => 'asc', 'label' => 'Type (A-Z)'], ['sort' => 'type', 'dir' => 'desc', 'label' => 'Type (Z-A)'] ]; foreach ($sortOptions as $option) { $params = array_merge($baseParams, [ 'sort' => $option['sort'], 'dir' => $option['dir'] ]); $isActive = ($currentSort === $option['sort'] && $currentDir === $option['dir']); echo '' . $option['label'] . ''; } ?>
    📁
    Folder
    -
    -
    📁
    Folder
    📄
    File