Skip to content

Commit

Permalink
add html report
Browse files Browse the repository at this point in the history
  • Loading branch information
phith0n committed May 3, 2019
1 parent 816cf7a commit ef90851
Show file tree
Hide file tree
Showing 8 changed files with 360 additions and 32 deletions.
6 changes: 1 addition & 5 deletions demo.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@

$code = <<<'CODE'
<?php
class S {
function test(){
echo 123;
}
}
$a = function() {};
CODE;

$nameResolver = new NameResolver();
Expand Down
37 changes: 19 additions & 18 deletions src/Chip/Alarm.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Alarm implements \JsonSerializable
protected $node;

/**
* @var Node $function
* @var Node\Stmt\Function_|Node\Stmt\ClassMethod $function
*/
protected $function;

Expand Down Expand Up @@ -113,37 +113,38 @@ public function formatOutput(string $code)
{
$code = explode("\n", $code);
$vulnerability = [
'level' => $this->getLevel()->getKey(),
'message' => $this->getMessage(),
'code' => $code,
'highlight' => [],
'level' => strtolower($this->getLevel()->getKey()),
'message' => $this->getMessage(),
'lines' => [],
'function' => '',
];

$node = $this->getNode();
$function = $this->getFunction();

if (!$node) {
return $vulnerability;
}

list($nodeStartLine, $nodeEndLine) = [$node->getStartLine(), $node->getEndLine()];

if ($function) {
list($functionStartLine, $functionEndLine) = [$function->getStartLine(), $function->getEndLine()];
list($nodeStartLine, $nodeEndLine) = [1, count($code)];
list($functionStartLine, $functionEndLine) = [$nodeStartLine, $nodeEndLine];
} else {
list($functionStartLine, $functionEndLine) = [max($nodeStartLine - 5, 1), $nodeEndLine + 5];
list($nodeStartLine, $nodeEndLine) = [$node->getStartLine(), $node->getEndLine()];

if ($function) {
list($functionStartLine, $functionEndLine) = [$function->getStartLine(), $function->getEndLine()];
$vulnerability['function'] = strval($function->name);
} else {
list($functionStartLine, $functionEndLine) = [max($nodeStartLine - 5, 1), $nodeEndLine + 5];
}
}

$code = array_slice($code, $functionStartLine - 1, $functionEndLine - $functionStartLine + 1);
$start = $functionStartLine;

array_map(function ($key) use ($start, $nodeStartLine, $nodeEndLine, &$vulnerability) {
array_map(function ($key, $line) use ($start, $nodeStartLine, $nodeEndLine, &$vulnerability) {
$startKey = $start + $key;

if ($nodeStartLine <= $startKey && $startKey <= $nodeEndLine) {
$vulnerability['highlight'][] = $startKey;
}
}, array_keys($code));
$highlight = $nodeStartLine <= $startKey && $startKey <= $nodeEndLine;
$vulnerability['lines'][] = [$startKey, $line, $highlight];
}, array_keys($code), $code);

return $vulnerability;
}
Expand Down
44 changes: 42 additions & 2 deletions src/Chip/Console/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Chip\AlarmLevel;
use Chip\ChipFactory;
use Chip\Report\ConsoleReport;
use Chip\Report\HTMLReport;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
Expand Down Expand Up @@ -45,6 +46,19 @@ protected function configure()
'Display message above this level, choice is [' . implode(', ', $this->level) . ']',
'warning'
);
$this->addOption(
'report',
'-r',
InputOption::VALUE_OPTIONAL,
'Output data format, choice is [console, html]',
'console'
);
$this->addOption(
'output',
'-o',
InputOption::VALUE_OPTIONAL,
'Write the report to the specified file path'
);
}

protected function interact(InputInterface $input, OutputInterface $output)
Expand All @@ -53,6 +67,16 @@ protected function interact(InputInterface $input, OutputInterface $output)
if (!in_array($level, $this->level, true)) {
throw new RuntimeException('level must be one of [' . implode(', ', $this->level) . ' ]');
}

$report = $input->getOption('report');
if (!in_array($report, ['console', 'html'])) {
throw new \RuntimeException('report must be one of [console, html]');
}

$outputPath = $input->getOption('output');
if ($report == 'html' && empty($outputPath)) {
throw new \RuntimeException('Ouput path cannot be empty');
}
}

/**
Expand All @@ -63,7 +87,18 @@ protected function interact(InputInterface $input, OutputInterface $output)
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$report = new ConsoleReport($output);
switch ($input->getOption('report')) {
case 'html':
$report = new HTMLReport($input->getOption('output'));
break;
case 'console':
default:
$report = new ConsoleReport($output);
break;
}
$report->assign('commandLine', 'chip.phar ' . strval($input));
$report->assign('startTime', date('Y-m-d H:i:s'));

$file = $input->getArgument("file");
$finder = new Finder();

Expand All @@ -76,15 +111,20 @@ protected function execute(InputInterface $input, OutputInterface $output)
}

$level = strtoupper($input->getOption('level'));
$filesCount = 0;
foreach ($finder as $fileobj) {
$filesCount++;
$content = $fileobj->getContents();
foreach ($this->checkCode($content) as $alarm) {
if ($alarm->getLevel()->getValue() >= AlarmLevel::$level()->getValue()) {
$output->writeln("\n==========");
$report->feed($fileobj->getPathname(), $content, $alarm);
}
}
}

$report->assign('filesCount', $filesCount);
$report->assign('endTime', date('Y-m-d H:i:s'));
$report->render();
}

/**
Expand Down
27 changes: 20 additions & 7 deletions src/Chip/Report/ConsoleReport.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class ConsoleReport implements ReportInterface
*/
protected $output;

/**
* @var $context
*/
protected $context = [];

public function __construct(OutputInterface $output)
{
$this->output = $output;
Expand All @@ -28,15 +33,18 @@ public function feed(string $filename, string $code, Alarm $alarm)
$ctx = $alarm->formatOutput($code);
$level = $ctx['level'];
$message = $ctx['message'];
$highlight = $ctx['highlight'];
$lines = $ctx['code'];
$lines = $ctx['lines'];

$this->output->writeln("<bg=red;fg=white>\n{$level}:{$filename}\n{$message}</>", OutputInterface::VERBOSITY_QUIET);
$this->output->writeln('');
$this->output->writeln([
"",
"==========",
"<bg=red;fg=white>\n{$level}:{$filename}\n{$message}</>",
"",
]);

foreach ($lines as $number => $line) {
$number++;
if (in_array($number, $highlight)) {
foreach ($lines as $line) {
list($number, $line, $highlight) = $line;
if ($highlight) {
$this->output->writeln("<fg=red;options=bold>{$number}:{$line}</>");
} else {
$this->output->writeln("{$number}:{$line}");
Expand All @@ -48,4 +56,9 @@ public function render()
{
// do nothing
}

public function assign($key, $value)
{
$this->context[$key] = $value;
}
}
114 changes: 114 additions & 0 deletions src/Chip/Report/HTMLReport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php
/**
* Created by PhpStorm.
* User: shiyu
* Date: 2019-05-02
* Time: 18:36
*/

namespace Chip\Report;

use Chip\Alarm;
use Twig\TwigFunction;

class HTMLReport implements ReportInterface
{
/**
* @var \Twig\Environment $engine
*/
protected $engine;

/**
* Output filename
*
* @var string
*/
protected $outputFilename;

/**
* Vulnerabilities array
* @var array $table
*/
protected $vulTable = [];

/**
* @var array $context
*/
protected $context = [];

public function __construct($outputFilename, $debug = false)
{
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/../Templates/Default');
$this->engine = new \Twig\Environment($loader, [
'debug' => $debug,
'cache' => false,
]);

$callback = new TwigFunction('formatCode', [$this, 'formatCode'], ['is_safe' => ['html']]);
$this->engine->addFunction($callback);
$this->outputFilename = $outputFilename;
}

public function assign($key, $value)
{
$this->context[$key] = $value;
}

public function feed(string $filename, string $code, Alarm $alarm)
{
$ctx = $alarm->formatOutput($code);
if (array_key_exists($filename, $this->vulTable)) {
$this->vulTable[$filename][] = $ctx;
} else {
$this->vulTable[$filename] = [$ctx];
}
}

public function render()
{
$vulnCount = array_reduce($this->vulTable, function ($carry, $item) {
$carry += count($item);
return $carry;
}, 0);
$this->assign('vulnCount', $vulnCount);
$this->assign('statistics', $this->statistics());
$this->assign('vulTable', $this->vulTable);

$data = $this->engine->render('index.twig.html', $this->context);
file_put_contents($this->outputFilename, $data);
}

protected function statistics()
{
$statistics = [];
foreach ($this->vulTable as $filename => $vuls) {
$statistics[$filename] = ['info' => 0, 'warning' => 0, 'danger' => 0, 'critical' => 0];
foreach ($vuls as $vul) {
$statistics[$filename][$vul['level']]++;
}
}
return $statistics;
}

public function formatCode($vuln)
{
$lines = array_map(function ($line) {
$code = htmlspecialchars($line[1], ENT_HTML401 | ENT_QUOTES, 'ISO-8859-1');
if ($line[2]) {
return "<span class='highlight'>{$line[0]}:{$code}</span>";
} else {
return "{$line[0]}:{$code}";
}
}, $vuln['lines']);

return implode("\n", $lines);
}

public function formatLevel($level)
{
$sw = [
'critical' => 'danger',
'danger' => ''
];
}
}
6 changes: 6 additions & 0 deletions src/Chip/Report/ReportInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@ public function feed(string $filename, string $code, Alarm $alarm);
* @return mixed
*/
public function render();

/**
* add a context variable to context
* @return mixed
*/
public function assign($key, $value);
}
Loading

0 comments on commit ef90851

Please sign in to comment.