<?php /* Sourceer 1.3.2, 5 July 2022 Source code viewer PHP 5+ required Copyright Santosh Patnaik GPL v3 license 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. See sourceer_README.txt/.htm for more. */ // PARAMETERS - the 4 arrays can be empty; one or more $cfg elements may not be present $errs = 0; // 1 to debug, else 0 $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', 'json'=>'js', 'php'=>'php', 'txt'=>'txt', 'xml'=>'xml', 'yml'=>'txt'); // 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' => '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' => '.', // 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; set to 0 for no compression or if compression causes issue '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' => 1, // 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 // ERROR DISPLAY FOR DEBUGGING ini_set('display_errors', strval($errs)); ini_set('display_startup_errors', strval($errs)); error_reporting(E_ALL | (defined('E_STRICT') ? E_STRICT : 0)); // 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 __construct($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 is_numeric($cfg['timeout']) and $cfg['timeout'] > 10) ? $cfg['timeout'] : 300; $cfg['title'] = isset($cfg['title']) ? str_replace(array('<', '>', '"'), array('<', '>', '"'), $cfg['title']) : '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 Sourceer code, file, directory browser, viewer\" /><meta name=\"keywords\" content=\"", $cfg['title'], ", 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'); 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', '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; } } // 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(); 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 '<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]) ? ' <div class="Sprop">'. $v[0][0]. '</div>' : ''), "</div>\n"; continue; } if($this->cfg['sec_dir_list']){ ++$no; echo '<div name="item" class="Sdir" id="', $no, '_0_', $v[0][1], '">', htmlspecialchars($this->fixed_charset($k), ENT_COMPAT, $enc), '/', (isset($v[0][0][0]) ? ' <div class="Sprop">'. $v[0][0]. '</div>' : ''), "</div>\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 '<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> <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>\n"; continue; } if($this->cfg['sec_file_list']){ ++$no; echo '<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>' : ''), ' <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>\n"; } } }else{ foreach($v1 as $k2 => $v2){ if($sec_ord or !$v2[2]){ ++$no; echo '<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> <div class="Sprop">', $v2[0][0], ((isset($v2[0][0][0]) and isset($v2[1][0][0])) ? ', ' : ''), $v2[1][0], '', "</div></div>\n"; continue; } if($this->cfg['sec_file_list']){ ++$no; echo '<div name="item" class="Sfile2" id="', $no, '_', $v2[0][1], '_', $v2[1][1], '">', htmlspecialchars($this->fixed_charset($k2), ENT_COMPAT, $enc), ' <div class="Sprop">', $v2[0][0], ((isset($v2[0][0][0]) and isset($v2[1][0][0])) ? ', ' : ''), $v2[1][0], '', "</div></div>\n"; } } } } } $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://bioinformatics.org/phplabware/internal_utilities">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 // magic quote handling if(!function_exists('get_magic_quotes_gpc')){ function get_magic_quotes_gpc(){ return false; } } if(!function_exists('get_magic_quotes_runtime')){ function get_magic_quotes_runtime(){ return false; } } if(!function_exists('set_magic_quotes_runtime')){ function set_magic_quotes_runtime($new_setting){ return true; } } if(get_magic_quotes_gpc()){ foreach($_GET as $k => $v){ $_GET[$k] = stripslashes($v); } ini_set('magic_quotes_gpc', 0); } if(get_magic_quotes_runtime()){ set_magic_quotes_runtime(0); } // 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 (!function_exists('apache_get_modules') or empty(array_intersect(array('mod_deflate', 'mod_gzip'), apache_get_modules()))) 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://bioinformatics.org/phplabware" title="PHP Labware">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