301 lines
11 KiB
PHP
301 lines
11 KiB
PHP
<?php
|
|
/* ================================================================
|
|
ARK - Alternative Registry Keeper, an HTML5/JS-enhanced PHP-based alternative to standard Apache Directory Indexing
|
|
Copyright © 2014 kts of kettek
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
================================================================ */
|
|
/* Okay, I admit it, this entire script is just an excuse to try PHP's heredoc syntax. */
|
|
// **** EXTERNAL BITS (you can touch 'em)
|
|
$root = dirname(__FILE__); // we assume root to be the location of this script
|
|
// A list of files to never index, relative to our ROOT
|
|
$blacklist = [
|
|
'/'.basename(__FILE__) // hide ourself
|
|
];
|
|
// Glob-style search pattern for our files in the requested directory
|
|
$glob = "*";
|
|
// Fields to render per file, anything supported by stat() can be defined here
|
|
$fields = [
|
|
"name" => "Filename",
|
|
"size" => "Size",
|
|
"mtime" => "Last Modified"
|
|
];
|
|
// date format for time related fields
|
|
$date = "Y-m-d H:i:s";
|
|
// Modify this for styling. I didn't want separate files, so ugliness abounds
|
|
$css = <<<CSS
|
|
body {
|
|
margin: 0; padding: 0 1em 0 1em;
|
|
font-family: sans-serif;
|
|
text-shadow: 0 2px 0px rgba(0, 0, 0, 0.05);
|
|
text-align: center;
|
|
}
|
|
small { color: #AAA; }
|
|
h1 {
|
|
margin: 0; padding: .25em;
|
|
color: #777;
|
|
-webkit-border-radius:3px 1px 1px 3px;
|
|
-moz-border-radius:3px 1px 1px 3px;
|
|
border-radius:3px 3px 0 0;
|
|
}
|
|
table {
|
|
width: 100%;
|
|
text-align: left;
|
|
-webkit-box-shadow:0 1px 4px rgba(0, 0, 0, 0.8), 0px 0px 6px rgba(0, 0, 0, 0.4) inset;
|
|
-moz-box-shadow:0 1px 4px rgba(0, 0, 0, 0.8), 0px 0px 6px rgba(0, 0, 0, 0.4) inset;
|
|
box-shadow:0 1px 4px rgba(0, 0, 0, 0.8), 0px 0px 6px rgba(0, 0, 0, 0.4) inset;
|
|
-webkit-border-radius:0px 0px 3px 3px;
|
|
-moz-border-radius:0px 0px 3px 3px;
|
|
border-radius:0px 0px 3px 3px;
|
|
}
|
|
td, th {
|
|
font-size: 14pt;
|
|
-webkit-border-radius:0px 0px 3px 0px;
|
|
-moz-border-radius:0px 0px 3px 0px;
|
|
border-radius:0px 0px 3px 0px;
|
|
}
|
|
td {
|
|
padding: 0.1em;
|
|
color: #444;
|
|
border-right: 1px solid #CCC; border-bottom: 1px solid #CCC;
|
|
}
|
|
th {
|
|
padding: 0.5em;
|
|
color: #AAA;
|
|
border-right: 1px solid #BBB; border-bottom: 1px solid #BBB;
|
|
}
|
|
a {
|
|
padding: .5em 0 .5em .5em;
|
|
display: inline-block;
|
|
width: 100%;
|
|
text-decoration: none;
|
|
color: #333;
|
|
transition: All 0.25s ease;
|
|
box-sizing: border-box;
|
|
}
|
|
a:visited { color: #888; }
|
|
td a:hover {
|
|
transform: scale(1.025) translate(8px);
|
|
-webkit-transform: scale(1.025) translate(8px);
|
|
-moz-transform: scale(1.025) translate(8px);
|
|
-o-transform: scale(1.025) translate(8px);
|
|
-ms-transform: scale(1.025) translate(8px);
|
|
}
|
|
.selected {
|
|
background-color: #EEE;
|
|
}
|
|
/* embedded icons */
|
|
a { background-repeat: no-repeat; background-position: .5em; padding-left: 44px; }
|
|
.parent { background-image:url("data:image/gif;base64,R0lGODlhFQARAKECALu7u8zMzP///////yH5BAEKAAIALAAAAAAVABEAAAImlI+py20RHINQJkrtwVULAECgt0VkeaKpwLXm4nINQNc2veb67hQAOw=="); }
|
|
.dir { background-image:url("data:image/gif;base64,R0lGODlhFQARAKECALu7u8zMzP///////yH5BAEKAAIALAAAAAAVABEAAAIglIIZxt0KnRSgWjsf3Lz7D4biSJbmiabq2gEpdcXyVQAAOw=="); }
|
|
.file { background-image:url("data:image/gif;base64,R0lGODlhFQARAKECALu7u8zMzP///////yH5BAEKAAIALAAAAAAVABEAAAIqlB2pCue+ImgPRjbruVcjvngCGHqkIp5JqrKnS8KgzNGd2eKvN/V+7ygAADs="); }
|
|
.link { background-image:url("data:image/gif;base64,R0lGODlhFQARAKECALu7u8zMzP///////yH5BAEKAAIALAAAAAAVABEAAAIulB8huXoPo4xu1gimybr7/yVMaATACTgXxF0O16UiDKI0iOfaCvHHzdLpGERfAQA7"); }
|
|
.load { background-image:url("data:image/gif;base64,R0lGODlhFQARAKECALu7u8zMzP///////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgACACwAAAAAFQARAAACLpSPqWvhzByYD54wD4VB4AVwn7U4HalMIcq27utVLyC72FmW4QrKEkuR8IAZRgEAIfkECQoAAwAsAAAAABUAEQAAAjKcj6lrwT9CAHBNWtPMajPpdJCEId4HhGZFqYfjWkoaa+UrjQvAPyfXuHFIwFevaBAiCgAh+QQJCgADACwCAAAAEQARAAACLJyPqRq928BjIEAlawry6WNNQzh200eKYTqFX2uyDPJSiSabtCOCfD8ACEUFACH5BAkKAAMALAAAAAAVABEAAAIvnI+pGhPf1nqggicTPDXmYSXX942k5J1iqnJMq5jwwcLyDLIbeXm92oHceCGAogAAOw=="); }
|
|
CSS;
|
|
$js = <<<JS
|
|
var sel = 0;
|
|
window.onload = function() {
|
|
if (window.history.replaceState === undefined) return;
|
|
window.history.replaceState({content: document.getElementsByTagName("body")[0].innerHTML, url: window.location.href, title: document.title}, document.title, window.location.href);
|
|
parseContent();
|
|
window.onpopstate = popState;
|
|
};
|
|
var bind = [
|
|
{key: [38, 75], func: function() { // up
|
|
var ele = document.getElementsByTagName("tr");
|
|
if (sel > 1) {
|
|
ele[sel].className = "";
|
|
ele[--sel].className = "selected";
|
|
}
|
|
}},
|
|
{key: [40, 74], func: function() { // down
|
|
var ele = document.getElementsByTagName("tr");
|
|
if (sel < ele.length-1) {
|
|
ele[sel].className = "";
|
|
ele[++sel].className = "selected";
|
|
}
|
|
}},
|
|
{key: [37, 72], func: function() { // left
|
|
var ele = document.getElementsByTagName("tr");
|
|
if (ele.length > 1 && ele[1].getElementsByTagName("A")[0].innerHTML == "..") ele[1].getElementsByTagName("A")[0].onclick();
|
|
}},
|
|
{key: [39, 76], func: function() { // right
|
|
if (sel > 0) document.getElementsByTagName("TR")[sel].getElementsByTagName("A")[0].onclick();
|
|
}}
|
|
];
|
|
document.onkeydown = function(event) {
|
|
if (event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) return;
|
|
for (var i = 0; i < bind.length; i++) {
|
|
for (var k = 0; k < bind[i].key.length; k++) {
|
|
if (event.which == bind[i].key[k]) bind[i].func();
|
|
}
|
|
}
|
|
};
|
|
var parseContent = function() {
|
|
var l = document.getElementsByTagName('A');
|
|
var i;
|
|
for (i = 0; i < l.length; i++) {
|
|
l[i].onclick = function() { if (this.className == "dir" || this.className == "parent") { this.className = "load"; swapContent(window.location.href+this.getAttribute('href')); return false; } else { window.open(this.href); return false; }};
|
|
}
|
|
};
|
|
var swapContent = function(target) {
|
|
var req = new XMLHttpRequest();
|
|
req.open("POST", target, true);
|
|
req.onreadystatechange = (function(req, target) { return function() {
|
|
switch (req.readyState) {
|
|
case 4:
|
|
document.getElementsByTagName("body")[0].innerHTML = req.responseText;
|
|
window.history.pushState({content: req.responseText, url: target, title: document.getElementsByTagName("H1")[0].innerHTML}, target, target);
|
|
document.title = document.getElementsByTagName("H1")[0].innerHTML;
|
|
parseContent();
|
|
if (sel != 0) { sel = 0; bind[1].func(); }
|
|
break;
|
|
}
|
|
}})(req, target);
|
|
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
|
req.send("p=true");
|
|
};
|
|
var popState = function(event) {
|
|
if (event.state == null) return;
|
|
document.getElementsByTagName("body")[0].innerHTML = event.state.content;
|
|
document.title = event.state.title;
|
|
parseContent();
|
|
};
|
|
JS;
|
|
// **** INTERNAL BITS
|
|
$VERSION = "ARK 1.0";
|
|
// ---- INIT LOGIC
|
|
$mode = 200; // Default mode of serve file
|
|
$index = []; // array of files to index
|
|
$dindex = []; // array of directories to index
|
|
// inherit $dir from $_GET["d"] or assign to "/"
|
|
$dir = (($_GET["d"]) ? '/'.$_GET["d"] : "/");
|
|
// Probably pointless file path sanitization.
|
|
$path = realpath($root."/".$dir)."/";
|
|
// Due to realpath, the following should resolve only if the requested $dir does not go above the path of this script.
|
|
if (($root_pos = strpos($path, $root)) === FALSE) {
|
|
$mode = 403;
|
|
} else if (!file_exists($path)) {
|
|
$mode = 404;
|
|
} else {
|
|
// seems we're good, let's populate $index and $dindex, then combine them
|
|
if (is_dir($path)) {
|
|
// It seems wiser to generate two separate arrays and combine rather than to use unshift and introduce reindexing each pass.
|
|
foreach (glob($path.$glob) as $file) {
|
|
if (in_array($dir.basename($file), $blacklist)) continue;
|
|
$type = filetype($file);
|
|
if ($type == "dir") {
|
|
$dindex[$file] = stat($file);
|
|
$dindex[$file]["type"] = $type;
|
|
$dindex[$file]["name"] = basename($file)."/";
|
|
} else {
|
|
$index[$file] = stat($file);
|
|
$index[$file]["type"] = $type;
|
|
$index[$file]["name"] = basename($file);
|
|
$index[$file]["ext"] = pathinfo($file, PATHINFO_EXTENSION);
|
|
}
|
|
}
|
|
$index = $dindex + $index;
|
|
}
|
|
}
|
|
// **** RENDERING
|
|
// ---- BEGIN HEAD
|
|
if (!isset($_POST['p'])) {
|
|
echo <<<HEAD
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
|
|
<title>Index of $dir</title>
|
|
<style type="text/css">\n$css\n</style>
|
|
<script type="text/javascript">\n$js\n</script>
|
|
</head>
|
|
<body>
|
|
HEAD;
|
|
}
|
|
// ---- BEGIN BODY CONTENT
|
|
switch($mode) {
|
|
case 200:
|
|
if (is_dir($path)) { // serve index
|
|
echo "<h1>Index of $dir</h1>\n";
|
|
echo "<table>\n";
|
|
echo "<tr>";
|
|
foreach ($fields as $fkey=>$fvalue) {
|
|
echo "<th>".$fvalue."</th>";
|
|
}
|
|
echo "</tr>\n";
|
|
if ($dir != "/") {
|
|
echo "<tr>";
|
|
foreach ($fields as $fkey=>$fvalue) {
|
|
if ($fkey == "name") {
|
|
echo "<td>";
|
|
echo "<a class=\"parent\" href=\"../\">";
|
|
echo "..</a>";
|
|
echo "</td>";
|
|
} else {
|
|
echo "<td></td>";
|
|
}
|
|
}
|
|
echo "</tr>\n";
|
|
}
|
|
foreach ($index as $key=>$value) {
|
|
echo "<tr>";
|
|
foreach ($fields as $fkey=>$fvalue) {
|
|
echo "<td>";
|
|
if ($fkey == "name") {
|
|
echo "<a class=\"".$value['type']."\" href=\"".$value[$fkey]."\">";
|
|
echo $value[$fkey]."</a>";
|
|
} else if (strstr($fkey, "time")) {
|
|
echo date($date, $value[$fkey]);
|
|
} else if ($fkey == "size") {
|
|
if ($value[$fkey] >= 1 << 30) {
|
|
echo round($value[$fkey] / (1<<30), 2).'GB';
|
|
} else if ($value[$fkey] >= 1 << 20) {
|
|
echo round($value[$fkey] / (1<<20), 2).'MB';
|
|
} else if ($value[$fkey] >= 1 << 10) {
|
|
echo round($value[$fkey] / (1<<10), 2).'KB';
|
|
} else {
|
|
echo $value[$fkey].' bytes';
|
|
}
|
|
} else {
|
|
echo $value[$fkey];
|
|
}
|
|
echo "</td>";
|
|
}
|
|
echo "</tr>\n";
|
|
}
|
|
echo "</table>\n";
|
|
} else { // serve file
|
|
|
|
}
|
|
break;
|
|
case 403:
|
|
echo '<h1>403: Access Denied</h1>';
|
|
echo "<a href=\"".$_SERVER['HTTP_REFERER']."\">Return to previous page</a>\n";
|
|
break;
|
|
case 404:
|
|
echo '<h1>404: No such file or directory</h1>';
|
|
echo "<a href=\"".$_SERVER['HTTP_REFERER']."\">Return to previous page</a>\n";
|
|
break;
|
|
}
|
|
echo "<a href=\"http://kettek.exoss.net/ark/\" title=\"Alternative Registry Keeper\"><small>Generated by $VERSION</small></a>";
|
|
// ---- BEGIN FOOT
|
|
if (!isset($_POST['p'])) {
|
|
echo <<<FOOT
|
|
</body>
|
|
</html>
|
|
FOOT;
|
|
}
|
|
?>
|