616 lines
17 KiB
PHP
616 lines
17 KiB
PHP
<?php
|
|
ini_set('display_errors', 'on');
|
|
error_reporting(-1);
|
|
|
|
define('FL_TITLE', 'Abyss');
|
|
define('FL_ROOT_NAME', 'abyss');
|
|
|
|
define('FL_FILE_DATE_FORMAT', 'Y-m-d H:i:s P');
|
|
define('FL_ALLOW_PATH_INFO', true);
|
|
define('FL_HIDDEN_PATH', [
|
|
'robots.txt',
|
|
'index.php',
|
|
'favicon.ico',
|
|
]);
|
|
|
|
define('FL_ROOT', __DIR__);
|
|
define('FL_DIR_SEP', DIRECTORY_SEPARATOR);
|
|
|
|
define('FL_ICON_ICO', "MSIcon\0");
|
|
define('FL_ICON_ICD', 'image');
|
|
define('FL_ICON_ICE', [
|
|
'ico',
|
|
'image/x-icon',
|
|
'image/vnd.microsoft.icon',
|
|
]);
|
|
define('FL_ICON_EXE', '/?_flst_pe_ico=%s');
|
|
define('FL_ICON_EXP', "Win32\0");
|
|
define('FL_ICON_EXD', 'application_xp');
|
|
define('FL_ICON_EXW', [
|
|
'exe',
|
|
'msi',
|
|
'application/x-dosexec',
|
|
'application/x-msi',
|
|
]);
|
|
define('FL_ICON_FMT', '//static.flash.moe/images/silk/%s.png');
|
|
define('FL_ICON_DIR', 'folder');
|
|
define('FL_ICON_DEF', 'page_white');
|
|
define('FL_ICON_ARC', 'page_white_zip');
|
|
define('FL_ICON_MAP', [
|
|
'css' => 'page_white_code',
|
|
'js' => 'script_code',
|
|
'sql' => 'page_white_database',
|
|
'ftm' => 'music',
|
|
|
|
'htm' => 'page_white_code',
|
|
'html' => 'page_white_code',
|
|
'text/html' => 'page_white_code',
|
|
|
|
'xml' => 'page_white_code',
|
|
'text/xml' => 'page_white_code',
|
|
|
|
'png' => 'image',
|
|
'jpg' => 'image',
|
|
'jpeg' => 'image',
|
|
'gif' => 'image',
|
|
'ico' => 'image',
|
|
'bmp' => 'image',
|
|
'image/png' => 'image',
|
|
'image/jpeg' => 'image',
|
|
'image/gif' => 'image',
|
|
'image/x-icon' => 'image',
|
|
'image/vnd.microsoft.icon' => 'image',
|
|
|
|
'ttf' => 'font',
|
|
'application/font-sfnt' => 'font',
|
|
|
|
'php' => 'page_white_php',
|
|
'text/x-php' => 'page_white_php',
|
|
|
|
'zip' => 'page_white_zip',
|
|
'7z' => 'page_white_zip',
|
|
'rar' => 'page_white_zip',
|
|
'xz' => 'page_white_zip',
|
|
'application/x-7z-compressed' => 'page_white_zip',
|
|
'application/x-xz' => 'page_white_zip',
|
|
'application/x-rar' => 'page_white_zip',
|
|
'application/zip' => 'page_white_zip',
|
|
|
|
'txt' => 'page_white_text',
|
|
'text/plain' => 'page_white_text',
|
|
|
|
'msi' => 'application_xp',
|
|
'exe' => 'application_xp',
|
|
'application/x-dosexec' => 'application_xp',
|
|
'application/x-msi' => 'application_xp',
|
|
|
|
'cab' => 'box',
|
|
'themepack' => 'box',
|
|
'application/vnd.ms-cab-compressed' => 'box',
|
|
'application/x-windows-themepack' => 'box',
|
|
|
|
'iso' => 'page_white_cd',
|
|
'application/x-iso9660-image' => 'page_white_cd',
|
|
|
|
'mkv' => 'film',
|
|
'mp4' => 'film',
|
|
'm4v' => 'film',
|
|
'video/mp4' => 'film',
|
|
'rm' => 'film',
|
|
|
|
'pdf' => 'page_white_acrobat',
|
|
'application/pdf' => 'page_white_acrobat',
|
|
|
|
'swf' => 'page_white_flash',
|
|
|
|
'ogg' => 'music',
|
|
'audio/ogg' => 'music',
|
|
'mid' => 'music',
|
|
]);
|
|
|
|
function flGetIcon(string $path, bool $skipSpecial = false): string {
|
|
if(is_dir($path)) {
|
|
if($path !== '..') {
|
|
$icoPath = $path . '/_dir.ico';
|
|
if(is_file($icoPath))
|
|
return FL_ICON_ICO . $icoPath;
|
|
|
|
$icoPath = $path . '/_icon.txt';
|
|
if(is_file($icoPath)) {
|
|
$icoName = trim(file_get_contents($icoPath));
|
|
if($icoName !== '')
|
|
return $icoName;
|
|
}
|
|
}
|
|
|
|
return FL_ICON_DIR;
|
|
}
|
|
|
|
$mime = @mime_content_type($path);
|
|
|
|
if(!$skipSpecial) {
|
|
if(in_array($mime, FL_ICON_ICE))
|
|
return FL_ICON_ICO . $path;
|
|
|
|
if(in_array($mime, FL_ICON_EXW))
|
|
return FL_ICON_EXP . $path;
|
|
}
|
|
|
|
$name = array_key_exists($mime, FL_ICON_MAP) ? FL_ICON_MAP[$mime] : '';
|
|
if($name !== '')
|
|
return $name;
|
|
|
|
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
|
if($ext === '')
|
|
return FL_ICON_DEF;
|
|
|
|
if(!$skipSpecial) {
|
|
if(in_array($ext, FL_ICON_ICE))
|
|
return FL_ICON_ICO . $path;
|
|
|
|
if(in_array($ext, FL_ICON_EXW))
|
|
return FL_ICON_EXP . $path;
|
|
}
|
|
|
|
// for split archives
|
|
if(ctype_digit($ext))
|
|
return FL_ICON_ARC;
|
|
|
|
if(array_key_exists($ext, FL_ICON_MAP))
|
|
return FL_ICON_MAP[$ext];
|
|
|
|
return FL_ICON_DEF;
|
|
}
|
|
|
|
function flGetIconPath(string $name): string {
|
|
if(str_starts_with($name, FL_ICON_ICO))
|
|
return '/' . substr($name, strlen(FL_ICON_ICO));
|
|
|
|
if(str_starts_with($name, FL_ICON_EXP))
|
|
return sprintf(FL_ICON_EXE, substr($name, strlen(FL_ICON_EXP)));
|
|
|
|
return sprintf(FL_ICON_FMT, $name);
|
|
}
|
|
|
|
function flGetIconAlt(string $name): string {
|
|
if(str_starts_with($name, FL_ICON_ICO))
|
|
return FL_ICON_ICD;
|
|
|
|
if(str_starts_with($name, FL_ICON_EXP))
|
|
return FL_ICON_EXD;
|
|
|
|
return $name;
|
|
}
|
|
|
|
function flPathClean(string $path): string {
|
|
return trim(parse_url(rawurldecode($path), PHP_URL_PATH), FL_DIR_SEP);
|
|
}
|
|
|
|
function flPathExplode(string $path): array {
|
|
return explode(FL_DIR_SEP, $path);
|
|
}
|
|
|
|
function flPathImplode(array $parts): string {
|
|
return implode(FL_DIR_SEP, $parts);
|
|
}
|
|
|
|
function flPathResolveAbsolute(array $parts): array {
|
|
$absParts = [];
|
|
|
|
foreach($parts as $part) {
|
|
if($part === '.')
|
|
continue;
|
|
else if($part === '..')
|
|
array_pop($absParts);
|
|
else
|
|
$absParts[] = $part;
|
|
}
|
|
|
|
return $absParts;
|
|
}
|
|
|
|
function flPathEnsureAbsolute(array $parts): bool {
|
|
foreach($parts as $part)
|
|
if($part === '.' || $part === '..')
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
if(isset($_GET['_flst_pe_ico']) && is_string($_GET['_flst_pe_ico'])) {
|
|
$flExePathParts = flPathExplode(flPathClean($_GET['_flst_pe_ico']));
|
|
if(!flPathEnsureAbsolute($flExePathParts)) {
|
|
http_response_code(404);
|
|
exit;
|
|
}
|
|
|
|
$flExePath = FL_ROOT . FL_DIR_SEP . flPathImplode($flExePathParts);
|
|
if(!is_file($flExePath)) {
|
|
http_response_code(404);
|
|
exit;
|
|
}
|
|
|
|
$output = shell_exec('wrestool -x -t14 ' . escapeshellarg($flExePath));
|
|
if(empty($output)) {
|
|
$iconName = FL_ICON_MAP[pathinfo($flExePath, PATHINFO_EXTENSION)] ?? FL_ICON_DEF;
|
|
header('Location: ' . sprintf(FL_ICON_FMT, $iconName));
|
|
exit;
|
|
}
|
|
|
|
header('Content-Type: image/x-icon');
|
|
echo $output;
|
|
exit;
|
|
}
|
|
|
|
if(isset($_GET['_flst_swf']) && is_string($_GET['_flst_swf'])) {
|
|
$swfPath = htmlspecialchars(trim($_GET['_flst_swf'], '/'));
|
|
$baseName = basename($swfPath);
|
|
echo <<<HTML
|
|
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<title>{$baseName}</title>
|
|
<style type="text/css">
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
position: relative;
|
|
outline-style: none;
|
|
}
|
|
html, body {
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="container"></div>
|
|
<script src="//static.flash.moe/lib/ruffle/2023-06-08/ruffle.js"></script>
|
|
<script>
|
|
window.RufflePlayer = window.RufflePlayer || {};
|
|
|
|
window.addEventListener('DOMContentLoaded', function() {
|
|
var ruffle = window.RufflePlayer.newest();
|
|
var player = ruffle.createPlayer();
|
|
document.body.appendChild(player);
|
|
player.load('{$swfPath}');
|
|
player.style.width = '100%';
|
|
player.style.height = '100%';
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTML;
|
|
return;
|
|
}
|
|
|
|
if(isset($_GET['_flst_asset']) && is_string($_GET['_flst_asset'])) {
|
|
$flAssetName = $_GET['_flst_asset'];
|
|
unset($flAssetBody, $flAssetType);
|
|
|
|
if($flAssetName === 'style.css') {
|
|
$flAssetType = 'text/css; charset=utf-8';
|
|
$flAssetBody = <<<EOF
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
position: relative;
|
|
outline-style: none !important;
|
|
}
|
|
html, body {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
body {
|
|
font: 12px/20px Tahoma, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif;
|
|
background-color: #111;
|
|
color: #fff;
|
|
}
|
|
.clear {
|
|
clear: both;
|
|
}
|
|
h1, h2 {
|
|
line-height: 1.5em;
|
|
}
|
|
h2 a {
|
|
color: #fff;
|
|
text-decoration: none;
|
|
}
|
|
h2 a:hover,
|
|
h2 a:focus {
|
|
text-decoration: underline;
|
|
}
|
|
.header {
|
|
margin: 0 auto;
|
|
max-width: 1000px;
|
|
padding: 10px 0;
|
|
}
|
|
.footer {
|
|
font-size: .9em;
|
|
margin: 5px auto;
|
|
text-align: center;
|
|
opacity: .4;
|
|
}
|
|
.listing {
|
|
margin: 0 auto;
|
|
max-width: 1000px;
|
|
background-color: #222;
|
|
}
|
|
.entry {
|
|
display: block;
|
|
text-decoration: none;
|
|
color: inherit;
|
|
margin: 2px;
|
|
border-width: 0;
|
|
background-color: #222;
|
|
transition: background-color .2s, box-shadow .2s;
|
|
}
|
|
div.entry {
|
|
border-bottom: 1px solid #444;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1000;
|
|
}
|
|
a.entry:hover,
|
|
a.entry:focus {
|
|
background-color: #444;
|
|
box-shadow: 0 0 10px #222;
|
|
}
|
|
a.entry:active {
|
|
background-color: #333;
|
|
box-shadow: 0 0 10px #222;
|
|
}
|
|
.entry-icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
line-height: 20px;
|
|
text-align: center;
|
|
margin: 3px;
|
|
float: left;
|
|
}
|
|
.entry-icon img {
|
|
vertical-align: middle;
|
|
border: 0;
|
|
}
|
|
.entry-name,
|
|
.entry-size,
|
|
.entry-mod {
|
|
padding: 5px;
|
|
}
|
|
.entry-name {
|
|
float: left;
|
|
min-width: 300px;
|
|
}
|
|
.entry-size {
|
|
float: right;
|
|
width: 100px;
|
|
text-align: right;
|
|
}
|
|
.entry-mod {
|
|
float: right;
|
|
max-width: 250px;
|
|
width: 100%;
|
|
text-align: center;
|
|
}
|
|
div > .entry-mod,
|
|
div > .entry-size {
|
|
text-align: left;
|
|
}
|
|
@media (max-width: 700px) {
|
|
.entry-mod,
|
|
.entry-size {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
EOF;
|
|
}
|
|
|
|
|
|
if(isset($flAssetBody)) {
|
|
// todo: cache control
|
|
http_response_code(200);
|
|
|
|
header('Content-Disposition: inline; filename="' . $flAssetName . '"');
|
|
if(isset($flAssetType)) {
|
|
header('Content-Type: ' . $flAssetType);
|
|
}
|
|
|
|
echo $flAssetBody;
|
|
} else {
|
|
http_response_code(404);
|
|
}
|
|
exit;
|
|
}
|
|
|
|
$flRoot = FL_ROOT . FL_DIR_SEP;
|
|
$flForceBrowse = FL_ALLOW_PATH_INFO && !empty($_GET['browse']);
|
|
$flServerKey = FL_ALLOW_PATH_INFO && substr($_SERVER['REQUEST_URI'], 0, strlen($_SERVER['SCRIPT_NAME'])) === $_SERVER['SCRIPT_NAME'] ? 'PATH_INFO' : 'REQUEST_URI';
|
|
$flLocationNeedsPrefix = $flForceBrowse || $flServerKey === 'PATH_INFO';
|
|
$flLocationPrefix = $flLocationNeedsPrefix ? $_SERVER['SCRIPT_NAME'] : '';
|
|
|
|
$flOriginalLocation = flPathClean(
|
|
isset($_GET['path']) && is_string($_GET['path'])
|
|
? $_GET['path']
|
|
: $_SERVER[$flServerKey]
|
|
);
|
|
|
|
$flLocationParts = flPathExplode($flOriginalLocation);
|
|
$flLocationAbsolute = flPathImplode(flPathResolveAbsolute($flLocationParts));
|
|
$flLocation = flPathImplode($flLocationParts);
|
|
|
|
if($flLocation !== $flLocationAbsolute) {
|
|
header(sprintf('Location: %s%s%s', $flLocationPrefix, FL_DIR_SEP, $flLocationAbsolute));
|
|
exit;
|
|
}
|
|
|
|
$flLocationParent = dirname($flLocation);
|
|
$flFullLocation = rtrim($flRoot . $flLocation, '/');
|
|
$flHeaderFile = $flFullLocation . '/_header.html';
|
|
|
|
if(!is_dir($flFullLocation)) {
|
|
http_response_code(404);
|
|
echo 'Not Found';
|
|
exit;
|
|
}
|
|
|
|
$flDirs = [];
|
|
$flFiles = [];
|
|
|
|
foreach(glob($flFullLocation . '/*') as $entry) {
|
|
$entryRelative = substr($entry, strlen($flRoot));
|
|
if(in_array($entryRelative, FL_HIDDEN_PATH))
|
|
continue;
|
|
|
|
$isDir = is_dir($entry);
|
|
if($isDir && is_file($entryRelative . '/_hidden.txt'))
|
|
continue;
|
|
|
|
$info = [
|
|
'type' => $isDir ? 'dir' : (is_file($entry) ? 'fil' : 'unk'),
|
|
'path' => $entryRelative,
|
|
'path_full' => $entry,
|
|
'name' => basename($entry),
|
|
];
|
|
|
|
if($info['type'] === 'fil') {
|
|
$info['size'] = filesize($entry);
|
|
$info['ext'] = strtolower(pathinfo($entry, PATHINFO_EXTENSION));
|
|
$info['mod'] = filemtime($entry);
|
|
|
|
if($info['ext'] === 'swf')
|
|
$info['path'] = '?_flst_swf=' . rawurlencode($info['path']);
|
|
}
|
|
|
|
if($info['type'] === 'dir') {
|
|
$info['index'] = !(is_file($entry . '/index.php') || is_file($entry . '/index.html'));
|
|
$flDirs[] = $info;
|
|
} else
|
|
$flFiles[] = $info;
|
|
}
|
|
|
|
function sort_insensitive($a, $b) { return strcmp(strtolower($a['path']), strtolower($b['path'])); }
|
|
usort($flDirs, 'sort_insensitive');
|
|
usort($flFiles, 'sort_insensitive');
|
|
|
|
$flEntries = array_merge($flDirs, $flFiles);
|
|
|
|
function flByteSymbol(int $bytes, bool $decimal = true, array $symbols = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']): string {
|
|
if($bytes < 1)
|
|
return 'Zero Bytes';
|
|
|
|
$divider = $decimal ? 1000 : 1024;
|
|
$exp = floor(log($bytes) / log($divider));
|
|
$bytes = $bytes / pow($divider, floor($exp));
|
|
$symbol = $symbols[$exp];
|
|
|
|
return sprintf("%.2f %s%sB", $bytes, $symbol, $symbol !== '' && !$decimal ? 'i' : '');
|
|
}
|
|
|
|
function flBuildListingEntry(array $columns, string $linkTarget = ''): string {
|
|
$eEntries = [];
|
|
|
|
foreach($columns as $name => $body)
|
|
$eEntries[] = sprintf('<div class="entry-%s">%s</div>', $name, $body);
|
|
|
|
return sprintf(
|
|
"<%1\$s class=\"entry\"%2\$s>\r\n%3\$s\r\n<div class=\"clear\"></div>\r\n</%1\$s>",
|
|
$linkTarget === '' ? 'div' : 'a',
|
|
$linkTarget === '' ? '' : sprintf(' href="%s"', $linkTarget),
|
|
implode("\r\n", $eEntries)
|
|
);
|
|
}
|
|
|
|
function flBuildListingFileEntry($item): string {
|
|
return '';
|
|
}
|
|
|
|
function flBuildListing(array $columns, array $items): string {
|
|
// columns -> [name, title, itemKey, itemFormat]
|
|
|
|
$header = [];
|
|
foreach($columns as $column)
|
|
$header[$column['name']] = $column['title'];
|
|
$lEntries = [flBuildListingEntry($header)];
|
|
|
|
foreach($items as $item)
|
|
$lEntries[] = flBuildListingFileEntry($item);
|
|
|
|
return sprintf("<div class=\"listing\">\r\n%s\r\n</div>", implode("\r\n", $lEntries));
|
|
}
|
|
?>
|
|
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<title><?=(empty($flLocation) ? FL_TITLE : ('/' . $flLocation . ' - ' . FL_TITLE));?></title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
|
<link href="/?_flst_asset=style.css" type="text/css" rel="stylesheet"/>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<?php if(!empty($flLocation)): ?>
|
|
<h2>[<a href="/"><?=FL_ROOT_NAME;?></a>] <?php $flLocationSoFar = ''; foreach($flLocationParts as $flLocationPart) { $flLocationSoFar .= '/' . $flLocationPart; echo '/<a href="' . $flLocationSoFar . '">' . $flLocationPart . '</a>'; } ?></h2>
|
|
<?php endif;
|
|
|
|
if(is_file($flHeaderFile))
|
|
echo file_get_contents($flHeaderFile);
|
|
?>
|
|
</div>
|
|
|
|
<div class="listing">
|
|
<div class="entry">
|
|
<div class="entry-icon"></div>
|
|
<div class="entry-name">Name</div>
|
|
<div class="entry-size">Size</div>
|
|
<div class="entry-mod">Last modified</div>
|
|
<div class="clear"></div>
|
|
</div>
|
|
<?php
|
|
if(!empty($flLocation)):
|
|
$iconName = flGetIcon('..');
|
|
?>
|
|
<a class="entry" href="<?='/' . trim($flLocationPrefix . '/' . ($flLocationParent === '.' ? '' : $flLocationParent), '/');?>">
|
|
<div class="entry-icon"><img src="<?=flGetIconPath($iconName);?>" alt="<?=flGetIconAlt($iconName);?>"/></div>
|
|
<div class="entry-name">../</div>
|
|
<div class="entry-size"></div>
|
|
<div class="entry-type"></div>
|
|
<div class="clear"></div>
|
|
</a>
|
|
<?php
|
|
endif;
|
|
foreach($flEntries as $entry):
|
|
$isDir = $entry['type'] === 'dir';
|
|
$isFile = $entry['type'] === 'fil';
|
|
|
|
if(!$isDir && !$isFile)
|
|
continue;
|
|
|
|
$name = $entry['name'] . ($isDir ? '/' : '');
|
|
if(str_starts_with($name, '_'))
|
|
continue;
|
|
|
|
$iconName = flGetIcon($entry['path']);
|
|
$href = ($isDir && ($flForceBrowse || ($flLocationNeedsPrefix && $entry['index'])) ? $flLocationPrefix : '') . '/' . $entry['path'];
|
|
?>
|
|
<a class="entry" href="<?=$href;?>">
|
|
<div class="entry-icon">
|
|
<img src="<?=flGetIconPath($iconName);?>" alt="<?=flGetIconAlt($iconName);?>" width="16" height="16"/>
|
|
</div>
|
|
<div class="entry-name"><?=$name;?></div>
|
|
<div class="entry-size"><?php if($isFile) echo flByteSymbol($entry['size']); ?></div>
|
|
<div class="entry-mod"><?php if($isFile) echo date(FL_FILE_DATE_FORMAT, $entry['mod']); ?></div>
|
|
<div class="clear"></div>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
flash.moe 2014-<?=date('Y');?>
|
|
</div>
|
|
</body>
|
|
</html>
|