arachni 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/README.md +9 -2
- data/components/checks/active/code_injection.rb +5 -5
- data/components/checks/active/code_injection_timing.rb +3 -3
- data/components/checks/active/no_sql_injection_differential.rb +3 -2
- data/components/checks/active/os_cmd_injection.rb +11 -5
- data/components/checks/active/os_cmd_injection_timing.rb +11 -4
- data/components/checks/active/path_traversal.rb +2 -2
- data/components/checks/active/sql_injection.rb +1 -1
- data/components/checks/active/sql_injection/patterns/mssql +1 -0
- data/components/checks/active/sql_injection_differential.rb +3 -2
- data/components/checks/active/unvalidated_redirect.rb +3 -3
- data/components/checks/passive/common_directories/directories.txt +2 -0
- data/components/checks/passive/common_files/filenames.txt +1 -0
- data/lib/arachni/browser.rb +17 -1
- data/lib/arachni/check/auditor.rb +5 -2
- data/lib/arachni/check/base.rb +30 -5
- data/lib/arachni/element/capabilities/analyzable/differential.rb +2 -5
- data/lib/arachni/element/capabilities/auditable.rb +3 -1
- data/lib/arachni/element/capabilities/with_dom.rb +1 -0
- data/lib/arachni/element/capabilities/with_node.rb +1 -1
- data/lib/arachni/element/cookie.rb +2 -2
- data/lib/arachni/element/form.rb +1 -1
- data/lib/arachni/element/header.rb +2 -2
- data/lib/arachni/element/link_template.rb +1 -1
- data/lib/arachni/framework.rb +21 -1144
- data/lib/arachni/framework/parts/audit.rb +282 -0
- data/lib/arachni/framework/parts/browser.rb +132 -0
- data/lib/arachni/framework/parts/check.rb +86 -0
- data/lib/arachni/framework/parts/data.rb +158 -0
- data/lib/arachni/framework/parts/platform.rb +34 -0
- data/lib/arachni/framework/parts/plugin.rb +61 -0
- data/lib/arachni/framework/parts/report.rb +128 -0
- data/lib/arachni/framework/parts/scope.rb +40 -0
- data/lib/arachni/framework/parts/state.rb +457 -0
- data/lib/arachni/http/client.rb +33 -30
- data/lib/arachni/http/request.rb +6 -2
- data/lib/arachni/issue.rb +55 -1
- data/lib/arachni/platform/manager.rb +25 -21
- data/lib/arachni/state/framework.rb +7 -1
- data/lib/arachni/utilities.rb +10 -0
- data/lib/version +1 -1
- data/spec/arachni/browser_spec.rb +13 -0
- data/spec/arachni/check/auditor_spec.rb +1 -0
- data/spec/arachni/check/base_spec.rb +80 -0
- data/spec/arachni/element/cookie_spec.rb +2 -2
- data/spec/arachni/framework/parts/audit_spec.rb +391 -0
- data/spec/arachni/framework/parts/browser_spec.rb +26 -0
- data/spec/arachni/framework/parts/check_spec.rb +24 -0
- data/spec/arachni/framework/parts/data_spec.rb +187 -0
- data/spec/arachni/framework/parts/platform_spec.rb +62 -0
- data/spec/arachni/framework/parts/plugin_spec.rb +41 -0
- data/spec/arachni/framework/parts/report_spec.rb +66 -0
- data/spec/arachni/framework/parts/scope_spec.rb +86 -0
- data/spec/arachni/framework/parts/state_spec.rb +528 -0
- data/spec/arachni/framework_spec.rb +17 -1344
- data/spec/arachni/http/client_spec.rb +12 -7
- data/spec/arachni/issue_spec.rb +35 -0
- data/spec/arachni/platform/manager_spec.rb +2 -3
- data/spec/arachni/state/framework_spec.rb +15 -0
- data/spec/components/checks/active/code_injection_timing_spec.rb +5 -5
- data/spec/components/checks/active/no_sql_injection_differential_spec.rb +4 -0
- data/spec/components/checks/active/os_cmd_injection_spec.rb +20 -7
- data/spec/components/checks/active/os_cmd_injection_timing_spec.rb +5 -5
- data/spec/components/checks/active/sql_injection_differential_spec.rb +4 -0
- data/spec/components/checks/active/sql_injection_spec.rb +2 -3
- data/spec/support/servers/arachni/browser.rb +31 -0
- data/spec/support/servers/checks/active/code_injection.rb +1 -1
- data/spec/support/servers/checks/active/no_sql_injection_differential.rb +36 -34
- data/spec/support/servers/checks/active/os_cmd_injection.rb +6 -12
- data/spec/support/servers/checks/active/os_cmd_injection_timing.rb +9 -4
- data/spec/support/servers/checks/active/sql_injection.rb +1 -1
- data/spec/support/servers/checks/active/sql_injection_differential.rb +37 -34
- data/spec/support/shared/element/capabilities/with_node.rb +25 -0
- data/spec/support/shared/framework.rb +26 -0
- data/ui/cli/output.rb +2 -0
- data/ui/cli/rpc/server/dispatcher/option_parser.rb +1 -1
- metadata +32 -4
- data/components/checks/active/sql_injection/patterns/coldfusion +0 -1
@@ -0,0 +1,282 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright 2010-2014 Tasos Laskos <tasos.laskos@arachni-scanner.com>
|
3
|
+
|
4
|
+
This file is part of the Arachni Framework project and is subject to
|
5
|
+
redistribution and commercial restrictions. Please see the Arachni Framework
|
6
|
+
web site for more information on licensing and terms of use.
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Arachni
|
10
|
+
class Framework
|
11
|
+
module Parts
|
12
|
+
|
13
|
+
# Provides {Page} audit functionality and everything related to it, like
|
14
|
+
# handling the {Session} and {Trainer}.
|
15
|
+
#
|
16
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
|
17
|
+
module Audit
|
18
|
+
include Support::Mixins::Observable
|
19
|
+
|
20
|
+
# @!method on_page_audit( &block )
|
21
|
+
advertise :on_page_audit
|
22
|
+
|
23
|
+
# @!method after_page_audit( &block )
|
24
|
+
advertise :after_page_audit
|
25
|
+
|
26
|
+
# @return [Trainer]
|
27
|
+
attr_reader :trainer
|
28
|
+
|
29
|
+
# @return [Session]
|
30
|
+
# Web application session manager.
|
31
|
+
attr_reader :session
|
32
|
+
|
33
|
+
# @return [Arachni::HTTP]
|
34
|
+
attr_reader :http
|
35
|
+
|
36
|
+
# @return [Array<String>]
|
37
|
+
# Page URLs which elicited no response from the server and were not audited.
|
38
|
+
# Not determined by HTTP status codes, we're talking network failures here.
|
39
|
+
attr_reader :failures
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
super
|
43
|
+
|
44
|
+
@http = HTTP::Client.instance
|
45
|
+
|
46
|
+
# Holds page URLs which returned no response.
|
47
|
+
@failures = []
|
48
|
+
@retries = {}
|
49
|
+
|
50
|
+
@current_url = ''
|
51
|
+
|
52
|
+
reset_session
|
53
|
+
reset_trainer
|
54
|
+
end
|
55
|
+
|
56
|
+
# @note Will update the {HTTP::Client#cookie_jar} with {Page#cookie_jar}.
|
57
|
+
# @note It will audit just the given `page` and not any subsequent pages
|
58
|
+
# discovered by the {Trainer} -- i.e. ignore any new elements that might
|
59
|
+
# appear as a result.
|
60
|
+
# @note It will pass the `page` to the {BrowserCluster} for analysis if the
|
61
|
+
# {Page::Scope#dom_depth_limit_reached? DOM depth limit} has
|
62
|
+
# not been reached and push resulting pages to {#push_to_page_queue} but
|
63
|
+
# will not audit those pages either.
|
64
|
+
#
|
65
|
+
# @param [Page] page
|
66
|
+
# Runs loaded checks against `page`
|
67
|
+
def audit_page( page )
|
68
|
+
return if !page
|
69
|
+
|
70
|
+
if page.scope.out?
|
71
|
+
print_info "Ignoring page due to exclusion criteria: #{page.dom.url}"
|
72
|
+
return false
|
73
|
+
end
|
74
|
+
|
75
|
+
# Initialize the BrowserCluster.
|
76
|
+
browser_cluster
|
77
|
+
|
78
|
+
state.audited_page_count += 1
|
79
|
+
add_to_sitemap( page )
|
80
|
+
sitemap.merge!( browser_sitemap )
|
81
|
+
|
82
|
+
print_line
|
83
|
+
print_status "[HTTP: #{page.code}] #{page.dom.url}"
|
84
|
+
|
85
|
+
if page.platforms.any?
|
86
|
+
print_info "Identified as: #{page.platforms.to_a.join( ', ' )}"
|
87
|
+
end
|
88
|
+
|
89
|
+
if crawl?
|
90
|
+
pushed = push_paths_from_page( page )
|
91
|
+
print_info "Analysis resulted in #{pushed.size} usable paths."
|
92
|
+
end
|
93
|
+
|
94
|
+
if host_has_browser?
|
95
|
+
print_info "DOM depth: #{page.dom.depth} (Limit: #{options.scope.dom_depth_limit})"
|
96
|
+
|
97
|
+
if page.dom.transitions.any?
|
98
|
+
print_info ' Transitions:'
|
99
|
+
page.dom.print_transitions( method(:print_info), ' ' )
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Aside from plugins and whatnot, the Trainer hooks here to update the
|
104
|
+
# ElementFilter so that it'll know if new elements appear during the
|
105
|
+
# audit, so it's a big deal.
|
106
|
+
notify_on_page_audit( page )
|
107
|
+
|
108
|
+
@current_url = page.dom.url.to_s
|
109
|
+
|
110
|
+
http.update_cookies( page.cookie_jar )
|
111
|
+
perform_browser_analysis( page )
|
112
|
+
|
113
|
+
# Remove elements which have already passed through here.
|
114
|
+
pre_audit_element_filter( page )
|
115
|
+
|
116
|
+
# Run checks which **don't** benefit from fingerprinting first, so that
|
117
|
+
# we can use the responses of their HTTP requests to fingerprint the
|
118
|
+
# webapp platforms, so that the checks which **do** benefit from knowing
|
119
|
+
# the remote platforms can run more efficiently.
|
120
|
+
ran = false
|
121
|
+
@checks.without_platforms.values.each do |check|
|
122
|
+
ran = true if check_page( check, page )
|
123
|
+
end
|
124
|
+
harvest_http_responses if ran
|
125
|
+
run_http = ran
|
126
|
+
|
127
|
+
ran = false
|
128
|
+
@checks.with_platforms.values.each do |check|
|
129
|
+
ran = true if check_page( check, page )
|
130
|
+
end
|
131
|
+
harvest_http_responses if ran
|
132
|
+
run_http ||= ran
|
133
|
+
|
134
|
+
if Arachni::Check::Auditor.has_timeout_candidates?
|
135
|
+
print_line
|
136
|
+
print_status "Verifying timeout-analysis candidates for: #{page.dom.url}"
|
137
|
+
print_info '---------------------------------------'
|
138
|
+
Arachni::Check::Auditor.timeout_audit_run
|
139
|
+
run_http = true
|
140
|
+
end
|
141
|
+
|
142
|
+
# Makes it easier on the GC.
|
143
|
+
page.clear_cache
|
144
|
+
|
145
|
+
notify_after_page_audit( page )
|
146
|
+
run_http
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
# Performs the audit.
|
152
|
+
def audit
|
153
|
+
handle_signals
|
154
|
+
return if aborted?
|
155
|
+
|
156
|
+
state.status = :scanning if !pausing?
|
157
|
+
|
158
|
+
push_to_url_queue( options.url )
|
159
|
+
options.scope.extend_paths.each { |url| push_to_url_queue( url ) }
|
160
|
+
options.scope.restrict_paths.each { |url| push_to_url_queue( url, true ) }
|
161
|
+
|
162
|
+
# Initialize the BrowserCluster.
|
163
|
+
browser_cluster
|
164
|
+
|
165
|
+
# Keep auditing until there are no more resources in the queues and the
|
166
|
+
# browsers have stopped spinning.
|
167
|
+
loop do
|
168
|
+
show_workload_msg = true
|
169
|
+
while !has_audit_workload? && wait_for_browser?
|
170
|
+
if show_workload_msg
|
171
|
+
print_line
|
172
|
+
print_status 'Workload exhausted, waiting for new pages' <<
|
173
|
+
' from the browser-cluster...'
|
174
|
+
end
|
175
|
+
show_workload_msg = false
|
176
|
+
|
177
|
+
last_pending_jobs ||= 0
|
178
|
+
pending_jobs = browser_cluster.pending_job_counter
|
179
|
+
if pending_jobs != last_pending_jobs
|
180
|
+
browser_cluster.print_info "Pending jobs: #{pending_jobs}"
|
181
|
+
end
|
182
|
+
last_pending_jobs = pending_jobs
|
183
|
+
|
184
|
+
sleep 0.1
|
185
|
+
end
|
186
|
+
|
187
|
+
audit_queues
|
188
|
+
|
189
|
+
break if page_limit_reached?
|
190
|
+
break if !has_audit_workload? && !wait_for_browser?
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Audits the {Data::Framework.url_queue URL} and {Data::Framework.page_queue Page}
|
195
|
+
# queues while maintaining a valid session with the webapp if we've got
|
196
|
+
# login capabilities.
|
197
|
+
def audit_queues
|
198
|
+
return if @audit_queues_done == false || !has_audit_workload? ||
|
199
|
+
page_limit_reached?
|
200
|
+
|
201
|
+
@audit_queues_done = false
|
202
|
+
|
203
|
+
# If for some reason we've got pages in the page queue this early,
|
204
|
+
# consume them and get it over with.
|
205
|
+
audit_page_queue
|
206
|
+
|
207
|
+
next_page = nil
|
208
|
+
while !suspended? && !page_limit_reached? &&
|
209
|
+
(page = next_page || pop_page_from_url_queue)
|
210
|
+
|
211
|
+
# Helps us schedule the next page to be grabbed along with the audit
|
212
|
+
# requests for the current page to avoid blocking.
|
213
|
+
next_page = nil
|
214
|
+
next_page_call = proc do
|
215
|
+
pop_page_from_url_queue { |p| next_page = p }
|
216
|
+
end
|
217
|
+
|
218
|
+
# If we can login capabilities make sure that our session is valid
|
219
|
+
# before grabbing and auditing the next page.
|
220
|
+
if session.can_login?
|
221
|
+
# Schedule the login check to happen along with the audit requests
|
222
|
+
# to prevent blocking and grab the next page as well.
|
223
|
+
session.logged_in? do |bool|
|
224
|
+
next next_page_call.call if bool
|
225
|
+
|
226
|
+
session.login
|
227
|
+
next_page_call
|
228
|
+
end
|
229
|
+
else
|
230
|
+
next_page_call.call
|
231
|
+
end
|
232
|
+
|
233
|
+
# We're counting on piggybacking the next page retrieval with the
|
234
|
+
# page audit, however if there wasn't an audit we need to force an
|
235
|
+
# HTTP run.
|
236
|
+
audit_page( page ) or http.run
|
237
|
+
|
238
|
+
if next_page && suspend?
|
239
|
+
data.page_queue << next_page
|
240
|
+
end
|
241
|
+
|
242
|
+
handle_signals
|
243
|
+
|
244
|
+
# Consume pages somehow triggered by the audit and pushed by the
|
245
|
+
# trainer or plugins or whatever.
|
246
|
+
audit_page_queue
|
247
|
+
end
|
248
|
+
|
249
|
+
audit_page_queue
|
250
|
+
|
251
|
+
@audit_queues_done = true
|
252
|
+
true
|
253
|
+
end
|
254
|
+
|
255
|
+
# Audits the page queue.
|
256
|
+
#
|
257
|
+
# @see #pop_page_from_queue
|
258
|
+
def audit_page_queue
|
259
|
+
while !suspended? && !page_limit_reached? && (page = pop_page_from_queue)
|
260
|
+
audit_page( page )
|
261
|
+
handle_signals
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def harvest_http_responses
|
266
|
+
print_status 'Harvesting HTTP responses...'
|
267
|
+
print_info 'Depending on server responsiveness and network' <<
|
268
|
+
' conditions this may take a while.'
|
269
|
+
|
270
|
+
# Run all the queued HTTP requests and harvest the responses.
|
271
|
+
http.run
|
272
|
+
|
273
|
+
# Needed for some HTTP callbacks.
|
274
|
+
http.run
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
@@ -0,0 +1,132 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright 2010-2014 Tasos Laskos <tasos.laskos@arachni-scanner.com>
|
3
|
+
|
4
|
+
This file is part of the Arachni Framework project and is subject to
|
5
|
+
redistribution and commercial restrictions. Please see the Arachni Framework
|
6
|
+
web site for more information on licensing and terms of use.
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Arachni
|
10
|
+
class Framework
|
11
|
+
module Parts
|
12
|
+
|
13
|
+
# Provides access to the {BrowserCluster} and relevant helpers.
|
14
|
+
#
|
15
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
|
16
|
+
module Browser
|
17
|
+
|
18
|
+
# @return [BrowserCluster, nil]
|
19
|
+
# A lazy-loaded browser cluster or `nil` if
|
20
|
+
# {OptionGroups::BrowserCluster#pool_size} or
|
21
|
+
# {OptionGroups::Scope#dom_depth_limit} are 0 or not
|
22
|
+
# {#host_has_browser?}.
|
23
|
+
def browser_cluster
|
24
|
+
return if options.browser_cluster.pool_size == 0 ||
|
25
|
+
Options.scope.dom_depth_limit == 0 || !host_has_browser?
|
26
|
+
|
27
|
+
# Initialization may take a while so since we lazy load this make sure
|
28
|
+
# that only one thread gets to this code at a time.
|
29
|
+
synchronize do
|
30
|
+
if !@browser_cluster
|
31
|
+
state.set_status_message :browser_cluster_startup
|
32
|
+
end
|
33
|
+
|
34
|
+
@browser_cluster ||= BrowserCluster.new
|
35
|
+
state.clear_status_messages
|
36
|
+
@browser_cluster
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Bool]
|
41
|
+
# `true` if the environment has a browser, `false` otherwise.
|
42
|
+
def host_has_browser?
|
43
|
+
Arachni::Browser.has_executable?
|
44
|
+
end
|
45
|
+
|
46
|
+
def wait_for_browser?
|
47
|
+
@browser_cluster && !browser_cluster.done?
|
48
|
+
end
|
49
|
+
|
50
|
+
def browser_job_skip_states
|
51
|
+
return if !@browser_cluster
|
52
|
+
browser_cluster.skip_states( browser_job.id )
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def shutdown_browser_cluster
|
58
|
+
return if !@browser_cluster
|
59
|
+
|
60
|
+
browser_cluster.shutdown
|
61
|
+
|
62
|
+
@browser_cluster = nil
|
63
|
+
@browser_job = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def browser_sitemap
|
67
|
+
return {} if !@browser_cluster
|
68
|
+
browser_cluster.sitemap
|
69
|
+
end
|
70
|
+
|
71
|
+
def browser_job_update_skip_states( states )
|
72
|
+
return if states.empty?
|
73
|
+
browser_cluster.update_skip_states browser_job.id, states
|
74
|
+
end
|
75
|
+
|
76
|
+
def handle_browser_page( page )
|
77
|
+
synchronize do
|
78
|
+
return if !push_to_page_queue page
|
79
|
+
|
80
|
+
pushed_paths = nil
|
81
|
+
if crawl?
|
82
|
+
pushed_paths = push_paths_from_page( page ).size
|
83
|
+
end
|
84
|
+
|
85
|
+
print_status "Got new page from the browser-cluster: #{page.dom.url}"
|
86
|
+
print_info "DOM depth: #{page.dom.depth} (Limit: #{options.scope.dom_depth_limit})"
|
87
|
+
|
88
|
+
if page.dom.transitions.any?
|
89
|
+
print_info ' Transitions:'
|
90
|
+
page.dom.print_transitions( method(:print_info), ' ' )
|
91
|
+
end
|
92
|
+
|
93
|
+
if pushed_paths
|
94
|
+
print_info " -- Analysis resulted in #{pushed_paths} usable paths."
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Passes the `page` to {BrowserCluster#queue} and then pushes
|
100
|
+
# the resulting pages to {#push_to_page_queue}.
|
101
|
+
#
|
102
|
+
# @param [Page] page
|
103
|
+
# Page to analyze.
|
104
|
+
def perform_browser_analysis( page )
|
105
|
+
return if !browser_cluster || !accepts_more_pages? ||
|
106
|
+
Options.scope.dom_depth_limit.to_i < page.dom.depth + 1 ||
|
107
|
+
!page.has_script?
|
108
|
+
|
109
|
+
browser_cluster.queue( browser_job.forward( resource: page ) ) do |response|
|
110
|
+
handle_browser_page response.page
|
111
|
+
end
|
112
|
+
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
def browser_job
|
117
|
+
# We'll recycle the same job since all of them will have the same
|
118
|
+
# callback. This will force the BrowserCluster to use the same block
|
119
|
+
# for all queued jobs.
|
120
|
+
#
|
121
|
+
# Also, this job should never end so that all analysis operations
|
122
|
+
# share the same state.
|
123
|
+
@browser_job ||= BrowserCluster::Jobs::ResourceExploration.new(
|
124
|
+
never_ending: true
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright 2010-2014 Tasos Laskos <tasos.laskos@arachni-scanner.com>
|
3
|
+
|
4
|
+
This file is part of the Arachni Framework project and is subject to
|
5
|
+
redistribution and commercial restrictions. Please see the Arachni Framework
|
6
|
+
web site for more information on licensing and terms of use.
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Arachni
|
10
|
+
class Framework
|
11
|
+
module Parts
|
12
|
+
|
13
|
+
# Provides a {Arachni::Check::Manager} and related helpers.
|
14
|
+
#
|
15
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
|
16
|
+
module Check
|
17
|
+
|
18
|
+
# @return [Arachni::Check::Manager]
|
19
|
+
attr_reader :checks
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
super
|
23
|
+
@checks = Arachni::Check::Manager.new( self )
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Array<Hash>]
|
27
|
+
# Information about all available {Checks}.
|
28
|
+
def list_checks( patterns = nil )
|
29
|
+
loaded = @checks.loaded
|
30
|
+
|
31
|
+
begin
|
32
|
+
@checks.clear
|
33
|
+
@checks.available.map do |name|
|
34
|
+
path = @checks.name_to_path( name )
|
35
|
+
next if !list_check?( path, patterns )
|
36
|
+
|
37
|
+
@checks[name].info.merge(
|
38
|
+
shortname: name,
|
39
|
+
author: [@checks[name].info[:author]].
|
40
|
+
flatten.map { |a| a.strip },
|
41
|
+
path: path.strip,
|
42
|
+
platforms: @checks[name].platforms,
|
43
|
+
elements: @checks[name].elements
|
44
|
+
)
|
45
|
+
end.compact
|
46
|
+
ensure
|
47
|
+
@checks.clear
|
48
|
+
@checks.load loaded
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Passes a page to the check and runs it.
|
55
|
+
# It also handles any exceptions thrown by the check at runtime.
|
56
|
+
#
|
57
|
+
# @param [Check::Base] check
|
58
|
+
# Check to run.
|
59
|
+
# @param [Page] page
|
60
|
+
def check_page( check, page )
|
61
|
+
# If we've been given platforms which the check doesn't support don't
|
62
|
+
# even bother running it.
|
63
|
+
if !check.supports_platforms?( Options.platforms )
|
64
|
+
print_info "Check #{check.shortname} does not support: " <<
|
65
|
+
Options.platforms.join( ', ' )
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
|
69
|
+
begin
|
70
|
+
@checks.run_one( check, page )
|
71
|
+
rescue => e
|
72
|
+
print_error "Error in #{check.to_s}: #{e.to_s}"
|
73
|
+
print_error_backtrace e
|
74
|
+
false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def list_check?( path, patterns = nil )
|
79
|
+
regexp_array_match( patterns, path )
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|