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.
- checksums.yaml +7 -0
- data/CHANGES.md +1091 -0
- data/FEATURES +16 -0
- data/README.md +174 -0
- data/bin/railroader +8 -0
- data/lib/railroader/app_tree.rb +191 -0
- data/lib/railroader/call_index.rb +219 -0
- data/lib/railroader/checks/base_check.rb +505 -0
- data/lib/railroader/checks/check_basic_auth.rb +88 -0
- data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
- data/lib/railroader/checks/check_content_tag.rb +200 -0
- data/lib/railroader/checks/check_create_with.rb +74 -0
- data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
- data/lib/railroader/checks/check_default_routes.rb +86 -0
- data/lib/railroader/checks/check_deserialize.rb +56 -0
- data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
- data/lib/railroader/checks/check_digest_dos.rb +38 -0
- data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
- data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
- data/lib/railroader/checks/check_escape_function.rb +21 -0
- data/lib/railroader/checks/check_evaluation.rb +35 -0
- data/lib/railroader/checks/check_execute.rb +189 -0
- data/lib/railroader/checks/check_file_access.rb +71 -0
- data/lib/railroader/checks/check_file_disclosure.rb +35 -0
- data/lib/railroader/checks/check_filter_skipping.rb +31 -0
- data/lib/railroader/checks/check_forgery_setting.rb +81 -0
- data/lib/railroader/checks/check_header_dos.rb +31 -0
- data/lib/railroader/checks/check_i18n_xss.rb +48 -0
- data/lib/railroader/checks/check_jruby_xml.rb +36 -0
- data/lib/railroader/checks/check_json_encoding.rb +47 -0
- data/lib/railroader/checks/check_json_parsing.rb +107 -0
- data/lib/railroader/checks/check_link_to.rb +132 -0
- data/lib/railroader/checks/check_link_to_href.rb +146 -0
- data/lib/railroader/checks/check_mail_to.rb +49 -0
- data/lib/railroader/checks/check_mass_assignment.rb +196 -0
- data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
- data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
- data/lib/railroader/checks/check_model_attributes.rb +119 -0
- data/lib/railroader/checks/check_model_serialize.rb +67 -0
- data/lib/railroader/checks/check_nested_attributes.rb +38 -0
- data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/railroader/checks/check_number_to_currency.rb +74 -0
- data/lib/railroader/checks/check_permit_attributes.rb +43 -0
- data/lib/railroader/checks/check_quote_table_name.rb +40 -0
- data/lib/railroader/checks/check_redirect.rb +256 -0
- data/lib/railroader/checks/check_regex_dos.rb +68 -0
- data/lib/railroader/checks/check_render.rb +97 -0
- data/lib/railroader/checks/check_render_dos.rb +37 -0
- data/lib/railroader/checks/check_render_inline.rb +53 -0
- data/lib/railroader/checks/check_response_splitting.rb +21 -0
- data/lib/railroader/checks/check_route_dos.rb +42 -0
- data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
- data/lib/railroader/checks/check_secrets.rb +40 -0
- data/lib/railroader/checks/check_select_tag.rb +59 -0
- data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
- data/lib/railroader/checks/check_send.rb +47 -0
- data/lib/railroader/checks/check_send_file.rb +19 -0
- data/lib/railroader/checks/check_session_manipulation.rb +35 -0
- data/lib/railroader/checks/check_session_settings.rb +176 -0
- data/lib/railroader/checks/check_simple_format.rb +58 -0
- data/lib/railroader/checks/check_single_quotes.rb +101 -0
- data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
- data/lib/railroader/checks/check_sql.rb +700 -0
- data/lib/railroader/checks/check_sql_cves.rb +106 -0
- data/lib/railroader/checks/check_ssl_verify.rb +48 -0
- data/lib/railroader/checks/check_strip_tags.rb +89 -0
- data/lib/railroader/checks/check_symbol_dos.rb +71 -0
- data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
- data/lib/railroader/checks/check_translate_bug.rb +45 -0
- data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
- data/lib/railroader/checks/check_unscoped_find.rb +57 -0
- data/lib/railroader/checks/check_validation_regex.rb +116 -0
- data/lib/railroader/checks/check_weak_hash.rb +148 -0
- data/lib/railroader/checks/check_without_protection.rb +80 -0
- data/lib/railroader/checks/check_xml_dos.rb +45 -0
- data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
- data/lib/railroader/checks.rb +209 -0
- data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
- data/lib/railroader/commandline.rb +179 -0
- data/lib/railroader/differ.rb +66 -0
- data/lib/railroader/file_parser.rb +54 -0
- data/lib/railroader/format/style.css +133 -0
- data/lib/railroader/options.rb +339 -0
- data/lib/railroader/parsers/rails2_erubis.rb +6 -0
- data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/railroader/parsers/rails3_erubis.rb +81 -0
- data/lib/railroader/parsers/template_parser.rb +108 -0
- data/lib/railroader/processor.rb +102 -0
- data/lib/railroader/processors/alias_processor.rb +1229 -0
- data/lib/railroader/processors/base_processor.rb +295 -0
- data/lib/railroader/processors/config_processor.rb +14 -0
- data/lib/railroader/processors/controller_alias_processor.rb +278 -0
- data/lib/railroader/processors/controller_processor.rb +249 -0
- data/lib/railroader/processors/erb_template_processor.rb +77 -0
- data/lib/railroader/processors/erubis_template_processor.rb +92 -0
- data/lib/railroader/processors/gem_processor.rb +64 -0
- data/lib/railroader/processors/haml_template_processor.rb +191 -0
- data/lib/railroader/processors/lib/basic_processor.rb +37 -0
- data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
- data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
- data/lib/railroader/processors/lib/find_call.rb +183 -0
- data/lib/railroader/processors/lib/find_return_value.rb +166 -0
- data/lib/railroader/processors/lib/module_helper.rb +111 -0
- data/lib/railroader/processors/lib/processor_helper.rb +88 -0
- data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
- data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
- data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
- data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
- data/lib/railroader/processors/lib/render_helper.rb +181 -0
- data/lib/railroader/processors/lib/render_path.rb +107 -0
- data/lib/railroader/processors/lib/route_helper.rb +68 -0
- data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
- data/lib/railroader/processors/library_processor.rb +74 -0
- data/lib/railroader/processors/model_processor.rb +91 -0
- data/lib/railroader/processors/output_processor.rb +144 -0
- data/lib/railroader/processors/route_processor.rb +17 -0
- data/lib/railroader/processors/slim_template_processor.rb +111 -0
- data/lib/railroader/processors/template_alias_processor.rb +118 -0
- data/lib/railroader/processors/template_processor.rb +85 -0
- data/lib/railroader/report/config/remediation.yml +71 -0
- data/lib/railroader/report/ignore/config.rb +153 -0
- data/lib/railroader/report/ignore/interactive.rb +362 -0
- data/lib/railroader/report/pager.rb +112 -0
- data/lib/railroader/report/renderer.rb +24 -0
- data/lib/railroader/report/report_base.rb +292 -0
- data/lib/railroader/report/report_codeclimate.rb +79 -0
- data/lib/railroader/report/report_csv.rb +55 -0
- data/lib/railroader/report/report_hash.rb +23 -0
- data/lib/railroader/report/report_html.rb +216 -0
- data/lib/railroader/report/report_json.rb +45 -0
- data/lib/railroader/report/report_markdown.rb +107 -0
- data/lib/railroader/report/report_table.rb +117 -0
- data/lib/railroader/report/report_tabs.rb +17 -0
- data/lib/railroader/report/report_text.rb +198 -0
- data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
- data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/error_overview.html.erb +29 -0
- data/lib/railroader/report/templates/header.html.erb +58 -0
- data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
- data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/overview.html.erb +38 -0
- data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
- data/lib/railroader/report/templates/template_overview.html.erb +21 -0
- data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
- data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
- data/lib/railroader/report.rb +88 -0
- data/lib/railroader/rescanner.rb +483 -0
- data/lib/railroader/scanner.rb +321 -0
- data/lib/railroader/tracker/collection.rb +93 -0
- data/lib/railroader/tracker/config.rb +154 -0
- data/lib/railroader/tracker/constants.rb +171 -0
- data/lib/railroader/tracker/controller.rb +161 -0
- data/lib/railroader/tracker/library.rb +17 -0
- data/lib/railroader/tracker/model.rb +90 -0
- data/lib/railroader/tracker/template.rb +33 -0
- data/lib/railroader/tracker.rb +362 -0
- data/lib/railroader/util.rb +503 -0
- data/lib/railroader/version.rb +3 -0
- data/lib/railroader/warning.rb +294 -0
- data/lib/railroader/warning_codes.rb +117 -0
- data/lib/railroader.rb +544 -0
- data/lib/ruby_parser/bm_sexp.rb +626 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
- 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
|