2008-02-13 22:39:27 +00:00
|
|
|
<?php
|
|
|
|
require_once( 'parser.php' );
|
|
|
|
|
|
|
|
define( 'FAULT_MINOR', 0 );
|
|
|
|
define( 'FAULT_MEDIUM', 1 );
|
|
|
|
define( 'FAULT_MAJOR', 2 );
|
|
|
|
|
2008-02-15 19:05:09 +00:00
|
|
|
$modules = array(
|
|
|
|
'scanner' => array(),
|
|
|
|
'output' => new OutputModule()
|
|
|
|
);
|
2008-02-14 21:27:50 +00:00
|
|
|
$stderr = fopen( 'php://stderr', 'w' );
|
|
|
|
$config = array(
|
|
|
|
'svn' => false,
|
|
|
|
'modules' => array(),
|
2008-02-15 19:05:09 +00:00
|
|
|
'output_format' => 'text',
|
|
|
|
'output_file' => 'php://stdout',
|
2008-02-14 22:45:04 +00:00
|
|
|
'quiet' => false,
|
2008-02-14 21:27:50 +00:00
|
|
|
);
|
2008-02-15 20:12:49 +00:00
|
|
|
$help = "Usage:
|
|
|
|
{$argv[0]} [options] file|path [file|path ...]
|
|
|
|
{$argv[0]} [options] -b base_path -r revision[,revision ...]
|
|
|
|
|
2008-02-14 21:27:50 +00:00
|
|
|
Options:
|
2008-02-15 19:05:09 +00:00
|
|
|
-b path Set the base path for the scan. Useful if you want
|
|
|
|
--base-path path to scan individual files in a code base that don't
|
2008-02-14 22:45:04 +00:00
|
|
|
live in the project's base directory.
|
|
|
|
|
2008-02-15 19:05:09 +00:00
|
|
|
-f format Select the output format. Defaults to text.
|
|
|
|
--format format
|
|
|
|
|
2008-02-14 21:27:50 +00:00
|
|
|
-h Display this usage information
|
|
|
|
--help
|
|
|
|
|
|
|
|
-m modulename Loads the requested scanning module. If this
|
|
|
|
--module modulename parameter is not specified, all available modules
|
|
|
|
will be loaded.
|
|
|
|
|
2008-02-15 19:05:09 +00:00
|
|
|
-o filename Write output to a file instead of stdout
|
|
|
|
--output filename
|
|
|
|
|
2008-02-14 22:45:04 +00:00
|
|
|
-q Suppresses all progress output
|
|
|
|
--quiet
|
|
|
|
|
2008-02-15 20:12:49 +00:00
|
|
|
-r Get files from a comma separated list of SVN revisions.
|
|
|
|
You must supply a base path first for this!
|
|
|
|
|
|
|
|
--svn Enables SVN integration
|
2008-02-14 21:27:50 +00:00
|
|
|
";
|
2008-02-15 19:05:09 +00:00
|
|
|
$faults = array();
|
2008-02-13 22:39:27 +00:00
|
|
|
|
|
|
|
class ScannerModule {
|
|
|
|
var $faults;
|
|
|
|
var $blame;
|
|
|
|
|
|
|
|
function ScannerModule() {
|
|
|
|
$this->faults = array();
|
|
|
|
$this->blame = array();
|
2008-02-14 21:27:50 +00:00
|
|
|
err( "Initializing " . get_class( $this ) . "...\n" );
|
2008-02-13 22:39:27 +00:00
|
|
|
}
|
|
|
|
function fault( $object, $level, $reason = '' ) {
|
2008-02-15 20:12:49 +00:00
|
|
|
global $config, $revisions, $faults;
|
|
|
|
if( count( $revisions ) > 0 && !in_array( $this->blame[$object['line']]['revision'], $revisions ) ) {
|
|
|
|
/* If files have been added using SVN revisions, filter out any faulty
|
|
|
|
changes that aren't a part of the requested changeset(s).
|
|
|
|
*/
|
|
|
|
return false;
|
|
|
|
}
|
2008-02-14 21:27:50 +00:00
|
|
|
$object['file'] = filename( $object['file'] );
|
2008-02-15 19:05:09 +00:00
|
|
|
$faults[] = $this->faults[] = array(
|
|
|
|
'module' => get_class( $this ),
|
2008-02-13 22:39:27 +00:00
|
|
|
'object' => $object,
|
|
|
|
'level' => $level,
|
|
|
|
'reason' => $reason,
|
2008-02-14 21:27:50 +00:00
|
|
|
'svn' => ( $config['svn'] === true ) ? $this->blame[$object['line']] : ''
|
2008-02-13 22:39:27 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
function parserCallback( $object ) {
|
|
|
|
}
|
|
|
|
function preScan( $filename ) {
|
2008-02-14 21:27:50 +00:00
|
|
|
global $config;
|
|
|
|
if( $config['svn'] === true ) {
|
|
|
|
$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]
|
|
|
|
);
|
|
|
|
}
|
2008-02-13 22:39:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function postScan( $filename ) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-15 19:05:09 +00:00
|
|
|
class OutputModule {
|
|
|
|
function display() {
|
|
|
|
$this->write( 'php://output' );
|
|
|
|
}
|
|
|
|
function write( $filename ) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-13 22:39:27 +00:00
|
|
|
function _callback( $object ) {
|
|
|
|
global $modules;
|
2008-02-15 19:05:09 +00:00
|
|
|
foreach( $modules['scanner'] as $module ) {
|
2008-02-13 22:39:27 +00:00
|
|
|
$module->parserCallback( $object );
|
|
|
|
}
|
|
|
|
}
|
2008-02-15 19:05:09 +00:00
|
|
|
function addModule( $module_instance ) {
|
|
|
|
global $modules;
|
|
|
|
if( $module_instance instanceof ScannerModule ) {
|
|
|
|
$modules['scanner'][] = $module_instance;
|
|
|
|
} elseif( $module_instance instanceof OutputModule ) {
|
|
|
|
$modules['output'] = $module_instance;
|
|
|
|
}
|
|
|
|
}
|
2008-02-14 21:27:50 +00:00
|
|
|
function filename( $filename ) {
|
|
|
|
global $base_path;
|
|
|
|
$filename = realpath( $filename );
|
|
|
|
if( strpos( $filename, $base_path ) === 0 ) {
|
|
|
|
$filename = substr( $filename, strlen( $base_path ) );
|
2008-02-13 22:39:27 +00:00
|
|
|
}
|
2008-02-14 21:27:50 +00:00
|
|
|
return $filename;
|
|
|
|
}
|
|
|
|
function err( $string ) {
|
2008-02-14 22:45:04 +00:00
|
|
|
global $stderr, $config;
|
|
|
|
if( $config['quiet'] === false ) {
|
|
|
|
fputs( $stderr, $string );
|
|
|
|
}
|
2008-02-13 22:39:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-02-14 21:27:50 +00:00
|
|
|
// Handle application arguments
|
2008-02-15 20:12:49 +00:00
|
|
|
$revisions = array();
|
2008-02-13 22:39:27 +00:00
|
|
|
$files = array();
|
2008-02-14 21:27:50 +00:00
|
|
|
$base_path = false;
|
2008-02-13 22:39:27 +00:00
|
|
|
for( $i = 1; $i < $argc; $i++ ) {
|
2008-02-14 21:27:50 +00:00
|
|
|
switch( $argv[$i] ) {
|
2008-02-14 22:45:04 +00:00
|
|
|
case '-b':
|
|
|
|
case '--base-path':
|
|
|
|
$new_base = $argv[++$i];
|
|
|
|
if( is_dir( $new_base ) ) {
|
|
|
|
$base_path = realpath( $new_base ) . '/';
|
|
|
|
}
|
|
|
|
break;
|
2008-02-15 19:05:09 +00:00
|
|
|
case '-f':
|
|
|
|
case '--format':
|
|
|
|
$config['output_format'] = $argv[++$i];
|
|
|
|
break;
|
2008-02-14 21:27:50 +00:00
|
|
|
case '-h':
|
|
|
|
case '--help':
|
|
|
|
die( $help );
|
|
|
|
break;
|
|
|
|
case '-m':
|
|
|
|
case '--module':
|
|
|
|
$config['modules'][] = $argv[++$i];
|
|
|
|
break;
|
2008-02-15 19:05:09 +00:00
|
|
|
case '-o':
|
|
|
|
case '--output':
|
|
|
|
$config['output_file'] = $argv[++$i];
|
|
|
|
break;
|
2008-02-14 22:45:04 +00:00
|
|
|
case '-q':
|
|
|
|
case '--quiet':
|
|
|
|
$config['quiet'] = true;
|
|
|
|
break;
|
2008-02-15 20:12:49 +00:00
|
|
|
case '-r':
|
|
|
|
$revs = explode( ',', $argv[++$i] );
|
|
|
|
if( $base_path === false ) {
|
|
|
|
die( "Set a base path before supplying SVN revisions\n" );
|
|
|
|
}
|
|
|
|
// First, find out what the full path is so we can trim it down to the relative one.
|
|
|
|
$xml = shell_exec( "svn info --xml $base_path" );
|
|
|
|
$parser = xml_parser_create();
|
|
|
|
xml_parser_set_option( $parser, XML_OPTION_SKIP_WHITE, 1 );
|
|
|
|
xml_parse_into_struct( $parser, $xml, $values, $index );
|
|
|
|
xml_parser_free( $parser );
|
|
|
|
foreach( $values as $value ) {
|
|
|
|
switch( $value['tag'] ) {
|
|
|
|
case 'URL':
|
|
|
|
$svn_url = $value['value'];
|
|
|
|
break;
|
|
|
|
case 'ROOT':
|
|
|
|
$svn_root = $value['value'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$svn_base = substr( $svn_url, strlen( $svn_root ) );
|
|
|
|
foreach( $revs as $rev ) {
|
|
|
|
$revisions[] = $rev = intval( $rev );
|
|
|
|
$xml = shell_exec( "svn log -v --xml -r $rev $base_path" );
|
|
|
|
$parser = xml_parser_create();
|
|
|
|
xml_parser_set_option( $parser, XML_OPTION_SKIP_WHITE, 1 );
|
|
|
|
xml_parse_into_struct( $parser, $xml, $values, $index );
|
|
|
|
xml_parser_free( $parser );
|
|
|
|
foreach( $values as $value ) {
|
|
|
|
if( $value['tag'] == 'PATH' ) {
|
|
|
|
$file = realpath( $base_path . substr( $value['value'], strlen( $svn_base ) ) );
|
|
|
|
if( !in_array( $file, $files ) ) {
|
|
|
|
$files[] = $file;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-02-14 21:27:50 +00:00
|
|
|
case '--svn':
|
|
|
|
$config['svn'] = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if( file_exists( $argv[$i] ) && strtolower( substr( $argv[$i], -4 ) ) == '.php' ) {
|
|
|
|
$base_path = ( $base_path === false ) ? realpath( dirname( $argv[$i] ) ) . '/' : $base_path;
|
|
|
|
$files[] = $argv[$i];
|
|
|
|
} else if( is_dir( $argv[$i] ) ) {
|
|
|
|
$base_path = ( $base_path === false ) ? realpath( $argv[$i] ) . '/' : $base_path;
|
2008-02-14 22:45:04 +00:00
|
|
|
exec( "find {$argv[$i]} -iname '*.php' 2>/dev/null", $output, $result );
|
2008-02-14 21:27:50 +00:00
|
|
|
$files = array_merge( $files, $output );
|
|
|
|
}
|
2008-02-13 22:39:27 +00:00
|
|
|
}
|
2008-02-14 21:27:50 +00:00
|
|
|
|
2008-02-13 22:39:27 +00:00
|
|
|
}
|
|
|
|
if( count( $files ) == 0 ) {
|
2008-02-14 21:27:50 +00:00
|
|
|
die( $help );
|
2008-02-13 22:39:27 +00:00
|
|
|
}
|
|
|
|
|
2008-02-14 21:27:50 +00:00
|
|
|
// 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' ) {
|
|
|
|
$module = substr( $module_file, 0, strlen( $module_file ) - 4 );
|
2008-02-15 19:05:09 +00:00
|
|
|
list( $type, $module ) = split( '_', $module );
|
|
|
|
switch( $type ) {
|
|
|
|
case 'output':
|
|
|
|
if( $module == $config['output_format'] ) {
|
|
|
|
require_once( "modules/{$module_file}" );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'scanner':
|
|
|
|
if(
|
|
|
|
count( $config['modules'] ) == 0
|
|
|
|
|| in_array( $module, $config['modules'] )
|
|
|
|
) {
|
|
|
|
require_once( "modules/{$module_file}" );
|
|
|
|
}
|
|
|
|
break;
|
2008-02-14 21:27:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$parser = new PHPParser();
|
|
|
|
$parser->registerCallback( '_callback' );
|
|
|
|
|
|
|
|
err( "Parsing files...\n" );
|
|
|
|
$counter = 0; $total = count( $files ); $lastpct = 0;
|
2008-02-13 22:39:27 +00:00
|
|
|
foreach( $files as $file ) {
|
2008-02-14 21:27:50 +00:00
|
|
|
$counter++;
|
|
|
|
if( $counter == 1 ) { err( 0 ); }
|
|
|
|
else {
|
|
|
|
$pct = intval( $counter / $total * 100 );
|
|
|
|
if( $pct != $lastpct && $pct % 2 == 0 ) {
|
|
|
|
err( $pct % 10 == 0 ? $pct : '.' );
|
|
|
|
$lastpct = $pct;
|
|
|
|
}
|
|
|
|
}
|
2008-02-15 19:05:09 +00:00
|
|
|
foreach( $modules['scanner'] as $module ) { $module->preScan( $file ); }
|
2008-02-13 22:39:27 +00:00
|
|
|
$parser->parseFile( $file );
|
|
|
|
}
|
2008-02-14 21:27:50 +00:00
|
|
|
err( "\n" );
|
2008-02-15 19:05:09 +00:00
|
|
|
$modules['output']->write( $config['output_file'] );
|
2008-02-15 20:12:49 +00:00
|
|
|
|
|
|
|
sleep( 1 );
|
2008-02-15 19:05:09 +00:00
|
|
|
err( sprintf( "Found %d faults in %d files.\n", count( $faults ), count( $files ) ) );
|
2008-02-13 22:39:27 +00:00
|
|
|
?>
|