Created the scanner app, along with two simple modules. The parser is now event driven... use of the parsed_objects array is now deprecated and will be removed once the old function call report that relies on it is replaced. Mantis: 2691
git-svn-id: file:///srv/svn/scanner/trunk@2 a0501263-5b7a-4423-a8ba-1edf086583e7
This commit is contained in:
parent
4244ed6d8b
commit
d53e4e74f1
6 changed files with 241 additions and 33 deletions
12
modules/functions.php
Normal file
12
modules/functions.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
class FunctionsModule extends ScannerModule {
|
||||
function FunctionsModule() {
|
||||
$this->ScannerModule();
|
||||
}
|
||||
function preScan( $filename ) {
|
||||
parent::preScan( $filename );
|
||||
}
|
||||
}
|
||||
|
||||
$modules[] = new FunctionsModule();
|
||||
?>
|
28
modules/lint.php
Normal file
28
modules/lint.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
class LintModule extends ScannerModule {
|
||||
function LintModule() {
|
||||
$this->ScannerModule();
|
||||
}
|
||||
function preScan( $filename ) {
|
||||
parent::preScan( $filename );
|
||||
$output = array();
|
||||
exec( "php -l '$filename' 2>/dev/null", $output, $result );
|
||||
if( $result != 0 ) {
|
||||
foreach( $output as $linterror ) {
|
||||
$matches = array();
|
||||
if( preg_match( '/error:.*?on line (\d+)$/i', $linterror, $matches ) == 0 ) { continue; }
|
||||
$this->fault(
|
||||
array(
|
||||
'file' => $filename,
|
||||
'line' => $matches[1]
|
||||
),
|
||||
FAULT_MAJOR,
|
||||
$linterror
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$modules[] = new LintModule();
|
||||
?>
|
45
modules/pattern.php
Normal file
45
modules/pattern.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
class PatternModule extends ScannerModule {
|
||||
var $filters = array(
|
||||
array(
|
||||
'type' => PHPPARSER_EXPRESSION,
|
||||
'desc' => 'Echoing Sql',
|
||||
'level' => FAULT_MEDIUM,
|
||||
'pattern' => '/echo[\(\s].*?\$sql/i'
|
||||
),
|
||||
array(
|
||||
'type' => PHPPARSER_LANGUAGE_CONSTRUCT,
|
||||
'desc' => 'Evil Eval',
|
||||
'level' => FAULT_MEDIUM,
|
||||
'pattern' => '/^eval$/i'
|
||||
),
|
||||
array(
|
||||
'type' => PHPPARSER_FUNCTION_CALL,
|
||||
'desc' => 'PRINT_R or VAR_DUMP',
|
||||
'level' => FAULT_MEDIUM,
|
||||
'pattern' => '/^(print_r|var_dump)$/i'
|
||||
),
|
||||
array(
|
||||
'type' => PHPPARSER_EXPRESSION,
|
||||
'desc' => 'Developer Email',
|
||||
'level' => FAULT_MINOR,
|
||||
'pattern' => '/(?<!dev|qa)@payquik\.com/'
|
||||
),
|
||||
);
|
||||
|
||||
function PatternModule() {
|
||||
$this->ScannerModule();
|
||||
}
|
||||
function parserCallback( $object ) {
|
||||
foreach( $this->filters as $filter ) {
|
||||
if( $object['type'] == $filter['type'] ) {
|
||||
if( preg_match( $filter['pattern'], $object['name'] ) > 0 ) {
|
||||
$this->fault( $object, $filter['level'], "Triggered Filter '{$filter['desc']}'" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$modules[] = new PatternModule();
|
||||
?>
|
74
parser.php
74
parser.php
|
@ -16,8 +16,6 @@ define( 'PHPPARSER_INCLUDE', 4 );
|
|||
define( 'PHPPARSER_EXPRESSION', 5 );
|
||||
define( 'PHPPARSER_LANGUAGE_CONSTRUCT', 6 );
|
||||
|
||||
|
||||
|
||||
class PHPParser {
|
||||
|
||||
var $fetch_mode;
|
||||
|
@ -25,11 +23,13 @@ class PHPParser {
|
|||
var $internal_functions;
|
||||
var $defined_functions;
|
||||
var $parsed_objects;
|
||||
var $callbacks;
|
||||
|
||||
function PHPParser( $fetch_mode = PHPPARSER_FETCH_ALL, $functionlist = array() ) {
|
||||
$this->reset( $fetch_mode );
|
||||
$this->internal_functions = get_defined_functions(); $this->internal_functions = $this->internal_functions['internal'];
|
||||
$this->addFunctionDefinitions( $functionlist );
|
||||
$this->callbacks = array();
|
||||
}
|
||||
|
||||
function reset( $fetch_mode = null ) {
|
||||
|
@ -47,6 +47,28 @@ class PHPParser {
|
|||
}
|
||||
}
|
||||
|
||||
function registerCallback( $function_name, $fetch_mode = PHPPARSER_FETCH_ALL ) {
|
||||
if( function_exists( $function_name ) ) {
|
||||
$this->callbacks[] = array(
|
||||
'function' => $function_name,
|
||||
'fetch' => $fetch_mode
|
||||
);
|
||||
return count( $this->callbacks );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function foundObject( $object ) {
|
||||
if( !is_array( $object ) ) {
|
||||
// Now how the hell did that happen?
|
||||
return false;
|
||||
}
|
||||
foreach( $this->callbacks as $callback ) {
|
||||
call_user_func( $callback['function'], $object );
|
||||
}
|
||||
$this->parsed_objects[] = $object;
|
||||
}
|
||||
|
||||
function parseFile( $file ) {
|
||||
if( !file_exists( $file ) || !is_readable( $file ) ) { return false; }
|
||||
|
@ -76,7 +98,7 @@ class PHPParser {
|
|||
foreach( $tokens as $token ) {
|
||||
//echo ( is_string( $token ) ? 'CHAR: ' . $token : token_name( $token[0] ) . ': ' . $token[1] ) . "\n";
|
||||
if( !in_array( 0, $open_blocks ) ) {
|
||||
echo "PARSER ERROR: LOST ZERO BLOCK AT FILE {$this->file_name} LINE $line\n";
|
||||
//echo "PARSER ERROR: LOST ZERO BLOCK AT FILE {$this->file_name} LINE $line\n";
|
||||
$open_blocks = array_merge( array(0), $open_blocks );
|
||||
}
|
||||
$classname = isset( $class['name'] ) ? $class['name'] : '';
|
||||
|
@ -121,7 +143,7 @@ class PHPParser {
|
|||
&& $last_token != T_NEW
|
||||
//&& ( (bool)($this->fetch_mode & PHPPARSER_FETCH_INTERNAL) && !in_array( $string, $this->internal_functions ) )
|
||||
) {
|
||||
$this->parsed_objects[] = array(
|
||||
$this->foundObject( array(
|
||||
'type' => PHPPARSER_FUNCTION_CALL,
|
||||
'name' => strtolower( $string ),
|
||||
'file' => $this->file_name,
|
||||
|
@ -131,23 +153,23 @@ class PHPParser {
|
|||
'in_class' => $classname,
|
||||
'in_function' => $functionname,
|
||||
'open_blocks' => $open_blocks
|
||||
);
|
||||
) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
if( !in_array( $token, array( '(', ')' ) ) ) { $last_token = null; }
|
||||
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_EXPRESSIONS) && in_array( $token, array( '{', '}', ';', '=', '?', ':' ) ) && strlen( trim( $expression ) ) > 0 ) {
|
||||
$this->parsed_objects[] = array(
|
||||
'type' => PHPPARSER_EXPRESSION,
|
||||
'name' => trim( $expression ),
|
||||
'file' => $this->file_name,
|
||||
'line' => $line,
|
||||
'block' => $block,
|
||||
'depth' => $depth,
|
||||
'in_class' => $classname,
|
||||
'in_function' => $functionname,
|
||||
'open_blocks' => $open_blocks
|
||||
);
|
||||
$this->foundObject( array(
|
||||
'type' => PHPPARSER_EXPRESSION,
|
||||
'name' => trim( $expression ),
|
||||
'file' => $this->file_name,
|
||||
'line' => $line,
|
||||
'block' => $block,
|
||||
'depth' => $depth,
|
||||
'in_class' => $classname,
|
||||
'in_function' => $functionname,
|
||||
'open_blocks' => $open_blocks
|
||||
) );
|
||||
$expression = '';
|
||||
} else { $expression .= $token; }
|
||||
} else {
|
||||
|
@ -165,7 +187,7 @@ class PHPParser {
|
|||
switch( $last_token ) {
|
||||
case T_CLASS:
|
||||
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_CLASSES) ) {
|
||||
$this->parsed_objects[] = array(
|
||||
$this->foundObject( array(
|
||||
'type' => PHPPARSER_CLASS_DEF,
|
||||
'name' => strtolower( $string ),
|
||||
'file' => $this->file_name,
|
||||
|
@ -174,13 +196,13 @@ class PHPParser {
|
|||
'depth' => $depth,
|
||||
'in_class' => $classname,
|
||||
'in_function' => $functionname
|
||||
);
|
||||
) );
|
||||
}
|
||||
$class = array( 'name' => $text, 'block' => $block );
|
||||
break;
|
||||
case T_FUNCTION:
|
||||
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_FUNCTIONS) && empty( $classname ) ) { // Not interested in member function definitions
|
||||
$this->parsed_objects[] = array(
|
||||
$this->foundObject( array(
|
||||
'type' => PHPPARSER_FUNCTION_DEF,
|
||||
'name' => strtolower( $string ),
|
||||
'file' => $this->file_name,
|
||||
|
@ -189,7 +211,7 @@ class PHPParser {
|
|||
'depth' => $depth,
|
||||
'in_class' => $classname,
|
||||
'in_function' => $functionname
|
||||
);
|
||||
) );
|
||||
}
|
||||
$function = array( 'name' => $text, 'block' => $block );
|
||||
break;
|
||||
|
@ -202,7 +224,7 @@ class PHPParser {
|
|||
case T_REQUIRE:
|
||||
case T_REQUIRE_ONCE:
|
||||
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_INCLUDES) ) {
|
||||
$this->parsed_objects[] = array(
|
||||
$this->foundObject( array(
|
||||
'type' => PHPPARSER_INCLUDE,
|
||||
'name' => trim( str_replace( '\'', '', $text ) ),
|
||||
'file' => $this->file_name,
|
||||
|
@ -211,7 +233,7 @@ class PHPParser {
|
|||
'depth' => $depth,
|
||||
'in_class' => $classname,
|
||||
'in_function' => $functionname
|
||||
);
|
||||
) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -236,7 +258,7 @@ class PHPParser {
|
|||
//*/
|
||||
case T_EVAL:
|
||||
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_INTERNAL) ) {
|
||||
$this->parsed_objects[] = array(
|
||||
$this->foundObject( array(
|
||||
'type' => PHPPARSER_FUNCTION_CALL,
|
||||
'name' => $text,
|
||||
'file' => $this->file_name,
|
||||
|
@ -245,7 +267,7 @@ class PHPParser {
|
|||
'depth' => $depth,
|
||||
'in_class' => $classname,
|
||||
'in_function' => $functionname
|
||||
);
|
||||
) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -266,7 +288,7 @@ class PHPParser {
|
|||
T_REQUIRE_ONCE,
|
||||
T_UNSET
|
||||
) ) ) {
|
||||
$this->parsed_objects[] = array(
|
||||
$this->foundObject( array(
|
||||
'type' => PHPPARSER_LANGUAGE_CONSTRUCT,
|
||||
'name' => $text,
|
||||
'file' => $this->file_name,
|
||||
|
@ -275,7 +297,7 @@ class PHPParser {
|
|||
'depth' => $depth,
|
||||
'in_class' => $classname,
|
||||
'in_function' => $functionname
|
||||
);
|
||||
) );
|
||||
}
|
||||
$expression .= ( in_array( $id, array(
|
||||
/* Values */
|
||||
|
|
98
scanner.php
Normal file
98
scanner.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
require_once( 'parser.php' );
|
||||
|
||||
/*
|
||||
define( 'FAULT_MINOR', 0 );
|
||||
define( 'FAULT_MEDIUM', 1 );
|
||||
define( 'FAULT_MAJOR', 2 );
|
||||
*/
|
||||
|
||||
$modules = array();
|
||||
|
||||
class ScannerModule {
|
||||
var $faults;
|
||||
var $blame;
|
||||
|
||||
function ScannerModule() {
|
||||
$this->faults = array();
|
||||
$this->blame = array();
|
||||
}
|
||||
function fault( $object, $level, $reason = '' ) {
|
||||
$this->faults[] = array(
|
||||
'object' => $object,
|
||||
'level' => $level,
|
||||
'reason' => $reason,
|
||||
'svn' => $this->blame[$object['line']]
|
||||
);
|
||||
}
|
||||
function parserCallback( $object ) {
|
||||
}
|
||||
function preScan( $filename ) {
|
||||
$this->blame = array();
|
||||
$output = array();
|
||||
exec( "svn blame '$filename' 2>/dev/null", $output, $result );
|
||||
if( $result == 0 ) {
|
||||
foreach( $output as $line => $text ) {
|
||||
$matches = array();
|
||||
preg_match( '/^\s*(\d+)\s+([^\s]+)/', $text, $matches );
|
||||
$this->blame[$line + 1] = array(
|
||||
'author' => $matches[2],
|
||||
'revision' => $matches[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
function postScan( $filename ) {
|
||||
}
|
||||
}
|
||||
|
||||
function _callback( $object ) {
|
||||
global $modules;
|
||||
foreach( $modules as $module ) {
|
||||
$module->parserCallback( $object );
|
||||
}
|
||||
}
|
||||
|
||||
// Dig into the modules folder and load up what we find
|
||||
$module_files = scandir( 'modules' );
|
||||
foreach( $module_files as $module_file ) {
|
||||
if( strtolower( substr( $module_file, -4 ) ) == '.php' ) {
|
||||
require_once( "modules/{$module_file}" );
|
||||
}
|
||||
}
|
||||
|
||||
$parser = new PHPParser( PHPPARSER_FETCH_EXPRESSIONS | PHPPARSER_FETCH_CALLS | PHPPARSER_FETCH_INTERNAL | PHPPARSER_FETCH_CONSTRUCTS );
|
||||
$parser->registerCallback( '_callback' );
|
||||
|
||||
$files = array();
|
||||
for( $i = 1; $i < $argc; $i++ ) {
|
||||
if( file_exists( $argv[$i] ) && strtolower( substr( $argv[$i], -4 ) ) == '.php' ) {
|
||||
$files[] = $argv[$i];
|
||||
}
|
||||
}
|
||||
|
||||
if( count( $files ) == 0 ) {
|
||||
die( "No files were specified.\n" );
|
||||
}
|
||||
|
||||
foreach( $files as $file ) {
|
||||
foreach( $modules as $module ) { $module->preScan( $file ); }
|
||||
$parser->parseFile( $file );
|
||||
}
|
||||
|
||||
foreach( $modules as $module ) {
|
||||
foreach( $module->faults as $fault ) {
|
||||
$svn = serialize( $fault['svn'] );
|
||||
printf( "%s %s - %s\n -- %s:%d, %s r%d\n",
|
||||
get_class( $module ),
|
||||
$fault['level'],
|
||||
$fault['reason'],
|
||||
$fault['object']['file'],
|
||||
$fault['object']['line'],
|
||||
isset( $fault['svn']['author'] ) ? $fault['svn']['author'] : '?',
|
||||
isset( $fault['svn']['revision'] ) ? $fault['svn']['revision'] : '?'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
13
test.php
13
test.php
|
@ -1,5 +1,4 @@
|
|||
<?php
|
||||
|
||||
require_once( 'parser.php' );
|
||||
|
||||
$filters = array(
|
||||
|
@ -25,19 +24,22 @@ $filters = array(
|
|||
),
|
||||
);
|
||||
|
||||
$parser = new PHPParser( PHPPARSER_FETCH_EXPRESSIONS | PHPPARSER_FETCH_CALLS | PHPPARSER_FETCH_INTERNAL | PHPPARSER_FETCH_CONSTRUCTS );
|
||||
$parser->parseFile( __FILE__ );
|
||||
function test( $object ) {
|
||||
global $filters;
|
||||
|
||||
foreach( $parser->parsed_objects as $object ) {
|
||||
foreach( $filters as $filter ) {
|
||||
if( $object['type'] == $filter['type'] ) {
|
||||
if( preg_match( $filter['pattern'], $object['name'] ) > 0 ) {
|
||||
echo "Triggered Filter '{$filter['desc']}' at line {$object['line']}\n";
|
||||
echo "fn: Triggered Filter '{$filter['desc']}' at line {$object['line']}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$parser = new PHPParser( PHPPARSER_FETCH_EXPRESSIONS | PHPPARSER_FETCH_CALLS | PHPPARSER_FETCH_INTERNAL | PHPPARSER_FETCH_CONSTRUCTS );
|
||||
$parser->registerCallback( 'test' );
|
||||
$parser->parseFile( __FILE__ );
|
||||
|
||||
$sql = "select * from failure";
|
||||
echo "Here's the $sql!\n";
|
||||
mail( 'correl@payquik.com', 'subject', 'stuffs' );
|
||||
|
@ -45,6 +47,7 @@ eval( "echo \"here's eval!\n\";" );
|
|||
print_r( $sql );
|
||||
var_dump( $sql );
|
||||
echo "done\n";
|
||||
}}}
|
||||
|
||||
/* OUTPUT:
|
||||
|
||||
|
|
Loading…
Reference in a new issue