tarantula-rails3 0.3.3

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.
Files changed (68) hide show
  1. data/CHANGELOG +49 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +161 -0
  4. data/Rakefile +83 -0
  5. data/VERSION.yml +4 -0
  6. data/examples/example_helper.rb +57 -0
  7. data/examples/relevance/core_extensions/ellipsize_example.rb +19 -0
  8. data/examples/relevance/core_extensions/file_example.rb +8 -0
  9. data/examples/relevance/core_extensions/response_example.rb +29 -0
  10. data/examples/relevance/core_extensions/test_case_example.rb +20 -0
  11. data/examples/relevance/tarantula/attack_handler_example.rb +29 -0
  12. data/examples/relevance/tarantula/basic_attack_example.rb +12 -0
  13. data/examples/relevance/tarantula/crawler_example.rb +375 -0
  14. data/examples/relevance/tarantula/form_example.rb +50 -0
  15. data/examples/relevance/tarantula/form_submission_example.rb +171 -0
  16. data/examples/relevance/tarantula/html_document_handler_example.rb +43 -0
  17. data/examples/relevance/tarantula/html_report_helper_example.rb +46 -0
  18. data/examples/relevance/tarantula/html_reporter_example.rb +82 -0
  19. data/examples/relevance/tarantula/invalid_html_handler_example.rb +33 -0
  20. data/examples/relevance/tarantula/io_reporter_example.rb +11 -0
  21. data/examples/relevance/tarantula/link_example.rb +84 -0
  22. data/examples/relevance/tarantula/log_grabber_example.rb +26 -0
  23. data/examples/relevance/tarantula/rails_integration_proxy_example.rb +88 -0
  24. data/examples/relevance/tarantula/result_example.rb +85 -0
  25. data/examples/relevance/tarantula/tidy_handler_example.rb +58 -0
  26. data/examples/relevance/tarantula/transform_example.rb +20 -0
  27. data/examples/relevance/tarantula_example.rb +23 -0
  28. data/laf/images/header_bg.jpg +0 -0
  29. data/laf/images/logo.png +0 -0
  30. data/laf/images/tagline.png +0 -0
  31. data/laf/javascripts/jquery-1.2.3.js +3408 -0
  32. data/laf/javascripts/jquery-ui-tabs.js +890 -0
  33. data/laf/javascripts/jquery.tablesorter.js +861 -0
  34. data/laf/javascripts/tarantula.js +10 -0
  35. data/laf/stylesheets/tarantula.css +346 -0
  36. data/lib/relevance/core_extensions/ellipsize.rb +34 -0
  37. data/lib/relevance/core_extensions/file.rb +9 -0
  38. data/lib/relevance/core_extensions/metaclass.rb +78 -0
  39. data/lib/relevance/core_extensions/response.rb +9 -0
  40. data/lib/relevance/core_extensions/string_chars_fix.rb +11 -0
  41. data/lib/relevance/core_extensions/test_case.rb +19 -0
  42. data/lib/relevance/tarantula.rb +58 -0
  43. data/lib/relevance/tarantula/attack.rb +18 -0
  44. data/lib/relevance/tarantula/attack_handler.rb +37 -0
  45. data/lib/relevance/tarantula/basic_attack.rb +40 -0
  46. data/lib/relevance/tarantula/crawler.rb +254 -0
  47. data/lib/relevance/tarantula/detail.html.erb +81 -0
  48. data/lib/relevance/tarantula/form.rb +23 -0
  49. data/lib/relevance/tarantula/form_submission.rb +88 -0
  50. data/lib/relevance/tarantula/html_document_handler.rb +36 -0
  51. data/lib/relevance/tarantula/html_report_helper.rb +39 -0
  52. data/lib/relevance/tarantula/html_reporter.rb +105 -0
  53. data/lib/relevance/tarantula/index.html.erb +37 -0
  54. data/lib/relevance/tarantula/invalid_html_handler.rb +18 -0
  55. data/lib/relevance/tarantula/io_reporter.rb +34 -0
  56. data/lib/relevance/tarantula/link.rb +94 -0
  57. data/lib/relevance/tarantula/log_grabber.rb +16 -0
  58. data/lib/relevance/tarantula/rails_integration_proxy.rb +68 -0
  59. data/lib/relevance/tarantula/recording.rb +12 -0
  60. data/lib/relevance/tarantula/response.rb +13 -0
  61. data/lib/relevance/tarantula/result.rb +77 -0
  62. data/lib/relevance/tarantula/test_report.html.erb +32 -0
  63. data/lib/relevance/tarantula/tidy_handler.rb +32 -0
  64. data/lib/relevance/tarantula/transform.rb +17 -0
  65. data/lib/relevance/tasks/tarantula_tasks.rake +42 -0
  66. data/lib/tarantula-rails3.rb +9 -0
  67. data/template/tarantula_test.rb +22 -0
  68. metadata +164 -0
@@ -0,0 +1,81 @@
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">&laquo; Back</a></li>
23
+ <li><a href="#fragment-1" class="active">Body</a></li>
24
+ <li><a href="#fragment-2" class="active">Log</a></li>
25
+ </ul>
26
+
27
+ <div id="report">
28
+ <h3>Detail of <%= result.short_description %> <em>Generated on <%= Time.now %></em></h3>
29
+ <p><b>Resource</b> <a href="<%= result.full_url %>"><%= result.full_url %></a></p>
30
+ <p><b>Response</b> <span class="r<%= result.code.first %>"><%= result.code %></span></p>
31
+ <p><b>Referrer</b> <%= result.referrer || "" %></p>
32
+
33
+ <table class="output">
34
+ <tbody>
35
+ <tr>
36
+ <th colspan="2">#&nbsp;&nbsp;Data</th>
37
+ </tr>
38
+ <% if result.data %>
39
+ <%= result.wrap_in_line_number_table_row(result.data) %>
40
+ <% else %>
41
+ <tr>
42
+ <td colspan="2">No Data</td>
43
+ </tr>
44
+ <% end %>
45
+ </tbody>
46
+ </table>
47
+
48
+ <table class="output" id="fragment-1">
49
+ <tbody>
50
+ <tr>
51
+ <th colspan="2">#&nbsp;&nbsp;Body</th>
52
+ </tr>
53
+ <% if result.body %>
54
+ <%= result.wrap_in_line_number_table_row(result.body) %>
55
+ <% else %>
56
+ <tr>
57
+ <td colspan="2">No Body</td>
58
+ </tr>
59
+ <% end %>
60
+ </tbody>
61
+ </table>
62
+
63
+ <table class="output" id="fragment-2">
64
+ <tbody>
65
+ <tr>
66
+ <th colspan="2">#&nbsp;&nbsp;Log</th>
67
+ </tr>
68
+ <% if result.log %>
69
+ <%= result.wrap_in_line_number_table_row(result.log) {|line| wrap_stack_trace_line(line)} %>
70
+ <% else %>
71
+ <tr>
72
+ <td colspan="2">No Log</td>
73
+ </tr>
74
+ <% end %>
75
+ </tbody>
76
+ </table>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </body>
81
+ </html>
@@ -0,0 +1,23 @@
1
+ class Relevance::Tarantula::Form
2
+ extend Forwardable
3
+ def_delegators("@tag", :search)
4
+
5
+ attr_accessor :crawler, :referrer
6
+
7
+ def initialize(tag, crawler, referrer)
8
+ @tag, @crawler, @referrer = tag, crawler, referrer
9
+ end
10
+
11
+ def action
12
+ @tag['action'].downcase
13
+ end
14
+
15
+ def method
16
+ (rails_method_hack or @tag['method'] or 'get').downcase
17
+ end
18
+
19
+ def rails_method_hack
20
+ (tag = @tag.at('input[@name="_method"]')) && tag["value"]
21
+ end
22
+
23
+ end
@@ -0,0 +1,88 @@
1
+ class Relevance::Tarantula::FormSubmission
2
+ include Relevance::Tarantula
3
+ attr_accessor :method, :action, :data, :attack, :form
4
+
5
+ class << self
6
+ def attacks
7
+ # normalize from hash input to Attack
8
+ @attacks = @attacks.map do |val|
9
+ Hash === val ? Relevance::Tarantula::Attack.new(val) : val
10
+ end
11
+ @attacks
12
+ end
13
+ def attacks=(atts)
14
+ # normalize from hash input to Attack
15
+ @attacks = atts.map do |val|
16
+ Hash === val ? Relevance::Tarantula::Attack.new(val) : val
17
+ end
18
+ end
19
+ end
20
+ @attacks = [Relevance::Tarantula::BasicAttack.new]
21
+
22
+ def initialize(form, attack = Relevance::Tarantula::BasicAttack.new)
23
+ @form = form
24
+ @method = form.method
25
+ @action = form.action
26
+ @attack = attack
27
+ @data = mutate_selects(form).merge(mutate_text_areas(form)).merge(mutate_inputs(form))
28
+ end
29
+
30
+ def crawl
31
+ begin
32
+ response = form.crawler.submit(method, action, data)
33
+ log "Response #{response.code} for #{self}"
34
+ rescue ActiveRecord::RecordNotFound => e
35
+ log "Skipping #{action}, presumed ok that record is missing"
36
+ response = Relevance::Tarantula::Response.new(:code => "404", :body => e.message, :content_type => "text/plain")
37
+ end
38
+ form.crawler.handle_form_results(self, response)
39
+ response
40
+ end
41
+
42
+ def self.mutate(form)
43
+ attacks.map{|attack| new(form, attack)} if attacks
44
+ end
45
+
46
+ def to_s
47
+ "#{action} #{method} #{data.inspect} #{attack.inspect}"
48
+ end
49
+
50
+ # a form's signature is what makes it unique (e.g. action + fields)
51
+ # used to keep track of which forms we have submitted already
52
+ def signature
53
+ [action, data.keys.sort, attack.name]
54
+ end
55
+
56
+ def create_random_data_for(form, tag_selector)
57
+ form.search(tag_selector).inject({}) do |form_args, input|
58
+ # TODO: test
59
+ form_args[input['name']] = random_data(input) if input['name']
60
+ form_args
61
+ end
62
+ end
63
+
64
+ def mutate_inputs(form)
65
+ create_random_data_for(form, 'input')
66
+ end
67
+
68
+ def mutate_text_areas(form)
69
+ create_random_data_for(form, 'textarea')
70
+ end
71
+
72
+ def mutate_selects(form)
73
+ form.search('select').inject({}) do |form_args, select|
74
+ options = select.search('option')
75
+ option = options.rand
76
+ form_args[select['name']] = option['value']
77
+ form_args
78
+ end
79
+ end
80
+
81
+ def random_data(input)
82
+ case input['name']
83
+ when /^_method$/ then input['value']
84
+ else
85
+ attack.input(input)
86
+ end
87
+ end
88
+ 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
@@ -0,0 +1,34 @@
1
+ class Relevance::Tarantula::IOReporter
2
+
3
+ include Relevance::Tarantula
4
+ attr_accessor :io, :results
5
+ delegate :successes, :failures, :to => :results
6
+
7
+ IOResultOverview = Struct.new(:code, :url)
8
+
9
+ def initialize(io)
10
+ @io = io
11
+ @results = Struct.new(:successes, :failures).new([], [])
12
+ end
13
+
14
+ def report(result)
15
+ return if result.nil?
16
+
17
+ unless result.success # collection = result.success ? results.successes : results.failures
18
+ results.failures << IOResultOverview.new(
19
+ result.code, result.url
20
+ )
21
+ end
22
+ end
23
+
24
+ def finish_report(test_name)
25
+ unless (failures).empty?
26
+ io.puts "****** FAILURES"
27
+ failures.each do |failure|
28
+ io.puts "#{failure.code}: #{failure.url}"
29
+ end
30
+ raise "#{failures.size} failures"
31
+ end
32
+ end
33
+
34
+ end