The following files exists in this folder. Click to view.
source.php417 lines ASCII Unix (LF) 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
<?php
/**
* Class for display sourcecode.
*
* @author Mikael Roos, me@mikaelroos.se
* @copyright Mikael Roos 2010 - 2015
* @link https://github.com/mosbth/csource
*/
class CSource
{
/** @var array<string,mixed> */
private array $options = [];
// Explicitly declared properties (PHP 8.2-safe)
private array $validImageExtensions = [];
private string $spaces = " ";
private array $ignore = [];
private string $secureDir = ".";
private string $baseDir = ".";
private ?string $queryPath = null;
private string $suggestedPath = "";
/** @var string|false */
private $realPath = "";
/** @var array<string,mixed> */
private array $pathinfo = [];
private ?string $path = null;
private ?string $file = null;
private ?string $extension = null;
private ?string $dir = null;
private array $breadcrumb = [];
private ?string $message = null;
private ?string $encoding = null;
private ?string $lineendings = null;
private ?string $content = null;
/**
* Constructor
*
* @param array $options to alter the default behaviour.
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function __construct(array $options = [])
{
$default = [
'image_extensions' => ['png', 'jpg', 'jpeg', 'gif', 'ico'],
'spaces_to_replace_tab' => ' ',
'ignore' => ['.', '..', '.git', '.svn', '.netrc', '.ssh'],
'add_ignore' => null, // add array with additional filenames to ignore
'secure_dir' => '.', // Only display files below this directory
'base_dir' => '.', // Which directory to start look in, defaults to current working directory of the actual script.
'query_dir' => isset($_GET['dir']) ? strip_tags(trim((string)$_GET['dir'])) : null, // Selected directory as ?dir=xxx
'query_file' => isset($_GET['file']) ? strip_tags(trim((string)$_GET['file'])) : null, // Selected file as ?file=xxx
'query_path' => isset($_GET['path']) ? strip_tags(trim((string)$_GET['path'])) : null, // Selected path as ?path=xxx
];
// Add more files to ignore
if (isset($options['add_ignore'])) {
$default['ignore'] = array_merge($default['ignore'], (array)$options['add_ignore']);
}
$this->options = $options = array_merge($default, $options);
// Backwards compatible with source.php query arguments for ?dir=xxx&file=xxx
if (!array_key_exists('query_path', $this->options) || $this->options['query_path'] === null) {
$qd = $this->options['query_dir'] ?? '';
$qf = $this->options['query_file'] ?? '';
$this->options['query_path'] = trim($qd . '/' . $qf, '/');
}
$this->validImageExtensions = (array)$options['image_extensions'];
$this->spaces = (string)$options['spaces_to_replace_tab'];
$this->ignore = (array)$options['ignore'];
$this->secureDir = (string)realpath((string)$options['secure_dir']);
$this->baseDir = (string)realpath((string)$options['base_dir']);
$this->queryPath = $this->options['query_path'] !== '' ? (string)$this->options['query_path'] : null;
$this->suggestedPath = rtrim($this->baseDir, '/') . '/' . ($this->queryPath ?? '');
$this->realPath = realpath($this->suggestedPath);
$this->pathinfo = pathinfo($this->realPath ?: $this->suggestedPath);
if (!isset($this->pathinfo['extension'])) {
$this->pathinfo['extension'] = null;
}
if (is_string($this->realPath) && is_dir($this->realPath)) {
$this->file = null;
$this->extension = null;
$this->dir = $this->realPath;
$this->path = trim($this->queryPath ?? '', '/');
} elseif (is_link($this->suggestedPath)) {
$this->pathinfo = pathinfo($this->suggestedPath);
$this->file = $this->pathinfo['basename'] ?? null;
$this->extension = isset($this->pathinfo['extension']) ? strtolower($this->pathinfo['extension']) : null;
$this->dir = $this->pathinfo['dirname'] ?? null;
$this->path = trim(dirname($this->queryPath ?? ''), '/');
} elseif (is_string($this->realPath) && is_readable($this->realPath)) {
$this->file = basename($this->realPath);
$this->extension = $this->pathinfo['extension'] ? strtolower($this->pathinfo['extension']) : null;
$this->dir = dirname($this->realPath);
$this->path = trim(dirname($this->queryPath ?? ''), '/');
} else {
$this->file = null;
$this->extension = null;
$this->dir = null;
}
if ($this->path === '.') {
$this->path = null;
}
$this->breadcrumb = empty($this->path) ? [] : explode('/', $this->path);
// Check that dir lies below securedir
$this->message = null;
$msg = "<p><i>WARNING: The path you have selected is not a valid path or restricted due to security constraints.</i></p>";
if (!$this->dir || strncmp($this->secureDir, $this->dir, strlen($this->secureDir)) !== 0) {
$this->file = null;
$this->extension = null;
$this->dir = null;
$this->message = $msg;
}
// Check that all parts of the path is valid items
foreach ($this->breadcrumb as $val) {
if (in_array($val, $this->ignore, true)) {
$this->file = null;
$this->extension = null;
$this->dir = null;
$this->message = $msg;
break;
}
}
}
/**
* List the sourcecode.
*/
public function view(): string
{
return $this->getBreadcrumbFromPath()
. ($this->message ?? '')
. ($this->readCurrentDir() ?? '')
. ($this->getFileContent() ?? '');
}
/**
* Create a breadcrumb of the current dir and path.
*/
public function getBreadcrumbFromPath(): string
{
$html = "<ul class='src-breadcrumb'>\n";
$html .= "<li><a href='?'>" . htmlspecialchars(basename($this->baseDir), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "</a>/</li>";
$path = '';
foreach ($this->breadcrumb as $val) {
$valEsc = htmlspecialchars($val, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$path .= "$val/";
$href = '?path=' . rawurlencode($path);
$html .= "<li><a href='{$href}'>{$valEsc}</a>/</li>";
}
$html .= "</ul>\n";
return $html;
}
/**
* Read all files of the current directory.
*/
public function readCurrentDir(): ?string
{
if (!$this->dir) {
return null;
}
$html = "<ul class='src-filelist'>";
foreach (glob($this->dir . '/{*,.?*}', GLOB_MARK | GLOB_BRACE) as $val) {
$base = basename($val);
if (in_array($base, $this->ignore, true)) {
continue;
}
$file = $base . (is_dir($val) ? '/' : '');
$path = (empty($this->path) ? '' : $this->path . '/') . $file;
$fileEsc = htmlspecialchars($file, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$href = '?path=' . rawurlencode($path);
$html .= "<li><a href='{$href}'>{$fileEsc}</a></li>\n";
}
$html .= "</ul>\n";
return $html;
}
/**
* Get the details such as encoding and line endings from the file.
*/
public function detectFileDetails(): void
{
$this->encoding = null;
// Detect character encoding
if (function_exists('mb_detect_encoding')) {
if ($res = mb_detect_encoding((string)$this->content, "auto, ISO-8859-1", true)) {
$this->encoding = $res;
}
}
// Is it BOM?
if (isset($this->content) && substr($this->content, 0, 3) === chr(0xEF) . chr(0xBB) . chr(0xBF)) {
$this->encoding = trim(($this->encoding ?? '') . " BOM");
}
// Checking style of line-endings
$this->lineendings = null;
if (isset($this->encoding) && isset($this->content)) {
$lines = explode("\n", $this->content);
$len = strlen($lines[0] ?? '');
if ($len > 0 && substr($lines[0], $len - 1, 1) === "\r") {
$this->lineendings = " Windows (CRLF) ";
} else {
$this->lineendings = " Unix (LF) ";
}
}
}
/**
* Remove passwords from known files from all files starting with config*.
*/
public function filterPasswords(): void
{
if (!isset($this->content)) {
return;
}
$pattern = [
'/(\'|")(DB_PASSWORD|DB_USER)(.+)/',
'/\$(password|passwd|pwd|pw|user|username)(\s*=\s*)(\'|")(.+)/i',
//'/(' . "'" . '|")(password|passwd|pwd|pw)(' . "'" . '|")\s*=>\s*(.+)/i',
'/(\'|")(password|passwd|pwd|pw|user|username)(\'|")(\s*=>\s*)(\'|")(.+)([\'|"].*)/i',
'/(\[[\'|"])(password|passwd|pwd|pw|user|username)([\'|"]\])(\s*=\s*)(\'|")(.+)([\'|"].*)/i',
];
$message = "Intentionally removed by CSource";
$replace = [
'\1\2\1, "' . $message . '");',
'$\1\2\3' . $message . '\3;',
'\1\2\3\4\5' . $message . '\7',
'\1\2\3\4\5' . $message . '\7',
];
$this->content = preg_replace($pattern, $replace, $this->content);
}
/**
* Get the content of the file and format it.
*
* @SuppressWarnings(PHPMD.ExitExpression)
*/
public function getFileContent(): ?string
{
if (!isset($this->file) || !isset($this->realPath) || !is_string($this->realPath)) {
return null;
}
$this->content = @file_get_contents($this->realPath);
if ($this->content === false) {
return "<p><i>Could not read file.</i></p>";
}
$this->detectFileDetails();
$this->filterPasswords();
// Display svg-image or enable link to display svg-image.
$linkToDisplaySvg = "";
if ($this->extension === 'svg') {
if (isset($_GET['displaysvg'])) {
header("Content-type: image/svg+xml");
echo $this->content;
exit;
} else {
$req = $_SERVER['REQUEST_URI'] ?? '';
$sep = (strpos($req, '?') === false) ? '?' : '&';
// Avoid double param if already present
if (strpos($req, 'displaysvg') === false) {
$req .= $sep . 'displaysvg';
}
$linkToDisplaySvg = "<a href='" . htmlspecialchars($req, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "'>Display as SVG</a>";
}
}
// Display image if a valid image file
if (in_array((string)$this->extension, $this->validImageExtensions, true)) {
$baseDir = !empty($this->options['base_dir'])
? rtrim((string)$this->options['base_dir'], '/') . '/'
: '';
$imgSrc = $baseDir . ($this->path ? ($this->path . '/') : '') . $this->file;
$imgSrc = htmlspecialchars($imgSrc, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$this->content = "<div style='overflow:auto;'><img src='{$imgSrc}' alt='[image not found]'></div>";
} else {
// Display file content and format for a syntax
// Replace real tab characters with spaces
$this->content = str_replace("\t", $this->spaces, $this->content);
// highlight_string returns HTML
$this->content = highlight_string($this->content, true);
$i = 0;
$rownums = "";
$text = "";
$content = explode('<br />', $this->content);
foreach ($content as $row) {
$i++;
$rownums .= "<code><a id='L{$i}' href='#L{$i}'>{$i}</a></code><br />";
$text .= $row . '<br />';
}
$enc = htmlspecialchars(trim(($this->encoding ?? '')), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$le = htmlspecialchars(trim(($this->lineendings ?? '')), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$this->content = <<< EOD
<div class='src-container'>
<div class='src-header'><code>{$i} lines {$enc} {$le} {$linkToDisplaySvg}</code></div>
<div class='src-rows'>{$rownums}</div>
<div class='src-code'>{$text}</div>
</div>
EOD;
}
$fileEsc = htmlspecialchars((string)$this->file, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
return "<h3 id='file'><code><a href='#file'>{$fileEsc}</a></code></h3>{$this->content}";
}
}
/**
* Do it.
*/
$source = new CSource();
$content = $source->view();
?>
<!doctype html>
<html lang='en'>
<meta charset='utf-8' />
<title>View sourcecode</title>
<meta name="robots" content="noindex" />
<meta name="robots" content="noarchive" />
<meta name="robots" content="nofollow" />
<style>
/**
* Style for source.php
*/
.src-breadcrumb,
.src-filelist {
font-family: monospace;
list-style-type: none;
padding: 0;
margin: 0 0 22px 0;
}
.src-breadcrumb li {
padding: 0;
display: inline;
}
.src-container {
min-width: 40em;
}
.src-header {
color: #000;
border: solid 1px #999;
border-bottom: 0;
background: #eee;
padding: 0.5em 0.5em 0.5em 0.5em;
}
.src-rows {
float: left;
text-align: right;
color: #999;
border: solid 1px #999;
border-right: none;
background: #eee;
padding: 0.5em 0.5em 0.5em 0.5em;
}
.src-rows a:link,
.src-rows a:visited,
.src-rows a:hover,
.src-rows a:active {
text-decoration: none;
color: inherit;
}
.src-code {
white-space: nowrap;
border: solid 1px #999;
background: #f9f9f9;
padding: 0.5em 0.5em 0.5em 0.5em;
overflow: auto;
}
</style>
<body>
<h1>View sourcecode</h1>
<p>
The following files exists in this folder. Click to view.
</p>
<?= $content ?>
</body>
</html>