<?php /** * Author : AVONTURE Christophe - https://www.avonture.be. * * Based on the work of @cafewebmaster.com and of Bernhard Waldbrunner, * PHP grep, Copyright (C) 2012 * * Last mod: * 2019-01-04 - Abandonment of jQuery and migration to VueJs * 2022-02-08 - Force VueJs v2 (since v3 is now the default) * 2022-02-10 - Add a try-catch to avoid fatal error when trying to read huge files */ define('DEBUG', false); define('DEMO', false); define('REPO', 'https://github.com/cavo789/php_grep'); set_time_limit(0); if (!defined('DS')) { define('DS', DIRECTORY_SEPARATOR); } class AvontureFct { /** * Scan a folder recursively and list files containing a specific pattern. * * @param string $folder The folder where the search should be made (f.i. `/var/www/html`) * @param string $query The pattern to retrieve (f.i. `base64_decode`) * @param string $filter The file's filter (like ยด*.php`) * @param bool $links Should we follow symbolic links or not? * @param bool $regex Is the query is a regex or not? * * @return array The list of files where the $query has been retrieved */ public static function php_grep(string $folder, string $query, string $filter, bool $links, bool $regex): array { $fp = opendir($folder); $ret = []; while (false !== ($f = readdir($fp))) { $file_path = rtrim($folder, DS) . DS . $f; if ('.' == $f || '..' == $f || (!$links && is_link($file_path)) || (is_file($file_path) && !fnmatch($filter, $f))) { continue; } if (is_dir($file_path)) { // Recursive call since we've found a subdirectory $tmp = AvontureFct::php_grep($file_path, $query, $filter, $links, $regex); if (!empty($tmp)) { $ret = array_merge($ret, $tmp); } continue; } try { // We've found a file, try to read it and find the pattern in it if ($regex ? preg_match($query, file_get_contents($file_path)) : stristr(file_get_contents($file_path), $query)) { $ret[] = htmlspecialchars($file_path); } } catch (\Exception $exception) { $ret[] = "ERROR - " & htmlspecialchars($file_path) . " - Exception met : " . $exception->getMessage(); } } closedir($fp); return $ret; } } if (DEBUG === true) { ini_set('display_errors', '1'); ini_set('display_startup_errors', '1'); ini_set('html_errors', '1'); ini_set('docref_root', 'http://www.php.net/'); ini_set('error_prepend_string', "<div style='color:red; font-family:verdana; border:1px solid red; padding:5px;'>"); ini_set('error_append_string', '</div>'); error_reporting(E_ALL); } else { ini_set('error_reporting', E_ALL & ~E_NOTICE); } // Try to, if required, allocate the max allowed memory to this script. ini_set('memory_limit', -1); // Retrieve posted data $data = json_decode(file_get_contents('php://input'), true); // Get absolute path of this file $default = str_replace('/', DS, dirname($_SERVER['SCRIPT_FILENAME'])); // Folder where to start the scan $folder = $default; if ($data !== []) { $task = trim(filter_var(($data['task'] ?? ''), FILTER_UNSAFE_RAW)); if (in_array($task, ['doSearch', 'doKill'])) { switch ($task) { case 'doSearch': // Get parameters // Folder where to start the scan $folder = rtrim(base64_decode(filter_var(($data['folder'] ?? $default), FILTER_UNSAFE_RAW)), DS) . DS; // String to search $query = trim(base64_decode(filter_var(($data['query'] ?? ''), FILTER_UNSAFE_RAW))); // Filter for file's restriction (like *.php) $filter = trim(base64_decode(filter_var(($data['filter'] ?? ''), FILTER_UNSAFE_RAW))); if ('' == $filter) { $filter = '*'; } // Follow symbolic links or not (boolean) $links = boolval(trim(filter_var(($data['links'] ?? ''), FILTER_UNSAFE_RAW))); // is the $query parameter contains a regular expression or not (boolean) $regex = boolval(trim(filter_var(($data['regex'] ?? ''), FILTER_UNSAFE_RAW))); $results = ''; if ('' !== $query) { $results = json_encode(AvontureFct::php_grep($folder, $query, $filter, $links, $regex)); } header('Content-Type: text/json'); echo $results; break; case 'doKill': if (!DEMO) { // Don't kill when demo mode enabled //unlink(__FILE__); } // return 0 when the file still exists $arr['removed'] = is_file(__FILE__) ? 0 : 1; echo json_encode($arr); break; default: echo 'Unsupported task'; break; } die(); } } // Get the GitHub corner $github = ''; if (is_file($cat = __DIR__ . DIRECTORY_SEPARATOR . 'octocat.tmpl')) { $github = str_replace('%REPO%', REPO, file_get_contents($cat)); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="author" content="Christophe Avonture" /> <meta name="robots" content="noindex, nofollow" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=9; IE=8;" /> <title>PHP-Grep</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"> <style> body { margin-top: 25px; } * { font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; font-size: 10pt; } label { width: 5em; display: inline-block; } ol { margin-left: 5em; padding-left: 0.3em; } input[type=submit] { font-size: 12pt; } .doKill { margin-left:10px; } </style> </head> <body> <?php echo $github; ?> <div id="app" class="container"> <div class="page-header"><h1>PHP-grep</h1></div> <div class="container"> <div v-if="showIntro"> <how-to-use demo="https://raw.githubusercontent.com/cavo789/php_grep/master/images/demo.gif"> <ul> <li>Update the folder if needed, where to start the search</li> <li>Enter the text to search for in the Expression area</li> <li>Specify a filter like *.php (or nothing to scan all files)</li> <li>Click on the "Start the search" button</li> </ul> </how-to-use> <br/> <form class="form-horizontal"> <div class="form-group"> <label for="path" class="col-sm-2 control-label">Folder:</label> <div class="col-sm-10"> <input type="text" name="path" size="70" class="form-control" v-model="folder" @change="doReset" /> </div> </div> <div class="form-group"> <label for="query" class="col-sm-2 control-label">Expression:</label> <div class="col-sm-10"> <input type="text" id="query" name="query" size="70" class="form-control" placeholder="Text you are looking for" v-model="query" @keydown="doReset" /> </div> </div> <div class="form-group"> <label for="filter" class="col-sm-2 control-label">Filter on files:</label> <div class="col-sm-10"> <input type="text" id="filter" name="filter" size="30" placeholder="For example: *.php, *.css, *.js, ... or * for all files" class="form-control" v-model="filter" @change="doReset" /> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> <input type="checkbox" id="links" name="links" v-model="links" @change="doReset" /> <label for="links" class="control-label">Follow the symbolic links</label> </div> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> <input type="checkbox" id="regex" name="regex" v-model="regex" @change="doReset" /> <label for="regex" class="control-label">Regular expressions</label> </div> </div> </div> </form> <div class="row"> <button type="button" @click="doSearch" class="btn btn-primary" :class="{ disabled: noQuery }">Start the search</button> <button type="button" @click="doKill" class="btn btn-danger pull-right doKill"> Remove this script</button> </div> <br/> </div> <h2 v-if="isKilledSuccess" class="text-success"> The script has been successfully removed from the server </h2> <h2 v-if="isKilledFailure" class="text-danger" style="font-size:2em;"> An error has occurred while trying removing the script; please do it yourself </h2> <div v-if="noResult"> <p>No occurrence of <strong>{{ query }}</strong> has been found in folder <strong>{{ folder }}</strong> (sub-folders included).</p> </div> <files-list :files="files" v-if="files.length>0"></files-list> </div> </div> <script src="https://unpkg.com/vue@2"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script type="text/javascript"> Vue.component('how-to-use', { props: { demo: { type: String, required: true } }, template: `<details> <summary>How to use?</summary> <div class="row"> <div class="col-sm"> <slot></slot> </div> <div class="col-sm"><img v-bind:src="demo" alt="Demo"></div> </div> </details>` }); Vue.component('file', { template: `<li><slot></slot></li>` }); Vue.component('files-list', { props: { files: { type: Array, required: true } }, template: `<div> <p style="text-decoration:underline;">{{ files.length }} file(s) found:</p> <ol> <file v-for="(file, key) in files" :key="key">{{ file }}</file> </ol> </div>` }); var app = new Vue({ el: '#app', data: { folder: '<?php echo str_replace('\\', '\\\\', $folder);?>', filter: '', files: [], isKilledSuccess: false, isKilledFailure: false, links: false, query: '', regex: false, showIntro: true, showResult: false }, methods: { doReset() { this.showResult = false; this.files = []; }, doSearch() { if(this.query!=='') { var $data = { task: 'doSearch', folder: btoa(this.folder), query: btoa(this.query), filter: btoa(this.filter), links: this.links ? 1 : 0, regex: this.regex ? 1 : 0 } axios.post('<?php echo basename(__FILE__); ?>', $data) .then(response => { console.log('Found '+response.data.length + ' files'); this.showResult = true; this.files = response.data; }) .catch(function (error) {console.log(error);}); } }, doKill() { var $data = { task: 'doKill' } axios.post('<?php echo basename(__FILE__); ?>', $data) .then(response => { this.isKilledSuccess = (response.data.removed == 1); this.isKilledFailure = (response.data.removed == 0); this.showIntro = false; }) .catch(function (error) {console.log(error);}); } }, computed: { noQuery() { return (this.query == '') }, noResult() { return (this.showResult && (this.files.length==0)) } } }); </script> </body> </html>