<?php
/*
Bontiv-Sourceer 1.0, 30 Jan 2012
GPL v3 license
Forked from Sourceer (http://bioinformatics.org/phplabware/internal_utilities/)
A PHP Labware internal utility - www.bioinformatics.org/phplabware/internal_utilities
*/
/*
PHP file/directory utility software/script; a directory/file lister/browser with source code (highlighted) viewer. Among other things, useful for presenting source code of software projects (a much simple alternative to Trac, PHPDoc, Doxygen, SVN/CVS systems, etc.) Uses code of DirPHP v1.0 by Stuart Montgomery.
Put sourceer.php (can be renamed) in appropriate directory. Set PARAMETERS at top. sourceer.php can be included in another script; the root directory then will be the directory of that script. Delete/comment out the PARAMETERS code above MAIN CALL code if using that code in the parent file.
*/
error_reporting(E_ALL | (defined('E_STRICT') ? E_STRICT : 0));
ini_set('display_errors', 0); // 1 to debug
// PARAMETERS - the 4 arrays can be empty; one or more $cfg elements may not be present
$sec_files = array(); // files not to be shown - put in filepaths (no trailing slashes, /) relative to $cfg 'root' ; depending on other parameters, may not be accessible as well; e.g., "array('./a.php', '../b.php')". PHP PCRE-compatible regular expressions specified by putting in an array like "array('!\.htaccess$!i', '!\.ini$!i')"
$sec_dirs = array(); // as $sec_files but for directories; no trailing slashes (/)
$src_filetypes = array(''=>'txt', 'css'=>'css', 'htaccess'=>'txt', 'htm'=>'html', 'html'=>'html',
'info'=>'txt', 'inc'=>'txt', 'js'=>'js', 'php'=>'php', 'txt'=>'txt', 'xml'=>'xml', 'c' => 'c',
'h' => 'c', 'cc' => 'c', 'hh' => 'h', 'am' => 'txt', 'in' => 'txt', 'ac' => 'txt', 'pl' => 'pl',
'xml' => 'xml', 'py' => 'py', 'cpp' => 'c'); // source code viewable for these types - lower-case extensions and equivalent filetype as key-value pairs; the empty extension '' is for file-names without extensions;
$cfg = array(
// title for the web-pages
'title' => 'Bontiv-Sourceer source code viewer',
// root directory for browsing. Use '.' if same as sourceer.php (or the parent script when sourceer.php is included), or './..' for the directory above it, and so on.
'root' => './src_dir',
// 1 to allow move to higher level than root
'up_root' => 0,
// if 1, a password or correct IP address needed
'auth' => 0,
// MD5 hash of password prefixed with 'sourceer' [the code here generates the hash for the default password 'pass' (change 'pass' to something else; for more security, create MD5 hash of your new password prefixed with 'sourceer' and enter that hash - e.g., 'fgdjhg467sfhj87654gsg')]. A password is needed if 'auth' is 1 above and IP address is not in 'ok_ips' below
'hash' => md5('sourceer'. 'pass'),
// function to use for highlighting. If 0, PHP's highlighting function, which really works only for PHP file-types, will be used. If set to a string, will call the named function -- the raw source code, the file-type, the file-path and the config charset will be provided as arguments. The function should return an array with these four non-keyed values: the formatted code, CSS declarations, JS code, and code for foot that will be added to the HTML.
'hiliter' => 0,
// allowed IP addresses; like "array('129.0.0.1', '129.0.0.2')"
'ok_ips' => array(),
// CSS and Javascript declarations and HTML to add before and after the output. E.g., '<div id="xx">' or '<body>' for 'head', and '' or '</table>' for foot. To have a short and context-specific title auto-inserted by Sourceer, use '_Sourceer_dynamic_title_' in the text. Set to 0 or remove to let Sourceer create them. If set as an array, like "array('<p>Home</p>')", will get appended to default head or CSS or JS (or prepended to foot) created by Sourceer. If not array but string, will replace.
'css' => 0,
'js' => 0,
'head' => 0,
'foot' => 0,
// ** Unlikely to change anything below
// charset
'charset' => 'utf-8', // best if same as filesystem's
// compress output
'compress' => 1,
// cookie name to use for auto-login (1 h validity) when using password
'cookie' => 'sourceer',
// date format; PHP's date() function-compatible
'date_type' => 'm/d/y',
// if $src_filetypes file-types downloadable
'dl' => 0,
// to indicate file-sizes & mod. times
'file_info' => 1,
// language - RFC3066 specified-values such as 'en' for English
'lang' => 'en',
// string to append to URLs. E.g., if sourceer.php is used at URL 'domain.com/wiki.php?page=home', you may want to set it to 'page=home'
'query_plus' => '',
// if $sec_dirs can be looked into
'sec_dir_into' => 0,
// show $sec_dirs in directory content lists
'sec_dir_list' => 0,
// if $sec_files downloadable
'sec_file_dl' => 0,
// show $sec_files in directory content lists
'sec_file_list' => 0,
// show source of $sec_files
'sec_file_src' => 0,
// turn off checking files/dirs for being secured
'sec_check_off' => 0,
// show source code of $src_filetypes file-types
'src' => 1,
// script execution time limit, in seconds
'timeout' => 300,
// base URL (URL to sourceer.php, or to the parent script using it); code here figures it out automatically, but you can change it to, e.g., "http://domain.com/source/sourceer.php", "sourceer.php", "domain.com/wiki.php?page=home", etc.
'base_url' => (!empty($_SERVER['PHP_SELF']) ? htmlspecialchars($_SERVER['PHP_SELF'], ENT_COMPAT) : preg_replace('`(\?.*)?$`','',$_SERVER['REQUEST_URI'])),
); // ends PARAMETERS
// MAIN CALL
$sourceer = new Sourceer($sec_dirs, $sec_files, $src_filetypes, $cfg); // the 4 parameters may not even be specified ("sourceer();")
$sourceer->work(); // ends MAIN CALL
// CLASS DEFINITION
class Sourceer{
var $cfg;
var $foot;
var $list_total;
var $neck;
var $out;
var $sec_dirs;
var $sec_files;
var $self;
var $src_filetypes;
function Sourceer($sec_dirs = array(), $sec_files = array(), $src_filetypes = array(), $cfg = array()){ // constructor function
// defaults
$cfg = is_array($cfg) ? $cfg : array();
$cfg['auth'] = (isset($cfg['auth']) and $cfg['auth'] == 1) ? 1 : 0;
$cfg['charset'] = isset($cfg['charset']) ? $cfg['charset'] : 'utf-8';
$cfg['base_url'] = isset($cfg['base_url']) ? $cfg['base_url'] : (!empty($_SERVER['PHP_SELF']) ? htmlspecialchars($_SERVER['PHP_SELF'], ENT_COMPAT, $cfg['charset']) : preg_replace('`(\?.*)?$`','',$_SERVER['REQUEST_URI']));
$cfg['compress'] = (isset($cfg['compress']) and $cfg['compress'] == 1) ? 1 : 0;
$cfg['cookie'] = (isset($cfg['cookie']) and !isset($cfg['cookie'][64])) ? $cfg['cookie'] : 'sourceer';
$cfg['date_type'] = isset($cfg['date_type']) ? $cfg['date_type'] : 'm/d/y';
$cfg['dl'] = (isset($cfg['dl']) and $cfg['dl'] == 1) ? 1 : 0;
$cfg['file_info'] = (isset($cfg['file_info']) and $cfg['file_info'] == 1) ? 1 : 0;
$cfg['hash'] = isset($cfg['hash']) ? $cfg['hash'] : md5('sourceer'. 'pass');
$cfg['hiliter'] = (!empty($cfg['hiliter']) and function_exists($cfg['hiliter'])) ? $cfg['hiliter'] : 0;
$cfg['lang'] = isset($cfg['lang']) ? $cfg['lang'] : 'en';
$cfg['ok_ips'] = (isset($cfg['ok_ips']) and is_array($cfg['ok_ips'])) ? $cfg['ok_ips'] : array();
$cfg['query_plus'] = isset($cfg['query_plus']) ? $cfg['query_plus'] : '';
$cfg['root'] = (isset($cfg['root']) and strlen($cfg['root'] = trim(preg_replace('`///*`', '/', $cfg['root']), '/'))) ? $cfg['root'] : '.';
$cfg['sec_check_off'] = (isset($cfg['sec_check_off']) and $cfg['sec_check_off'] == 1) ? 1 : 0;
$cfg['sec_file_dl'] = (isset($cfg['sec_file_dl']) and $cfg['sec_file_dl'] == 1) ? 1 : 0;
$cfg['sec_dir_into'] = (isset($cfg['sec_dir_into']) and $cfg['sec_dir_into'] == 1) ? 1 : 0;
$cfg['sec_dir_list'] = (isset($cfg['sec_dir_list']) and $cfg['sec_dir_list'] == 1) ? 1 : 0;
$cfg['sec_file_list'] = (isset($cfg['sec_file_list']) and $cfg['sec_file_list'] == 1) ? 1 : 0;
$cfg['sec_file_src'] = (isset($cfg['sec_file_src']) and $cfg['sec_file_src'] == 1) ? 1 : 0;
$cfg['src'] = (isset($cfg['src']) and $cfg['src'] == 1) ? 1 : 0;
$cfg['timeout'] = (isset($cfg['timeout']) and ctype_digit($cfg['timeout']) and $cfg['timeout'] > 10) ? $cfg['timeout'] : 300;
$cfg['title'] = isset($cfg['title']) ? str_replace(array('<', '>', '"'), array('<', '>', '"'), $cfg['title']) : 'Bontiv-Sourceer file and code viewer';
$cfg['up_root'] = (isset($cfg['up_root']) and $cfg['up_root'] == 1) ? 1 : 0;
// CSS in HTML head
if(!isset($cfg['css']) or $cfg['css'] === 0 or is_array($cfg['css'])){
$cfg['css'] = "<style type=\"text/css\" media=\"all\" id=\"sourceerStyle\"><!--/*--><![CDATA[/*><!--*/\na:link, a:visited { text-decoration: none; color: blue; }\na:hover, a:active { text-decoration: underline; }\nbody, div, html, p { font-size: 14px; font-family:\'Lucida Grande\', \'Lucida Sans Unicode\', \'Helvetica\', \'Verdana\', sans-serif; }\ndiv.Smsg { color: red; padding-top: 5px; padding-bottom: 5px; }\ndiv.Sdir { margin-bottom: 1px; margin-top: 3px; } /* directory item */\ndiv.Sfile1 { margin-bottom: 1px; margin-top: 3px; } /* source-viewable file item */\na.Sfile1:link, a.Sfile1:visited { color: green; }\ndiv.Sfile2 { margin-bottom: 1px; margin-top: 3px; } /* file item */\ndiv.Sprop { margin-left: 50px; display:inline; font-size: 80%; color: gray; }\ndiv.Ssubtle { font-size: 80%; color: gray; text-align: right; }\nspan.Snew { color: black; } /* new item */\nspan.Syoung { color: #333333; } /* young item */\nspan.Sold { color: #999999; } /* old item */\nspan.Sancient { color: #cccccc; } /* ancient item */\n#Sbody {}\n#Scode { background-color: #efefef; padding: 5px; margin-bottom: 5px; font-family:\'Bitstream Vera Sans Mono\', \'Courier New\', \'Courier\', monospace; } /* source code */\n#Sfoot { margin-top: 10px; padding-top: 5px; padding-bottom: 5px; border-top: 1px solid gray; }\n#Shead { margin-bottom: 10px; padding-top: 5px; padding-bottom: 5px; border-bottom: 1px solid gray; }\n#Slist {} /* file listing */\n#Slog { padding-top: 5px; padding-bottom: 5px; } /* login */\n#Smain { margin: auto; width: 95%; font-family:\'Lucida Grande\', \'Lucida Sans Unicode\', \'Helvetica\', \'Verdana\', sans-serif; } /* outer-most */\n#Sneck { margin-bottom: 10px; padding-top: 5px; padding-bottom: 5px; font-size: 130%; border-bottom: 1px dotted gray; } /* path */\n#Stotal { display: none; margin-left: 50px; font-size: 70%; color: gray; } /* with JS-based sort links */\n/*]]>*/--></style>". (is_array($cfg['css']) ? $cfg['css'][0] : '');
}
// JS in HTML head
if(!isset($cfg['js']) or $cfg['js'] === 0 or is_array($cfg['js'])){
$cfg['js'] = "<script type=\"text/javascript\"><!--//--><![CDATA[//><!-- //--><!]]></script>". (is_array($cfg['js']) ? $cfg['js'][0] : '');
}
// HTML head
if(!isset($cfg['head']) or $cfg['head'] === 0 or is_array($cfg['head'])){
ob_start();
echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"", $cfg['lang'], "\" lang=\"", $cfg['lang'], "\">\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=", $cfg['charset'], "\" /><meta http-equiv=\"Content-Language\" content=\"", $cfg['lang'], "\" /><meta name=\"description\" content=\"", $cfg['title'], "; PHP Labware Bontiv-Sourceer code, file, directory browser, viewer\" /><meta name=\"keywords\" content=\"", $cfg['title'], ", Bontiv-Sourceer, PHP code/file/directory viewer/browser, PHP Labware, code, file, directory, browse, view, PHP\" />\n_Sourceer_dynamic_css_\n_Sourceer_dynamic_js_\n<title>_Sourceer_dynamic_title_ | ", $cfg['title'], "</title>\n</head>\n<body>\n<div id=\"Smain\">\n<div id=\"Shead\">", $cfg['title'], (is_array($cfg['head']) ? $cfg['head'][0] : ''), "</div><!-- ended div Shead -->\n";
$cfg['head'] = ob_get_contents();
ob_end_clean();
}
// HTML foot
if(!isset($cfg['foot']) or $cfg['foot'] === 0 or is_array($cfg['foot'])){
$cfg['foot'] = "<div id=\"Sfoot\">". (is_array($cfg['foot']) ? $cfg['foot'][0] : ''). "</div><!-- ended div Sfoot -->\n</div><!-- ended div Smain -->\n</body>\n</html>";
}
$this->cfg = $cfg;
unset($cfg);
$this->list_total = 0; // dir content list
$this->neck = '';
$this->out = array('filepath'=>0, 'task'=>'instantiate', 'title'=>''); // returned by work()
$this->sec_dirs = is_array($sec_dirs) ? $sec_dirs : array();
$this->sec_files = is_array($sec_files) ? $sec_files : array();
$this->self = htmlspecialchars($this->cfg['base_url']. ((strpos($this->cfg['base_url'], '?') !== false) ? '' : '?'). $this->cfg['query_plus'], ENT_COMPAT, $this->cfg['charset']); // for making URLs
$this->src_filetypes = is_array($src_filetypes) ? $src_filetypes : array(''=>'txt', 'css'=>'css', 'htaccess'=>'txt', 'htm'=>'html', 'html'=>'html', 'js'=>'js', 'php'=>'php', 'txt'=>'txt', 'xml'=>'xml');
# Not allowed in safe mode
#set_time_limit($this->cfg['timeout']);
}
function auth(){ // authenticate user
// cookie
if(isset($_COOKIE[$this->cfg['cookie']]) && $_COOKIE[$this->cfg['cookie']] == $this->cfg['hash']){
return 1;
}
// IP
if(count($this->cfg['ok_ips'])){
if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) and $_SERVER['HTTP_X_FORWARDED_FOR'] !=''){
$ip = str_replace(array('\\', '|'), ' ', $_SERVER['HTTP_X_FORWARDED_FOR']);
}else{
if(isset($_SERVER['HTTP_CLIENT_IP']) and $_SERVER['HTTP_CLIENT_IP'] !=''){
$ip = $_SERVER['HTTP_CLIENT_IP'];
}elseif(isset($_SERVER['REMOTE_ADDR']) and $_SERVER['REMOTE_ADDR'] !=''){
$ip = $_SERVER['REMOTE_ADDR'];
}else{
$ip = '0.0.0.0';
}
}
if(in_array($ip, $this->cfg['ok_ips'])){
return 1;
}
}
// password
if(isset($_POST['Sp'])){
if(md5('sourceer'. $_POST['Sp']) == $this->cfg['hash']){
$to = isset($_POST['St']) ? str_replace('&Sp=', '&', base64_decode(str_replace(array('-','_','.'), array('+','/','='), $_POST['St']))) : htmlspecialchars_decode($this->self, ENT_COMPAT);
@ob_end_clean();
setcookie($this->cfg['cookie'], $this->cfg['hash'], time()+3600);
header('Location: '. $to);
exit;
}
}
return 0;
}
function do_dl($f, $fn){ // download handling
if($this->cfg['dl'] == 0){
$this->out['error'] = 'Downloads through PHP is turned off';
return 'off';
}
if(!array_key_exists(strtolower(substr(strrchr($fn, '.'), 1)), $this->src_filetypes) or ($this->is_sec($f) && $this->cfg['sec_file_dl'] == 0)){
$this->out['error'] = 'Download through PHP of this file is prohibited';
return 'denied';
}
if(is_file($f)){
if(filesize($f)){
if($fh = fopen($f, 'rb')){
$m = array('css' => 'text/css', 'htm' => 'text/html', 'html' => 'text/html', 'js' => 'application/x-javascript', 'rtf' => 'text/rtf', 'c' => 'text/plain', 'txt' => 'text/plain'); // some mimes
$e = strtolower(substr(strrchr($f, '.'), 1));
@ob_end_clean();
header('Accept-Ranges: bytes');
header('Content-Type: '. (array_key_exists($e, $m) ? $m[$e] : 'application/octet-stream'));
header("Content-Transfer-Encoding: binary\n");
header('Content-Disposition: inline; filename="'.$fn.'"');
set_time_limit(0);
while((!feof($fh)) and (connection_status()==0)){
print(fread($fh, 1024*2));
}
fclose($fh);
exit;
}else{
$this->out['error'] = 'File could not be sent, possibly because of file-permission issues';
return 'issue';
}
}else{
$this->out['error'] = 'File appears to be empty';
return 'empty';
}
}else{
$this->out['error'] = 'Specified file likely does not exist';
return 'absent';
}
$this->out['error'] = 'Error';
return 'error';
}
function do_src($f, $fn){ // source code view handling
if($this->cfg['src'] == 0){
$this->out['error'] = 'Source code viewing through PHP is turned off';
return 'off';
}
$e = strtolower(substr(strrchr($fn, '.'), 1));
if(!array_key_exists($e, $this->src_filetypes) or ($this->is_sec($f) && $this->cfg['sec_file_src'] == 0)){
$this->out['error'] = 'Source code viewing for this file is prohibited';
return 'denied';
}elseif(is_file($f)){
if(filesize($f)){
if(($c = file_get_contents($f)) === false){
$this->out['error'] = 'File contents could not be retrieved, possibly because of file-permission issues';
return 'issue';
}
$c = $this->fixed_charset($c);
$ft = $this->src_filetypes[$e];
if($this->cfg['hiliter']){ // external highlighting function
$x = $this->cfg['hiliter']($c, $ft, $this->out['filepath'], $this->cfg['charset']);
if(is_array($x)){
return $x;
}
}
if ($e == '')
{
if (preg_match('`#\! */bin/sh`i', $c)) $ft = 'shell';
if (preg_match('`#\! */bin/bash`i', $c)) $ft = 'shell';
}
if (in_array($ft, array('c', 'pl', 'xml', 'shell', 'py')))
{
return array('<link href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" rel="stylesheet" type="text/css" />
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js" type="text/javascript"></script>
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPerl.js" type="text/javascript"></script>
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js" type="text/javascript"></script>
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushBash.js" type="text/javascript"></script>
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPython.js" type="text/javascript"></script>
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCpp.js" type="text/javascript"></script><pre class="brush: '. $ft .'">'
. htmlspecialchars($c)
. '</pre><script type="text/javascript">
SyntaxHighlighter.all()
</script>');
}
// highlight for PHP; for others, mere format
return array(str_replace(array('<br />', '<br>', ' ', '<font color="', '</font>', "\n ", ' '), array("\n<br />\n", "\n<br>\n", ' ', '<span style="color: ', '</span>', "\n ", ' '), "\n". ($ft == 'php' ? highlight_string($c, true) : nl2br(htmlspecialchars($c, ENT_COMPAT, $this->cfg['charset'])))));
}else{
$this->out['error'] = 'File appears to be empty';
return 'empty';
}
}else{
$this->out['error'] = 'Specified file likely does not exist';
return 'absent';
}
$this->out['error'] = 'Error';
return 'error';
}
function fixed_charset($in){ // weak attempt to fix; lot of code needed otherwise
$enc = strtolower(str_replace('-', '', $this->cfg['charset']));
if($enc == 'utf8'){
if(preg_match('`^.{1}`us', $in) or !preg_match('`[^\x00-\x7F]`', $in)){ // is utf-8
return $in;
}
return utf8_encode($in);
}
if(function_exists('mb_detect_encoding')){
return mb_convert_encoding($in, $this->cfg['charset'], mb_detect_encoding($in));
}
return $in;
}
function get_prop($p, $f, $n = 0){ // get file property
if(!$this->cfg['file_info']){
return '';
}
if($p == 'size'){
$s = filesize($f);
if($s === false){
return array(0, 0);
}elseif($s > 1024 && $s < 1048576){
return array(round($s / 1024, 2). ' <small>KB</small>', $s);
}elseif ($s > 1048576){
return array(round($s / 1048576, 2). ' <small>MB</small>', $s);
}elseif ($s > 1073741824){
return array(round($s / 1073741824, 2). ' <small>GB</small>', $s);
}else{
return array($s . ' <small>B</small>', $s);
}
}elseif($p == 'mod_time' && ($t = filemtime($f)) !== false){
$d = $n-$t;
$a = $d < 86400 ? round($d/3600, 1). ' h' : ($d < 2630880 ? round($d/86400, 1). ' d' : ($d < 31570560 ? round($d/2630880, 1). ' m' : round($d/31570560, 1). ' y'));
return array('<span class="S'. ($d < 1000000 ? 'new' : ($d < 9000000 ? 'young' : ($d < 31570560 ? 'old' : 'ancient'))). '" onmouseover="javascript: Sloct = new Date(1000*'.gmdate('U', $t).'); this.title=Sloct.toLocaleString() + \', local time\';">'. gmdate($this->cfg['date_type'], $t). ' <small>GMT</small>, '. $a. ' old</span>', $t);
}
return array(0, 0);
}
function is_sec($f, $dir = 0){ // if file/directory is a secured (restricted) one; $f is path to file/dir relative to cfg['root']
if($this->cfg['sec_check_off']){
return 0;
}
$s = !$dir ? $this->sec_files : $this->sec_dirs;
if(!($c = count($s))){
return 0;
}
for($i=$c; --$i>=0;){
if(!is_array(($j = $s[$i]))){
if($f == $j){
return 1;
}
continue;
}
foreach($j as $k){
if(preg_match($k, $f)){return 1;}
}
}
return 0;
}
function rel_path($dest, $root = '', $dir_sep = '/', $pre = '.'){ // gets relative path between two absolute or real paths
$root = explode($dir_sep, $root);
$dest = explode($dir_sep, $dest);
$fix = '';
$path = $pre;
$diff = 0;
for($i = -1; ++$i < max(($rC = count($root)), ($dC = count($dest)));){
if(isset($root[$i]) and isset($dest[$i])){
if($diff){
$path .= $dir_sep. '..';
$fix .= $dir_sep. $dest[$i];
continue;
}
if($root[$i] != $dest[$i]){
$diff = 1;
$path .= $dir_sep. '..';
$fix .= $dir_sep. $dest[$i];
continue;
}
}elseif(!isset($root[$i]) and isset($dest[$i])){
for($j = $i-1; ++$j < $dC;){
$fix .= $dir_sep. $dest[$j];
}
break;
}elseif(isset($root[$i]) and !isset($dest[$i])){
for($j = $i-1; ++$j < $rC;){
$fix = $dir_sep. '..'. $fix;
}
break;
}
}
return $path. $fix;
}
function set_cfg($k, $v){ // config set
$this->cfg[$k] = $v;
}
function show_dir($d, $s){ // list dir content; $d is true relative path starting with './'; $s, faux; list items given name 'item' and unique IDs of form 'sort-number_bytesize_filemtime' for enduser to manipulate list by JS etc.
if(!is_dir($d)){
$this->out['error'] = 'Directory specified probably doesn\'t exist or is not a directory';
return 'absent';
}
if(!empty($this->sec_dirs) and !$this->cfg['sec_check_off'] and !$this->cfg['sec_dir_into'] and $this->is_sec($d, 1)){
$this->out['error'] = 'Traversal of directory specified is prohibited';
return 'denied';
}
if(($dh = opendir($d)) === false){
$this->out['error'] = 'Directory specified could not be opened, possibly due to file-permission issues';
return 'issue';
}
$Sl = rawurlencode(trim($s, '/')); // for the locator Sl GET parameter in URL
$self = $this->self;
$no = 0; // count items
// gets all files/dirs, noting sec level
$sec_off_f = ($this->cfg['sec_check_off'] == 1) ? 1 : (!empty($this->sec_files) ? 0 : 1);
$sec_off_d = ($this->cfg['sec_check_off'] == 1) ? 1 : (!empty($this->sec_dirs) ? 0 : 1);
$now = time();
while(($fn = readdir($dh)) !== false){
if($fn == '.' || $fn == '..'){
continue;
}
$f = $d. '/'. $fn;
$f2 = $s. '/'. $fn;
if(is_dir($f)){
if($sec_off_d or ($sec = $this->is_sec($f2, 1)) !== 2){
$sec = isset($sec) ? $sec : 1;
$df[$fn] = array($this->get_prop('mod_time', $f, $now), $sec);
unset($sec);
}
}elseif($sec_off_f or ($sec = $this->is_sec($f2)) !== 2){
$sec = isset($sec) ? $sec : 1;
$e = strtolower(substr(strrchr($fn, '.'), 1));
$ndf[$e][$fn] = array($this->get_prop('size', $f), $this->get_prop('mod_time', $f, $now), $sec);
unset($sec);
}
}
closedir($dh);
// show list
$enc = $this->cfg['charset'];
$no = 0;
ob_start();
echo '<table border=0 cellpadding=0 cellspacing=0>';
if(isset($df)){ // dirs
ksort($df, SORT_STRING);
$sec_ord = ($sec_off_d or ($this->cfg['sec_dir_list'] and $this->cfg['sec_dir_into'])) ? 1 : 0;
foreach($df as $k => $v){
if($sec_ord or !$v[1]){ // no hiding
++$no;
echo '<tr><td><div name="item" class="Sdir" id="', $no, '_0_', $v[0][1], '"><a href="', $self, '&Sd='. rawurlencode($k), '&Sl=', htmlspecialchars($Sl, ENT_COMPAT, $enc), '" title="browse directory">', htmlspecialchars($this->fixed_charset($k), ENT_COMPAT, $enc), '/</a>', (isset($v[0][0][0]) ? ' </td><td><div class="Sprop">'. $v[0][0]. '</div>' : ''), "</div></td></tr>\n";
continue;
}
if($this->cfg['sec_dir_list']){
++$no;
echo '<tr><td><div name="item" class="Sdir" id="', $no, '_0_', $v[0][1], '">', htmlspecialchars($this->fixed_charset($k), ENT_COMPAT, $enc), '/', (isset($v[0][0][0]) ? ' </td><td><div class="Sprop">'. $v[0][0]. '</div>' : ''), "</div></td></tr>\n";
}
}
}
if(isset($ndf)){ // files
ksort($ndf, SORT_STRING);
foreach($ndf as $v){
ksort($v, SORT_STRING);
}
$sec_src = $sec_dl = 0;
$sec_ord = ($sec_off_f or ($this->cfg['sec_file_list'] and $this->cfg['sec_file_dl'] and $this->cfg['sec_file_src'])) ? 1 : 0;
if(!$sec_ord and $this->cfg['src'] and $this->cfg['sec_file_src']){
$sec_src = 1;
}
if(!$sec_ord and $this->cfg['dl'] and $this->cfg['sec_file_dl']){
$sec_dl = 1;
}
// for non-Sourceer (direct) URL for file
$d_enc = implode('/', array_map('rawurlencode', explode('/', trim($d, '/'))));
foreach($ndf as $k1 => $v1){
if(array_key_exists($k1, $this->src_filetypes)){
foreach($v1 as $k2 => $v2){
$k3 = rawurlencode($k2);
if($sec_ord or !$v2[2]){
++$no;
echo '<tr><td><div name="item" class="Sfile1" id="', $no, '_', $v2[0][1], '_', $v2[1][1], '"><a href="', (($this->cfg['src']) ? $self. '&Sfs='. $k3. '&Sl='. htmlspecialchars($Sl, ENT_COMPAT, $enc). '" title="view source" class="Sfile1' : htmlspecialchars($Sl, ENT_COMPAT, $enc). '/'. $k3. '" class="Sfile2'), '">', htmlspecialchars($this->fixed_charset($k2), ENT_COMPAT, $enc), '</a> </td><td><div class="Sprop">', (!empty($v2[0][0]) ? $v2[0][0] : '?'), ((isset($v2[0][0][0]) and isset($v2[1][0][0])) ? '; ' : ' '), (!empty($v2[1][0]) ? $v2[1][0] : '?'), '', (($this->cfg['dl'] && !empty($v2[0][0])) ? ' <a href="'. $self. '&Sfd='. $k3. '&Sl='. htmlspecialchars($Sl, ENT_COMPAT, $enc). '" title="visit" class="Sfile1">download</a>' : ''), "</div></div></td></tr>\n";
continue;
}
if($this->cfg['sec_file_list']){
++$no;
echo '<tr><td><div name="item" class="Sfile1" id="', $no, '_', $v2[0][1], '_', $v2[1][1], '">', ($sec_src ? '<a href="'. $self. '&Sfs='. $k3. '&Sl='. htmlspecialchars($Sl, ENT_COMPAT, $enc). '" title="view source" class="Sfile1">' : ''), htmlspecialchars($this->fixed_charset($k2), ENT_COMPAT, $enc), ($sec_src ? '</a>' : ''), ' </td><td><div class="Sprop">', $v2[0][0], ((isset($v2[0][0][0]) and isset($v2[1][0][0])) ? '; ' : ''), $v2[1][0], ($sec_dl ? ' <a href="'. $self. '&Sfd='. $k3. '&Sl='. htmlspecialchars($Sl, ENT_COMPAT, $enc). '" title="visit" class="Sfile1">download</a>' : ''), "</div></div></td></tr>\n";
}
}
}else{
foreach($v1 as $k2 => $v2){
if($sec_ord or !$v2[2]){
++$no;
echo '<tr><td><div name="item" class="Sfile2" id="', $no, '_', $v2[0][1], '_', $v2[1][1], '"><a href="', $d_enc, '/', rawurlencode($k2), '" class="Sfile2" title="visit">', htmlspecialchars($this->fixed_charset($k2), ENT_COMPAT, $enc), '</a> </td><td><div class="Sprop">', $v2[0][0], ((isset($v2[0][0][0]) and isset($v2[1][0][0])) ? ', ' : ''), $v2[1][0], '', "</div></div></td></tr>\n";
continue;
}
if($this->cfg['sec_file_list']){
++$no;
echo '<tr><td><div name="item" class="Sfile2" id="', $no, '_', $v2[0][1], '_', $v2[1][1], '">', htmlspecialchars($this->fixed_charset($k2), ENT_COMPAT, $enc), ' </td><td><div class="Sprop">', $v2[0][0], ((isset($v2[0][0][0]) and isset($v2[1][0][0])) ? ', ' : ''), $v2[1][0], '', "</div></div></td></tr>\n";
}
}
}
}
}
echo '</table>';
$o = ob_get_contents();
@ob_end_clean();
if(!$no){
$this->out['error'] = 'Directory specified may be empty, or all of its content may be hidden';
return 'empty';
}
$this->list_total = $no;
return array($o);
$this->out['error'] = 'Error';
return 'error';
}
function show_foot(){ // ending HTML
echo "\n", '<div class="Ssubtle">Presented with <a href="http://remi.bonnetchangai.free.fr/">Bontiv-Sourceer</a></div>', "\n", '</div><!-- ended div Sbody -->';
echo $this->cfg['foot'];
}
function show_head(){ // sends HTML doctype, head, and body's top
if(!headers_sent()){
header('Content-Type: text/html; charset='. $this->cfg['charset']);
}
$enc = $this->cfg['charset'];
if(!strlen($this->neck) and isset($this->out['filepath'])){
if($this->out['filepath'] == './'){
$this->neck = '.<big>/</big> <small><em>root</em></small>';
}else{
$n = $l = '';
for($i = 0, $c = count(($nA = explode('/', trim($this->out['filepath'], '/')))); $i < $c; ++$i){
if($c-$i == 1){
$n .= htmlspecialchars($this->fixed_charset($nA[$i]), ENT_COMPAT, $enc);
continue;
}
$l .= $nA[$i]. '/';
$n .= '<a href="'. $this->self. '&Sl='. rawurlencode(trim($l, '/')). '" title="browse this directory">'. htmlspecialchars($this->fixed_charset($nA[$i]), ENT_COMPAT, $enc). '</a><big>/</big>';
}
$this->neck = $n;
}
if($this->cfg['file_info'] and $this->list_total > 2){
$this->neck .= '<span id="Stotal" style="display:none;">'. $this->list_total. ' items sorted by <a href="#" onclick="javascript:Ssort(\'a\'); return false;" title="directories and alphabetically ordered files grouped by extension">type</a>, <a href="#" onclick="javascript:Ssort(\'s\'); return false;" title="excludes directories">size</a> or <a href="#" onclick="javascript:Ssort(\'t\'); return false;" title="since last modified">age</a></span>';
}
}
echo str_replace(array('_Sourceer_dynamic_title_', '_Sourceer_dynamic_css_', '_Sourceer_dynamic_js_', '_Sourceer_dynamic_title_'), array(htmlspecialchars($this->fixed_charset($this->out['title']), ENT_COMPAT, $enc), $this->cfg['css'], $this->cfg['js']), $this->cfg['head']), "<div class=\"Ssubtle\">", trim((($this->out['filepath'] !== './') ? '<a href="'. $this->self. '&Sl=." title="top-most directory, or home">Root</a>' : ''). ($this->out['task'] != 'help' ? ' | <a href="'. $this->self. '&Sh=1" title="Sourceer help">Help</a>' : ''). (($this->cfg['auth'] and isset($_COOKIE[$this->cfg['cookie']])) ? ' | <a href="'. $this->self. '&So=1" title="logout from Sourceer">Logout</a>' : ''), '| '), '</div>', "\n", '<div id="Sneck">', $this->neck, "</div><!-- ended div Sneck -->\n", (isset($this->out['error']) ? '<div class="Smsg">'. $this->out['error']. ($this->out['task'] != 'login' ? '<p><small><a href="'. $this->self. '&Sl=." title="top-most directory / home">Go to root</a></small></p>' : ''). '</div>' : ''), '<div id="Sbody">';
}
function work(){ // main handler
if(get_magic_quotes_gpc()){
if (isset($_GET['Sd']))
$_GET['Sd'] = stripslashes($_GET['Sd']);
if (isset($_GET['Sfd']))
$_GET['Sfd'] = stripslashes($_GET['Sfd']);
if (isset($_GET['Sfs']))
$_GET['Sfs'] = stripslashes($_GET['Sfs']);
if (isset($_GET['Sl']))
$_GET['Sl'] = stripslashes($_GET['Sl']);
}
// silently secure _GET vars - no ./, ../, //, etc., except Sl that can't have //
$_GET['Sd'] = (isset($_GET['Sd']) and strlen(($_GET['Sd'] = preg_replace('`^\.+$`', '', basename($_GET['Sd']))))) ? $_GET['Sd'] : false; // dir to view
$_GET['Sfd'] = (isset($_GET['Sfd']) and strlen(($_GET['Sfd'] = preg_replace('`^\.+$`', '', basename($_GET['Sfd']))))) ? $_GET['Sfd'] : false; // file to dl
$_GET['Sfs'] = (isset($_GET['Sfs']) and strlen(($_GET['Sfs'] = preg_replace('`^\.+$`', '', basename($_GET['Sfs']))))) ? $_GET['Sfs'] : false; // file for code
$_GET['Sl'] = (isset($_GET['Sl']) and strlen($_GET['Sl'] = trim(preg_replace('`///*`', '/', $_GET['Sl']), '/'))) ? $_GET['Sl'] : ''; // dir locator
// compression
if($_GET['Sfd'] === false and $this->cfg['compress'] and function_exists('gzencode') and isset($_SERVER['HTTP_ACCEPT_ENCODING']) and preg_match('`gzip|deflate`i', $_SERVER['HTTP_ACCEPT_ENCODING']) and !ini_get('zlib.output_compression')){
ob_start('ob_gzhandler');
}else{
ob_start();
}
// help
if(isset($_GET['Sh'])){
$this->out['title'] = $this->neck = 'Help';
$this->out['task'] = 'help';
$this->show_head();
echo 'Here you can browse files and directories (indicated with a trailing slash, /) inside a <em><a href="', $this->self, '&Sl=." title="see root">root</a></em> directory specified by the site administrator.', (!$this->cfg['file_info'] ? '' : ' A file\'s <strong>size</strong>, modification <strong>time</strong> (in GMT; hover cursor on a time to get the local time) and <strong>age</strong> will be indicated (on some systems, indicated directory modification times may be incorrect). Files and sub-directories are listed alphabetically and by type. On most browsers if use of Javascript is enabled, it is possible to <strong>sort</strong> the list by age or size as well -- use the provided links; re-clicking reverses the sort order.'), ' Sub-directories can similarly be browsed.', (!$this->cfg['src'] ? '' : '<br /><br />The <strong>source code</strong> (possibly syntax-highlighted'. ($this->cfg['jssrc'] ? ' which also might require the use of Javascript in your browser' : ''). ') of certain types of files '. (count($this->src_filetypes) ? ' - <code>'. implode('</code>, <code>', array_map('htmlspecialchars', array_unique($this->src_filetypes))). '</code> - ' : ''). 'can be viewed'. (!$this->cfg['dl'] ? '' : ' and such files <strong>downloaded</strong>'). '.'), ' Such file items would be shown in a different style in the directory content lists. Other files may possibly, depending on the server configuration, be downloaded (visited) by clicking the links shown on their names.<br /><br />Depending on the configuration set up by the administrator, some files and directories might not be shown or be <strong>inaccessible</strong>. Also, files and directories with atypical non-English characters in their names may not be accessible and/or may have their names displayed with strange characters.', (!$this->cfg['auth'] ? '' : ' Access to these pages requires being at a specific IP address, or a password.'), '<br /><br />If you are trying to access a specific file or directory by manipulating the <strong>URL query string</strong>, note that the slash (/) character is permitted only in the value for <em>Sl</em>. ', ($this->cfg['up_root'] ? 'Sub-strings like \'/..\' may be used in <em>Sl</em> to browse up-root.' : 'Sub-strings like \'/..\' are removed from the query string.'), '<br /><br />The content shown is generated using the single-file <a href="http://remi.bonnetchangai.free.fr" title="PHP Labware">Bontiv-Sourceer</a> PHP software.';
$this->show_foot();
@ob_end_flush();
return $this->out;
}
// auth work if needed
if($this->cfg['auth']){
// logout
if(isset($_GET['So']) and isset($_COOKIE[$this->cfg['cookie']])){
@ob_end_clean();
setcookie($this->cfg['cookie'], '', time()-10000);
header("Location: " . htmlspecialchars_decode($this->self, ENT_COMPAT));
exit;
}
// login
if($this->cfg['auth'] && $this->auth() == 0){
if(empty($_SERVER['REQUEST_URI'])){
$arr = explode('/', $_SERVER['PHP_SELF']);
$_SERVER['REQUEST_URI'] = '/'. $arr[count($arr)-1];
if(isset($_SERVER['argv'][0]) && $_SERVER['argv'][0] != ''){
$_SERVER['REQUEST_URI'] .= '?'. $_SERVER['argv'][0];
}elseif(isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] != ''){
$_SERVER['REQUEST_URI'] .= '?'. $_SERVER['QUERY_STRING'];
}
}
$to = str_replace(array('+','/','='), array('-','_','.'), base64_encode($_SERVER['REQUEST_URI']));
$this->out['title'] = $this->neck = 'Login';
$this->out['task'] = 'login';
if(isset($_POST['Sp'])){
$this->out['error'] = 'Right password needed';
}
$this->show_head();
echo '<form id="Sform" action="', $this->self, '" method="post"><div id="Slog">Password: <input type="password" id="Sp" name="Sp" /> <input type="submit" value="Log in"><input type="hidden" id="St" name="St" value="', htmlspecialchars($to, ENT_COMPAT, $enc), '" />';
$qplus = $this->cfg['query_plus'];
if(!empty($qplus)){
$enc = $this->cfg['charset'];
$qplus = explode('&', $qplus);
foreach($qplus as $v){
if(($v1 = strpos($v, '=')) !== false){
echo '<input type="hidden" name="', ($v2 = htmlspecialchars(substr($v, 0, $v1), ENT_COMPAT, $enc)), '" id="', $v2, '" value="', htmlspecialchars(substr($v, $v1+1), ENT_COMPAT, $enc), '" />';
}
}
}
echo '</div></form>';
$this->show_foot();
@ob_end_flush();
return $this->out;
}
}
// path check for existence, security, etc.
$this->out['task'] = 'pathcheck';
$dPth = preg_replace('`///*`', '/', $this->cfg['root']. ($_GET['Sl'] != '' ? '/'. $_GET['Sl'] : '')); // true working dir relative path
if(($dTruPth = realpath($dPth)) !== false and ($rTruPth = realpath($this->cfg['root'])) !== false and is_dir($dTruPth)){
$dTruPth = str_replace(DIRECTORY_SEPARATOR, '/', $dTruPth); // true working-dir realpath
$rTruPth = str_replace(DIRECTORY_SEPARATOR, '/', $rTruPth);
$dRelPthT = trim($this->rel_path($dTruPth, $rTruPth, '/', $this->cfg['root']), '/'); // true working-dir relative path
$dRelPthF = trim($this->rel_path($dTruPth, $rTruPth, '/', '.'), '/'); // faux working-dir relative path
$this->out['filepath'] = $dRelPthF. '/'. ($_GET['Sfd'] ? $_GET['Sfd'] : ($_GET['Sfs'] ? $_GET['Sfs'] : ($_GET['Sd'] ? $_GET['Sd']. '/' : ''))); // faux working-dir relative path
if((preg_match('`(^|/)\.\.(/|$)`', $dRelPthF) and !$this->cfg['up_root']) or (!empty($this->sec_dirs) and $this->is_sec($dRelPthF, 1) and !$this->cfg['sec_dir_into'])){
$this->out['title'] = 'prohibited directory: '. $this->out['filepath'];
$this->neck = 'Prohibited directory: '. str_replace('/', '<big>/</big>', htmlspecialchars($this->out['filepath'], ENT_COMPAT, $enc));
$this->out['error'] = 'Traversal of directory specified is prohibited';
$this->show_head();
$this->show_foot();
@ob_end_flush();
return $this->out;
}
}else{
$this->out['title'] = 'absent?: '. $dPth. '/';
$this->neck = 'Absent directory?: '. str_replace('/', '<big>/</big>', htmlspecialchars($dPth, ENT_COMPAT, $enc). '/');
$this->out['error'] = 'Directory specified probably doesn\'t exist, or is not a directory, or could not be accessed, possibly because of file-permission or server configuration issues';
$this->show_head();
$this->show_foot();
@ob_end_flush();
return $this->out;
}
// download
if($_GET['Sfd'] !== false){
$this->cfg['compress'] = 0;
$this->out['task'] = 'download';
if(($x = $this->do_dl($dRelPthT. '/'. $_GET['Sfd'], $_GET['Sfd'])) !== 1){
$this->out['title'] = $x. ': '. $dRelPthF. '/'. $_GET['Sfd'];
$this->show_head();
$this->show_foot();
@ob_end_flush();
return $this->out;
}
}
// source code view
if($_GET['Sfs'] !== false){
$this->out['task'] = 'source';
$x = $this->do_src($dRelPthT. '/'. $_GET['Sfs'], $_GET['Sfs']);
$this->out['title'] = (is_array($x) ? 'source code' : $x). ': '. $dRelPthF. '/'. $_GET['Sfs'];
if(is_array($x)){
$this->cfg['css'] .= !empty($x[1]) ? $x[1] : '';
$this->cfg['js'] .= !empty($x[2]) ? $x[2] : '';
$this->cfg['foot'] = (!empty($x[3]) ? $x[3] : ''). $this->cfg['foot'];
}
$this->show_head();
if(is_array($x)){
echo "\n", '<div id="Scode">', "\n", $x[0], "\n", '</div><!-- ended div Scode -->', "\n";
}
$this->show_foot();
@ob_end_flush();
$this->out['css'] = $this->cfg['css'];
$this->out['js'] = $this->cfg['js'];
$this->out['foot'] = $this->cfg['foot'];
return $this->out;
}
// directory listing
$this->out['task'] = 'browse';
$x = $this->show_dir($dRelPthT. ($_GET['Sd'] !== false ? '/'. $_GET['Sd'] : ''), $dRelPthF. ($_GET['Sd'] !== false ? '/'. $_GET['Sd'] : ''));
$this->out['title'] = (is_array($x) ? 'directory' : $x). ': '. $dRelPthF. '/'. ($_GET['Sd'] !== false ? $_GET['Sd']. '/' : '');
if(is_array($x)){
$this->cfg['foot'] = '<script type="text/javascript"><!--//--><![CDATA[//><!--
// sorting script; also unhides div Stotal only if JS ability
var Se = document.getElementById(\'Stotal\'); if(Se != null){if(Se.style.display == \'none\'){Se.style.display = \'inline\';}}
var So = 1;
function Ssort(o){var d = So == 1 ? 0 : 1; So = d; var e = document.getElementById(\'Slist\'); var i = [];
for(var x = e.firstChild; x != null; x = x.nextSibling){if(x.nodeType == 1){i.push(x.getAttribute(\'id\'));}}
i.sort(function(j,k){if(o==\'a\'){j1=parseInt(j); k1=parseInt(k); if(j1>k1){return (d == 0 ? 1 : -1);}else if(j1<k1){return d == 1 ? 1 : -1;}else{return 0;}}else if(o==\'s\'){j1=j.substring(j.indexOf(\'_\')+1, j.lastIndexOf(\'_\')); k1=parseInt(k.substring(k.indexOf(\'_\')+1, k.lastIndexOf(\'_\'))); if(j1>k1){return d == 0 ? 1 : -1;}else if(j1<k1){return d == 1 ? 1 : -1;}else{return 0;}}else{j1=parseInt(j.substring(j.lastIndexOf(\'_\')+1)); k1=parseInt(k.substring(k.lastIndexOf(\'_\')+1)); if(j1<k1){return d == 0 ? 1 : -1;}else if(j1>k1){return d == 1 ? 1 : -1;}else{return 0;}}}); for(var m = 0; m < i.length; m++) {e.appendChild(document.getElementById(i[m]));}}
// ended sorting script
//--><!]]></script>'. $this->cfg['foot'];
}
$this->show_head();
if(is_array($x)){
echo "\n", '<div id="Slist">', "\n", $x[0], "\n", '</div><!-- ended div Slist -->', "\n";
}
$this->show_foot();
@ob_end_flush();
$this->out['css'] = $this->cfg['css'];
$this->out['js'] = $this->cfg['js'];
$this->out['foot'] = $this->cfg['foot'];
return $this->out;
}
} // ends CLASS DEFINITION