Finder – A PHP Utility for Finding a Text String in a Complex Directory Structure

I was having to re-engineer some (open source!) source code which had an extremely complicated directory structure with hundreds of files. These files are called through various plugins and class functions. To make the change I wanted involved trying to track down a particular variable which appeared at numerous points throughout. It’s almost as though the authors didn’t want you to be able to make any changes!

What was needed was a utility to be able to search for the text string throughout the entire directory structure. It would be handy to be able to view the file as well to be able to confirm this is the file I needed to edit. So the “finder” utility was born. There’s probably more out there but, hey, its fun writing your own.

Warning

The finder utility will allow you to view any file downstream of there the utility is placed in the directory. This is clearly a security issue, so it should only be used as a utility and then removed or disabled – the options to be able to do that are contained within the utility itself.

I’ve included an IP check as a basic level of authentication.

Program Structure

There are 3 files:

  • finder.php
  • finder_getsource.php
  • prettify.css

finder.php is the main file. finder_getsource.php retrieves the file contents of the file and is displayed after an AJAX call to the file from finder.php

prettify.css is the css file for the Google Code Prettifier.

Installation

You’ll need to edit finder.php to add in the IP address of where you want to access the utility from. Its a basic level of security.

finder.php

<?php
/// Disable this program:
// die('This programme is disabled');

if( $_GET['action'] == 'disable' ) {
	$file = file_get_contents( 'finder.php' );
	$file = str_replace( "// die('This programme is disabled');", "die('This programme is disabled');", $file);
	file_put_contents( 'finder.php', $file );
	header('Location: finder.php');
}

if( $_GET['action'] == 'remove' ) {
	unlink( 'finder_getsource.php' );
	unlink( 'prettify.css' );
	unlink( 'finder.php' );
	die('Where to now?');
}

/*
	File: 	finder.php
	By:		toneus www.toneus.co.uk
	Rev:	1.0 
	
	History:	Made on 9 August 2019
	
	Description: 
	This utility finds all the files and directories downstream of its location
	and searches for a searchterm within those files.
	
	It returns a list of the files containing the search term. You can also view (but not edit) the file.
	
	Useful for where you may need to find where a variable is used in a complex filesystem without having to download
	and open every file.
	
	How It Works:
	
	The DirectoryIterator php class is used to traverse the directory structure.
	
	Every file is opened and if the search term is found the path is outputted.
	
	We could've stored the contents of every file to make for easy viewing but the memory required could become huge.
	So instead we read the file once, and store the path if the search text is found.
	
	If the user wants to view the file (by clicking on the outputted result), we then do an AJAX call to finder_getsource.php 
	with the path and that will return the file contents which will be displayed in a hideable DIV element.
	
	NOTES!
	
	**********************************************
	* The idea is this is used as a utility.	 *
	* But if it is kept on the server and is	 *
	* found, it can be used to read the contents *
	* of potentially every file on the server.	 *
	* So some sort of access requirement should	 *
	* be added.									 *
	**********************************************
	
	
*/

/* 
	One method of access control is to allow access only from certain IP addresses.
	It's basic but is perfectly acceptable for our needs.
*/

// Define a list of allowable IP addresses. 
	$iplist[] = "123.45.678.900"; // Put your IP address in here

	
	
// Is this file being called from an allowable IP?
	if( !in_array( $_SERVER['REMOTE_ADDR'], $iplist ) ) 
die( '<span style="color:red; font-weight: bold;">No can do.</span>. 
Access Restriction is set up. You\'ll need to edit this file to fix.' );
	
	if($_GET) {
		$_GET['searchTerm'] ? $searchTerm = $_GET['searchTerm'] : false;
		$_GET['caseSensitive'] ? $caseSensitive = true : $caseSensitive = false;
	}
?>

<!doctype html>
<html>
	<head>
		<title>Finder</title>
		<style>
			body {
				font-family: "sergoe UI", Arial, sans-serif;
			}
			#resultdiv {
				border: 1px solid #123;
				background-color: #def;
				color: #123;
				font-family: courier;
			}
			button, .entry {
				cursor: pointer;
			}
			
			.entry:hover {
				background-color: #faa;
			}
			
			#codeWindow {
				position: fixed;
				top: 10vh;
				left: 10vh;
				height: 80vh;
				width: 80vw;
				background-color: #fff;
				visibility: hidden;
				box-shadow: 0px 0px 25px 30px;
				overflow: scroll;
				font-family: courier;
				padding: 5px;
			}
			.title {
				background-color: #000;
				color: #fff;
				position: fixed;
			}
			
			#warning {
				border: 1px solid red;
				font-size: 2em;
				padding: 2em;
			}
			
			#warning-close {
				display: flex;
				justify-content: center;
				align-content: center;
				float: right;
				height:2rem;
				width: 2rem;
				cursor: pointer;
				background-color:#eee;
				border:1px solid black;
			}
			
		</style>
		
		<link rel="stylesheet" href="prettify.css" />
		
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
		<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js?autorun=true"></script>
		<script>
		
			function viewCode( id ) {
				var path = $('#' + id).html();
				$.ajax( {
					url: "finder_getsource.php",
					data: {path: path }
				})
				.done(function( html ) {
					$('#codeWindow').html( html );
					$('#codeWindow').css('visibility','visible');
					PR.prettyPrint();
				});
			}
			
			function hideWarning() {
				$('#warning').css('display','none');
				sessionStorage.setItem('displayWarning', "no");
			}
			
			$( document ).ready( function() {
				if( sessionStorage.getItem('displayWarning') == "no" ) hideWarning();
			});
			
		</script>
				
				
				

				
	<head>
	<body>
		<h1>Finder</h1>
		<div id="warning">
		<div id="warning-close" onclick="hideWarning();">X</div>
			<p>⚠ This programme is a useful utility for finding a search term within any file downstream of this file location.</p>
			<p>However, it is a security risk as it allows the files to be read. Eg config files could be read and exploited.</p>
			<p>Basic protection is offered by means of IP validation.</p>
			<p>You can <a href="<?php echo $_SERVER['PHP_SELF'] . '?action=disable';?>">Disable</a>
			or <a href="<?php echo $_SERVER['PHP_SELF'] . '?action=remove';?>">Remove</a> it if required.
		</div>
		<form>
			<input type="text" placeholder="SearchTerm" name="searchTerm" value="<?php echo $searchTerm ? $searchTerm : ""; ?>" /> 
			<input type="checkbox" name="caseSensitive" <?php echo $caseSensitive ? "checked" : ""; ?> /> Case Sensitive Search <br/>
			<p><button type="submit">Search</button> 
			<button type="reset" onclick="window.location.assign('<?php echo $_SERVER['PHP_SELF']; ?>');">Reset</button></p>
		</form>
	
<?php

// If $searchTerm is set then let's go.

	if( $searchTerm ) {

		echo '<p>Looking for <b>' . htmlspecialchars($searchTerm) . '</b></p>';
?>

		<div id="resultdiv">
		
<?php 
		$cnt = 1; 
		function fillArrayWithFileNodes( DirectoryIterator $dir ) {
			global $searchTerm, $caseSensitive, $cnt;
			$data = array();
			foreach ( $dir as $node ) {
				if ( $node->isDir() && !$node->isDot() ) {
					$data[$node->getFilename()] = fillArrayWithFileNodes( new DirectoryIterator( $node->getPathname() ) );
				}
				else if ( $node->isFile() )	{
					$file = $node->getFilename();
					$path = $node->getPathname();
					$data[] = $file;
					$check = file_get_contents( $path );
					if( lookForString( $check, $searchTerm, $caseSensitive ) ) {
						echo '<div id="entry'. $cnt . '" class="entry" onclick="viewCode(\'entry' . $cnt . '\');">' . $path . "</div>";
						$cnt++;
					}

				}
			}
			return $data;
		}
		
		function lookForString ( $check, $searchTerm, $caseSensitive = false ) {
			if( $caseSensitive ) {
				$found = strpos ( $check, $searchTerm );
			} else {
				$found = stripos ( $check, $searchTerm );
			}
			
			if( $found === false ) return false; else return true;
		
		}
		
		$fileData = fillArrayWithFileNodes( new DirectoryIterator( dirname(__FILE__) ) );		

	}
?>
	</div>
	<p>END</p>
	<div id="codeWindow" onclick="$(this).css('visibility','hidden');">
	</div>
	</body>
</html>

finder_getsource.php

<?php

/* 
	
	finder_getsource.php

	This is the AJAX handler for finder.php.
	
	A GET request is made with path=PATH/AND/FILENAME
	
	This utility returns the formatted contents of the file.
	
*/



if( $path = $_GET['path'] ) { // Make sure its a valid call
	
	if( file_exists( $path ) ) { // Make sure the file exists
		
		$info = pathinfo( $path ); // Retrieve info on the file
		
	
		$code = file_get_contents( $path ); // Get the file contents
		
		// Code Prettifier will take care of php, js and html extensions, otherwise remove html
		if( $info['extension'] != 'js' || $info['extension'] != 'html' || $info['extension'] != 'php') {
			$code = htmlspecialchars( $code ); // Convert into html entities
		}
		
		echo '<div class="title">' . $path . '</div> <div> </div><pre class="prettyprint">' . $code . '</pre>'; // Put the path as a title and format the file as code
	

		
	} else echo "Error (No such File)";
	
} else echo "Error (Bad Request)";
		

prettify.css

/**
 * @license
 * Copyright (C) 2015 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* Pretty printing styles. Used with prettify.js. */


/* SPAN elements with the classes below are added by prettyprint. */
.pln { color: #000 }  /* plain text */

@media screen {
  .str { color: #080 }  /* string content */
  .kwd { color: #008 }  /* a keyword */
  .com { color: #800 }  /* a comment */
  .typ { color: #606 }  /* a type name */
  .lit { color: #066 }  /* a literal value */
  /* punctuation, lisp open bracket, lisp close bracket */
  .pun, .opn, .clo { color: #660 }
  .tag { color: #008 }  /* a markup tag name */
  .atn { color: #606 }  /* a markup attribute name */
  .atv { color: #080 }  /* a markup attribute value */
  .dec, .var { color: #606 }  /* a declaration; a variable name */
  .fun { color: red }  /* a function name */
}

/* Use higher contrast and text-weight for printable form. */
@media print, projection {
  .str { color: #060 }
  .kwd { color: #006; font-weight: bold }
  .com { color: #600; font-style: italic }
  .typ { color: #404; font-weight: bold }
  .lit { color: #044 }
  .pun, .opn, .clo { color: #440 }
  .tag { color: #006; font-weight: bold }
  .atn { color: #404 }
  .atv { color: #060 }
}

/* Put a border around prettyprinted code snippets. */
pre.prettyprint { padding: 2px; border: 1px solid #888 }

/* Specify class=linenums on a pre to get line numbering */
ol.linenums { margin-top: 0; margin-bottom: 0 } /* IE indents via margin-left */
li.L0,
li.L1,
li.L2,
li.L3,
li.L5,
li.L6,
li.L7,
li.L8 { list-style-type: none }
/* Alternate shading for lines */
li.L1,
li.L3,
li.L5,
li.L7,
li.L9 { background: #eee }

One thought on “Finder – A PHP Utility for Finding a Text String in a Complex Directory Structure

Leave a Reply

Your email address will not be published. Required fields are marked *