icons = [ 'DEFAULT' => ['file.svg', 'File'], 'UP' => ['corner-left-up.svg', 'Up'], 'DIR' => ['folder-fill.svg', 'Directory'], 'IMG' => ['image.svg', '[IMG]'], 'TXT' => ['file-text.svg', '[TXT]'], 'CMP' => ['file.svg', '[CMP]'], 'BIN' => ['file.svg', '[BIN]'], 'VID' => ['video.svg', '[VID]'], 'SND' => ['music.svg', '[SND]'], ]; $this->definedSuffix = []; $this->addMapping(['gif', 'png', 'jpg', 'jpeg', 'webp', 'tif', 'tiff', 'bmp', 'svg', 'raw'], 'IMG'); $this->addMapping(['txt', 'md5', 'c', 'cpp', 'cc', 'h', 'sh', 'html', 'htm', 'shtml', 'php', 'phtml', 'css', 'js'], 'TXT'); $this->addMapping(['gz', 'tgz', 'zip', 'Z', 'z'], 'CMP'); $this->addMapping(['bin', 'exe'], 'BIN'); $this->addMapping(['mpg', 'avi', 'mpeg', 'ram', 'wmv'], 'VID'); $this->addMapping(['mp3', 'mp2', 'ogg', 'wav', 'wma', 'aac', 'mp4', 'rm'], 'SND'); } private function addMapping($suffixArray, $iconTag) { if (!isset($this->icons[$iconTag])) { // should not happen, unrecognized tag $iconTag = 'DEFAULT'; } foreach ($suffixArray as $suffix) { $this->definedSuffix[$suffix] = $iconTag; } } private static function getMap() { if (self::$instance == null) { self::$instance = new self(); } return self::$instance; } public static function getFileIconTag($file) { $map = self::getMap(); $pos = strrpos($file, '.'); if ($pos !== false) { $suffix = substr($file, $pos + 1); if ($suffix !== false) { if (isset($map->definedSuffix[$suffix])) { return $map->definedSuffix[$suffix]; } } } return 'DEFAULT'; } public static function img($iconTag) { $me = self::getMap(); return '' . $me->icons[$iconTag][1] . ''; } } // END of customization section class FileStat { protected $name; protected $size; protected $mtime; protected $isdir; protected $iconTag; protected $restricted = false; public function __construct($path, $filename) { $this->name = $filename; if ($path == '..') { $this->size = -2; $this->iconTag = 'UP'; $this->mtime = 0; return; } $s = @stat($path . $filename); if ($s == false) { $this->restricted = true; return; } $this->mtime = $s[9]; if (($s[2] & 040000) > 0) { $this->isdir = true; $this->size = -1; $this->iconTag = 'DIR'; } else { $this->isdir = false; $this->size = $s[7]; $this->iconTag = IconMap::getFileIconTag($filename); } } public function isRestricted() { // due to openbase_dir, file is outside of DOCROOT return $this->restricted; } public function isDir() { return $this->isdir; } public function getUrl($base) { $encoded = $base . rawurlencode($this->name); if ($this->isdir) { $encoded .= '/'; } return $encoded; } public function getIconTag() { return $this->iconTag; } public function sortName() { // case insensitive compare $name = htmlspecialchars(strtolower($this->name), ENT_QUOTES | ENT_SUBSTITUTE); return $this->isdir ? '*' . $name : $name; // to make directories list on top } public function dispName() { $name = $this->name; if (strlen($name) > UserSettings::$NAME_WIDTH) { $name = substr($name, 0, UserSettings::$NAME_WIDTH - 3) . '...'; } return htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE); } public function dispTime() { return ($this->mtime > 0) ? date(UserSettings::$TIME_FORMAT, $this->mtime) : ''; } public function sortTime() { if ($this->isdir) { // to make dir sorted together, deduct 50 years return ($this->mtime - 1576800000); } return $this->mtime; } public function dispSize() { if ($this->iconTag == 'UP') { // parent dir return ''; } if ($this->isdir) { return '-'; } return sprintf("%7ldk", ($this->size + 1023) / 1024); } public function sortSize() { return $this->size; } public static function cmpNA($a, $b) { return strcasecmp($a->name, $b->name); } public static function cmpND($a, $b) { return - self::cmpNA($a, $b); } public static function cmpSA($a, $b) { $ret = $a->size - $b->size; if ($ret == 0) { // if same size, compare by name ascending return self::cmpNA($a, $b); } return $ret; } public static function cmpSD($a, $b) { return - self::cmpSA($a, $b); } public static function cmpMA($a, $b) { return $a->mtime - $b->mtime; } public static function cmpMD($a, $b) { return - self::cmpMA($a, $b); } } class Directory { private $list; private $path; private $len; public function __construct($path) { $this->path = $path; $handle = opendir($path); if ($handle === false) { return; } clearstatcache(); $this->list = []; while (false !== ($file = readdir($handle))) { if (!UserSettings::shouldExclude($file)) { $fs = new FileStat($path, $file); if (!$fs->isRestricted()) { $this->list[] = $fs; } } } closedir($handle); $this->len = count($this->list); } public function cannotLoad() { return ($this->list === null); } public function getListCount() { return $this->len; } public function sortList($order) { usort($this->list, ['\\LiteSpeedAutoIndex\\FileStat', "cmp$order"]); } public function populateList(&$dirs, &$files) { $files = []; foreach ($this->list as $fileStat) { if ($fileStat->isDir()) { $dirs[] = $fileStat; } else { $files[] = $fileStat; } } } } class Index { protected $dir; protected $uri; protected $path; protected $isFancy; protected $sort; public function printPage() { $this->init(); $this->printHeader(); $this->printContent(); $this->printFooter(); } protected function init() { if (empty($_SERVER['LS_AI_PATH'])) { $this->exit403('[ERROR] Auto Index script can not be accessed directly!'); } $this->path = $_SERVER['LS_AI_PATH']; ini_set('open_basedir', $_SERVER['DOCUMENT_ROOT']); $this->dir = new Directory($this->path); if ($this->dir->cannotLoad()) { $this->exit403('

403 Access Denied

'); } $uri = $_SERVER['REQUEST_URI']; if (($pos = strpos($uri, '?')) !== false) { $uri = substr($_SERVER['REQUEST_URI'], 0, $pos); } $this->uri = $uri; if (isset($_SERVER['LS_AI_MIME_TYPE'])) { header('Content-Type: ' . $_SERVER['LS_AI_MIME_TYPE']); } $this->isFancy = empty($_SERVER['LS_FI_OFF']); // if not set Fancy Index Off, by default it's ON if ($this->isFancy) { $this->initSort(); } } protected function initSort() { $order = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; if ($order == '' || strlen($order) != 2 || !in_array($order, ['NA', 'ND', 'MA', 'MD', 'SA', 'SD'])) { $order = 'NA'; // set to default } $this->sort = []; $a = 'ascending'; $d = 'descending'; switch ($order) { case 'NA': $this->sort['name_aria'] = $a; // for indicator for current active sort order. $this->sort['N_link'] = 'ND'; // for current header click action link, reverse to current order break; case 'ND': $this->sort['name_aria'] = $d; break; case 'MA': $this->sort['mod_aria'] = $a; $this->sort['M_link'] = 'MD'; break; case 'MD': $this->sort['mod_aria'] = $d; break; case 'SA': $this->sort['size_aria'] = $a; $this->sort['S_link'] = 'SD'; break; case 'SD': $this->sort['size_aria'] = $d; break; } $this->dir->sortList($order); } protected function exit403($msg) { http_response_code(403); echo $msg; exit; } protected function getAssetLinks() { return ''; } protected function getEndBodyScripts() { return ''; } protected function printHeader() { $disp_uri = htmlentities(urldecode($this->uri), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); $title = 'Index of ' . $disp_uri; echo '' . $this->getAssetLinks() . '' . $title . '' . '

' . $title . "

\n"; $includeHeader = UserSettings::getHeaderName(); if ($includeHeader) { $this->printIncludes($this->path, $includeHeader); } } protected function printIncludes($path, $name) { $testNames = ["$name.html", "$name.htm", $name]; foreach ($testNames as $testname) { $filename = $path . $testname; if (file_exists($filename) && !is_link($filename)) { $content = file_get_contents($filename); if (!$content) { break; } $style = ($name == 'HEADER') ? 'header-text' : 'readme-text'; echo "
\n" . htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE) . "
\n"; break; } } } protected function printFooter() { $includeReadme = UserSettings::getReadmeName(); if ($includeReadme) { $this->printIncludes($this->path, $includeReadme); } echo '
Proudly Served by LiteSpeed Web Server at ' . $_SERVER['SERVER_NAME'] . ' Port ' . $_SERVER['SERVER_PORT'] . '
' . $this->getEndBodyScripts() . "\n"; } protected function printOneEntry($base, $fileStat) { $url = $fileStat->getUrl($base); $buf = ''; if ($this->isFancy) { $buf .= IconMap::img($fileStat->getIconTag()) . $fileStat->dispName() . '' . $fileStat->dispTime() . '' . $fileStat->dispSize() . ''; } else { $buf .= $fileStat->dispName() . ''; } $buf .= "\n"; echo $buf; } protected function printContent() { echo '
'; if ($this->isFancy) { $this->printFancyTableHeader(); } else { $this->printTableHeader(); } $dirs = $files = []; $this->dir->populateList($dirs, $files); if ($this->uri != '/') { $fileStat = new FileStat('..', ''); $base = substr($this->uri, 0, strlen($this->uri) - 1); if (($off = strrpos($base, '/')) !== false) { $base = substr($base, 0, $off + 1); $this->printParentLine($base, $fileStat); } } foreach ($dirs as $fileStat) { $this->printOneEntry($this->uri, $fileStat); } foreach ($files as $fileStat) { $this->printOneEntry($this->uri, $fileStat); } echo "
\n"; } protected function ariaClass($type) { $name = $type . '_aria'; return isset($this->sort[$name]) ? ' aria-sort="' . $this->sort[$name] . '"' : ''; } protected function headerQs($type) { $name = $type . '_link'; return isset($this->sort[$name]) ? $this->sort[$name] : $type . 'A'; // default is ascending } protected function printFancyTableHeader() { $th = '' . $th . $this->ariaClass('name') . '>Name' . $th . $this->ariaClass('mod') . '>Last Modified' . $th . $this->ariaClass('size') . '>Size' . "\n"; } protected function printTableHeader() { echo '' . 'Name' . 'Last Modified' . 'Size' . "\n"; } protected function printParentLine($base, $fileStat) { $url = $fileStat->getUrl($base); if ($this->isFancy) { $buf = '' . IconMap::img('UP') . 'Parent Directory' . ''; } else { $buf = '' . $fileStat->dispName() . ''; } echo $buf . "\n"; } } class IndexWithJS extends Index { protected function getAssetLinks() { return '' . '' . ''; } protected function getEndBodyScripts() { if ($this->dir->getListCount() >= UserSettings::$FILTER_SHOW) { return << new Tablesort(document.getElementById("table-content")); var keywordInput = document.getElementById('filter-keyword'); document.addEventListener('keyup', filterTable); function filterTable(e) { if (e.target.id != 'filter-keyword') return; var cols = document.querySelectorAll('tbody td:first-child'); var keyword = keywordInput.value.toLowerCase(); for (i = 0; i < cols.length; i++) { var text = cols[i].textContent.toLowerCase(); if (text != 'parent directory') { cols[i].parentNode.style.display = text.indexOf(keyword) === -1 ? 'none' : 'table-row'; } } } EJS; } else { return ''; } } protected function printHeader() { parent::printHeader(); if ($this->dir->getListCount() >= UserSettings::$FILTER_SHOW) { echo '
' . "\n"; } } protected function printFancyTableHeader() { $onclick = ' onclick="return false"'; $sortnum = ' data-sort-method="number"'; $th = '' . $th . $this->ariaClass('name') . '>Name' . $th . $sortnum . $this->ariaClass('mod') . '>Last Modified' . $th . $sortnum . $this->ariaClass('size') . '>Size' . "\n"; } protected function printOneEntry($base, $fileStat) { $url = $fileStat->getUrl($base); $buf = ''; if ($this->isFancy) { $buf .= '' . IconMap::img($fileStat->getIconTag()) . $fileStat->dispName() . '' . $fileStat->dispTime() . '' . $fileStat->dispSize() . ''; } else { $buf .= '' . $fileStat->dispName() . ''; } $buf .= "\n"; echo $buf; } protected function printParentLine($base, $fileStat) { $url = $fileStat->getUrl($base); if ($this->isFancy) { $buf = '' . IconMap::img('UP') . 'Parent Directory' . ''; } else { $buf = 'Parent Directory'; } echo $buf . "\n"; } }