Files
piratepoet/tasks/fix-full-path-disclosure.php
Jan Jakeš 54549ff037 Convert variable names to camel case
[MAILPOET-1796]
2020-01-14 15:22:42 +01:00

105 lines
3.4 KiB
PHP

<?php
// inject the following code to all PHP files in given directory to prevent full path disclosure
// (directly used PHP file may output path-leaking errors, such as some used symbols are missing)
$code = "if (!defined('ABSPATH')) exit;";
// paths to skip (relative to given <directory> parameter)
$skipPaths = [
'mailpoet-cron.php',
];
// process command line arguments
if (count($argv) !== 2) {
$cmd = basename(__FILE__);
fwrite(STDERR, "This command injects full path disclosure prevention code to all '*.php' files in given <directory>.\n");
fwrite(STDERR, "Usage:\n\n $cmd <directory>\n");
exit(1);
}
// absolutize path (if not absolute)
$directory = rtrim($argv[1] . '/');
if (mb_substr($directory, 0, 1) !== DIRECTORY_SEPARATOR) {
$directory = getcwd() . DIRECTORY_SEPARATOR . $directory;
}
// iterate all files in given directory recursively
$iterator = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file) {
if (substr($file, -3) !== 'php') {
continue;
}
$shortPath = substr($file, strlen($directory));
if (in_array($shortPath, $skipPaths, true)) {
continue;
}
// determine first line after which we can insert full path disclosure protection
// (must be after open tag, strict_types declaration, and namespace declaration)
$line = null;
$data = file_get_contents($file);
if ($data === false) {
throw new Exception("Could not read file '$file'.");
}
$tokens = token_get_all($data);
// find first PHP open tag
for ($i = 0; $i < count($tokens); $i++) {
$token = $tokens[$i];
if (is_array($token) && $token[0] === T_OPEN_TAG) {
$line = $token[2];
break;
}
}
if ($line === null) {
continue;
}
// check for declare(strict_types=...)/namespace statements
for (; $i < count($tokens); $i++) {
$token = $tokens[$i];
// try to find declare with 'strict_types'
if (is_array($token) && $token[0] === T_DECLARE) {
$found = false;
$lineIncrement = 0;
while ($tokens[++$i] !== ';') {
$declareToken = $tokens[$i];
if (is_array($declareToken) && $declareToken[0] === T_STRING && $declareToken[1] === 'strict_types') {
$found = true;
}
$lineIncrement += substr_count(is_array($declareToken) ? $declareToken[1] : $declareToken, "\n");
}
if ($found) {
$line = $token[2] + $lineIncrement;
}
}
// try to find namespace declaration
if (is_array($token) && $token[0] === T_NAMESPACE) {
$line = $token[2];
while ($tokens[++$i] !== ';') {
$line += substr_count(is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i], "\n");
}
break; // when namespace declaration found we can end search
}
}
// inject $code after line give by detected $line
// NOTE: UTF-8 'u' modifier is not added on purpose since we only need to count '\n' occurrences
// and 'u' modifier breaks on some symbols (i.e. those used in Symfony's mb_string polyfill)
$data = preg_replace(sprintf('/^(.*?\n){%u}/is', $line), "$0\n$code\n\n", $data);
if ($data === null) {
throw new Exception("Error when injecting code to file '$file'.");
}
$result = file_put_contents($file, $data);
if ($result === false) {
throw new Exception("Could not write file '$file'.");
}
}