ARK/index.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;
}
?>