railroader 4.3.4

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 (165) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +1091 -0
  3. data/FEATURES +16 -0
  4. data/README.md +174 -0
  5. data/bin/railroader +8 -0
  6. data/lib/railroader/app_tree.rb +191 -0
  7. data/lib/railroader/call_index.rb +219 -0
  8. data/lib/railroader/checks/base_check.rb +505 -0
  9. data/lib/railroader/checks/check_basic_auth.rb +88 -0
  10. data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
  11. data/lib/railroader/checks/check_content_tag.rb +200 -0
  12. data/lib/railroader/checks/check_create_with.rb +74 -0
  13. data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
  14. data/lib/railroader/checks/check_default_routes.rb +86 -0
  15. data/lib/railroader/checks/check_deserialize.rb +56 -0
  16. data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
  17. data/lib/railroader/checks/check_digest_dos.rb +38 -0
  18. data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
  19. data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
  20. data/lib/railroader/checks/check_escape_function.rb +21 -0
  21. data/lib/railroader/checks/check_evaluation.rb +35 -0
  22. data/lib/railroader/checks/check_execute.rb +189 -0
  23. data/lib/railroader/checks/check_file_access.rb +71 -0
  24. data/lib/railroader/checks/check_file_disclosure.rb +35 -0
  25. data/lib/railroader/checks/check_filter_skipping.rb +31 -0
  26. data/lib/railroader/checks/check_forgery_setting.rb +81 -0
  27. data/lib/railroader/checks/check_header_dos.rb +31 -0
  28. data/lib/railroader/checks/check_i18n_xss.rb +48 -0
  29. data/lib/railroader/checks/check_jruby_xml.rb +36 -0
  30. data/lib/railroader/checks/check_json_encoding.rb +47 -0
  31. data/lib/railroader/checks/check_json_parsing.rb +107 -0
  32. data/lib/railroader/checks/check_link_to.rb +132 -0
  33. data/lib/railroader/checks/check_link_to_href.rb +146 -0
  34. data/lib/railroader/checks/check_mail_to.rb +49 -0
  35. data/lib/railroader/checks/check_mass_assignment.rb +196 -0
  36. data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
  37. data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
  38. data/lib/railroader/checks/check_model_attributes.rb +119 -0
  39. data/lib/railroader/checks/check_model_serialize.rb +67 -0
  40. data/lib/railroader/checks/check_nested_attributes.rb +38 -0
  41. data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
  42. data/lib/railroader/checks/check_number_to_currency.rb +74 -0
  43. data/lib/railroader/checks/check_permit_attributes.rb +43 -0
  44. data/lib/railroader/checks/check_quote_table_name.rb +40 -0
  45. data/lib/railroader/checks/check_redirect.rb +256 -0
  46. data/lib/railroader/checks/check_regex_dos.rb +68 -0
  47. data/lib/railroader/checks/check_render.rb +97 -0
  48. data/lib/railroader/checks/check_render_dos.rb +37 -0
  49. data/lib/railroader/checks/check_render_inline.rb +53 -0
  50. data/lib/railroader/checks/check_response_splitting.rb +21 -0
  51. data/lib/railroader/checks/check_route_dos.rb +42 -0
  52. data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
  53. data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
  54. data/lib/railroader/checks/check_secrets.rb +40 -0
  55. data/lib/railroader/checks/check_select_tag.rb +59 -0
  56. data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
  57. data/lib/railroader/checks/check_send.rb +47 -0
  58. data/lib/railroader/checks/check_send_file.rb +19 -0
  59. data/lib/railroader/checks/check_session_manipulation.rb +35 -0
  60. data/lib/railroader/checks/check_session_settings.rb +176 -0
  61. data/lib/railroader/checks/check_simple_format.rb +58 -0
  62. data/lib/railroader/checks/check_single_quotes.rb +101 -0
  63. data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
  64. data/lib/railroader/checks/check_sql.rb +700 -0
  65. data/lib/railroader/checks/check_sql_cves.rb +106 -0
  66. data/lib/railroader/checks/check_ssl_verify.rb +48 -0
  67. data/lib/railroader/checks/check_strip_tags.rb +89 -0
  68. data/lib/railroader/checks/check_symbol_dos.rb +71 -0
  69. data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
  70. data/lib/railroader/checks/check_translate_bug.rb +45 -0
  71. data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
  72. data/lib/railroader/checks/check_unscoped_find.rb +57 -0
  73. data/lib/railroader/checks/check_validation_regex.rb +116 -0
  74. data/lib/railroader/checks/check_weak_hash.rb +148 -0
  75. data/lib/railroader/checks/check_without_protection.rb +80 -0
  76. data/lib/railroader/checks/check_xml_dos.rb +45 -0
  77. data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
  78. data/lib/railroader/checks.rb +209 -0
  79. data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
  80. data/lib/railroader/commandline.rb +179 -0
  81. data/lib/railroader/differ.rb +66 -0
  82. data/lib/railroader/file_parser.rb +54 -0
  83. data/lib/railroader/format/style.css +133 -0
  84. data/lib/railroader/options.rb +339 -0
  85. data/lib/railroader/parsers/rails2_erubis.rb +6 -0
  86. data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
  87. data/lib/railroader/parsers/rails3_erubis.rb +81 -0
  88. data/lib/railroader/parsers/template_parser.rb +108 -0
  89. data/lib/railroader/processor.rb +102 -0
  90. data/lib/railroader/processors/alias_processor.rb +1229 -0
  91. data/lib/railroader/processors/base_processor.rb +295 -0
  92. data/lib/railroader/processors/config_processor.rb +14 -0
  93. data/lib/railroader/processors/controller_alias_processor.rb +278 -0
  94. data/lib/railroader/processors/controller_processor.rb +249 -0
  95. data/lib/railroader/processors/erb_template_processor.rb +77 -0
  96. data/lib/railroader/processors/erubis_template_processor.rb +92 -0
  97. data/lib/railroader/processors/gem_processor.rb +64 -0
  98. data/lib/railroader/processors/haml_template_processor.rb +191 -0
  99. data/lib/railroader/processors/lib/basic_processor.rb +37 -0
  100. data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
  101. data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
  102. data/lib/railroader/processors/lib/find_call.rb +183 -0
  103. data/lib/railroader/processors/lib/find_return_value.rb +166 -0
  104. data/lib/railroader/processors/lib/module_helper.rb +111 -0
  105. data/lib/railroader/processors/lib/processor_helper.rb +88 -0
  106. data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
  107. data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
  108. data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
  109. data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
  110. data/lib/railroader/processors/lib/render_helper.rb +181 -0
  111. data/lib/railroader/processors/lib/render_path.rb +107 -0
  112. data/lib/railroader/processors/lib/route_helper.rb +68 -0
  113. data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
  114. data/lib/railroader/processors/library_processor.rb +74 -0
  115. data/lib/railroader/processors/model_processor.rb +91 -0
  116. data/lib/railroader/processors/output_processor.rb +144 -0
  117. data/lib/railroader/processors/route_processor.rb +17 -0
  118. data/lib/railroader/processors/slim_template_processor.rb +111 -0
  119. data/lib/railroader/processors/template_alias_processor.rb +118 -0
  120. data/lib/railroader/processors/template_processor.rb +85 -0
  121. data/lib/railroader/report/config/remediation.yml +71 -0
  122. data/lib/railroader/report/ignore/config.rb +153 -0
  123. data/lib/railroader/report/ignore/interactive.rb +362 -0
  124. data/lib/railroader/report/pager.rb +112 -0
  125. data/lib/railroader/report/renderer.rb +24 -0
  126. data/lib/railroader/report/report_base.rb +292 -0
  127. data/lib/railroader/report/report_codeclimate.rb +79 -0
  128. data/lib/railroader/report/report_csv.rb +55 -0
  129. data/lib/railroader/report/report_hash.rb +23 -0
  130. data/lib/railroader/report/report_html.rb +216 -0
  131. data/lib/railroader/report/report_json.rb +45 -0
  132. data/lib/railroader/report/report_markdown.rb +107 -0
  133. data/lib/railroader/report/report_table.rb +117 -0
  134. data/lib/railroader/report/report_tabs.rb +17 -0
  135. data/lib/railroader/report/report_text.rb +198 -0
  136. data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
  137. data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
  138. data/lib/railroader/report/templates/error_overview.html.erb +29 -0
  139. data/lib/railroader/report/templates/header.html.erb +58 -0
  140. data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
  141. data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
  142. data/lib/railroader/report/templates/overview.html.erb +38 -0
  143. data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
  144. data/lib/railroader/report/templates/template_overview.html.erb +21 -0
  145. data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
  146. data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
  147. data/lib/railroader/report.rb +88 -0
  148. data/lib/railroader/rescanner.rb +483 -0
  149. data/lib/railroader/scanner.rb +321 -0
  150. data/lib/railroader/tracker/collection.rb +93 -0
  151. data/lib/railroader/tracker/config.rb +154 -0
  152. data/lib/railroader/tracker/constants.rb +171 -0
  153. data/lib/railroader/tracker/controller.rb +161 -0
  154. data/lib/railroader/tracker/library.rb +17 -0
  155. data/lib/railroader/tracker/model.rb +90 -0
  156. data/lib/railroader/tracker/template.rb +33 -0
  157. data/lib/railroader/tracker.rb +362 -0
  158. data/lib/railroader/util.rb +503 -0
  159. data/lib/railroader/version.rb +3 -0
  160. data/lib/railroader/warning.rb +294 -0
  161. data/lib/railroader/warning_codes.rb +117 -0
  162. data/lib/railroader.rb +544 -0
  163. data/lib/ruby_parser/bm_sexp.rb +626 -0
  164. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  165. metadata +386 -0
@@ -0,0 +1,85 @@
1
+ require 'railroader/processors/base_processor'
2
+ require 'railroader/tracker/template'
3
+
4
+ #Base Processor for templates/views
5
+ class Railroader::TemplateProcessor < Railroader::BaseProcessor
6
+
7
+ #Initializes template information.
8
+ def initialize tracker, template_name, called_from = nil, file_name = nil
9
+ super(tracker)
10
+ @current_template = Railroader::Template.new template_name, called_from, file_name, tracker
11
+ @file_name = file_name
12
+
13
+ if called_from
14
+ template_name = (template_name.to_s + "." + called_from.to_s).to_sym
15
+ end
16
+
17
+ tracker.templates[template_name] = @current_template
18
+
19
+ @inside_concat = false
20
+ end
21
+
22
+ #Process the template Sexp.
23
+ def process exp
24
+ begin
25
+ super
26
+ rescue => e
27
+ except = e.exception("Error when processing #{@current_template.name}: #{e.message}")
28
+ except.set_backtrace(e.backtrace)
29
+ raise except
30
+ end
31
+ end
32
+
33
+ #Ignore initial variable assignment
34
+ def process_lasgn exp
35
+ if exp.lhs == :_erbout and exp.rhs.node_type == :str #ignore
36
+ ignore
37
+ elsif exp.lhs == :_buf and exp.rhs.node_type == :str
38
+ ignore
39
+ else
40
+ exp.rhs = process exp.rhs
41
+ exp
42
+ end
43
+ end
44
+
45
+ #Adds output to the list of outputs.
46
+ def process_output exp
47
+ exp.value = process exp.value
48
+ @current_template.add_output exp unless exp.original_line
49
+ exp
50
+ end
51
+
52
+ def process_escaped_output exp
53
+ process_output exp
54
+ end
55
+
56
+ # Pull out actual output value from template
57
+ def normalize_output arg
58
+ if call? arg and [:to_s, :html_safe!, :freeze].include? arg.method
59
+ arg.target
60
+ elsif node_type? arg, :if
61
+ branches = [arg.then_clause, arg.else_clause].compact
62
+
63
+ if branches.empty?
64
+ s(:nil)
65
+ elsif branches.length == 2
66
+ Sexp.new(:or, *branches)
67
+ else
68
+ branches.first
69
+ end
70
+ else
71
+ arg
72
+ end
73
+ end
74
+
75
+ def add_escaped_output output
76
+ add_output output, :escaped_output
77
+ end
78
+
79
+ def add_output output, type = :output
80
+ s = Sexp.new(type, output)
81
+ s.line(output.line)
82
+ @current_template.add_output s
83
+ s
84
+ end
85
+ end
@@ -0,0 +1,71 @@
1
+ ---
2
+ basic_auth_password: 300000
3
+ cross_site_scripting: 300000
4
+ xss_content_tag: 300000
5
+ CVE_2014_3514_call: 600000
6
+ all_default_routes: 2000000
7
+ unsafe_deserialize: 2000000
8
+ local_request_config: 100000
9
+ CVE_2012_3424: 4000000
10
+ CVE_2011_2932: 8000000
11
+ code_eval: 2000000
12
+ command_injection: 2000000
13
+ file_access: 2000000
14
+ CVE_2014_7829: 4000000
15
+ CVE_2011_2929: 4000000
16
+ csrf_protection_disabled: 4000000
17
+ CVE_2013_6414: 4000000
18
+ CVE_2013_4491: 4000000
19
+ CVE_2013_1856: 4000000
20
+ CVE_2015_3226: 4000000
21
+ CVE_2013_0333: 4000000
22
+ xss_link_to: 300000
23
+ xss_link_to_href: 300000
24
+ CVE_2011_0446: 300000
25
+ mass_assign_call: 2000000
26
+ dangerous_attr_accessible: 2000000
27
+ no_attr_accessible: 2000000
28
+ CVE_2013_0277: 2000000
29
+ CVE_2010_3933: 4000000
30
+ CVE_2014_0081: 300000
31
+ CVE_2011_2930: 600000
32
+ open_redirect: 300000
33
+ regex_dos: 600000
34
+ dynamic_render_path: 4000000
35
+ CVE_2014_0082: 4000000
36
+ cross_site_scripting_inline: 600000
37
+ CVE_2011_3186: 2000000
38
+ safe_buffer_vuln: 4000000
39
+ CVE_2013_1855: 4000000
40
+ CVE_2013_1857: 4000000
41
+ CVE_2012_3463: 600000
42
+ select_options_vuln: 4000000
43
+ dangerous_send: 600000
44
+ session_key_manipulation: 600000
45
+ http_cookies: 600000
46
+ session_secret: 600000
47
+ secure_cookies: 600000
48
+ CVE_2013_6416: 600000
49
+ CVE_2012_3464: 4000000
50
+ csrf_blacklist: 300000
51
+ auth_blacklist: 300000
52
+ sql_injection: 1200000
53
+ CVE-2012-2660: 4000000
54
+ CVE-2012-2661: 4000000
55
+ CVE-2012-2695: 4000000
56
+ CVE-2012-5664: 4000000
57
+ CVE-2013-0155: 4000000
58
+ CVE-2013-6417: 4000000
59
+ CVE-2014-3482: 4000000
60
+ CVE-2014-3483: 4000000
61
+ ssl_verification_bypass: 2500000
62
+ CVE_2011_2931: 4000000
63
+ unsafe_symbol_creation: 300000
64
+ translate_vuln: 300000
65
+ unsafe_constantize: 600000
66
+ unscoped_find: 300000
67
+ validation_regex: 300000
68
+ mass_assign_without_protection: 600000
69
+ CVE_2015_3227: 4000000
70
+ CVE_2013_0156: 4000000
71
+ weak_hash_digest: 800000
@@ -0,0 +1,153 @@
1
+ require 'set'
2
+ require 'json'
3
+
4
+ module Railroader
5
+ class IgnoreConfig
6
+ attr_reader :shown_warnings, :ignored_warnings
7
+ attr_accessor :file
8
+
9
+ def initialize file, new_warnings
10
+ @file = file
11
+ @new_warnings = new_warnings
12
+ @already_ignored = []
13
+ @ignored_fingerprints = Set.new
14
+ @used_fingerprints = Set.new
15
+ @notes = {}
16
+ @shown_warnings = @ignored_warnings = nil
17
+ @changed = false
18
+ end
19
+
20
+ # Populate ignored_warnings and shown_warnings based on ignore
21
+ # configuration
22
+ def filter_ignored
23
+ @shown_warnings = []
24
+ @ignored_warnings = []
25
+
26
+ @new_warnings.each do |w|
27
+ if ignored? w
28
+ @ignored_warnings << w
29
+ else
30
+ @shown_warnings << w
31
+ end
32
+ end
33
+
34
+ @shown_warnings
35
+ end
36
+
37
+ # Remove warning from ignored list
38
+ def unignore warning
39
+ @ignored_fingerprints.delete warning.fingerprint
40
+ if @already_ignored.reject! { |w|w[:fingerprint] == warning.fingerprint }
41
+ @changed = true
42
+ end
43
+ end
44
+
45
+ # Determine if warning should be ignored
46
+ def ignored? warning
47
+ @used_fingerprints << warning.fingerprint
48
+ @ignored_fingerprints.include? warning.fingerprint
49
+ end
50
+
51
+ def ignore warning
52
+ @changed = true unless ignored? warning
53
+ @ignored_fingerprints << warning.fingerprint
54
+ end
55
+
56
+ # Add note for warning
57
+ def add_note warning, note
58
+ @changed = true
59
+ @notes[warning.fingerprint] = note
60
+ end
61
+
62
+ # Retrieve note for warning if it exists. Returns nil if no
63
+ # note is found
64
+ def note_for warning
65
+ if warning.is_a? Warning
66
+ fingerprint = warning.fingerprint
67
+ else
68
+ fingerprint = warning[:fingerprint]
69
+ end
70
+
71
+ @already_ignored.each do |w|
72
+ if fingerprint == w[:fingerprint]
73
+ return w[:note]
74
+ end
75
+ end
76
+
77
+ nil
78
+ end
79
+
80
+ # The set of unused ignore entries
81
+ def obsolete_fingerprints
82
+ (@ignored_fingerprints - @used_fingerprints).to_a
83
+ end
84
+
85
+ def prune_obsolete
86
+ obsolete = obsolete_fingerprints.to_set
87
+ @ignored_fingerprints -= obsolete
88
+
89
+ @already_ignored.reject! do |w|
90
+ if obsolete.include? w[:fingerprint]
91
+ @changed = true
92
+ end
93
+ end
94
+ end
95
+
96
+ # Read configuration to file
97
+ def read_from_file file = @file
98
+ if File.exist? file
99
+ @already_ignored = JSON.parse(File.read(file), :symbolize_names => true)[:ignored_warnings]
100
+ else
101
+ Railroader.notify "[Notice] Could not find ignore configuration in #{file}"
102
+ @already_ignored = []
103
+ end
104
+
105
+ @already_ignored.each do |w|
106
+ @ignored_fingerprints << w[:fingerprint]
107
+ @notes[w[:fingerprint]] = w[:note]
108
+ end
109
+ end
110
+
111
+ # Save configuration to file
112
+ def save_to_file warnings, file = @file
113
+ warnings = warnings.map do |w|
114
+ if w.is_a? Warning
115
+ w_hash = w.to_hash
116
+ w_hash[:file] = w.relative_path
117
+ w = w_hash
118
+ end
119
+
120
+ w[:note] = @notes[w[:fingerprint]] || ""
121
+ w
122
+ end.sort_by { |w| w[:fingerprint] }
123
+
124
+ output = {
125
+ :ignored_warnings => warnings,
126
+ :updated => Time.now.to_s,
127
+ :railroader_version => Railroader::Version
128
+ }
129
+
130
+ File.open file, "w" do |f|
131
+ f.puts JSON.pretty_generate(output)
132
+ end
133
+ end
134
+
135
+ # Save old ignored warnings and newly ignored ones
136
+ def save_with_old
137
+ warnings = @ignored_warnings.dup
138
+
139
+ # Only add ignored warnings not already ignored
140
+ @already_ignored.each do |w|
141
+ fingerprint = w[:fingerprint]
142
+
143
+ unless @ignored_warnings.find { |ignored_warning| ignored_warning.fingerprint == fingerprint }
144
+ warnings << w
145
+ end
146
+ end
147
+
148
+ if @changed
149
+ save_to_file warnings
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,362 @@
1
+ Railroader.load_railroader_dependency 'highline'
2
+
3
+ module Railroader
4
+ class InteractiveIgnorer
5
+ def initialize file, warnings
6
+ @ignore_config = Railroader::IgnoreConfig.new(file, warnings)
7
+ @new_warnings = warnings
8
+ @skip_ignored = false
9
+ @skip_rest = false
10
+ @ignore_rest = false
11
+ @quit = false
12
+ @restart = false
13
+ end
14
+
15
+ def start
16
+ file_menu
17
+ initial_menu
18
+
19
+ @ignore_config.filter_ignored
20
+
21
+ unless @quit
22
+ penultimate_menu
23
+ final_menu
24
+ end
25
+
26
+ if @restart
27
+ @restart = false
28
+ start
29
+ end
30
+
31
+ @ignore_config
32
+ end
33
+
34
+ private
35
+
36
+ def file_menu
37
+ loop do
38
+ @ignore_config.file = HighLine.new.ask "Input file: " do |q|
39
+ if @ignore_config.file and not @ignore_config.file.empty?
40
+ q.default = @ignore_config.file
41
+ else
42
+ q.default = "config/railroader.ignore"
43
+ end
44
+ end
45
+
46
+ if File.exist? @ignore_config.file
47
+ @ignore_config.read_from_file
48
+ return
49
+ else
50
+ if yes_or_no "No such file. Continue with empty config? "
51
+ return
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def initial_menu
58
+ HighLine.new.choose do |m|
59
+ m.choice "Inspect all warnings" do
60
+ @skip_ignored = false
61
+ pre_show_help
62
+ process_warnings
63
+ end
64
+
65
+ m.choice "Hide previously ignored warnings" do
66
+ @skip_ignored = true
67
+ pre_show_help
68
+ process_warnings
69
+ end
70
+
71
+ m.choice "Prune obsolete ignored warnings" do
72
+ prune_obsolete
73
+ end
74
+
75
+ m.choice "Skip - use current ignore configuration" do
76
+ @quit = true
77
+ @ignore_config.filter_ignored
78
+ end
79
+ end
80
+ end
81
+
82
+ def warning_menu
83
+ HighLine.new.choose do |m|
84
+ m.prompt = "Action: "
85
+ m.layout = :one_line
86
+ m.list_option = ", "
87
+ m.select_by = :name
88
+
89
+ m.choice "i"
90
+ m.choice "n"
91
+ m.choice "k"
92
+ m.choice "u"
93
+ m.choice "a"
94
+ m.choice "s"
95
+ m.choice "q"
96
+ m.choice "?" do
97
+ show_help
98
+ "?"
99
+ end
100
+ end
101
+ end
102
+
103
+ def pre_show_help
104
+ say "-" * 30
105
+ say "Actions:", :cyan
106
+ show_help
107
+ end
108
+
109
+ def show_help
110
+ say <<-HELP
111
+ i - Add warning to ignore list
112
+ n - Add warning to ignore list and add note
113
+ s - Skip this warning (will remain ignored or shown)
114
+ u - Remove this warning from ignore list
115
+ a - Ignore this warning and all remaining warnings
116
+ k - Skip this warning and all remaining warnings
117
+ q - Quit, do not update ignored warnings
118
+ ? - Display this help
119
+ HELP
120
+ end
121
+
122
+ def penultimate_menu
123
+ obsolete = @ignore_config.obsolete_fingerprints
124
+ return unless obsolete.any?
125
+
126
+ if obsolete.length > 1
127
+ plural = 's'
128
+ verb = 'are'
129
+ else
130
+ plural = ''
131
+ verb = 'is'
132
+ end
133
+
134
+ say "\n#{obsolete.length} fingerprint#{plural} #{verb} unused:", :green
135
+ obsolete.each do |obs|
136
+ say obs
137
+ end
138
+
139
+ if yes_or_no "\nRemove fingerprint#{plural}?"
140
+ @ignore_config.prune_obsolete
141
+ end
142
+ end
143
+
144
+ def prune_obsolete
145
+ @ignore_config.filter_ignored
146
+ obsolete = @ignore_config.obsolete_fingerprints
147
+ @ignore_config.prune_obsolete
148
+
149
+ say "Removed #{obsolete.length} obsolete fingerprint#{'s' if obsolete.length > 1} from ignore config.", :yellow
150
+ end
151
+
152
+ def final_menu
153
+ summarize_changes
154
+
155
+ HighLine.new.choose do |m|
156
+ m.choice "Save changes" do
157
+ save
158
+ end
159
+
160
+ m.choice "Start over" do
161
+ start_over
162
+ end
163
+
164
+ m.choice "Quit, do not save changes" do
165
+ quit
166
+ end
167
+ end
168
+ end
169
+
170
+ def save
171
+ @ignore_config.file = HighLine.new.ask "Output file: " do |q|
172
+ if @ignore_config.file and not @ignore_config.file.empty?
173
+ q.default = @ignore_config.file
174
+ else
175
+ q.default = "config/railroader.ignore"
176
+ end
177
+ end
178
+
179
+ @ignore_config.save_with_old
180
+ end
181
+
182
+ def start_over
183
+ reset_config
184
+ @restart = true
185
+ end
186
+
187
+ def reset_config
188
+ @ignore_config = Railroader::IgnoreConfig.new(@ignore_config.file, @new_warnings)
189
+ end
190
+
191
+ def process_warnings
192
+ @warning_count = @new_warnings.length
193
+
194
+ @new_warnings.each_with_index do |w, index|
195
+ @current_index = index
196
+
197
+ if skip_ignored? w or @skip_rest
198
+ next
199
+ elsif @ignore_rest
200
+ ignore w
201
+ elsif @quit or @restart
202
+ return
203
+ else
204
+ ask_about w
205
+ end
206
+ end
207
+ end
208
+
209
+ def ask_about warning
210
+ pretty_display warning
211
+ warning_action warning_menu, warning
212
+ end
213
+
214
+ def warning_action action, warning
215
+ case action
216
+ when "i"
217
+ ignore warning
218
+ when "n"
219
+ ignore_and_note warning
220
+ when "s"
221
+ # do nothing
222
+ when "u"
223
+ unignore warning
224
+ when "a"
225
+ ignore_rest warning
226
+ when "k"
227
+ skip_rest warning
228
+ when "q"
229
+ quit
230
+ when "?"
231
+ ask_about warning
232
+ else
233
+ raise "Unexpected action"
234
+ end
235
+ end
236
+
237
+ def ignore warning
238
+ @ignore_config.ignore warning
239
+ end
240
+
241
+ def ignore_and_note warning
242
+ note = HighLine.new.ask("Note: ")
243
+ @ignore_config.ignore warning
244
+ @ignore_config.add_note warning, note
245
+ end
246
+
247
+ def unignore warning
248
+ @ignore_config.unignore warning
249
+ end
250
+
251
+ def skip_rest warning
252
+ @skip_rest = true
253
+ end
254
+
255
+ def ignore_rest warning
256
+ ignore warning
257
+ @ignore_rest = true
258
+ end
259
+
260
+ def quit
261
+ reset_config
262
+ @ignore_config.read_from_file
263
+ @ignore_config.filter_ignored
264
+ @quit = true
265
+ end
266
+
267
+ def pretty_display warning
268
+ progress = "#{@current_index + 1}/#{@warning_count}"
269
+ say "-------- #{progress} #{"-" * (20 - progress.length)}", :cyan
270
+ show_confidence warning
271
+
272
+ label "Category"
273
+ say warning.warning_type
274
+
275
+ label "Message"
276
+ say warning.message
277
+
278
+ if warning.code
279
+ label "Code"
280
+ say warning.format_code
281
+ end
282
+
283
+ if warning.relative_path
284
+ label "File"
285
+ say warning.relative_path
286
+ end
287
+
288
+ if warning.line
289
+ label "Line"
290
+ say warning.line
291
+ end
292
+
293
+ if already_ignored? warning
294
+ show_note warning
295
+ say "Already ignored", :red
296
+ end
297
+
298
+ say ""
299
+ end
300
+
301
+ def already_ignored? warning
302
+ @ignore_config.ignored? warning
303
+ end
304
+
305
+ def skip_ignored? warning
306
+ @skip_ignored and already_ignored? warning
307
+ end
308
+
309
+ def summarize_changes
310
+ say "-" * 30
311
+
312
+ say "Ignoring #{@ignore_config.ignored_warnings.length} warnings", :yellow
313
+ say "Showing #{@ignore_config.shown_warnings.length} warnings", :green
314
+ end
315
+
316
+ def label name
317
+ say "#{name}: ", :green
318
+ end
319
+
320
+ def show_confidence warning
321
+ label "Confidence"
322
+
323
+ case warning.confidence
324
+ when 0
325
+ say "High", :red
326
+ when 1
327
+ say "Medium", :yellow
328
+ when 2
329
+ say "Weak", :cyan
330
+ else
331
+ say "Unknown"
332
+ end
333
+ end
334
+
335
+ def show_note warning
336
+ note = @ignore_config.note_for warning
337
+
338
+ if note
339
+ label "Note"
340
+ say note
341
+ end
342
+ end
343
+
344
+ def say text, color = nil
345
+ text = text.to_s
346
+
347
+ if color
348
+ HighLine.new.say HighLine.new.color(text, color)
349
+ else
350
+ HighLine.new.say text
351
+ end
352
+ end
353
+
354
+ def yes_or_no message
355
+ answer = HighLine.new.ask message do |q|
356
+ q.in = ["y", "n", "yes", "no"]
357
+ end
358
+
359
+ answer.match /^y/i
360
+ end
361
+ end
362
+ end