pluginscan 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.gitlab-ci.yml +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +46 -0
- data/.rubocop_todo.yml +36 -0
- data/CHANGELOG.md +89 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +90 -0
- data/README.md +56 -0
- data/Rakefile +2 -0
- data/TODO.md +8 -0
- data/bin/pluginscan +53 -0
- data/lib/file_creator.rb +18 -0
- data/lib/pluginscan.rb +69 -0
- data/lib/pluginscan/error.rb +9 -0
- data/lib/pluginscan/error_printer.rb +17 -0
- data/lib/pluginscan/file_finder.rb +42 -0
- data/lib/pluginscan/printer.rb +14 -0
- data/lib/pluginscan/reports/cloc_report.rb +27 -0
- data/lib/pluginscan/reports/cloc_report/cloc.rb +21 -0
- data/lib/pluginscan/reports/cloc_report/cloc_printer.rb +42 -0
- data/lib/pluginscan/reports/cloc_report/cloc_scanner.rb +41 -0
- data/lib/pluginscan/reports/cloc_report/system_cloc.rb +33 -0
- data/lib/pluginscan/reports/issues_report.rb +24 -0
- data/lib/pluginscan/reports/issues_report/error_list_printer.rb +99 -0
- data/lib/pluginscan/reports/issues_report/issue_checks.rb +382 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/check.rb +55 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/comment_checker.rb +13 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/function_check.rb +32 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/variable_check.rb +14 -0
- data/lib/pluginscan/reports/issues_report/issue_checks/variable_safety_checker.rb +112 -0
- data/lib/pluginscan/reports/issues_report/issues_models/check_findings.rb +29 -0
- data/lib/pluginscan/reports/issues_report/issues_models/issues.rb +31 -0
- data/lib/pluginscan/reports/issues_report/issues_printer.rb +34 -0
- data/lib/pluginscan/reports/issues_report/issues_printer/check_findings_printer.rb +37 -0
- data/lib/pluginscan/reports/issues_report/issues_printer/file_issues_printer.rb +36 -0
- data/lib/pluginscan/reports/issues_report/issues_printer/finding_printer.rb +38 -0
- data/lib/pluginscan/reports/issues_report/issues_printer_factory.rb +19 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner.rb +49 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner/file_issues_scanner.rb +39 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner/line_issues_scanner.rb +15 -0
- data/lib/pluginscan/reports/issues_report/issues_scanner/utf8_checker.rb +14 -0
- data/lib/pluginscan/reports/sloccount_report.rb +26 -0
- data/lib/pluginscan/reports/sloccount_report/sloccount.rb +19 -0
- data/lib/pluginscan/reports/sloccount_report/sloccount_printer.rb +22 -0
- data/lib/pluginscan/reports/sloccount_report/sloccount_scanner.rb +86 -0
- data/lib/pluginscan/reports/vulnerability_report.rb +28 -0
- data/lib/pluginscan/reports/vulnerability_report/advisories_api.rb +23 -0
- data/lib/pluginscan/reports/vulnerability_report/vulnerabilities_printer.rb +55 -0
- data/lib/pluginscan/reports/vulnerability_report/vulnerability_scanner.rb +17 -0
- data/lib/pluginscan/reports/vulnerability_report/wp_vuln_db_api.rb +77 -0
- data/lib/pluginscan/version.rb +3 -0
- data/pluginscan.gemspec +31 -0
- data/spec/acceptance/cloc_spec.rb +54 -0
- data/spec/acceptance/create_error_list_file_spec.rb +29 -0
- data/spec/acceptance/issues_spec.rb +197 -0
- data/spec/acceptance/pluginscan_spec.rb +18 -0
- data/spec/acceptance/sloccount_spec.rb +39 -0
- data/spec/acceptance/vulnerabilities_spec.rb +57 -0
- data/spec/acceptance_spec_helper.rb +10 -0
- data/spec/checks_examples_spec.rb +352 -0
- data/spec/file_creator_spec.rb +51 -0
- data/spec/pluginscan/cloc_scanner/cloc_scanner_spec.rb +64 -0
- data/spec/pluginscan/cloc_scanner/cloc_spec.rb +30 -0
- data/spec/pluginscan/file_finder_spec.rb +91 -0
- data/spec/pluginscan/issues_scanner/check_findings_spec.rb +22 -0
- data/spec/pluginscan/issues_scanner/error_list_printer_ignores_spec.rb +35 -0
- data/spec/pluginscan/issues_scanner/error_list_printer_spec.rb +42 -0
- data/spec/pluginscan/issues_scanner/file_issues_scanner_spec.rb +25 -0
- data/spec/pluginscan/issues_scanner/issues_printer_factory_spec.rb +9 -0
- data/spec/pluginscan/issues_scanner/issues_spec.rb +55 -0
- data/spec/pluginscan/issues_scanner/variable_check_spec.rb +13 -0
- data/spec/pluginscan/issues_scanner/variable_safety_checker_spec.rb +81 -0
- data/spec/pluginscan/issues_scanner_spec.rb +21 -0
- data/spec/pluginscan/sloccount_scanner/sloccount_scanner_spec.rb +95 -0
- data/spec/pluginscan/sloccount_scanner/sloccount_spec.rb +72 -0
- data/spec/pluginscan/vulnerability_scanner_spec.rb +96 -0
- data/spec/process_spec_helper.rb +6 -0
- data/spec/spec_helper.rb +70 -0
- data/spec/support/acceptance_helpers.rb +68 -0
- data/spec/support/file_helpers.rb +35 -0
- data/spec/support/heredoc_helper.rb +7 -0
- data/spec/support/process_helpers.rb +25 -0
- data/spec/support/shared_examples_for_issue_checks.rb +31 -0
- data/spec/support/vcr_helper.rb +6 -0
- data/vcr_cassettes/wpvulndb/relevanssi.yml +78 -0
- metadata +342 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
# Extends Check with helpers for making patterns and ignores based on a list of function names
|
3
|
+
class FunctionCheck < Check
|
4
|
+
def initialize(check_hash)
|
5
|
+
check_hash.default = []
|
6
|
+
check_hash[:patterns] = self.class.patterns(check_hash[:patterns], check_hash[:function_names])
|
7
|
+
check_hash[:ignores] = self.class.ignores(check_hash[:ignores], check_hash[:function_names])
|
8
|
+
super(check_hash)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.function_list(functions)
|
12
|
+
# ^ Start of line
|
13
|
+
# [^a-z0-9|_] Characters which would imply that the word isn't the whole function name
|
14
|
+
# \s* any amount of whitespace
|
15
|
+
# \\( a literal open bracket - i.e. the start of the function arguments
|
16
|
+
functions.map{ |function| Regexp.new("(^|[^a-z0-9|_])(#{function})\s*\\(") }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.function_ignores(functions)
|
20
|
+
# If the author is creating a similarly named function then that should be ignored (?)
|
21
|
+
functions.map { |function| Regexp.new("function\s+[^a-z0-9|_]?#{function}\s*\\(") }
|
22
|
+
end
|
23
|
+
|
24
|
+
private def self.patterns(patterns, function_names)
|
25
|
+
Array(patterns) + function_list(function_names)
|
26
|
+
end
|
27
|
+
|
28
|
+
private def self.ignores(ignores, function_names)
|
29
|
+
Array(ignores) + function_ignores(function_names) + CommentChecker::COMMENT_REGEXPS
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
# Extends Check with helpers for making patterns and ignores based on a list of variables or constantst
|
3
|
+
class VariableCheck < Check
|
4
|
+
def initialize(check_hash)
|
5
|
+
super(check_hash)
|
6
|
+
@patterns = Array(check_hash[:variables]).map{ |var| Regexp.new(Regexp.escape(var)) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def ignore?(variable, content)
|
10
|
+
VariableSafetyChecker.new.all_safe?(variable, content) || CommentChecker.commented?(content)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
# Responsible for deciding whether usages of a variable in a string are safe
|
3
|
+
class VariableSafetyChecker
|
4
|
+
# Functions which, if they surround the variable, make it safe
|
5
|
+
# because they return a boolean and not the value of the varaible
|
6
|
+
SAFE_FUNCTIONS = [
|
7
|
+
"isset",
|
8
|
+
"empty",
|
9
|
+
"in_array",
|
10
|
+
"strpos",
|
11
|
+
"strlen",
|
12
|
+
"if",
|
13
|
+
"switch",
|
14
|
+
"is_email",
|
15
|
+
|
16
|
+
# PHP typechecks - seen in the wild:
|
17
|
+
"is_array",
|
18
|
+
|
19
|
+
# PHP typechecks - not seen in the wild:
|
20
|
+
"is_bool",
|
21
|
+
"is_callable",
|
22
|
+
"is_double",
|
23
|
+
"is_float",
|
24
|
+
"is_int",
|
25
|
+
"is_integer",
|
26
|
+
"is_long",
|
27
|
+
"is_null",
|
28
|
+
"is_numeric",
|
29
|
+
"is_object",
|
30
|
+
"is_real",
|
31
|
+
"is_resource",
|
32
|
+
"is_scalar",
|
33
|
+
"is_string",
|
34
|
+
|
35
|
+
"intval",
|
36
|
+
"absint",
|
37
|
+
"wp_verify_nonce",
|
38
|
+
"count",
|
39
|
+
"sizeof",
|
40
|
+
"unset",
|
41
|
+
|
42
|
+
# Candidates for inclusion - not seen in the wild:
|
43
|
+
# "gettype",
|
44
|
+
# "settype",
|
45
|
+
# "boolval",
|
46
|
+
# "doubleval", # might match eval?
|
47
|
+
# "floatval",
|
48
|
+
].freeze
|
49
|
+
|
50
|
+
# Infixes which, if they are used around the variable, make it safe,
|
51
|
+
# because they are checking the value, not returning it
|
52
|
+
SAFE_INFIXES = [
|
53
|
+
'==',
|
54
|
+
'===',
|
55
|
+
'!=',
|
56
|
+
'!==',
|
57
|
+
'<',
|
58
|
+
'>',
|
59
|
+
'<=',
|
60
|
+
'>=',
|
61
|
+
].freeze
|
62
|
+
|
63
|
+
INFIX_CHARS = %w(= < > !).freeze
|
64
|
+
|
65
|
+
def all_safe?(variable, content)
|
66
|
+
match_count(variable, content) <= safe_count(variable, content)
|
67
|
+
end
|
68
|
+
|
69
|
+
def match_count(variable, content)
|
70
|
+
content.scan(variable).count # `scan` returns ALL matches
|
71
|
+
end
|
72
|
+
|
73
|
+
def safe_count(variable, content)
|
74
|
+
safe_function_count(variable, content) +
|
75
|
+
safe_infix_count(variable, content)
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
# The number of matches which are safe by being wrapped in a function
|
81
|
+
private def safe_function_count(variable, content)
|
82
|
+
SAFE_FUNCTIONS.map { |function|
|
83
|
+
wrapped_in_function_count(function, variable, content)
|
84
|
+
}.inject(:+)
|
85
|
+
end
|
86
|
+
|
87
|
+
# The number of matches which are safe by being checked in an infix
|
88
|
+
private def safe_infix_count(variable, content)
|
89
|
+
SAFE_INFIXES.map { |infix|
|
90
|
+
used_in_infix_check_count(infix, variable, content)
|
91
|
+
}.inject(:+)
|
92
|
+
end
|
93
|
+
|
94
|
+
# TODO: the below methods feel private, but are directly tested
|
95
|
+
# That makes me feel like there's an object to be extracted here
|
96
|
+
def used_in_infix_check_count(infix, variable, content)
|
97
|
+
variable = Regexp.escape variable
|
98
|
+
infix = Regexp.escape infix
|
99
|
+
non_infix = "[^#{Regexp.escape INFIX_CHARS.join}]"
|
100
|
+
|
101
|
+
equals_before_regexp = /#{non_infix}#{infix}\ *#{variable}\ *\[/
|
102
|
+
equals_after_regexp = /#{variable}\ *\[[^\[]+\]\ *#{infix}#{non_infix}/
|
103
|
+
content.scan(equals_before_regexp).count +
|
104
|
+
content.scan(equals_after_regexp).count
|
105
|
+
end
|
106
|
+
|
107
|
+
def wrapped_in_function_count(function_name, variable, content)
|
108
|
+
variable = Regexp.escape variable
|
109
|
+
content.scan(/#{function_name}\ *\(\ *#{variable}[^)]*\)/).count
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
Finding = Struct.new(:lineno, :line, :match, :ignored)
|
3
|
+
|
4
|
+
class CheckFindings
|
5
|
+
attr_reader :findings
|
6
|
+
attr_reader :check
|
7
|
+
|
8
|
+
def initialize(check)
|
9
|
+
@check = check
|
10
|
+
@findings = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(more_findings)
|
14
|
+
@findings += more_findings
|
15
|
+
end
|
16
|
+
|
17
|
+
def any_findings?
|
18
|
+
!findings.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def all_ignored?
|
22
|
+
@findings.all?(&:ignored)
|
23
|
+
end
|
24
|
+
|
25
|
+
def count
|
26
|
+
findings.count
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
# Wraps a couple of helper methods around an array of issues
|
3
|
+
class Issues
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(issues)
|
7
|
+
@issues = issues
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(&block)
|
11
|
+
@issues.each(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def scanned_files_count
|
15
|
+
@issues.count
|
16
|
+
end
|
17
|
+
|
18
|
+
def found_problems_count
|
19
|
+
found_problems.reduce(0){ |count, (_file, file_findings)| count + file_findings.map(&:count).reduce(:+) }
|
20
|
+
end
|
21
|
+
|
22
|
+
private def files
|
23
|
+
@issues.keys
|
24
|
+
end
|
25
|
+
|
26
|
+
private def found_problems
|
27
|
+
# Ignore files which didn't have any problems
|
28
|
+
Issues.new(@issues.select { |_file, file_findings| !file_findings.empty? })
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'pluginscan/printer'
|
2
|
+
require 'pluginscan/reports/issues_report/issues_printer/file_issues_printer'
|
3
|
+
require 'pluginscan/reports/issues_report/issues_printer/check_findings_printer'
|
4
|
+
require 'pluginscan/reports/issues_report/issues_printer/finding_printer'
|
5
|
+
|
6
|
+
module Pluginscan
|
7
|
+
class IssuesPrinter < Printer
|
8
|
+
def initialize(hide_ignores, output = $stdout)
|
9
|
+
@hide_ignores = hide_ignores
|
10
|
+
@output = output
|
11
|
+
end
|
12
|
+
|
13
|
+
def print(data)
|
14
|
+
issues = data[:issues]
|
15
|
+
file_count = data[:file_count]
|
16
|
+
print_headline(issues, file_count)
|
17
|
+
print_results(issues)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def print_headline(issues, file_count)
|
23
|
+
@output.puts "Scanned #{issues.scanned_files_count} out of #{file_count} files and found #{issues.found_problems_count} things:".color(:blue)
|
24
|
+
end
|
25
|
+
|
26
|
+
def print_results(issues)
|
27
|
+
printer = FileIssuesPrinter.new(@hide_ignores, @output)
|
28
|
+
issues.each do |file, findings|
|
29
|
+
findings = findings.reject(&:all_ignored?) if @hide_ignores
|
30
|
+
printer.print(file, findings)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
# Responsible for printing a check description and all of the
|
3
|
+
# findings associated with that check
|
4
|
+
class CheckFindingsPrinter < Printer
|
5
|
+
def print(check, findings)
|
6
|
+
return if findings.empty? && check.name != 'Encoding' # Encoding deliberately has no findings because its a file-level check. TODO: Find a better way to handle this. The benefits of filtering out findings before we try and print them are significant.
|
7
|
+
|
8
|
+
@output.puts CheckView.new(check).title_line
|
9
|
+
print_findings(findings)
|
10
|
+
print_blank_line
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def print_findings(findings)
|
16
|
+
printer = FindingPrinter.new(@output)
|
17
|
+
findings.each do |finding|
|
18
|
+
printer.print(finding)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Decorate Check with view-specific methods
|
24
|
+
class CheckView < SimpleDelegator
|
25
|
+
def initialize(check)
|
26
|
+
@check = check
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def title_line
|
31
|
+
name = "#{@check.name}:".color(:red)
|
32
|
+
message = @check.message.color(:yellow)
|
33
|
+
|
34
|
+
" #{name} #{message}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'delegate' # This shouldn't (?) be required - should be autoloaded, but apparently isn't
|
2
|
+
|
3
|
+
module Pluginscan
|
4
|
+
# Print a findings report for an individual file
|
5
|
+
class FileIssuesPrinter < Printer
|
6
|
+
def initialize(hide_ignores, output = $stdout)
|
7
|
+
@hide_ignores = hide_ignores
|
8
|
+
@output = output
|
9
|
+
end
|
10
|
+
|
11
|
+
def print(file, checks_findings)
|
12
|
+
return if checks_findings.empty?
|
13
|
+
|
14
|
+
@output.puts FileView.new(file).file_path
|
15
|
+
print_findings(checks_findings)
|
16
|
+
# Doesn't need a blank line: each block of findings ends with a blank line
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def print_findings(checks_findings)
|
22
|
+
printer = CheckFindingsPrinter.new(@output)
|
23
|
+
checks_findings.each do |check_findings|
|
24
|
+
findings = check_findings.findings
|
25
|
+
findings.reject!(&:ignored) if @hide_ignores
|
26
|
+
printer.print(check_findings.check, findings)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class FileView < SimpleDelegator
|
32
|
+
def file_path
|
33
|
+
color(:green)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Pluginscan
|
2
|
+
# Responsible for printing a single finding on the command line
|
3
|
+
class FindingPrinter < Printer
|
4
|
+
def print(finding)
|
5
|
+
finding = FindingView.new(finding)
|
6
|
+
finding = IgnoredFindingView.new(finding) if finding.ignored
|
7
|
+
@output.puts finding.source_line
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class FindingView < SimpleDelegator
|
12
|
+
def source_line
|
13
|
+
data = {
|
14
|
+
lineno: lineno,
|
15
|
+
line: highlight_matches(line.strip),
|
16
|
+
}
|
17
|
+
|
18
|
+
# pad line number to 5. Because some plugin files really are over 10k lines
|
19
|
+
format " %<lineno>5s: %{line}\n", data
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def highlight_matches(line)
|
25
|
+
return line unless match # TODO: nil checks are evil!
|
26
|
+
|
27
|
+
line.gsub match, match.color(:cyan)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class IgnoredFindingView < SimpleDelegator
|
32
|
+
def source_line
|
33
|
+
i_symbol = "[I]".color(:red)
|
34
|
+
|
35
|
+
super.gsub(/^ /, " #{i_symbol}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'pluginscan/reports/issues_report/issues_printer'
|
2
|
+
require 'pluginscan/reports/issues_report/error_list_printer'
|
3
|
+
|
4
|
+
module Pluginscan
|
5
|
+
# Responsible for creating an object which can print out the list of issues
|
6
|
+
# in one of several different ways
|
7
|
+
class IssuesPrinterFactory
|
8
|
+
def self.create_printer(issues_format, hide_ignores = false, output = $stdout)
|
9
|
+
case issues_format
|
10
|
+
when :report
|
11
|
+
IssuesPrinter.new(hide_ignores, output)
|
12
|
+
when :error_list
|
13
|
+
ErrorListPrinter.new(hide_ignores, output)
|
14
|
+
else
|
15
|
+
fail Pluginscan::UnknownIssuesFormat, "Unknown issues formatter '#{issues_format}'"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'pluginscan/reports/issues_report/issues_scanner/file_issues_scanner'
|
2
|
+
require 'pluginscan/reports/issues_report/issues_scanner/line_issues_scanner'
|
3
|
+
require 'pluginscan/reports/issues_report/issues_scanner/utf8_checker'
|
4
|
+
require 'pluginscan/reports/issues_report/issues_models/check_findings'
|
5
|
+
require 'pluginscan/reports/issues_report/issues_models/issues'
|
6
|
+
|
7
|
+
module Pluginscan
|
8
|
+
class IssuesScanner
|
9
|
+
def initialize(checks)
|
10
|
+
file_issues_scanner = FileIssuesScanner.new(checks)
|
11
|
+
@file_scanner = FileScanner.new(file_issues_scanner)
|
12
|
+
end
|
13
|
+
|
14
|
+
def scan(files)
|
15
|
+
issues = scan_files(files)
|
16
|
+
|
17
|
+
Issues.new(issues)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def scan_files(file_paths)
|
23
|
+
file_paths.inject({}) do |issues, file_path|
|
24
|
+
issues.merge file_path => scan_file(file_path)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def scan_file(file_path)
|
29
|
+
file_contents = File.read(file_path)
|
30
|
+
@file_scanner.scan(file_contents)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Responsible for checking that a file's contents is valid
|
35
|
+
# and if so, running an issues scan on it
|
36
|
+
class FileScanner
|
37
|
+
def initialize(issues_scanner)
|
38
|
+
@issues_scanner = issues_scanner
|
39
|
+
end
|
40
|
+
|
41
|
+
def scan(file_contents)
|
42
|
+
# Check file contents are valid UTF-8 to avoid exceptions later
|
43
|
+
invalid_utf8 = UTF8Checker.new.check(file_contents)
|
44
|
+
return Array(invalid_utf8) if invalid_utf8
|
45
|
+
|
46
|
+
@issues_scanner.scan(file_contents)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Pluginscan
|
4
|
+
# Responsible for scanning a file for a set of issue types
|
5
|
+
class FileIssuesScanner
|
6
|
+
attr_reader :file_results
|
7
|
+
|
8
|
+
def initialize(checks)
|
9
|
+
@checks = checks
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns an array of CheckFindings objects
|
13
|
+
def scan(file_contents)
|
14
|
+
# Run each check on the file
|
15
|
+
checks_findings = @checks.map { |check| LinesIssuesScanner.new(check).scan(file_contents) }
|
16
|
+
checks_findings.select(&:any_findings?)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Pluginscan
|
22
|
+
# Responsible for scanning each line of a file for issues of a certain type
|
23
|
+
class LinesIssuesScanner
|
24
|
+
def initialize(check)
|
25
|
+
@line_issues_scanner = LineIssuesScanner.new(check)
|
26
|
+
@check_findings = CheckFindings.new(check)
|
27
|
+
end
|
28
|
+
|
29
|
+
def scan(file_contents)
|
30
|
+
string_io = StringIO.new(file_contents)
|
31
|
+
@check_findings.tap do |check_findings|
|
32
|
+
string_io.each_line do |line|
|
33
|
+
check_findings.add @line_issues_scanner.scan(line, string_io.lineno)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|