Location: PHPKode > scripts > Sourceer > sourceer/sourceer.php
<?php

/*
Sourceer 1.3.1, 31 July 2008
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.
*/

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'); // 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
 '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

// 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('&lt;', '&gt;', '&quot;'), $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>', '&nbsp;', '<font color="', '</font>', "\n ", '  '), array("\n<br />\n", "\n<br>\n", ' ', '<span style="color: ', '</span>', "\n&nbsp;", ' &nbsp;'), "\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, '&amp;Sd='. rawurlencode($k), '&amp;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. '&amp;Sfs='. $k3. '&amp;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. '&amp;Sfd='. $k3. '&amp;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. '&amp;Sfs='. $k3. '&amp;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. '&amp;Sfd='. $k3. '&amp;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. '&amp;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. '&amp;Sl=." title="top-most directory, or home">Root</a>' : ''). ($this->out['task'] != 'help' ? ' | <a href="'. $this->self. '&amp;Sh=1" title="Sourceer help">Help</a>' : ''). (($this->cfg['auth'] and isset($_COOKIE[$this->cfg['cookie']])) ? ' | <a href="'. $this->self. '&amp;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. '&amp;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()){
   $_GET['Sd'] = stripslashes($_GET['Sd']);
   $_GET['Sfd'] = stripslashes($_GET['Sfd']);
   $_GET['Sfs'] = stripslashes($_GET['Sfs']);
   $_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, '&amp;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" />&nbsp;<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
Return current item: Sourceer