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
data/lib/railroader.rb ADDED
@@ -0,0 +1,544 @@
1
+ require 'set'
2
+ require 'railroader/version'
3
+
4
+ module Railroader
5
+
6
+ #This exit code is used when warnings are found and the --exit-on-warn
7
+ #option is set
8
+ Warnings_Found_Exit_Code = 3
9
+
10
+ #Exit code returned when no Rails application is detected
11
+ No_App_Found_Exit_Code = 4
12
+
13
+ #Exit code returned when railroader was outdated
14
+ Not_Latest_Version_Exit_Code = 5
15
+
16
+ #Exit code returned when user requests non-existent checks
17
+ Missing_Checks_Exit_Code = 6
18
+
19
+ #Exit code returned when errors were found and the --exit-on-error
20
+ #option is set
21
+ Errors_Found_Exit_Code = 7
22
+
23
+ @debug = false
24
+ @quiet = false
25
+ @loaded_dependencies = []
26
+ @vendored_paths = false
27
+
28
+ #Run Railroader scan. Returns Tracker object.
29
+ #
30
+ #Options:
31
+ #
32
+ # * :app_path - path to root of Rails app (required)
33
+ # * :additional_checks_path - array of additional directories containing additional out-of-tree checks to run
34
+ # * :additional_libs_path - array of additional application relative lib directories (ex. app/mailers) to process
35
+ # * :assume_all_routes - assume all methods are routes (default: true)
36
+ # * :check_arguments - check arguments of methods (default: true)
37
+ # * :collapse_mass_assignment - report unprotected models in single warning (default: false)
38
+ # * :combine_locations - combine warning locations (default: true)
39
+ # * :config_file - configuration file
40
+ # * :escape_html - escape HTML by default (automatic)
41
+ # * :exit_on_error - only affects Commandline module (default: true)
42
+ # * :exit_on_warn - only affects Commandline module (default: true)
43
+ # * :github_repo - github repo to use for file links (user/repo[/path][@ref])
44
+ # * :highlight_user_input - highlight user input in reported warnings (default: true)
45
+ # * :html_style - path to CSS file
46
+ # * :ignore_model_output - consider models safe (default: false)
47
+ # * :index_libs - add libraries to call index (default: true)
48
+ # * :interprocedural - limited interprocedural processing of method calls (default: false)
49
+ # * :message_limit - limit length of messages
50
+ # * :min_confidence - minimum confidence (0-2, 0 is highest)
51
+ # * :output_files - files for output
52
+ # * :output_formats - formats for output (:to_s, :to_tabs, :to_csv, :to_html)
53
+ # * :parallel_checks - run checks in parallel (default: true)
54
+ # * :parser_timeout - set timeout for parsing an individual file (default: 10 seconds)
55
+ # * :print_report - if no output file specified, print to stdout (default: false)
56
+ # * :quiet - suppress most messages (default: true)
57
+ # * :rails3 - force Rails 3 mode (automatic)
58
+ # * :report_routes - show found routes on controllers (default: false)
59
+ # * :run_checks - array of checks to run (run all if not specified)
60
+ # * :safe_methods - array of methods to consider safe
61
+ # * :skip_libs - do not process lib/ directory (default: false)
62
+ # * :skip_checks - checks not to run (run all if not specified)
63
+ # * :absolute_paths - show absolute path of each file (default: false)
64
+ # * :summary_only - only output summary section of report for plain/table (:summary_only, :no_summary, true)
65
+ #
66
+ #Alternatively, just supply a path as a string.
67
+ def self.run options
68
+ options = set_options options
69
+
70
+ @quiet = !!options[:quiet]
71
+ @debug = !!options[:debug]
72
+
73
+ if @quiet
74
+ options[:report_progress] = false
75
+ end
76
+
77
+ scan options
78
+ end
79
+
80
+ #Sets up options for run, checks given application path
81
+ def self.set_options options
82
+ if options.is_a? String
83
+ options = { :app_path => options }
84
+ end
85
+
86
+ if options[:quiet] == :command_line
87
+ command_line = true
88
+ options.delete :quiet
89
+ end
90
+
91
+ options = default_options.merge(load_options(options)).merge(options)
92
+
93
+ if options[:quiet].nil? and not command_line
94
+ options[:quiet] = true
95
+ end
96
+
97
+ if options[:rails4]
98
+ options[:rails3] = true
99
+ elsif options[:rails5]
100
+ options[:rails3] = true
101
+ options[:rails4] = true
102
+ end
103
+
104
+ options[:output_formats] = get_output_formats options
105
+ options[:github_url] = get_github_url options
106
+
107
+ options
108
+ end
109
+
110
+ #Load options from YAML file
111
+ def self.load_options line_options
112
+ custom_location = line_options[:config_file]
113
+ quiet = line_options[:quiet]
114
+ app_path = line_options[:app_path]
115
+
116
+ #Load configuration file
117
+ if config = config_file(custom_location, app_path)
118
+ require 'date' # https://github.com/dtao/safe_yaml/issues/80
119
+ self.load_railroader_dependency 'safe_yaml/load'
120
+ options = SafeYAML.load_file config, :deserialize_symbols => true
121
+
122
+ if options
123
+ options.each { |k, v| options[k] = Set.new v if v.is_a? Array }
124
+
125
+ # After parsing the yaml config file for options, convert any string keys into symbols.
126
+ options.keys.select {|k| k.is_a? String}.map {|k| k.to_sym }.each {|k| options[k] = options[k.to_s]; options.delete(k.to_s) }
127
+
128
+ unless line_options[:allow_check_paths_in_config]
129
+ if options.include? :additional_checks_path
130
+ options.delete :additional_checks_path
131
+
132
+ notify "[Notice] Ignoring additional check paths in config file. Use --allow-check-paths-in-config to allow" unless (options[:quiet] || quiet)
133
+ end
134
+ end
135
+
136
+ # notify if options[:quiet] and quiet is nil||false
137
+ notify "[Notice] Using configuration in #{config}" unless (options[:quiet] || quiet)
138
+ options
139
+ else
140
+ notify "[Notice] Empty configuration file: #{config}" unless quiet
141
+ {}
142
+ end
143
+ else
144
+ {}
145
+ end
146
+ end
147
+
148
+ CONFIG_FILES = [
149
+ File.expand_path("~/.railroader/config.yml"),
150
+ File.expand_path("/etc/railroader/config.yml")
151
+ ]
152
+
153
+ def self.config_file custom_location, app_path
154
+ app_config = File.expand_path(File.join(app_path, "config", "railroader.yml"))
155
+ supported_locations = [File.expand_path(custom_location || ""), app_config] + CONFIG_FILES
156
+ supported_locations.detect {|f| File.file?(f) }
157
+ end
158
+
159
+ #Default set of options
160
+ def self.default_options
161
+ { :assume_all_routes => true,
162
+ :check_arguments => true,
163
+ :collapse_mass_assignment => false,
164
+ :combine_locations => true,
165
+ :engine_paths => ["engines/*"],
166
+ :exit_on_error => true,
167
+ :exit_on_warn => true,
168
+ :highlight_user_input => true,
169
+ :html_style => "#{File.expand_path(File.dirname(__FILE__))}/railroader/format/style.css",
170
+ :ignore_model_output => false,
171
+ :ignore_redirect_to_model => true,
172
+ :index_libs => true,
173
+ :message_limit => 100,
174
+ :min_confidence => 2,
175
+ :output_color => true,
176
+ :pager => true,
177
+ :parallel_checks => true,
178
+ :parser_timeout => 10,
179
+ :relative_path => false,
180
+ :report_progress => true,
181
+ :safe_methods => Set.new,
182
+ :skip_checks => Set.new,
183
+ }
184
+ end
185
+
186
+ #Determine output formats based on options[:output_formats]
187
+ #or options[:output_files]
188
+ def self.get_output_formats options
189
+ #Set output format
190
+ if options[:output_format] && options[:output_files] && options[:output_files].size > 1
191
+ raise ArgumentError, "Cannot specify output format if multiple output files specified"
192
+ end
193
+ if options[:output_format]
194
+ get_formats_from_output_format options[:output_format]
195
+ elsif options[:output_files]
196
+ get_formats_from_output_files options[:output_files]
197
+ else
198
+ begin
199
+ self.load_railroader_dependency 'terminal-table', :allow_fail
200
+ return [:to_s]
201
+ rescue LoadError
202
+ return [:to_json]
203
+ end
204
+ end
205
+ end
206
+
207
+ def self.get_formats_from_output_format output_format
208
+ case output_format
209
+ when :html, :to_html
210
+ [:to_html]
211
+ when :csv, :to_csv
212
+ [:to_csv]
213
+ when :pdf, :to_pdf
214
+ [:to_pdf]
215
+ when :tabs, :to_tabs
216
+ [:to_tabs]
217
+ when :json, :to_json
218
+ [:to_json]
219
+ when :markdown, :to_markdown
220
+ [:to_markdown]
221
+ when :cc, :to_cc, :codeclimate, :to_codeclimate
222
+ [:to_codeclimate]
223
+ when :plain ,:to_plain, :text, :to_text, :to_s
224
+ [:to_text]
225
+ when :table, :to_table
226
+ [:to_table]
227
+ else
228
+ [:to_text]
229
+ end
230
+ end
231
+ private_class_method :get_formats_from_output_format
232
+
233
+ def self.get_formats_from_output_files output_files
234
+ output_files.map do |output_file|
235
+ case output_file
236
+ when /\.html$/i
237
+ :to_html
238
+ when /\.csv$/i
239
+ :to_csv
240
+ when /\.pdf$/i
241
+ :to_pdf
242
+ when /\.tabs$/i
243
+ :to_tabs
244
+ when /\.json$/i
245
+ :to_json
246
+ when /\.md$/i
247
+ :to_markdown
248
+ when /(\.cc|\.codeclimate)$/i
249
+ :to_codeclimate
250
+ when /\.plain$/i
251
+ :to_text
252
+ when /\.table$/i
253
+ :to_table
254
+ else
255
+ :to_text
256
+ end
257
+ end
258
+ end
259
+ private_class_method :get_formats_from_output_files
260
+
261
+ def self.get_github_url options
262
+ if github_repo = options[:github_repo]
263
+ full_repo, ref = github_repo.split '@', 2
264
+ name, repo, path = full_repo.split '/', 3
265
+ unless name && repo && !(name.empty? || repo.empty?)
266
+ raise ArgumentError, "Invalid GitHub repository format"
267
+ end
268
+ path.chomp '/' if path
269
+ ref ||= 'master'
270
+ ['https://github.com', name, repo, 'blob', ref, path].compact.join '/'
271
+ else
272
+ nil
273
+ end
274
+ end
275
+ private_class_method :get_github_url
276
+
277
+ #Output list of checks (for `-k` option)
278
+ def self.list_checks options
279
+ require 'railroader/scanner'
280
+
281
+ add_external_checks options
282
+
283
+ if options[:list_optional_checks]
284
+ $stderr.puts "Optional Checks:"
285
+ checks = Checks.optional_checks
286
+ else
287
+ $stderr.puts "Available Checks:"
288
+ checks = Checks.checks
289
+ end
290
+
291
+ format_length = 30
292
+
293
+ $stderr.puts "-" * format_length
294
+ checks.each do |check|
295
+ $stderr.printf("%-#{format_length}s%s\n", check.name, check.description)
296
+ end
297
+ end
298
+
299
+ #Output configuration to YAML
300
+ def self.dump_config options
301
+ require 'yaml'
302
+ if options[:create_config].is_a? String
303
+ file = options[:create_config]
304
+ else
305
+ file = nil
306
+ end
307
+
308
+ options.delete :create_config
309
+
310
+ options.each do |k,v|
311
+ if v.is_a? Set
312
+ options[k] = v.to_a
313
+ end
314
+ end
315
+
316
+ if file
317
+ File.open file, "w" do |f|
318
+ YAML.dump options, f
319
+ end
320
+ notify "Output configuration to #{file}"
321
+ else
322
+ notify YAML.dump(options)
323
+ end
324
+ end
325
+
326
+ def self.ensure_latest
327
+ current = Railroader::Version
328
+ latest = Gem.latest_version_for('railroader').to_s
329
+ if current != latest
330
+ "Railroader #{current} is not the latest version #{latest}"
331
+ end
332
+ end
333
+
334
+ #Run a scan. Generally called from Railroader.run instead of directly.
335
+ def self.scan options
336
+ #Load scanner
337
+ notify "Loading scanner..."
338
+
339
+ begin
340
+ require 'railroader/scanner'
341
+ rescue LoadError
342
+ raise NoRailroaderError, "Cannot find lib/ directory."
343
+ end
344
+
345
+ add_external_checks options
346
+
347
+ #Start scanning
348
+ scanner = Scanner.new options
349
+ tracker = scanner.tracker
350
+
351
+ check_for_missing_checks options[:run_checks], options[:skip_checks]
352
+
353
+ notify "Processing application in #{tracker.app_path}"
354
+ scanner.process
355
+
356
+ if options[:parallel_checks]
357
+ notify "Running checks in parallel..."
358
+ else
359
+ notify "Runnning checks..."
360
+ end
361
+
362
+ tracker.run_checks
363
+
364
+ self.filter_warnings tracker, options
365
+
366
+ if options[:output_files]
367
+ notify "Generating report..."
368
+
369
+ write_report_to_files tracker, options[:output_files]
370
+ elsif options[:print_report]
371
+ notify "Generating report..."
372
+
373
+ write_report_to_formats tracker, options[:output_formats]
374
+ end
375
+
376
+ tracker
377
+ end
378
+
379
+ def self.write_report_to_files tracker, output_files
380
+ require 'fileutils'
381
+ tracker.options[:output_color] = false unless tracker.options[:output_color] == :force
382
+
383
+ output_files.each_with_index do |output_file, idx|
384
+ dir = File.dirname(output_file)
385
+ unless Dir.exist? dir
386
+ FileUtils.mkdir_p(dir)
387
+ end
388
+
389
+ File.open output_file, "w" do |f|
390
+ f.write tracker.report.format(tracker.options[:output_formats][idx])
391
+ end
392
+ notify "Report saved in '#{output_file}'"
393
+ end
394
+ end
395
+ private_class_method :write_report_to_files
396
+
397
+ def self.write_report_to_formats tracker, output_formats
398
+ unless $stdout.tty? or tracker.options[:output_color] == :force
399
+ tracker.options[:output_color] = false
400
+ end
401
+
402
+ if not $stdout.tty? or not tracker.options[:pager] or output_formats.length > 1 # does this ever happen??
403
+ output_formats.each do |output_format|
404
+ puts tracker.report.format(output_format)
405
+ end
406
+ else
407
+ require "railroader/report/pager"
408
+
409
+ Railroader::Pager.new(tracker).page_report(tracker.report, output_formats.first)
410
+ end
411
+ end
412
+ private_class_method :write_report_to_formats
413
+
414
+ #Rescan a subset of files in a Rails application.
415
+ #
416
+ #A full scan must have been run already to use this method.
417
+ #The returned Tracker object from Railroader.run is used as a starting point
418
+ #for the rescan.
419
+ #
420
+ #Options may be given as a hash with the same values as Railroader.run.
421
+ #Note that these options will be merged into the Tracker.
422
+ #
423
+ #This method returns a RescanReport object with information about the scan.
424
+ #However, the Tracker object will also be modified as the scan is run.
425
+ def self.rescan tracker, files, options = {}
426
+ require 'railroader/rescanner'
427
+
428
+ tracker.options.merge! options
429
+
430
+ @quiet = !!tracker.options[:quiet]
431
+ @debug = !!tracker.options[:debug]
432
+
433
+ Rescanner.new(tracker.options, tracker.processor, files).recheck
434
+ end
435
+
436
+ def self.notify message
437
+ $stderr.puts message unless @quiet
438
+ end
439
+
440
+ def self.debug message
441
+ $stderr.puts message if @debug
442
+ end
443
+
444
+ # Compare JSON ouptut from a previous scan and return the diff of the two scans
445
+ def self.compare options
446
+ require 'json'
447
+ require 'railroader/differ'
448
+ raise ArgumentError.new("Comparison file doesn't exist") unless File.exist? options[:previous_results_json]
449
+
450
+ begin
451
+ previous_results = JSON.parse(File.read(options[:previous_results_json]), :symbolize_names => true)[:warnings]
452
+ rescue JSON::ParserError
453
+ self.notify "Error parsing comparison file: #{options[:previous_results_json]}"
454
+ exit!
455
+ end
456
+
457
+ tracker = run(options)
458
+
459
+ new_results = JSON.parse(tracker.report.to_json, :symbolize_names => true)[:warnings]
460
+
461
+ Railroader::Differ.new(new_results, previous_results).diff
462
+ end
463
+
464
+ def self.load_railroader_dependency name, allow_fail = false
465
+ return if @loaded_dependencies.include? name
466
+
467
+ unless @vendored_paths
468
+ path_load = "#{File.expand_path(File.dirname(__FILE__))}/../bundle/load.rb"
469
+
470
+ if File.exist? path_load
471
+ require path_load
472
+ end
473
+
474
+ @vendored_paths = true
475
+ end
476
+
477
+ begin
478
+ require name
479
+ rescue LoadError => e
480
+ if allow_fail
481
+ raise e
482
+ else
483
+ $stderr.puts e.message
484
+ $stderr.puts "Please install the appropriate dependency: #{name}."
485
+ exit!(-1)
486
+ end
487
+ end
488
+ end
489
+
490
+ def self.filter_warnings tracker, options
491
+ require 'railroader/report/ignore/config'
492
+
493
+ app_tree = Railroader::AppTree.from_options(options)
494
+
495
+ if options[:ignore_file]
496
+ file = options[:ignore_file]
497
+ elsif app_tree.exists? "config/railroader.ignore"
498
+ file = app_tree.expand_path("config/railroader.ignore")
499
+ elsif not options[:interactive_ignore]
500
+ return
501
+ end
502
+
503
+ notify "Filtering warnings..."
504
+
505
+ if options[:interactive_ignore]
506
+ require 'railroader/report/ignore/interactive'
507
+ config = InteractiveIgnorer.new(file, tracker.warnings).start
508
+ else
509
+ notify "[Notice] Using '#{file}' to filter warnings"
510
+ config = IgnoreConfig.new(file, tracker.warnings)
511
+ config.read_from_file
512
+ config.filter_ignored
513
+ end
514
+
515
+ tracker.ignored_filter = config
516
+ end
517
+
518
+ def self.add_external_checks options
519
+ options[:additional_checks_path].each do |path|
520
+ Railroader::Checks.initialize_checks path
521
+ end if options[:additional_checks_path]
522
+ end
523
+
524
+ def self.check_for_missing_checks included_checks, excluded_checks
525
+ missing = Railroader::Checks.missing_checks(included_checks || Set.new, excluded_checks || Set.new)
526
+
527
+ unless missing.empty?
528
+ raise MissingChecksError, "Could not find specified check#{missing.length > 1 ? 's' : ''}: #{missing.map {|c| "`#{c}`"}.join(', ')}"
529
+ end
530
+ end
531
+
532
+ def self.debug= val
533
+ @debug = val
534
+ end
535
+
536
+ def self.quiet= val
537
+ @quiet = val
538
+ end
539
+
540
+ class DependencyError < RuntimeError; end
541
+ class NoRailroaderError < RuntimeError; end
542
+ class NoApplication < RuntimeError; end
543
+ class MissingChecksError < RuntimeError; end
544
+ end