1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @license http://opensource.org/licenses/mit-license.php MIT |
5
|
|
|
* @link https://github.com/nicoSWD |
6
|
|
|
* @author Nicolas Oelgart <[email protected]> |
7
|
|
|
*/ |
8
|
|
|
namespace nicoSWD\SecHeaderCheck\Infrastructure\ResultPrinter; |
9
|
|
|
|
10
|
|
|
use nicoSWD\SecHeaderCheck\Domain\Header\SecurityHeader; |
11
|
|
|
use nicoSWD\SecHeaderCheck\Domain\Result\AuditionResult; |
12
|
|
|
use nicoSWD\SecHeaderCheck\Domain\Result\HeaderWithObservations; |
13
|
|
|
use nicoSWD\SecHeaderCheck\Domain\Result\ObservationCollection; |
14
|
|
|
use nicoSWD\SecHeaderCheck\Domain\ResultPrinter\OutputOptions; |
15
|
|
|
use nicoSWD\SecHeaderCheck\Domain\ResultPrinter\ResultPrinterInterface; |
16
|
|
|
|
17
|
|
|
final class ConsoleResultPrinter implements ResultPrinterInterface |
18
|
|
|
{ |
19
|
|
|
public function getOutput(AuditionResult $scanResults, OutputOptions $outputOptions): string |
20
|
|
|
{ |
21
|
|
|
$output = ''; |
22
|
|
|
$totalWarnings = 0; |
23
|
|
|
|
24
|
|
|
foreach ($scanResults->getObservations() as $observations) { |
25
|
|
|
if ($observations->getObservations()->empty() && !$outputOptions->showAllHeaders()) { |
26
|
|
|
continue; |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
$totalWarnings++; |
30
|
|
|
|
31
|
|
|
if ($this->hasErrors($observations)) { |
32
|
|
|
$res = '<fg=red>Fail </>'; |
33
|
|
|
} elseif ($this->hasWarnings($observations)) { |
34
|
|
|
$res = '<fg=yellow>Pass </>'; |
35
|
|
|
} else { |
36
|
|
|
$res = '<fg=green>Pass </>'; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
$output .= $res . '<bg=default;fg=white>' . $this->prettyName($observations->getHeaderName()) . ': ' . $this->shortenHeaderValue($observations->getHeaderName(), $observations->getHeaderValue()) . ' </>' . $this->getWarnings($observations->getObservations()); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
$missingHeaders = $scanResults->getMissingHeaders(); |
43
|
|
|
|
44
|
|
|
if ($missingHeaders) { |
|
|
|
|
45
|
|
|
$output .= PHP_EOL . '<bg=red>Missing headers</>: '; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
foreach ($missingHeaders as $headerName) { |
49
|
|
|
$totalWarnings++; |
50
|
|
|
$output .= '<bg=red;fg=black>' . $this->prettyName($headerName) . '</> '; |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
if ($missingHeaders) { |
|
|
|
|
54
|
|
|
$output .= PHP_EOL; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
if ($totalWarnings === 0) { |
58
|
|
|
$output .= ' <bg=green;fg=black> </>' . PHP_EOL ; |
59
|
|
|
$output .= ' <bg=green;fg=black> Congrats, no warnings! </>' . PHP_EOL ; |
60
|
|
|
$output .= ' <bg=green;fg=black> </>' . PHP_EOL ; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
// $output .= PHP_EOL .'Total Score: <comment>' . $scanResults->getScore() . '</comment> out of <comment>10</comment> (<fg=red>Fail</>)'; |
64
|
|
|
|
65
|
|
|
return $output; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
private function prettyName($headerName): string |
69
|
|
|
{ |
70
|
|
|
return '<fg=cyan>' . implode('-', array_map('ucfirst', explode('-', $headerName))) . '</>'; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
private function getWarnings(ObservationCollection $observations): string |
74
|
|
|
{ |
75
|
|
|
$out = ''; |
76
|
|
|
$c = 1; |
77
|
|
|
|
78
|
|
|
foreach ($observations as $observation) { |
79
|
|
|
$out .= PHP_EOL . ' ' . $c++ . ')'; |
80
|
|
|
|
81
|
|
|
if ($observation->isInfo()) { |
82
|
|
|
$out .= '<fg=yellow> ' . (string) $observation . '</> '; |
83
|
|
|
} elseif ($observation->isWarning()) { |
84
|
|
|
$out .= '<fg=red> ' . (string) $observation . '</> '; |
85
|
|
|
} elseif ($observation->isKudos()) { |
86
|
|
|
$out .= '<fg=green> ' . (string) $observation . '</> '; |
87
|
|
|
} else { |
88
|
|
|
$out .= '<fg=red> ' . (string) $observation . '</> '; |
89
|
|
|
} |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
return $out . PHP_EOL; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
private function shortenHeaderValue(string $headerName, string $headerValue): string |
96
|
|
|
{ |
97
|
|
|
$width = (int) `tput cols`; |
|
|
|
|
98
|
|
|
|
99
|
|
|
if ($headerName === SecurityHeader::SET_COOKIE) { |
100
|
|
|
$callback = function (array $match): string { |
101
|
|
|
if (strlen($match['value']) < 20) { |
102
|
|
|
return $match['full_match']; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
return sprintf( |
106
|
|
|
'%s=%s<bg=cyan>(...)</>%s', |
107
|
|
|
$match['name'], |
108
|
|
|
substr($match['value'], 0, 8), |
109
|
|
|
substr($match['value'], -8) |
110
|
|
|
); |
111
|
|
|
}; |
112
|
|
|
|
113
|
|
|
return preg_replace_callback('~(?<full_match>(?<name>.*?)=(?<value>.*?;))~', $callback, $headerValue); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
return $headerValue; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
private function hasErrors(HeaderWithObservations $header): bool |
120
|
|
|
{ |
121
|
|
|
foreach ($header->getObservations() as $observation) { |
122
|
|
|
if ($observation->isError() || $observation->isWarning()) { |
123
|
|
|
return true; |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
return false; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
private function hasWarnings(HeaderWithObservations $header): bool |
131
|
|
|
{ |
132
|
|
|
foreach ($header->getObservations() as $observation) { |
133
|
|
|
if ($observation->isInfo()) { |
134
|
|
|
return true; |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
return false; |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.