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
|
@@ -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
|