romanbsd-tarantula 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +47 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +162 -0
- data/Rakefile +69 -0
- data/VERSION.yml +4 -0
- data/examples/example_helper.rb +48 -0
- data/examples/relevance/core_extensions/ellipsize_example.rb +19 -0
- data/examples/relevance/core_extensions/file_example.rb +8 -0
- data/examples/relevance/core_extensions/response_example.rb +29 -0
- data/examples/relevance/core_extensions/test_case_example.rb +20 -0
- data/examples/relevance/tarantula/attack_form_submission_example.rb +79 -0
- data/examples/relevance/tarantula/attack_handler_example.rb +29 -0
- data/examples/relevance/tarantula/crawler_example.rb +386 -0
- data/examples/relevance/tarantula/form_example.rb +50 -0
- data/examples/relevance/tarantula/form_submission_example.rb +71 -0
- data/examples/relevance/tarantula/html_document_handler_example.rb +43 -0
- data/examples/relevance/tarantula/html_report_helper_example.rb +46 -0
- data/examples/relevance/tarantula/html_reporter_example.rb +82 -0
- data/examples/relevance/tarantula/invalid_html_handler_example.rb +33 -0
- data/examples/relevance/tarantula/io_reporter_example.rb +11 -0
- data/examples/relevance/tarantula/link_example.rb +67 -0
- data/examples/relevance/tarantula/log_grabber_example.rb +26 -0
- data/examples/relevance/tarantula/rails_integration_proxy_example.rb +88 -0
- data/examples/relevance/tarantula/result_example.rb +85 -0
- data/examples/relevance/tarantula/tidy_handler_example.rb +58 -0
- data/examples/relevance/tarantula/transform_example.rb +20 -0
- data/examples/relevance/tarantula/w3c_validator_example.rb +71 -0
- data/examples/relevance/tarantula_example.rb +23 -0
- data/laf/images/button_active.png +0 -0
- data/laf/images/button_hover.png +0 -0
- data/laf/images/button_inactive.png +0 -0
- data/laf/images/header_bg.jpg +0 -0
- data/laf/images/logo.png +0 -0
- data/laf/images/tagline.png +0 -0
- data/laf/javascripts/jquery-1.2.3.js +3408 -0
- data/laf/javascripts/jquery-ui-tabs.js +890 -0
- data/laf/javascripts/jquery.tablesorter.js +861 -0
- data/laf/javascripts/niftyLayout.js +11 -0
- data/laf/javascripts/niftycube-details.js +298 -0
- data/laf/javascripts/niftycube.js +298 -0
- data/laf/javascripts/tarantula.js +10 -0
- data/laf/stylesheets/tarantula.css +345 -0
- data/laf/v2/detail.html +59 -0
- data/laf/v2/images/button_active.png +0 -0
- data/laf/v2/images/button_hover.png +0 -0
- data/laf/v2/images/button_inactive.png +0 -0
- data/laf/v2/images/header_bg.jpg +0 -0
- data/laf/v2/images/logo.png +0 -0
- data/laf/v2/images/tagline.png +0 -0
- data/laf/v2/index.html +77 -0
- data/laf/v2/stylesheets/tarantula.v2.css +324 -0
- data/lib/relevance/core_extensions/ellipsize.rb +34 -0
- data/lib/relevance/core_extensions/file.rb +9 -0
- data/lib/relevance/core_extensions/metaclass.rb +78 -0
- data/lib/relevance/core_extensions/response.rb +9 -0
- data/lib/relevance/core_extensions/string_chars_fix.rb +11 -0
- data/lib/relevance/core_extensions/test_case.rb +19 -0
- data/lib/relevance/tarantula/attack.rb +15 -0
- data/lib/relevance/tarantula/attack_form_submission.rb +75 -0
- data/lib/relevance/tarantula/attack_handler.rb +37 -0
- data/lib/relevance/tarantula/crawler.rb +264 -0
- data/lib/relevance/tarantula/detail.html.erb +82 -0
- data/lib/relevance/tarantula/form.rb +21 -0
- data/lib/relevance/tarantula/form_submission.rb +70 -0
- data/lib/relevance/tarantula/html_document_handler.rb +36 -0
- data/lib/relevance/tarantula/html_report_helper.rb +39 -0
- data/lib/relevance/tarantula/html_reporter.rb +105 -0
- data/lib/relevance/tarantula/index.html.erb +37 -0
- data/lib/relevance/tarantula/invalid_html_handler.rb +18 -0
- data/lib/relevance/tarantula/io_reporter.rb +34 -0
- data/lib/relevance/tarantula/link.rb +56 -0
- data/lib/relevance/tarantula/log_grabber.rb +16 -0
- data/lib/relevance/tarantula/rails_integration_proxy.rb +68 -0
- data/lib/relevance/tarantula/recording.rb +12 -0
- data/lib/relevance/tarantula/response.rb +13 -0
- data/lib/relevance/tarantula/result.rb +66 -0
- data/lib/relevance/tarantula/test_report.html.erb +32 -0
- data/lib/relevance/tarantula/tidy_handler.rb +32 -0
- data/lib/relevance/tarantula/transform.rb +17 -0
- data/lib/relevance/tarantula/w3c_validator.rb +33 -0
- data/lib/relevance/tarantula.rb +59 -0
- data/tasks/tarantula_tasks.rake +36 -0
- data/template/tarantula_test.rb +22 -0
- data/vendor/w3c_validators/CHANGELOG +14 -0
- data/vendor/w3c_validators/LICENSE +60 -0
- data/vendor/w3c_validators/README +120 -0
- data/vendor/w3c_validators/README.svn +4 -0
- data/vendor/w3c_validators/lib/w3c_validators/constants.rb +80 -0
- data/vendor/w3c_validators/lib/w3c_validators/css_validator.rb +149 -0
- data/vendor/w3c_validators/lib/w3c_validators/exceptions.rb +4 -0
- data/vendor/w3c_validators/lib/w3c_validators/feed_validator.rb +110 -0
- data/vendor/w3c_validators/lib/w3c_validators/markup_validator.rb +227 -0
- data/vendor/w3c_validators/lib/w3c_validators/message.rb +82 -0
- data/vendor/w3c_validators/lib/w3c_validators/results.rb +62 -0
- data/vendor/w3c_validators/lib/w3c_validators/validator.rb +157 -0
- data/vendor/w3c_validators/lib/w3c_validators.rb +5 -0
- data/vendor/w3c_validators/rakefile.rb +53 -0
- data/vendor/w3c_validators/test/fixtures/invalid_css.css +2 -0
- data/vendor/w3c_validators/test/fixtures/invalid_encoding.html +10 -0
- data/vendor/w3c_validators/test/fixtures/invalid_feed.xml +19 -0
- data/vendor/w3c_validators/test/fixtures/invalid_html5.html +16 -0
- data/vendor/w3c_validators/test/fixtures/invalid_markup.html +11 -0
- data/vendor/w3c_validators/test/fixtures/valid_css.css +2 -0
- data/vendor/w3c_validators/test/fixtures/valid_feed.xml +20 -0
- data/vendor/w3c_validators/test/fixtures/valid_html5.html +16 -0
- data/vendor/w3c_validators/test/fixtures/valid_markup.html +11 -0
- data/vendor/w3c_validators/test/test_css_validator.rb +51 -0
- data/vendor/w3c_validators/test/test_exceptions.rb +35 -0
- data/vendor/w3c_validators/test/test_feed_validator.rb +61 -0
- data/vendor/w3c_validators/test/test_helper.rb +6 -0
- data/vendor/w3c_validators/test/test_html5_validator.rb +64 -0
- data/vendor/w3c_validators/test/test_markup_validator.rb +94 -0
- data/vendor/xss-shield/MIT-LICENSE +20 -0
- data/vendor/xss-shield/README +76 -0
- data/vendor/xss-shield/init.rb +16 -0
- data/vendor/xss-shield/lib/xss_shield/erb_hacks.rb +111 -0
- data/vendor/xss-shield/lib/xss_shield/haml_hacks.rb +42 -0
- data/vendor/xss-shield/lib/xss_shield/safe_string.rb +47 -0
- data/vendor/xss-shield/lib/xss_shield/secure_helpers.rb +40 -0
- data/vendor/xss-shield/lib/xss_shield.rb +6 -0
- data/vendor/xss-shield/test/test_actionview_integration.rb +40 -0
- data/vendor/xss-shield/test/test_erb.rb +44 -0
- data/vendor/xss-shield/test/test_haml.rb +43 -0
- data/vendor/xss-shield/test/test_helpers.rb +25 -0
- data/vendor/xss-shield/test/test_safe_string.rb +55 -0
- metadata +218 -0
@@ -0,0 +1,264 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/base'
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "rails_integration_proxy"))
|
4
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "html_document_handler.rb"))
|
5
|
+
|
6
|
+
class Relevance::Tarantula::Crawler
|
7
|
+
extend Forwardable
|
8
|
+
include Relevance::Tarantula
|
9
|
+
|
10
|
+
class CrawlTimeout < RuntimeError; end
|
11
|
+
|
12
|
+
attr_accessor :proxy, :handlers, :skip_uri_patterns, :log_grabber,
|
13
|
+
:reporters, :links_to_crawl, :links_queued, :forms_to_crawl,
|
14
|
+
:form_signatures_queued, :max_url_length, :response_code_handler,
|
15
|
+
:times_to_crawl, :fuzzers, :test_name, :crawl_timeout
|
16
|
+
attr_reader :transform_url_patterns, :referrers, :failures, :successes, :crawl_start_times, :crawl_end_times
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@max_url_length = 1024
|
20
|
+
@successes = []
|
21
|
+
@failures = []
|
22
|
+
@handlers = [@response_code_handler = Result]
|
23
|
+
@links_queued = Set.new
|
24
|
+
@form_signatures_queued = Set.new
|
25
|
+
@links_to_crawl = []
|
26
|
+
@forms_to_crawl = []
|
27
|
+
@crawl_start_times, @crawl_end_times = [], []
|
28
|
+
@crawl_timeout = 20.minutes
|
29
|
+
@referrers = {}
|
30
|
+
@skip_uri_patterns = [
|
31
|
+
/^javascript/,
|
32
|
+
/^mailto/,
|
33
|
+
/^http/,
|
34
|
+
]
|
35
|
+
self.transform_url_patterns = [
|
36
|
+
[/#.*$/, '']
|
37
|
+
]
|
38
|
+
@reporters = [Relevance::Tarantula::IOReporter.new($stderr)]
|
39
|
+
@decoder = HTMLEntities.new
|
40
|
+
@times_to_crawl = 1
|
41
|
+
@fuzzers = [Relevance::Tarantula::FormSubmission]
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(meth, *args)
|
45
|
+
super unless Result::ALLOW_NNN_FOR =~ meth.to_s
|
46
|
+
@response_code_handler.send(meth, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def transform_url_patterns=(patterns)
|
50
|
+
@transform_url_patterns = patterns.map do |pattern|
|
51
|
+
Array === pattern ? Relevance::Tarantula::Transform.new(*pattern) : pattern
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def crawl(url = "/")
|
56
|
+
orig_links_queued = @links_queued.dup
|
57
|
+
orig_form_signatures_queued = @form_signatures_queued.dup
|
58
|
+
orig_links_to_crawl = @links_to_crawl.dup
|
59
|
+
orig_forms_to_crawl = @forms_to_crawl.dup
|
60
|
+
@times_to_crawl.times do |num|
|
61
|
+
queue_link url
|
62
|
+
|
63
|
+
begin
|
64
|
+
do_crawl num
|
65
|
+
rescue CrawlTimeout => e
|
66
|
+
puts e.message
|
67
|
+
end
|
68
|
+
|
69
|
+
puts "#{(num+1).ordinalize} crawl" if @times_to_crawl > 1
|
70
|
+
|
71
|
+
if num + 1 < @times_to_crawl
|
72
|
+
@links_queued = orig_links_queued
|
73
|
+
@form_signatures_queued = orig_form_signatures_queued
|
74
|
+
@links_to_crawl = orig_links_to_crawl
|
75
|
+
@forms_to_crawl = orig_forms_to_crawl
|
76
|
+
@referrers = {}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
rescue Interrupt
|
80
|
+
$stderr.puts "CTRL-C"
|
81
|
+
ensure
|
82
|
+
report_results
|
83
|
+
end
|
84
|
+
|
85
|
+
def finished?
|
86
|
+
@links_to_crawl.empty? && @forms_to_crawl.empty?
|
87
|
+
end
|
88
|
+
|
89
|
+
def do_crawl(number)
|
90
|
+
while (!finished?)
|
91
|
+
@crawl_start_times << Time.now
|
92
|
+
crawl_queued_links(number)
|
93
|
+
crawl_queued_forms(number)
|
94
|
+
@crawl_end_times << Time.now
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def crawl_queued_links(number = 0)
|
99
|
+
while (link = @links_to_crawl.pop)
|
100
|
+
response = proxy.send(link.method, link.href)
|
101
|
+
log "Response #{response.code} for #{link}"
|
102
|
+
handle_link_results(link, response)
|
103
|
+
blip(number)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def save_result(result)
|
108
|
+
reporters.each do |reporter|
|
109
|
+
reporter.report(result)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def handle_link_results(link, response)
|
114
|
+
handlers.each do |h|
|
115
|
+
begin
|
116
|
+
save_result h.handle(Result.new(:method => link.method,
|
117
|
+
:url => link.href,
|
118
|
+
:response => response,
|
119
|
+
:log => grab_log!,
|
120
|
+
:referrer => referrers[link],
|
121
|
+
:test_name => test_name).freeze)
|
122
|
+
rescue Exception => e
|
123
|
+
log "error handling #{link} #{e.message}"
|
124
|
+
# TODO: pass to results
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def crawl_form(form)
|
130
|
+
response = proxy.send(form.method, form.action, form.data)
|
131
|
+
log "Response #{response.code} for #{form}"
|
132
|
+
response
|
133
|
+
rescue ActiveRecord::RecordNotFound => e
|
134
|
+
log "Skipping #{form.action}, presumed ok that record is missing"
|
135
|
+
Relevance::Tarantula::Response.new(:code => "404", :body => e.message, :content_type => "text/plain")
|
136
|
+
end
|
137
|
+
|
138
|
+
def crawl_queued_forms(number = 0)
|
139
|
+
while (form = @forms_to_crawl.pop)
|
140
|
+
response = crawl_form(form)
|
141
|
+
handle_form_results(form, response)
|
142
|
+
blip(number)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def elasped_time_for_pass(num)
|
147
|
+
Time.now - crawl_start_times[num]
|
148
|
+
end
|
149
|
+
|
150
|
+
def grab_log!
|
151
|
+
@log_grabber && @log_grabber.grab!
|
152
|
+
end
|
153
|
+
|
154
|
+
def handle_form_results(form, response)
|
155
|
+
handlers.each do |h|
|
156
|
+
save_result h.handle(Result.new(:method => form.method,
|
157
|
+
:url => form.action,
|
158
|
+
:response => response,
|
159
|
+
:log => grab_log!,
|
160
|
+
:referrer => form.action,
|
161
|
+
:data => form.data.inspect,
|
162
|
+
:test_name => test_name).freeze)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def should_skip_url?(url)
|
167
|
+
return true if url.blank?
|
168
|
+
if @skip_uri_patterns.any? {|pattern| pattern =~ url}
|
169
|
+
log "Skipping #{url}"
|
170
|
+
return true
|
171
|
+
end
|
172
|
+
if url.length > max_url_length
|
173
|
+
log "Skipping long url #{url}"
|
174
|
+
return true
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def should_skip_link?(link)
|
179
|
+
should_skip_url?(link.href) || @links_queued.member?(link)
|
180
|
+
end
|
181
|
+
|
182
|
+
def should_skip_form_submission?(fs)
|
183
|
+
should_skip_url?(fs.action) || @form_signatures_queued.member?(fs.signature)
|
184
|
+
end
|
185
|
+
|
186
|
+
def transform_url(url)
|
187
|
+
return unless url
|
188
|
+
url = @decoder.decode(url)
|
189
|
+
@transform_url_patterns.each do |pattern|
|
190
|
+
url = pattern[url]
|
191
|
+
end
|
192
|
+
url
|
193
|
+
end
|
194
|
+
|
195
|
+
def queue_link(dest, referrer = nil)
|
196
|
+
dest = Link.new(dest)
|
197
|
+
dest.href = transform_url(dest.href)
|
198
|
+
return if should_skip_link?(dest)
|
199
|
+
@referrers[dest] = referrer if referrer
|
200
|
+
@links_to_crawl << dest
|
201
|
+
@links_queued << dest
|
202
|
+
dest
|
203
|
+
end
|
204
|
+
|
205
|
+
def queue_form(form, referrer = nil)
|
206
|
+
fuzzers.each do |fuzzer|
|
207
|
+
fuzzer.mutate(Form.new(form)).each do |fs|
|
208
|
+
# fs = fuzzer.new(Form.new(form))
|
209
|
+
fs.action = transform_url(fs.action)
|
210
|
+
return if should_skip_form_submission?(fs)
|
211
|
+
@referrers[fs.action] = referrer if referrer
|
212
|
+
@forms_to_crawl << fs
|
213
|
+
@form_signatures_queued << fs.signature
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def report_dir
|
219
|
+
File.join(rails_root, "tmp", "tarantula")
|
220
|
+
end
|
221
|
+
|
222
|
+
def generate_reports
|
223
|
+
errors = []
|
224
|
+
reporters.each do |reporter|
|
225
|
+
begin
|
226
|
+
reporter.finish_report(test_name)
|
227
|
+
rescue RuntimeError => e
|
228
|
+
errors << e
|
229
|
+
end
|
230
|
+
end
|
231
|
+
unless errors.empty?
|
232
|
+
raise errors.map(&:message).join("\n")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def report_results
|
237
|
+
generate_reports
|
238
|
+
end
|
239
|
+
|
240
|
+
def total_links_count
|
241
|
+
@links_queued.size + @form_signatures_queued.size
|
242
|
+
end
|
243
|
+
|
244
|
+
def links_remaining_count
|
245
|
+
@links_to_crawl.size + @forms_to_crawl.size
|
246
|
+
end
|
247
|
+
|
248
|
+
def links_completed_count
|
249
|
+
total_links_count - links_remaining_count
|
250
|
+
end
|
251
|
+
|
252
|
+
def blip(number = 0)
|
253
|
+
unless verbose
|
254
|
+
print "\r #{links_completed_count} of #{total_links_count} links completed "
|
255
|
+
timeout_if_too_long(number)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def timeout_if_too_long(number = 0)
|
260
|
+
if elasped_time_for_pass(number) > crawl_timeout
|
261
|
+
raise CrawlTimeout, "Exceeded crawl timeout of #{crawl_timeout} seconds - skipping to the next crawl..."
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
7
|
+
<link rel="stylesheet" href="../stylesheets/tarantula.css" type="text/css" media="screen" title="no title" charset="utf-8" />
|
8
|
+
<title>Detail</title>
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
<div id="container">
|
12
|
+
<div id="header">
|
13
|
+
<h1>Tarantula by Relevance</h1>
|
14
|
+
<h2>Eight legs, two fangs ... and an attitude</h2>
|
15
|
+
<p>Tarantula is an open source tool for testing Rails web
|
16
|
+
applications. Tarantula is developed by <a href="http://thinkrelevance.com">Relevance, Inc.</a>
|
17
|
+
and lives at <a href="http://github.com/relevance/tarantula">http://github.com/relevance/tarantula</a>.</p>
|
18
|
+
<hr/>
|
19
|
+
</div>
|
20
|
+
<div id="page">
|
21
|
+
<ul class="tabs">
|
22
|
+
<li><a href="../index.html">« Back</a></li>
|
23
|
+
<!-- TODO dynamically hide/show body/log sections based on selected "tab" - dynamically assign the "active" class to the selected tab -->
|
24
|
+
<li><a href="#fragment-1" class="active">Body</a></li>
|
25
|
+
<li><a href="#fragment-2" class="active">Log</a></li>
|
26
|
+
</ul>
|
27
|
+
|
28
|
+
<div id="report">
|
29
|
+
<h3>Detail of <%= short_description %> <em>Generated on <%= Time.now %></em></h3>
|
30
|
+
<p><b>Resource</b> <a href="<%= full_url %>"><%= full_url %></a></p>
|
31
|
+
<p><b>Response</b> <span class="r<%= code.first %>"><%= code %></span></p>
|
32
|
+
<p><b>Referrer</b> <%= referrer || "" %></p>
|
33
|
+
|
34
|
+
<table class="output">
|
35
|
+
<tbody>
|
36
|
+
<tr>
|
37
|
+
<th colspan="2"># Data</th>
|
38
|
+
</tr>
|
39
|
+
<% if data %>
|
40
|
+
<%= wrap_in_line_number_table_row(data) %>
|
41
|
+
<% else %>
|
42
|
+
<tr>
|
43
|
+
<td colspan="2">No Data</td>
|
44
|
+
</tr>
|
45
|
+
<% end %>
|
46
|
+
</tbody>
|
47
|
+
</table>
|
48
|
+
|
49
|
+
<table class="output" id="fragment-1">
|
50
|
+
<tbody>
|
51
|
+
<tr>
|
52
|
+
<th colspan="2"># Body</th>
|
53
|
+
</tr>
|
54
|
+
<% if body %>
|
55
|
+
<%= wrap_in_line_number_table_row(body) %>
|
56
|
+
<% else %>
|
57
|
+
<tr>
|
58
|
+
<td colspan="2">No Body</td>
|
59
|
+
</tr>
|
60
|
+
<% end %>
|
61
|
+
</tbody>
|
62
|
+
</table>
|
63
|
+
|
64
|
+
<table class="output" id="fragment-2">
|
65
|
+
<tbody>
|
66
|
+
<tr>
|
67
|
+
<th colspan="2"># Log</th>
|
68
|
+
</tr>
|
69
|
+
<% if log %>
|
70
|
+
<%= wrap_in_line_number_table_row(log) {|line| wrap_stack_trace_line(line)} %>
|
71
|
+
<% else %>
|
72
|
+
<tr>
|
73
|
+
<td colspan="2">No Log</td>
|
74
|
+
</tr>
|
75
|
+
<% end %>
|
76
|
+
</tbody>
|
77
|
+
</table>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
</div>
|
81
|
+
</body>
|
82
|
+
</html>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Relevance::Tarantula::Form
|
2
|
+
extend Forwardable
|
3
|
+
def_delegators("@tag", :search)
|
4
|
+
|
5
|
+
def initialize(tag)
|
6
|
+
@tag = tag
|
7
|
+
end
|
8
|
+
|
9
|
+
def action
|
10
|
+
@tag['action'].downcase
|
11
|
+
end
|
12
|
+
|
13
|
+
def method
|
14
|
+
(rails_method_hack or @tag['method'] or 'get').downcase
|
15
|
+
end
|
16
|
+
|
17
|
+
def rails_method_hack
|
18
|
+
(tag = @tag.at('input[@name="_method"]')) && tag["value"]
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Relevance::Tarantula::FormSubmission
|
2
|
+
attr_accessor :method, :action, :data
|
3
|
+
def initialize(form)
|
4
|
+
@method = form.method
|
5
|
+
@action = form.action
|
6
|
+
@data = mutate_selects(form).merge(mutate_text_areas(form)).merge(mutate_inputs(form))
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.mutate(form)
|
10
|
+
[self.new(form)]
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"#{action} #{method} #{data.inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# a form's signature is what makes it unique (e.g. action + fields)
|
18
|
+
# used to keep track of which forms we have submitted already
|
19
|
+
def signature
|
20
|
+
[action, data.keys.sort]
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_random_data_for(form, tag_selector)
|
24
|
+
form.search(tag_selector).inject({}) do |form_args, input|
|
25
|
+
# TODO: test
|
26
|
+
form_args[input['name']] = random_data(input) if input['name']
|
27
|
+
form_args
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def mutate_inputs(form)
|
32
|
+
create_random_data_for(form, 'input')
|
33
|
+
end
|
34
|
+
|
35
|
+
def mutate_text_areas(form)
|
36
|
+
create_random_data_for(form, 'textarea')
|
37
|
+
end
|
38
|
+
|
39
|
+
def mutate_selects(form)
|
40
|
+
form.search('select').inject({}) do |form_args, select|
|
41
|
+
options = select.search('option')
|
42
|
+
option = options.rand
|
43
|
+
form_args[select['name']] = option['value']
|
44
|
+
form_args
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def random_data(input)
|
49
|
+
case input['name']
|
50
|
+
when /amount/ : random_int
|
51
|
+
when /_id$/ : random_whole_number
|
52
|
+
when /uploaded_data/ : nil
|
53
|
+
when /^_method$/ : input['value']
|
54
|
+
when nil : input['value']
|
55
|
+
else random_int
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def big_number
|
60
|
+
10000 # arbitrary
|
61
|
+
end
|
62
|
+
|
63
|
+
def random_int
|
64
|
+
rand(big_number) - (big_number/2)
|
65
|
+
end
|
66
|
+
|
67
|
+
def random_whole_number
|
68
|
+
rand(big_number)
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'hpricot'
|
2
|
+
|
3
|
+
class Relevance::Tarantula::HtmlDocumentHandler
|
4
|
+
extend Forwardable
|
5
|
+
def_delegators("@crawler", :queue_link, :queue_form)
|
6
|
+
|
7
|
+
def initialize(crawler)
|
8
|
+
@crawler = crawler
|
9
|
+
end
|
10
|
+
# HTML::Document shouts to stderr when it sees ugly HTML
|
11
|
+
# We don't want this -- the InvalidHtmlHandler will deal with it
|
12
|
+
def html_doc_without_stderr_noise(html)
|
13
|
+
body = nil
|
14
|
+
Recording.stderr do
|
15
|
+
body = Hpricot html
|
16
|
+
end
|
17
|
+
body
|
18
|
+
end
|
19
|
+
def handle(result)
|
20
|
+
response = result.response
|
21
|
+
url = result.url
|
22
|
+
return unless response.html?
|
23
|
+
body = html_doc_without_stderr_noise(response.body)
|
24
|
+
body.search('a').each do |tag|
|
25
|
+
queue_link(tag, url)
|
26
|
+
end
|
27
|
+
body.search('link').each do |tag|
|
28
|
+
queue_link(tag, url)
|
29
|
+
end
|
30
|
+
body.search('form').each do |form|
|
31
|
+
form['action'] = url unless form['action']
|
32
|
+
queue_form(form, url)
|
33
|
+
end
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "erb"
|
2
|
+
module Relevance::Tarantula::HtmlReportHelper
|
3
|
+
include ERB::Util
|
4
|
+
include Relevance::Tarantula
|
5
|
+
def wrap_in_line_number_table_row(text, &blk)
|
6
|
+
x = Builder::XmlMarkup.new
|
7
|
+
|
8
|
+
x.tr do
|
9
|
+
lines = text.split("\n")
|
10
|
+
x.td(:class => "numbers") do
|
11
|
+
lines.size.times do |index|
|
12
|
+
x.span(index+1, :class => "line number")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
x.td(:class => "lines") do
|
16
|
+
lines.each do |line|
|
17
|
+
x.span(line, :class => "line")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
x.target!
|
23
|
+
end
|
24
|
+
|
25
|
+
def textmate_url(file, line_no)
|
26
|
+
"txmt://open?url=file://#{File.expand_path(File.join(rails_root,file))}&line_no=#{line_no}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def wrap_stack_trace_line(text)
|
30
|
+
if text =~ %r{^\s*(/[^:]+):(\d+):([^:]+)$}
|
31
|
+
file = h($1) # .to_s_xss_protected
|
32
|
+
line_number = $2
|
33
|
+
message = h($3) # .to_s_xss_protected
|
34
|
+
"<a href='#{textmate_url(file, line_number)}'>#{file}:#{line_number}</a>:#{message}" # .mark_as_xss_protected
|
35
|
+
else
|
36
|
+
h(text) # .to_s_xss_protected
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
class Relevance::Tarantula::HtmlReporter
|
2
|
+
|
3
|
+
include Relevance::Tarantula
|
4
|
+
attr_accessor :basedir, :results
|
5
|
+
delegate :successes, :failures, :to => :results
|
6
|
+
|
7
|
+
HtmlResultOverview = Struct.new(:code, :url, :description, :method, :referrer, :file_name)
|
8
|
+
|
9
|
+
def initialize(basedir)
|
10
|
+
@basedir = basedir
|
11
|
+
@results = Struct.new(:successes, :failures).new([], [])
|
12
|
+
FileUtils.mkdir_p(@basedir)
|
13
|
+
end
|
14
|
+
|
15
|
+
def report(result)
|
16
|
+
return if result.nil?
|
17
|
+
|
18
|
+
create_detail_report(result)
|
19
|
+
|
20
|
+
collection = result.success ? results.successes : results.failures
|
21
|
+
collection << HtmlResultOverview.new(
|
22
|
+
result.code, result.url, result.description, result.method, result.referrer, result.file_name
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def finish_report(test_name)
|
27
|
+
puts "Writing results to #{basedir}"
|
28
|
+
copy_styles unless styles_exist?
|
29
|
+
create_index unless index_exists?
|
30
|
+
update_index(test_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_detail_report(result)
|
34
|
+
template = ERB.new(template("detail.html.erb"))
|
35
|
+
output(result.file_name, template.result(result.send(:binding)), result.test_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def copy_styles
|
39
|
+
# not using cp_r because it picks up .svn crap
|
40
|
+
FileUtils.mkdir_p(File.join(basedir, "stylesheets"))
|
41
|
+
Dir.glob("#{tarantula_home}/laf/stylesheets/*.css").each do |file|
|
42
|
+
FileUtils.cp(file, File.join(basedir, "stylesheets"))
|
43
|
+
end
|
44
|
+
FileUtils.mkdir_p(File.join(basedir, "images"))
|
45
|
+
Dir.glob("#{tarantula_home}/laf/images/*.{jpg,gif,png}").each do |file|
|
46
|
+
FileUtils.cp(file, File.join(basedir, "images"))
|
47
|
+
end
|
48
|
+
FileUtils.mkdir_p(File.join(basedir, "javascripts"))
|
49
|
+
Dir.glob("#{tarantula_home}/laf/javascripts/*.js").each do |file|
|
50
|
+
FileUtils.cp(file, File.join(basedir, "javascripts"))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_index
|
55
|
+
template = ERB.new(template("index.html.erb"))
|
56
|
+
output("index.html", template.result(binding))
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_index(test_name)
|
60
|
+
File.open(File.join(basedir, "index.html"), "r+") do |file|
|
61
|
+
doc = Hpricot file.read
|
62
|
+
tabs_container = doc.search "#tabs-container ul"
|
63
|
+
results_container = doc.search "#results-container"
|
64
|
+
tabs_container.append tab_html(test_name)
|
65
|
+
results_container.append results_html(test_name)
|
66
|
+
file.rewind
|
67
|
+
file.write doc.to_s
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def index_exists?
|
72
|
+
File.exists?(File.join(basedir, "index.html"))
|
73
|
+
end
|
74
|
+
|
75
|
+
def styles_exist?
|
76
|
+
File.exists?(File.join(basedir, "stylesheets", "tarantula.css"))
|
77
|
+
end
|
78
|
+
|
79
|
+
def tab_html(test_name)
|
80
|
+
"<li><a href='##{test_name}'><span>#{test_name}</span></a></li>"
|
81
|
+
end
|
82
|
+
|
83
|
+
def results_html(test_name)
|
84
|
+
template = ERB.new(template("test_report.html.erb"))
|
85
|
+
template.result(binding)
|
86
|
+
end
|
87
|
+
|
88
|
+
def template(name)
|
89
|
+
File.read(File.join(File.dirname(__FILE__), name))
|
90
|
+
end
|
91
|
+
|
92
|
+
def output(name, body, subdir = '')
|
93
|
+
FileUtils.mkdir_p(File.join(basedir, subdir)) unless subdir.empty?
|
94
|
+
File.open(File.join(basedir, subdir, name), "w") do |file|
|
95
|
+
file.write body
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# CSS class for HTML status codes
|
100
|
+
def class_for_code(code)
|
101
|
+
"r#{Integer(code)/100}"
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
7
|
+
<link rel="stylesheet" href="stylesheets/tarantula.css" type="text/css" media="screen" title="no title" charset="utf-8">
|
8
|
+
<script type="text/javascript" src="javascripts/jquery-1.2.3.js"></script>
|
9
|
+
<script type="text/javascript" src="javascripts/jquery.tablesorter.js"></script>
|
10
|
+
<script type="text/javascript" src="javascripts/jquery-ui-tabs.js"></script>
|
11
|
+
<script type="text/javascript" src="javascripts/tarantula.js"></script>
|
12
|
+
|
13
|
+
<title>Tarantula</title>
|
14
|
+
</head>
|
15
|
+
|
16
|
+
<body>
|
17
|
+
<div id="container">
|
18
|
+
<div id="header">
|
19
|
+
<h1>Tarantula by Relevance</h1>
|
20
|
+
<h2>Eight legs, two fangs ... and an attitude</h2>
|
21
|
+
<p>Tarantula is an open source tool for testing Rails web
|
22
|
+
applications. Tarantula is developed by <a href="http://thinkrelevance.com">Relevance, Inc.</a>
|
23
|
+
and lives at <a href="http://github.com/relevance/tarantula">http://github.com/relevance/tarantula</a>.</p>
|
24
|
+
<hr/>
|
25
|
+
</div>
|
26
|
+
<div id="page">
|
27
|
+
<div id="tabs-container">
|
28
|
+
<ul class="tabs"> </ul>
|
29
|
+
</div>
|
30
|
+
|
31
|
+
<div id="results-container">
|
32
|
+
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</body>
|
37
|
+
</html>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Relevance::Tarantula::InvalidHtmlHandler
|
2
|
+
include Relevance::Tarantula
|
3
|
+
def handle(result)
|
4
|
+
response = result.response
|
5
|
+
return unless response.html?
|
6
|
+
begin
|
7
|
+
body = HTML::Document.new(response.body, true)
|
8
|
+
rescue Exception => e
|
9
|
+
error_result = result.dup
|
10
|
+
error_result.success = false
|
11
|
+
error_result.description = "Bad HTML (Scanner)"
|
12
|
+
error_result.data = e.message
|
13
|
+
error_result
|
14
|
+
else
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|