Parser modified to return inline html, variables and variable assignment. Included some phpunit tests. Assignment fetching is good, but not perfect (doesn't handle array index assignment or nested expressions yet, see tests for more information). Therefore, the variable scan is not enabled by default yet. Mantis: 2691
git-svn-id: file:///srv/svn/scanner/trunk@19 a0501263-5b7a-4423-a8ba-1edf086583e7
This commit is contained in:
parent
a3dfc6d2b8
commit
08bc427e4a
12 changed files with 432 additions and 23 deletions
|
@ -83,12 +83,15 @@ class FunctionsModule extends ScannerModule {
|
||||||
case PHPPARSER_INCLUDE:
|
case PHPPARSER_INCLUDE:
|
||||||
$this->included_files[] = $object;
|
$this->included_files[] = $object;
|
||||||
if( $object['name'] == 'global.php' ) {
|
if( $object['name'] == 'global.php' ) {
|
||||||
$object['name'] = 'libs/security/lib_security_input.php';
|
$global_includes = array(
|
||||||
$this->included_files[] = $object;
|
'libs/security/lib_security_input.php',
|
||||||
$object['name'] = 'libs/get/lib_get_portal.php';
|
'libs/get/lib_get_portal.php',
|
||||||
$this->included_files[] = $object;
|
'libs/logging/lib_logging_errors.php',
|
||||||
$object['name'] = 'libs/logging/lib_logging_errors.php';
|
);
|
||||||
$this->included_files[] = $object;
|
foreach ($global_includes as $global_include) {
|
||||||
|
$object['name'] = $global_include;
|
||||||
|
$this->included_files[] = $object;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PHPPARSER_FUNCTION_CALL:
|
case PHPPARSER_FUNCTION_CALL:
|
||||||
|
|
36
modules/scanner_variables.php
Normal file
36
modules/scanner_variables.php
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
class VariableModule extends ScannerModule {
|
||||||
|
private $assigned_variables = array();
|
||||||
|
private $captured = array();
|
||||||
|
|
||||||
|
function VariableModule() {
|
||||||
|
$this->ScannerModule();
|
||||||
|
}
|
||||||
|
function parserCallback( $object ) {
|
||||||
|
$pattern = '/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/';
|
||||||
|
$matches = array();
|
||||||
|
$scope = "{$object['in_class']}::{$object['in_function']}";
|
||||||
|
if (!isset($this->assigned_variables[$scope] ) )
|
||||||
|
$this->assigned_variables[$scope] = array();
|
||||||
|
if ($object['type'] == PHPPARSER_ASSIGNMENT) {
|
||||||
|
//$this->fault($object, 0, "Assignment: {$object['name']}");
|
||||||
|
list($variable, $value) = explode('=', $object['name']);
|
||||||
|
$this->assigned_variables[$scope][] = $variable;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
$object['type'] == PHPPARSER_VARIABLE
|
||||||
|
// Cannot yet accurately scan the global scope, so functions only
|
||||||
|
&& !empty($object['in_function'])
|
||||||
|
&& !in_array($object['name'], $this->assigned_variables[$scope])
|
||||||
|
&& !in_array($object['name'], array(
|
||||||
|
// Superglobals are exempt, obviously
|
||||||
|
'$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV'
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
$this->fault($object, FAULT_MEDIUM, "Undefined Variable: {$object['name']}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addModule( new VariableModule() );
|
||||||
|
?>
|
155
parser.php
155
parser.php
|
@ -6,6 +6,7 @@ define( 'PHPPARSER_FETCH_INCLUDES', 8 );
|
||||||
define( 'PHPPARSER_FETCH_INTERNAL', 16 );
|
define( 'PHPPARSER_FETCH_INTERNAL', 16 );
|
||||||
define( 'PHPPARSER_FETCH_CONSTRUCTS', 32 );
|
define( 'PHPPARSER_FETCH_CONSTRUCTS', 32 );
|
||||||
define( 'PHPPARSER_FETCH_EXPRESSIONS', 64 );
|
define( 'PHPPARSER_FETCH_EXPRESSIONS', 64 );
|
||||||
|
define( 'PHPPARSER_FETCH_INLINE_HTML', 128 );
|
||||||
|
|
||||||
define( 'PHPPARSER_FETCH_ALL', 65535 );
|
define( 'PHPPARSER_FETCH_ALL', 65535 );
|
||||||
|
|
||||||
|
@ -15,6 +16,9 @@ define( 'PHPPARSER_FUNCTION_CALL', 3 );
|
||||||
define( 'PHPPARSER_INCLUDE', 4 );
|
define( 'PHPPARSER_INCLUDE', 4 );
|
||||||
define( 'PHPPARSER_EXPRESSION', 5 );
|
define( 'PHPPARSER_EXPRESSION', 5 );
|
||||||
define( 'PHPPARSER_LANGUAGE_CONSTRUCT', 6 );
|
define( 'PHPPARSER_LANGUAGE_CONSTRUCT', 6 );
|
||||||
|
define( 'PHPPARSER_INLINE_HTML', 7 );
|
||||||
|
define( 'PHPPARSER_VARIABLE', 8 );
|
||||||
|
define( 'PHPPARSER_ASSIGNMENT', 9 );
|
||||||
|
|
||||||
class PHPParser {
|
class PHPParser {
|
||||||
|
|
||||||
|
@ -46,7 +50,7 @@ class PHPParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerCallback( $function_name, $fetch_mode = PHPPARSER_FETCH_ALL ) {
|
function registerCallback( $function_name, $fetch_mode = PHPPARSER_FETCH_ALL ) {
|
||||||
if( function_exists( $function_name ) ) {
|
if( is_callable( $function_name ) ) {
|
||||||
$this->callbacks[] = array(
|
$this->callbacks[] = array(
|
||||||
'function' => $function_name,
|
'function' => $function_name,
|
||||||
'fetch' => $fetch_mode
|
'fetch' => $fetch_mode
|
||||||
|
@ -83,15 +87,19 @@ class PHPParser {
|
||||||
|
|
||||||
$class = $classname = null;
|
$class = $classname = null;
|
||||||
$function = $functionname = null;
|
$function = $functionname = null;
|
||||||
|
$string = $last_token = null;
|
||||||
$switch = array();
|
$switch = array();
|
||||||
$expression = '';
|
$expression = '';
|
||||||
|
$assignment = false;
|
||||||
$line_text = '';
|
$line_text = '';
|
||||||
|
|
||||||
$internal_functions = get_defined_functions(); $internal_functions = $internal_functions['internal'];
|
$internal_functions = get_defined_functions(); $internal_functions = $internal_functions['internal'];
|
||||||
$local_functions = array();
|
$local_functions = array();
|
||||||
$local_classes = array();
|
$local_classes = array();
|
||||||
|
$variables = array();
|
||||||
$open_blocks = array( 0 );
|
$open_blocks = array( 0 );
|
||||||
$in_string = false;
|
$in_string = false;
|
||||||
|
$buffer = array(T_INLINE_HTML => '');
|
||||||
foreach( $tokens as $token ) {
|
foreach( $tokens as $token ) {
|
||||||
//echo ( is_string( $token ) ? 'CHAR: ' . $token : token_name( $token[0] ) . ': ' . $token[1] ) . "\n";
|
//echo ( is_string( $token ) ? 'CHAR: ' . $token : token_name( $token[0] ) . ': ' . $token[1] ) . "\n";
|
||||||
if( !in_array( 0, $open_blocks ) ) {
|
if( !in_array( 0, $open_blocks ) ) {
|
||||||
|
@ -105,7 +113,7 @@ class PHPParser {
|
||||||
}
|
}
|
||||||
if( $in_string ) {
|
if( $in_string ) {
|
||||||
$expression .= is_string( $token ) ? $token : $token[1];
|
$expression .= is_string( $token ) ? $token : $token[1];
|
||||||
$count = preg_match_all( '/\r?(\n|\r)/', is_string( $token ) ? $token : $token[1], $m );
|
$count = preg_match_all( '/\r?(\n|\r)/', is_string( $token ) ? $token : $token[1], $m = array() );
|
||||||
$line += $count;
|
$line += $count;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -155,6 +163,11 @@ class PHPParser {
|
||||||
) );
|
) );
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ')':
|
||||||
|
$in_function_params = false;
|
||||||
|
$in_foreach_params = false;
|
||||||
|
$in_list = false;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
/* Should be able to add a hook here later on to catch the use of defines,
|
/* Should be able to add a hook here later on to catch the use of defines,
|
||||||
which are basically just T_STRINGs that PHP can't find anything else to
|
which are basically just T_STRINGs that PHP can't find anything else to
|
||||||
|
@ -163,22 +176,81 @@ class PHPParser {
|
||||||
$string = null;
|
$string = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if( !in_array( $token, array( '(', ')' ) ) ) { $last_token = null; }
|
if (
|
||||||
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_EXPRESSIONS) && in_array( $token, array( '{', '}', ';', '=', '?', ':' ) ) && strlen( trim( $expression ) ) > 0 ) {
|
(bool)($this->fetch_mode & PHPPARSER_FETCH_EXPRESSIONS)
|
||||||
|
&& (
|
||||||
|
(($in_function_params || $in_foreach_params) && in_array( $token, array( '{', '}', '(', ')', ';', ',', '?', ':' ) ))
|
||||||
|
|| in_array( $token, array( '{', '}', ';', '=', '?', ':' ) )
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
$expression = trim($expression);
|
||||||
|
if ($assignment) {
|
||||||
|
// If this was a list assignment, we've got an array of variables to mark as assigned at once!
|
||||||
|
$variable = is_array($assignment) ? $assignment : array($assignment);
|
||||||
|
foreach ($variable as $var) {
|
||||||
|
$this->foundObject( array(
|
||||||
|
'type' => PHPPARSER_ASSIGNMENT,
|
||||||
|
'name' => "$var=$expression",
|
||||||
|
'file' => $this->file_name,
|
||||||
|
'context' => $lines[$line - 1],
|
||||||
|
'line' => $line,
|
||||||
|
'block' => $block,
|
||||||
|
'depth' => $depth,
|
||||||
|
'in_class' => $classname,
|
||||||
|
'in_function' => $functionname
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
$assignment = false;
|
||||||
|
}
|
||||||
|
elseif (strlen($expression) > 0) {
|
||||||
|
foreach($variables as $var) {
|
||||||
|
}
|
||||||
|
$this->foundObject( array(
|
||||||
|
'type' => PHPPARSER_EXPRESSION,
|
||||||
|
'name' => $expression,
|
||||||
|
'file' => $this->file_name,
|
||||||
|
'context' => $lines[$line - 1],
|
||||||
|
'line' => $line,
|
||||||
|
'block' => $block,
|
||||||
|
'depth' => $depth,
|
||||||
|
'in_class' => $classname,
|
||||||
|
'in_function' => $functionname,
|
||||||
|
'open_blocks' => $open_blocks
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
$expression = '';
|
||||||
|
$variable_declaration = false;
|
||||||
|
$assignment = ('=' == $token ? array_pop($variables) : false);
|
||||||
|
while (count($variables) > 0) {
|
||||||
|
$variable = array_pop($variables);
|
||||||
|
$this->foundObject( array(
|
||||||
|
'type' => PHPPARSER_VARIABLE,
|
||||||
|
'name' => $variable,
|
||||||
|
'file' => $this->file_name,
|
||||||
|
'context' => $lines[$line - 1],
|
||||||
|
'line' => $line,
|
||||||
|
'block' => $block,
|
||||||
|
'depth' => $depth,
|
||||||
|
'in_class' => $classname,
|
||||||
|
'in_function' => $functionname
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
} else { $expression .= $token; }
|
||||||
|
if( !empty($buffer[T_INLINE_HTML]) && (bool)($this->fetch_mode & PHPPARSER_FETCH_INLINE_HTML) ) {
|
||||||
$this->foundObject( array(
|
$this->foundObject( array(
|
||||||
'type' => PHPPARSER_EXPRESSION,
|
'type' => PHPPARSER_INLINE_HTML,
|
||||||
'name' => trim( $expression ),
|
'name' => $buffer[T_INLINE_HTML],
|
||||||
'file' => $this->file_name,
|
'file' => $this->file_name,
|
||||||
'context' => $lines[$line - 1],
|
'context' => $lines[$line - 1],
|
||||||
'line' => $line,
|
'line' => $line,
|
||||||
'block' => $block,
|
'block' => $block,
|
||||||
'depth' => $depth,
|
'depth' => $depth,
|
||||||
'in_class' => $classname,
|
'in_class' => $classname,
|
||||||
'in_function' => $functionname,
|
'in_function' => $functionname
|
||||||
'open_blocks' => $open_blocks
|
|
||||||
) );
|
) );
|
||||||
$expression = '';
|
$buffer[T_INLINE_HTML] = '';
|
||||||
} else { $expression .= $token; }
|
if( !in_array( $token, array( '(', ')' ) ) ) { $last_token = null; }
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
list($id, $text) = $token;
|
list($id, $text) = $token;
|
||||||
switch( $id ) {
|
switch( $id ) {
|
||||||
|
@ -209,6 +281,7 @@ class PHPParser {
|
||||||
$class = array( 'name' => $text, 'block' => $block );
|
$class = array( 'name' => $text, 'block' => $block );
|
||||||
break;
|
break;
|
||||||
case T_FUNCTION:
|
case T_FUNCTION:
|
||||||
|
$variable_declaration = false;
|
||||||
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_FUNCTIONS) && empty( $classname ) ) { // Not interested in member function definitions
|
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_FUNCTIONS) && empty( $classname ) ) { // Not interested in member function definitions
|
||||||
$this->foundObject( array(
|
$this->foundObject( array(
|
||||||
'type' => PHPPARSER_FUNCTION_DEF,
|
'type' => PHPPARSER_FUNCTION_DEF,
|
||||||
|
@ -223,6 +296,7 @@ class PHPParser {
|
||||||
) );
|
) );
|
||||||
}
|
}
|
||||||
$function = array( 'name' => $text, 'block' => $block );
|
$function = array( 'name' => $text, 'block' => $block );
|
||||||
|
$in_function_params = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -248,6 +322,45 @@ class PHPParser {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case T_GLOBAL:
|
||||||
|
case T_VAR:
|
||||||
|
case T_PRIVATE:
|
||||||
|
case T_PUBLIC:
|
||||||
|
case T_PROTECTED:
|
||||||
|
case T_STATIC:
|
||||||
|
$variable_declaration = true;
|
||||||
|
break;
|
||||||
|
case T_LIST:
|
||||||
|
$in_list = true;
|
||||||
|
$variables[] = array();
|
||||||
|
break;
|
||||||
|
case T_VARIABLE:
|
||||||
|
if ($in_list) {
|
||||||
|
$variables[count($variables) - 1][] = $text;
|
||||||
|
} else {
|
||||||
|
$variables[] = $text;
|
||||||
|
}
|
||||||
|
if( (bool)($this->fetch_mode & PHPPARSER_FETCH_EXPRESSIONS) ) {
|
||||||
|
if (
|
||||||
|
$variable_declaration
|
||||||
|
|| in_array($last_token, array(T_AS))
|
||||||
|
|| ($in_foreach_params && T_DOUBLE_ARROW == $last_token)
|
||||||
|
|| $in_function_params
|
||||||
|
) {
|
||||||
|
$this->foundObject( array(
|
||||||
|
'type' => PHPPARSER_ASSIGNMENT,
|
||||||
|
'name' => "$text=$text",
|
||||||
|
'file' => $this->file_name,
|
||||||
|
'context' => $lines[$line - 1],
|
||||||
|
'line' => $line,
|
||||||
|
'block' => $block,
|
||||||
|
'depth' => $depth,
|
||||||
|
'in_class' => $classname,
|
||||||
|
'in_function' => $functionname
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
///*
|
///*
|
||||||
case T_SWITCH:
|
case T_SWITCH:
|
||||||
$block_count++;
|
$block_count++;
|
||||||
|
@ -265,6 +378,14 @@ class PHPParser {
|
||||||
array_push( $open_blocks, $block );
|
array_push( $open_blocks, $block );
|
||||||
$depth = count( $open_blocks ) - 1;
|
$depth = count( $open_blocks ) - 1;
|
||||||
break;
|
break;
|
||||||
|
case T_AS:
|
||||||
|
$in_foreach_params = true;
|
||||||
|
break;
|
||||||
|
case T_INLINE_HTML:
|
||||||
|
// NOTE: There seems to be a string limit of around 400 characters, which is very easy to reach with this token
|
||||||
|
// We'll get around it by combining adjacent token results
|
||||||
|
$buffer[T_INLINE_HTML] .= $text;
|
||||||
|
break;
|
||||||
//*/
|
//*/
|
||||||
}
|
}
|
||||||
if( !in_array( $id, array( T_WHITESPACE, /*T_COMMENT, T_DOC_COMMENT,*/ T_STRING ) ) ) { $string = null; $last_token = $id; }
|
if( !in_array( $id, array( T_WHITESPACE, /*T_COMMENT, T_DOC_COMMENT,*/ T_STRING ) ) ) { $string = null; $last_token = $id; }
|
||||||
|
@ -355,6 +476,20 @@ class PHPParser {
|
||||||
) );
|
) );
|
||||||
$expression = '';
|
$expression = '';
|
||||||
}
|
}
|
||||||
|
if( $id != T_INLINE_HTML && !empty($buffer[T_INLINE_HTML]) && (bool)($this->fetch_mode & PHPPARSER_FETCH_INLINE_HTML) ) {
|
||||||
|
$this->foundObject( array(
|
||||||
|
'type' => PHPPARSER_INLINE_HTML,
|
||||||
|
'name' => $buffer[T_INLINE_HTML],
|
||||||
|
'file' => $this->file_name,
|
||||||
|
'context' => $lines[$line - 1],
|
||||||
|
'line' => $line,
|
||||||
|
'block' => $block,
|
||||||
|
'depth' => $depth,
|
||||||
|
'in_class' => $classname,
|
||||||
|
'in_function' => $functionname
|
||||||
|
) );
|
||||||
|
$buffer[T_INLINE_HTML] = '';
|
||||||
|
}
|
||||||
$count = preg_match_all( '/\r?(\n|\r)/', $text, $m );
|
$count = preg_match_all( '/\r?(\n|\r)/', $text, $m );
|
||||||
$line += $count;
|
$line += $count;
|
||||||
}
|
}
|
||||||
|
|
12
scanner.php
12
scanner.php
|
@ -190,7 +190,7 @@ for( $i = 1; $i < $argc; $i++ ) {
|
||||||
break;
|
break;
|
||||||
case '-m':
|
case '-m':
|
||||||
case '--module':
|
case '--module':
|
||||||
$config['modules'][] = $argv[++$i];
|
$config['modules'] = array_merge($config['modules'], explode(',', $argv[++$i]));
|
||||||
break;
|
break;
|
||||||
case '-o':
|
case '-o':
|
||||||
case '--output':
|
case '--output':
|
||||||
|
@ -265,8 +265,9 @@ for( $i = 1; $i < $argc; $i++ ) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
if (empty($config['modules'])) $config['modules'] = array('lint', 'functions', 'pattern');
|
||||||
|
|
||||||
if( count( $files ) == 0 ) {
|
if( count( $files ) == 0 ) {
|
||||||
if( count( $revisions ) > 0 ) {
|
if( count( $revisions ) > 0 ) {
|
||||||
die( "Revisions invalid or contained no files from the supplied path\n" );
|
die( "Revisions invalid or contained no files from the supplied path\n" );
|
||||||
|
@ -341,6 +342,7 @@ foreach( $files as $file ) {
|
||||||
foreach( $modules['scanner'] as $module ) { $module->preScan( $file ); }
|
foreach( $modules['scanner'] as $module ) { $module->preScan( $file ); }
|
||||||
$file_contents = ( $file['revision'] > 0 ? shell_exec( "svn cat -r {$rev} {$svn_root}{$svn_base}/{$file['filename']} 2>/dev/null" ) : file_get_contents( $file['filename'] ) );
|
$file_contents = ( $file['revision'] > 0 ? shell_exec( "svn cat -r {$rev} {$svn_root}{$svn_base}/{$file['filename']} 2>/dev/null" ) : file_get_contents( $file['filename'] ) );
|
||||||
$parser->parse( $file_contents );
|
$parser->parse( $file_contents );
|
||||||
|
foreach( $modules['scanner'] as $module ) { $module->postScan( $file ); }
|
||||||
if( $curses ) {
|
if( $curses ) {
|
||||||
ncurses_reset_prog_mode();
|
ncurses_reset_prog_mode();
|
||||||
$nc_faults->title( sprintf( 'Faults [%d found]', count( $faults ) ) );
|
$nc_faults->title( sprintf( 'Faults [%d found]', count( $faults ) ) );
|
||||||
|
@ -436,7 +438,9 @@ if( $curses ) {
|
||||||
ncurses_end();
|
ncurses_end();
|
||||||
} else {
|
} else {
|
||||||
$modules['output']->write( $config['output_file'] );
|
$modules['output']->write( $config['output_file'] );
|
||||||
sleep( 1 );
|
if (false === $config['quiet']) {
|
||||||
err( sprintf( "Found %d faults in %d files.\n", count( $faults ), count( $files ) ) );
|
sleep( 1 );
|
||||||
|
err( sprintf( "Found %d faults in %d files.\n", count( $faults ), count( $files ) ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
19
test.php
19
test.php
|
@ -23,11 +23,24 @@ $filters = array(
|
||||||
'pattern' => '/(?<!dev|qa)@payquik\.com/'
|
'pattern' => '/(?<!dev|qa)@payquik\.com/'
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
$foo = 'bar';
|
||||||
|
$_FILTERS = $filters;
|
||||||
|
|
||||||
function test( $object ) {
|
// Undefined variable, skipped until the global scope can be scanned properly.
|
||||||
global $filters;
|
echo $undefined;
|
||||||
|
|
||||||
|
function test( $object, $foo = false ) {
|
||||||
|
global $foo, $filters;
|
||||||
|
// Trigger undefined variable via non-referenced global variable.
|
||||||
|
$filters = $_FILTERS;
|
||||||
|
// Superglobal
|
||||||
|
echo $_GET['stuff'];
|
||||||
|
|
||||||
|
$bar = array();
|
||||||
|
$bar[$baz] = 'nutter'; // $baz should be undefined here
|
||||||
|
$zzz[$foo] = 'ok'; // $zzz should be defined here
|
||||||
|
|
||||||
foreach( $filters as $filter ) {
|
foreach( $filters as $key => $filter ) {
|
||||||
if( $object['type'] == $filter['type'] ) {
|
if( $object['type'] == $filter['type'] ) {
|
||||||
if( preg_match( $filter['pattern'], $object['name'] ) > 0 ) {
|
if( preg_match( $filter['pattern'], $object['name'] ) > 0 ) {
|
||||||
echo "fn: Triggered Filter '{$filter['desc']}' at line {$object['line']}\n";
|
echo "fn: Triggered Filter '{$filter['desc']}' at line {$object['line']}\n";
|
||||||
|
|
22
tests/AllTests.php
Normal file
22
tests/AllTests.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
require_once('PHPUnit/Framework.php');
|
||||||
|
require_once('PHPUnit/TextUI/TestRunner.php');
|
||||||
|
|
||||||
|
require_once('ParserTests.php');
|
||||||
|
require_once('ScannerTests.php');
|
||||||
|
|
||||||
|
|
||||||
|
class AllTests {
|
||||||
|
public static function main() {
|
||||||
|
PHPUnit_TextUI_TestRunner::run(self::suite());
|
||||||
|
}
|
||||||
|
public static function suite() {
|
||||||
|
$suite = new PHPUnit_Framework_TestSuite('Code Scanner');
|
||||||
|
|
||||||
|
$suite->addTestSuite('ParserTests');
|
||||||
|
$suite->addTestSuite('ScannerTests');
|
||||||
|
|
||||||
|
return $suite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
138
tests/ParserTests.php
Normal file
138
tests/ParserTests.php
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
require_once 'PHPUnit/Framework.php';
|
||||||
|
require_once '../parser.php';
|
||||||
|
|
||||||
|
class ParserTests extends PHPUnit_Framework_TestCase {
|
||||||
|
private $objects;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->objects = array();
|
||||||
|
}
|
||||||
|
public function callback($object) {
|
||||||
|
$this->objects[] = $object;
|
||||||
|
}
|
||||||
|
private function parse_and_count_type($code, $type, $fetch_mode = PHPPARSER_FETCH_ALL) {
|
||||||
|
$this->objects = array();
|
||||||
|
$this->parser = new PHPParser($fetch_mode);
|
||||||
|
$this->parser->registerCallback(array($this, 'callback'));
|
||||||
|
$this->parser->parse($code);
|
||||||
|
$count = 0;
|
||||||
|
foreach ($this->objects as $o) {
|
||||||
|
if ($type == $o['type']) $count++;
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
public function testFetchClassDefinitions() {
|
||||||
|
$code = '<?php
|
||||||
|
class Test {};
|
||||||
|
class Test2 {
|
||||||
|
class Test2a {};
|
||||||
|
};
|
||||||
|
?>';
|
||||||
|
$this->assertEquals(3, $this->parse_and_count_type($code, PHPPARSER_CLASS_DEF, PHPPARSER_FETCH_CLASSES));
|
||||||
|
}
|
||||||
|
public function testFetchFunctionDefinitions() {
|
||||||
|
$code = '<?php
|
||||||
|
function Function1() {}
|
||||||
|
function Function2($foo = "bar") {}
|
||||||
|
?>';
|
||||||
|
$this->assertEquals(2, $this->parse_and_count_type($code, PHPPARSER_FUNCTION_DEF, PHPPARSER_FETCH_FUNCTIONS));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFetchMethodDefinitions() {
|
||||||
|
$code = '<?php
|
||||||
|
class Test2 {
|
||||||
|
class Test2a {};
|
||||||
|
private function _init() {}
|
||||||
|
static public function test() {}
|
||||||
|
function do_something($when) {}
|
||||||
|
};
|
||||||
|
?>';
|
||||||
|
$this->assertEquals(3, $this->parse_and_count_type($code, PHPPARSER_FUNCTION_DEF, PHPPARSER_FETCH_FUNCTIONS));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFetchFunctionCalls() {
|
||||||
|
$code = '<?php
|
||||||
|
dosomething();
|
||||||
|
do_something_else ($val);
|
||||||
|
give_up(array(
|
||||||
|
$wife,
|
||||||
|
$kids,
|
||||||
|
$money));
|
||||||
|
?>';
|
||||||
|
$this->assertEquals(3, $this->parse_and_count_type($code, PHPPARSER_FUNCTION_CALL, PHPPARSER_FETCH_CALLS));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFetchIncludes() {
|
||||||
|
$code = '<?php
|
||||||
|
include "file.php";
|
||||||
|
require \'tools/stuff.php\';
|
||||||
|
include_once ("session.php");
|
||||||
|
require_once( "templates.php" );
|
||||||
|
?>';
|
||||||
|
$this->assertEquals(4, $this->parse_and_count_type($code, PHPPARSER_INCLUDE, PHPPARSER_FETCH_INCLUDES));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFetchConstructs() {
|
||||||
|
$code = '<?php
|
||||||
|
echo "saywhatnow";
|
||||||
|
echo("hey!");
|
||||||
|
eval($_GET["evil_input"]);
|
||||||
|
?>';
|
||||||
|
$this->assertEquals(3, $this->parse_and_count_type($code, PHPPARSER_LANGUAGE_CONSTRUCT, PHPPARSER_FETCH_CONSTRUCTS));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFetchAssignments() {
|
||||||
|
/*
|
||||||
|
Function arguments count as an assignment (as they are assigned when the function is called)
|
||||||
|
Variables that are declared are considered assigned, even if no value is actually set.
|
||||||
|
List assignments are counted once for each item in the list
|
||||||
|
The final line here tests nested expressions, which should be solved from the inside out.
|
||||||
|
*/
|
||||||
|
$code = '<?php
|
||||||
|
function test($array) {
|
||||||
|
global $dbconn, $options;
|
||||||
|
$foo = "bar";
|
||||||
|
list($a, $b, $c) = $array;
|
||||||
|
$hash["answer"] = 42;
|
||||||
|
$data[$index[0]] = "complex";
|
||||||
|
$data[$index = 0] = "nested";
|
||||||
|
};
|
||||||
|
?>';
|
||||||
|
$this->parse_and_count_type($code, PHPPARSER_ASSIGNMENT, PHPPARSER_FETCH_EXPRESSIONS);
|
||||||
|
$assignments = array();
|
||||||
|
foreach ($this->objects as $object) {
|
||||||
|
if (PHPPARSER_ASSIGNMENT == $object['type'])
|
||||||
|
$assignments[] = $object['name'];
|
||||||
|
}
|
||||||
|
$expected = array(
|
||||||
|
'$array=$array',
|
||||||
|
'$dbconn=$dbconn',
|
||||||
|
'$options=$options',
|
||||||
|
'$foo="bar"',
|
||||||
|
'$a=$array',
|
||||||
|
'$b=$array',
|
||||||
|
'$c=$array',
|
||||||
|
'$hash["answer"]=42',
|
||||||
|
'$data[$index[0]]="complex"',
|
||||||
|
'$index=0',
|
||||||
|
'$data[$index = 0]="nested"',
|
||||||
|
);
|
||||||
|
$this->assertEquals($expected, $assignments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCommentedCode() {
|
||||||
|
$code = '<?php
|
||||||
|
// $var_1
|
||||||
|
/* $var_2 */
|
||||||
|
/*
|
||||||
|
$var_3
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* $var_4
|
||||||
|
*/
|
||||||
|
?>';
|
||||||
|
$this->assertEquals(0, $this->parse_and_count_type($code, PHPPARSER_VARIABLE, PHPPARSER_FETCH_EXPRESSIONS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
34
tests/ScannerTests.php
Normal file
34
tests/ScannerTests.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
require_once 'PHPUnit/Framework.php';
|
||||||
|
|
||||||
|
class ScannerTests extends PHPUnit_Framework_TestCase {
|
||||||
|
private function run_scanner($options = array(), $files = array()) {
|
||||||
|
$modules = array_key_exists('modules', $options) ? (is_array($options['modules']) ? $options['modules'] : explode(',', $options['modules'])) : array();
|
||||||
|
$base = array_key_exists('base', $options) ? $options['base'] : false;
|
||||||
|
$files = is_array($files) ? $files : array($files);
|
||||||
|
|
||||||
|
$command = 'php ../scanner.php -q';
|
||||||
|
if (count($modules) > 0)
|
||||||
|
$command .= ' -m ' . implode(',', $modules);
|
||||||
|
if ($base)
|
||||||
|
$command .= ' -b ' . $base;
|
||||||
|
$command .= ' ' . implode(' ', $files);
|
||||||
|
return shell_exec($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testModuleLint() {
|
||||||
|
$result = $this->run_scanner(
|
||||||
|
array('modules' => 'lint'),
|
||||||
|
'samples/lint_failure.php'
|
||||||
|
);
|
||||||
|
$this->assertEquals(file_get_contents('samples/lint_failure.txt'), $result);
|
||||||
|
}
|
||||||
|
public function testModulePatterns() {
|
||||||
|
$result = $this->run_scanner(
|
||||||
|
array('modules' => 'pattern'),
|
||||||
|
'samples/patterns.php'
|
||||||
|
);
|
||||||
|
$this->assertEquals(file_get_contents('samples/patterns.txt'), $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
9
tests/samples/lint_failure.php
Normal file
9
tests/samples/lint_failure.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Lint Failure
|
||||||
|
|
||||||
|
This file contains a syntax error which will trigger a fault from the lint module
|
||||||
|
*/
|
||||||
|
|
||||||
|
$a = }}};
|
||||||
|
?>
|
1
tests/samples/lint_failure.txt
Normal file
1
tests/samples/lint_failure.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
LintModule 2 lint_failure.php 8 ? 0 Parse error: syntax error, unexpected '}' in samples/lint_failure.php on line 8
|
9
tests/samples/patterns.php
Normal file
9
tests/samples/patterns.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
$sql = "select * from failure";
|
||||||
|
echo "Here's the $sql!\n";
|
||||||
|
mail( 'correl@payquik.com', 'subject', 'stuffs' );
|
||||||
|
eval( "echo \"here's eval!\n\";" );
|
||||||
|
print_r( $sql );
|
||||||
|
var_dump( $sql );
|
||||||
|
echo "done\n";
|
||||||
|
?>
|
5
tests/samples/patterns.txt
Normal file
5
tests/samples/patterns.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
PatternModule 1 patterns.php 3 ? 0 Triggered Filter 'Echoing Sql'
|
||||||
|
PatternModule 0 patterns.php 4 ? 0 Triggered Filter 'Developer Email'
|
||||||
|
PatternModule 1 patterns.php 5 ? 0 Triggered Filter 'Evil Eval'
|
||||||
|
PatternModule 1 patterns.php 6 ? 0 Triggered Filter 'PRINT_R or VAR_DUMP'
|
||||||
|
PatternModule 1 patterns.php 7 ? 0 Triggered Filter 'PRINT_R or VAR_DUMP'
|
Loading…
Reference in a new issue