brakeman-lib 3.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES +872 -0
- data/FEATURES +16 -0
- data/README.md +169 -0
- data/WARNING_TYPES +95 -0
- data/bin/brakeman +89 -0
- data/lib/brakeman.rb +495 -0
- data/lib/brakeman/app_tree.rb +161 -0
- data/lib/brakeman/brakeman.rake +17 -0
- data/lib/brakeman/call_index.rb +219 -0
- data/lib/brakeman/checks.rb +191 -0
- data/lib/brakeman/checks/base_check.rb +518 -0
- data/lib/brakeman/checks/check_basic_auth.rb +88 -0
- data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +33 -0
- data/lib/brakeman/checks/check_content_tag.rb +160 -0
- data/lib/brakeman/checks/check_create_with.rb +75 -0
- data/lib/brakeman/checks/check_cross_site_scripting.rb +385 -0
- data/lib/brakeman/checks/check_default_routes.rb +86 -0
- data/lib/brakeman/checks/check_deserialize.rb +57 -0
- data/lib/brakeman/checks/check_detailed_exceptions.rb +55 -0
- data/lib/brakeman/checks/check_digest_dos.rb +38 -0
- data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
- data/lib/brakeman/checks/check_escape_function.rb +21 -0
- data/lib/brakeman/checks/check_evaluation.rb +36 -0
- data/lib/brakeman/checks/check_execute.rb +167 -0
- data/lib/brakeman/checks/check_file_access.rb +63 -0
- data/lib/brakeman/checks/check_file_disclosure.rb +35 -0
- data/lib/brakeman/checks/check_filter_skipping.rb +31 -0
- data/lib/brakeman/checks/check_forgery_setting.rb +74 -0
- data/lib/brakeman/checks/check_header_dos.rb +31 -0
- data/lib/brakeman/checks/check_i18n_xss.rb +48 -0
- data/lib/brakeman/checks/check_jruby_xml.rb +38 -0
- data/lib/brakeman/checks/check_json_encoding.rb +47 -0
- data/lib/brakeman/checks/check_json_parsing.rb +107 -0
- data/lib/brakeman/checks/check_link_to.rb +132 -0
- data/lib/brakeman/checks/check_link_to_href.rb +115 -0
- data/lib/brakeman/checks/check_mail_to.rb +49 -0
- data/lib/brakeman/checks/check_mass_assignment.rb +198 -0
- data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
- data/lib/brakeman/checks/check_model_attr_accessible.rb +55 -0
- data/lib/brakeman/checks/check_model_attributes.rb +119 -0
- data/lib/brakeman/checks/check_model_serialize.rb +67 -0
- data/lib/brakeman/checks/check_nested_attributes.rb +38 -0
- data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/brakeman/checks/check_number_to_currency.rb +74 -0
- data/lib/brakeman/checks/check_quote_table_name.rb +40 -0
- data/lib/brakeman/checks/check_redirect.rb +215 -0
- data/lib/brakeman/checks/check_regex_dos.rb +69 -0
- data/lib/brakeman/checks/check_render.rb +92 -0
- data/lib/brakeman/checks/check_render_dos.rb +37 -0
- data/lib/brakeman/checks/check_render_inline.rb +54 -0
- data/lib/brakeman/checks/check_response_splitting.rb +21 -0
- data/lib/brakeman/checks/check_route_dos.rb +42 -0
- data/lib/brakeman/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/brakeman/checks/check_sanitize_methods.rb +79 -0
- data/lib/brakeman/checks/check_secrets.rb +40 -0
- data/lib/brakeman/checks/check_select_tag.rb +60 -0
- data/lib/brakeman/checks/check_select_vulnerability.rb +60 -0
- data/lib/brakeman/checks/check_send.rb +48 -0
- data/lib/brakeman/checks/check_send_file.rb +19 -0
- data/lib/brakeman/checks/check_session_manipulation.rb +36 -0
- data/lib/brakeman/checks/check_session_settings.rb +170 -0
- data/lib/brakeman/checks/check_simple_format.rb +59 -0
- data/lib/brakeman/checks/check_single_quotes.rb +101 -0
- data/lib/brakeman/checks/check_skip_before_filter.rb +60 -0
- data/lib/brakeman/checks/check_sql.rb +660 -0
- data/lib/brakeman/checks/check_sql_cves.rb +101 -0
- data/lib/brakeman/checks/check_ssl_verify.rb +49 -0
- data/lib/brakeman/checks/check_strip_tags.rb +89 -0
- data/lib/brakeman/checks/check_symbol_dos.rb +64 -0
- data/lib/brakeman/checks/check_symbol_dos_cve.rb +30 -0
- data/lib/brakeman/checks/check_translate_bug.rb +45 -0
- data/lib/brakeman/checks/check_unsafe_reflection.rb +51 -0
- data/lib/brakeman/checks/check_unscoped_find.rb +41 -0
- data/lib/brakeman/checks/check_validation_regex.rb +116 -0
- data/lib/brakeman/checks/check_weak_hash.rb +151 -0
- data/lib/brakeman/checks/check_without_protection.rb +80 -0
- data/lib/brakeman/checks/check_xml_dos.rb +51 -0
- data/lib/brakeman/checks/check_yaml_parsing.rb +121 -0
- data/lib/brakeman/differ.rb +66 -0
- data/lib/brakeman/file_parser.rb +50 -0
- data/lib/brakeman/format/style.css +133 -0
- data/lib/brakeman/options.rb +301 -0
- data/lib/brakeman/parsers/rails2_erubis.rb +6 -0
- data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/brakeman/parsers/rails3_erubis.rb +74 -0
- data/lib/brakeman/parsers/template_parser.rb +89 -0
- data/lib/brakeman/processor.rb +102 -0
- data/lib/brakeman/processors/alias_processor.rb +1013 -0
- data/lib/brakeman/processors/base_processor.rb +277 -0
- data/lib/brakeman/processors/config_processor.rb +14 -0
- data/lib/brakeman/processors/controller_alias_processor.rb +273 -0
- data/lib/brakeman/processors/controller_processor.rb +326 -0
- data/lib/brakeman/processors/erb_template_processor.rb +80 -0
- data/lib/brakeman/processors/erubis_template_processor.rb +104 -0
- data/lib/brakeman/processors/gem_processor.rb +57 -0
- data/lib/brakeman/processors/haml_template_processor.rb +190 -0
- data/lib/brakeman/processors/lib/basic_processor.rb +37 -0
- data/lib/brakeman/processors/lib/find_all_calls.rb +223 -0
- data/lib/brakeman/processors/lib/find_call.rb +183 -0
- data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
- data/lib/brakeman/processors/lib/processor_helper.rb +75 -0
- data/lib/brakeman/processors/lib/rails2_config_processor.rb +145 -0
- data/lib/brakeman/processors/lib/rails2_route_processor.rb +313 -0
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +132 -0
- data/lib/brakeman/processors/lib/rails3_route_processor.rb +308 -0
- data/lib/brakeman/processors/lib/render_helper.rb +181 -0
- data/lib/brakeman/processors/lib/render_path.rb +107 -0
- data/lib/brakeman/processors/lib/route_helper.rb +68 -0
- data/lib/brakeman/processors/lib/safe_call_helper.rb +16 -0
- data/lib/brakeman/processors/library_processor.rb +119 -0
- data/lib/brakeman/processors/model_processor.rb +191 -0
- data/lib/brakeman/processors/output_processor.rb +171 -0
- data/lib/brakeman/processors/route_processor.rb +17 -0
- data/lib/brakeman/processors/slim_template_processor.rb +107 -0
- data/lib/brakeman/processors/template_alias_processor.rb +116 -0
- data/lib/brakeman/processors/template_processor.rb +74 -0
- data/lib/brakeman/report.rb +78 -0
- data/lib/brakeman/report/config/remediation.yml +71 -0
- data/lib/brakeman/report/ignore/config.rb +135 -0
- data/lib/brakeman/report/ignore/interactive.rb +311 -0
- data/lib/brakeman/report/renderer.rb +24 -0
- data/lib/brakeman/report/report_base.rb +286 -0
- data/lib/brakeman/report/report_codeclimate.rb +70 -0
- data/lib/brakeman/report/report_csv.rb +55 -0
- data/lib/brakeman/report/report_hash.rb +23 -0
- data/lib/brakeman/report/report_html.rb +216 -0
- data/lib/brakeman/report/report_json.rb +42 -0
- data/lib/brakeman/report/report_markdown.rb +156 -0
- data/lib/brakeman/report/report_table.rb +107 -0
- data/lib/brakeman/report/report_tabs.rb +17 -0
- data/lib/brakeman/report/templates/controller_overview.html.erb +22 -0
- data/lib/brakeman/report/templates/controller_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/error_overview.html.erb +29 -0
- data/lib/brakeman/report/templates/header.html.erb +58 -0
- data/lib/brakeman/report/templates/ignored_warnings.html.erb +25 -0
- data/lib/brakeman/report/templates/model_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/overview.html.erb +38 -0
- data/lib/brakeman/report/templates/security_warnings.html.erb +23 -0
- data/lib/brakeman/report/templates/template_overview.html.erb +21 -0
- data/lib/brakeman/report/templates/view_warnings.html.erb +34 -0
- data/lib/brakeman/report/templates/warning_overview.html.erb +17 -0
- data/lib/brakeman/rescanner.rb +483 -0
- data/lib/brakeman/scanner.rb +317 -0
- data/lib/brakeman/tracker.rb +347 -0
- data/lib/brakeman/tracker/collection.rb +93 -0
- data/lib/brakeman/tracker/config.rb +101 -0
- data/lib/brakeman/tracker/constants.rb +101 -0
- data/lib/brakeman/tracker/controller.rb +161 -0
- data/lib/brakeman/tracker/library.rb +17 -0
- data/lib/brakeman/tracker/model.rb +90 -0
- data/lib/brakeman/tracker/template.rb +33 -0
- data/lib/brakeman/util.rb +481 -0
- data/lib/brakeman/version.rb +3 -0
- data/lib/brakeman/warning.rb +255 -0
- data/lib/brakeman/warning_codes.rb +111 -0
- data/lib/ruby_parser/bm_sexp.rb +610 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
- metadata +362 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
Brakeman.load_brakeman_dependency 'erubis'
|
2
|
+
|
3
|
+
#This is from the rails_xss plugin for Rails 2
|
4
|
+
class Brakeman::Rails2XSSPluginErubis < ::Erubis::Eruby
|
5
|
+
def add_preamble(src)
|
6
|
+
#src << "@output_buffer = ActiveSupport::SafeBuffer.new;"
|
7
|
+
end
|
8
|
+
|
9
|
+
#This is different from rails_xss - fixes some line number issues
|
10
|
+
def add_text(src, text)
|
11
|
+
if text == "\n"
|
12
|
+
src << "\n"
|
13
|
+
elsif text.include? "\n"
|
14
|
+
lines = text.split("\n")
|
15
|
+
if text.match(/\n\z/)
|
16
|
+
lines.each do |line|
|
17
|
+
src << "@output_buffer.safe_concat('" << escape_text(line) << "');\n"
|
18
|
+
end
|
19
|
+
else
|
20
|
+
lines[0..-2].each do |line|
|
21
|
+
src << "@output_buffer.safe_concat('" << escape_text(line) << "');\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
src << "@output_buffer.safe_concat('" << escape_text(lines.last) << "');"
|
25
|
+
end
|
26
|
+
else
|
27
|
+
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
32
|
+
|
33
|
+
def add_expr_literal(src, code)
|
34
|
+
if code =~ BLOCK_EXPR
|
35
|
+
src << "@output_buffer.safe_concat((" << $1 << ").to_s);"
|
36
|
+
else
|
37
|
+
src << '@output_buffer << ((' << code << ').to_s);'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_expr_escaped(src, code)
|
42
|
+
src << '@output_buffer << ' << escaped_expr(code) << ';'
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_postamble(src)
|
46
|
+
#src << '@output_buffer.to_s'
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
Brakeman.load_brakeman_dependency 'erubis'
|
2
|
+
|
3
|
+
# This is from Rails 5 version of the Erubis handler
|
4
|
+
# https://github.com/rails/rails/blob/ec608107801b1e505db03ba76bae4a326a5804ca/actionview/lib/action_view/template/handlers/erb.rb#L7-L73
|
5
|
+
class Brakeman::Rails3Erubis < ::Erubis::Eruby
|
6
|
+
|
7
|
+
def add_preamble(src)
|
8
|
+
@newline_pending = 0
|
9
|
+
src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_text(src, text)
|
13
|
+
return if text.empty?
|
14
|
+
|
15
|
+
if text == "\n"
|
16
|
+
@newline_pending += 1
|
17
|
+
else
|
18
|
+
src << "@output_buffer.safe_append='"
|
19
|
+
src << "\n" * @newline_pending if @newline_pending > 0
|
20
|
+
src << escape_text(text)
|
21
|
+
src << "'.freeze;"
|
22
|
+
|
23
|
+
@newline_pending = 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Erubis toggles <%= and <%== behavior when escaping is enabled.
|
28
|
+
# We override to always treat <%== as escaped.
|
29
|
+
def add_expr(src, code, indicator)
|
30
|
+
case indicator
|
31
|
+
when '=='
|
32
|
+
add_expr_escaped(src, code)
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
39
|
+
|
40
|
+
def add_expr_literal(src, code)
|
41
|
+
flush_newline_if_pending(src)
|
42
|
+
if code =~ BLOCK_EXPR
|
43
|
+
src << '@output_buffer.append= ' << code
|
44
|
+
else
|
45
|
+
src << '@output_buffer.append=(' << code << ');'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_expr_escaped(src, code)
|
50
|
+
flush_newline_if_pending(src)
|
51
|
+
if code =~ BLOCK_EXPR
|
52
|
+
src << "@output_buffer.safe_expr_append= " << code
|
53
|
+
else
|
54
|
+
src << "@output_buffer.safe_expr_append=(" << code << ");"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_stmt(src, code)
|
59
|
+
flush_newline_if_pending(src)
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_postamble(src)
|
64
|
+
flush_newline_if_pending(src)
|
65
|
+
src << '@output_buffer.to_s'
|
66
|
+
end
|
67
|
+
|
68
|
+
def flush_newline_if_pending(src)
|
69
|
+
if @newline_pending > 0
|
70
|
+
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
|
71
|
+
@newline_pending = 0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Brakeman
|
2
|
+
class TemplateParser
|
3
|
+
include Brakeman::Util
|
4
|
+
attr_reader :tracker
|
5
|
+
KNOWN_TEMPLATE_EXTENSIONS = /.*\.(erb|haml|rhtml|slim)$/
|
6
|
+
|
7
|
+
TemplateFile = Struct.new(:path, :ast, :name, :type)
|
8
|
+
|
9
|
+
def initialize tracker, file_parser
|
10
|
+
@tracker = tracker
|
11
|
+
@file_parser = file_parser
|
12
|
+
@file_parser.file_list[:templates] ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_template path, text
|
16
|
+
type = path.match(KNOWN_TEMPLATE_EXTENSIONS)[1].to_sym
|
17
|
+
type = :erb if type == :rhtml
|
18
|
+
name = template_path_to_name path
|
19
|
+
Brakeman.debug "Parsing #{path}"
|
20
|
+
|
21
|
+
begin
|
22
|
+
src = case type
|
23
|
+
when :erb
|
24
|
+
type = :erubis if erubis?
|
25
|
+
parse_erb text
|
26
|
+
when :haml
|
27
|
+
parse_haml text
|
28
|
+
when :slim
|
29
|
+
parse_slim text
|
30
|
+
else
|
31
|
+
tracker.error "Unknown template type in #{path}"
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
if src and ast = @file_parser.parse_ruby(src, path)
|
36
|
+
@file_parser.file_list[:templates] << TemplateFile.new(path, ast, name, type)
|
37
|
+
end
|
38
|
+
rescue Racc::ParseError => e
|
39
|
+
tracker.error e, "could not parse #{path}"
|
40
|
+
rescue Haml::Error => e
|
41
|
+
tracker.error e, ["While compiling HAML in #{path}"] << e.backtrace
|
42
|
+
rescue StandardError, LoadError => e
|
43
|
+
tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
|
44
|
+
end
|
45
|
+
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_erb text
|
50
|
+
if tracker.config.escape_html?
|
51
|
+
if tracker.options[:rails3]
|
52
|
+
require 'brakeman/parsers/rails3_erubis'
|
53
|
+
Brakeman::Rails3Erubis.new(text).src
|
54
|
+
else
|
55
|
+
require 'brakeman/parsers/rails2_xss_plugin_erubis'
|
56
|
+
Brakeman::Rails2XSSPluginErubis.new(text).src
|
57
|
+
end
|
58
|
+
elsif tracker.config.erubis?
|
59
|
+
require 'brakeman/parsers/rails2_erubis'
|
60
|
+
Brakeman::ScannerErubis.new(text).src
|
61
|
+
else
|
62
|
+
require 'erb'
|
63
|
+
src = ERB.new(text, nil, "-").src
|
64
|
+
src.sub!(/^#.*\n/, '') if Brakeman::Scanner::RUBY_1_9
|
65
|
+
src
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def erubis?
|
70
|
+
tracker.config.escape_html? or
|
71
|
+
tracker.config.erubis?
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_haml text
|
75
|
+
Brakeman.load_brakeman_dependency 'haml'
|
76
|
+
Brakeman.load_brakeman_dependency 'sass'
|
77
|
+
|
78
|
+
Haml::Engine.new(text,
|
79
|
+
:escape_html => tracker.config.escape_html?).precompiled.gsub(/([^\\])\\n/, '\1')
|
80
|
+
end
|
81
|
+
|
82
|
+
def parse_slim text
|
83
|
+
Brakeman.load_brakeman_dependency 'slim'
|
84
|
+
|
85
|
+
Slim::Template.new(:disable_capture => true,
|
86
|
+
:generator => Temple::Generators::RailsOutputBuffer) { text }.precompiled_template
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
#Load all files in processors/
|
2
|
+
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/processors/*.rb").each { |f| require f.match(/brakeman\/processors.*/)[0] }
|
3
|
+
require 'brakeman/tracker'
|
4
|
+
require 'set'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
module Brakeman
|
8
|
+
#Makes calls to the appropriate processor.
|
9
|
+
#
|
10
|
+
#The ControllerProcessor, TemplateProcessor, and ModelProcessor will
|
11
|
+
#update the Tracker with information about what is parsed.
|
12
|
+
class Processor
|
13
|
+
include Util
|
14
|
+
|
15
|
+
def initialize(app_tree, options)
|
16
|
+
@app_tree = app_tree
|
17
|
+
@tracker = Tracker.new(@app_tree, self, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def tracked_events
|
21
|
+
@tracker
|
22
|
+
end
|
23
|
+
|
24
|
+
#Process configuration file source
|
25
|
+
def process_config src, file_name
|
26
|
+
ConfigProcessor.new(@tracker).process_config src, file_name
|
27
|
+
end
|
28
|
+
|
29
|
+
#Process Gemfile
|
30
|
+
def process_gems gem_files
|
31
|
+
GemProcessor.new(@tracker).process_gems gem_files
|
32
|
+
end
|
33
|
+
|
34
|
+
#Process route file source
|
35
|
+
def process_routes src
|
36
|
+
RoutesProcessor.new(@tracker).process_routes src
|
37
|
+
end
|
38
|
+
|
39
|
+
#Process controller source. +file_name+ is used for reporting
|
40
|
+
def process_controller src, file_name
|
41
|
+
if contains_class? src
|
42
|
+
ControllerProcessor.new(@app_tree, @tracker).process_controller src, file_name
|
43
|
+
else
|
44
|
+
LibraryProcessor.new(@tracker).process_library src, file_name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#Process variable aliasing in controller source and save it in the
|
49
|
+
#tracker.
|
50
|
+
def process_controller_alias name, src, only_method = nil, file = nil
|
51
|
+
ControllerAliasProcessor.new(@app_tree, @tracker, only_method).process_controller name, src, file
|
52
|
+
end
|
53
|
+
|
54
|
+
#Process a model source
|
55
|
+
def process_model src, file_name
|
56
|
+
result = ModelProcessor.new(@tracker).process_model src, file_name
|
57
|
+
AliasProcessor.new(@tracker).process_all result if result
|
58
|
+
end
|
59
|
+
|
60
|
+
#Process either an ERB or HAML template
|
61
|
+
def process_template name, src, type, called_from = nil, file_name = nil
|
62
|
+
case type
|
63
|
+
when :erb
|
64
|
+
result = ErbTemplateProcessor.new(@tracker, name, called_from, file_name).process src
|
65
|
+
when :haml
|
66
|
+
result = HamlTemplateProcessor.new(@tracker, name, called_from, file_name).process src
|
67
|
+
when :erubis
|
68
|
+
result = ErubisTemplateProcessor.new(@tracker, name, called_from, file_name).process src
|
69
|
+
when :slim
|
70
|
+
result = SlimTemplateProcessor.new(@tracker, name, called_from, file_name).process src
|
71
|
+
else
|
72
|
+
abort "Unknown template type: #{type} (#{name})"
|
73
|
+
end
|
74
|
+
|
75
|
+
#Each template which is rendered is stored separately
|
76
|
+
#with a new name.
|
77
|
+
if called_from
|
78
|
+
name = ("#{name}.#{called_from}").to_sym
|
79
|
+
end
|
80
|
+
|
81
|
+
@tracker.templates[name].src = result
|
82
|
+
@tracker.templates[name].type = type
|
83
|
+
end
|
84
|
+
|
85
|
+
#Process any calls to render() within a template
|
86
|
+
def process_template_alias template
|
87
|
+
TemplateAliasProcessor.new(@tracker, template).process_safely template.src
|
88
|
+
end
|
89
|
+
|
90
|
+
#Process source for initializing files
|
91
|
+
def process_initializer file_name, src
|
92
|
+
res = BaseProcessor.new(@tracker).process_file src, file_name
|
93
|
+
res = AliasProcessor.new(@tracker).process_safely res, nil, file_name
|
94
|
+
@tracker.initializers[Pathname.new(file_name).basename.to_s] = res
|
95
|
+
end
|
96
|
+
|
97
|
+
#Process source for a library file
|
98
|
+
def process_lib src, file_name
|
99
|
+
LibraryProcessor.new(@tracker).process_library src, file_name
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,1013 @@
|
|
1
|
+
require 'brakeman/util'
|
2
|
+
require 'ruby_parser/bm_sexp_processor'
|
3
|
+
require 'brakeman/processors/lib/processor_helper'
|
4
|
+
require 'brakeman/processors/lib/safe_call_helper'
|
5
|
+
|
6
|
+
#Returns an s-expression with aliases replaced with their value.
|
7
|
+
#This does not preserve semantics (due to side effects, etc.), but it makes
|
8
|
+
#processing easier when searching for various things.
|
9
|
+
class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
10
|
+
include Brakeman::ProcessorHelper
|
11
|
+
include Brakeman::SafeCallHelper
|
12
|
+
include Brakeman::Util
|
13
|
+
|
14
|
+
attr_reader :result, :tracker
|
15
|
+
|
16
|
+
#Returns a new AliasProcessor with an empty environment.
|
17
|
+
#
|
18
|
+
#The recommended usage is:
|
19
|
+
#
|
20
|
+
# AliasProcessor.new.process_safely src
|
21
|
+
def initialize tracker = nil, file_name = nil
|
22
|
+
super()
|
23
|
+
@env = SexpProcessor::Environment.new
|
24
|
+
@inside_if = false
|
25
|
+
@ignore_ifs = nil
|
26
|
+
@exp_context = []
|
27
|
+
@current_module = nil
|
28
|
+
@tracker = tracker #set in subclass as necessary
|
29
|
+
@helper_method_cache = {}
|
30
|
+
@helper_method_info = Hash.new({})
|
31
|
+
@or_depth_limit = (tracker && tracker.options[:branch_limit]) || 5 #arbitrary default
|
32
|
+
@meth_env = nil
|
33
|
+
@file_name = file_name
|
34
|
+
set_env_defaults
|
35
|
+
end
|
36
|
+
|
37
|
+
#This method processes the given Sexp, but copies it first so
|
38
|
+
#the original argument will not be modified.
|
39
|
+
#
|
40
|
+
#_set_env_ should be an instance of SexpProcessor::Environment. If provided,
|
41
|
+
#it will be used as the starting environment.
|
42
|
+
#
|
43
|
+
#This method returns a new Sexp with variables replaced with their values,
|
44
|
+
#where possible.
|
45
|
+
def process_safely src, set_env = nil, file_name = nil
|
46
|
+
@file_name = file_name
|
47
|
+
@env = set_env || SexpProcessor::Environment.new
|
48
|
+
@result = src.deep_clone
|
49
|
+
process @result
|
50
|
+
@result
|
51
|
+
end
|
52
|
+
|
53
|
+
#Process a Sexp. If the Sexp has a value associated with it in the
|
54
|
+
#environment, that value will be returned.
|
55
|
+
def process_default exp
|
56
|
+
@exp_context.push exp
|
57
|
+
|
58
|
+
begin
|
59
|
+
exp.map! do |e|
|
60
|
+
if sexp? e and not e.empty?
|
61
|
+
process e
|
62
|
+
else
|
63
|
+
e
|
64
|
+
end
|
65
|
+
end
|
66
|
+
rescue => err
|
67
|
+
@tracker.error err if @tracker
|
68
|
+
end
|
69
|
+
|
70
|
+
result = replace(exp)
|
71
|
+
|
72
|
+
@exp_context.pop
|
73
|
+
|
74
|
+
result
|
75
|
+
end
|
76
|
+
|
77
|
+
def replace exp, int = 0
|
78
|
+
return exp if int > 3
|
79
|
+
|
80
|
+
|
81
|
+
if replacement = env[exp] and not duplicate? replacement
|
82
|
+
replace(replacement.deep_clone(exp.line), int + 1)
|
83
|
+
elsif tracker and replacement = tracker.constant_lookup(exp) and not duplicate? replacement
|
84
|
+
replace(replacement.deep_clone(exp.line), int + 1)
|
85
|
+
else
|
86
|
+
exp
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
ARRAY_CONST = s(:const, :Array)
|
91
|
+
HASH_CONST = s(:const, :Hash)
|
92
|
+
RAILS_TEST = s(:call, s(:call, s(:const, :Rails), :env), :test?)
|
93
|
+
|
94
|
+
#Process a method call.
|
95
|
+
def process_call exp
|
96
|
+
return exp if process_call_defn? exp
|
97
|
+
target_var = exp.target
|
98
|
+
target_var &&= target_var.deep_clone
|
99
|
+
if exp.node_type == :safe_call
|
100
|
+
exp.node_type = :call
|
101
|
+
end
|
102
|
+
exp = process_default exp
|
103
|
+
|
104
|
+
#In case it is replaced with something else
|
105
|
+
unless call? exp
|
106
|
+
return exp
|
107
|
+
end
|
108
|
+
|
109
|
+
target = exp.target
|
110
|
+
method = exp.method
|
111
|
+
first_arg = exp.first_arg
|
112
|
+
|
113
|
+
if method == :send or method == :try
|
114
|
+
collapse_send_call exp, first_arg
|
115
|
+
end
|
116
|
+
|
117
|
+
if node_type? target, :or and [:+, :-, :*, :/].include? method
|
118
|
+
res = process_or_simple_operation(exp)
|
119
|
+
return res if res
|
120
|
+
elsif target == ARRAY_CONST and method == :new
|
121
|
+
return Sexp.new(:array, *exp.args)
|
122
|
+
elsif target == HASH_CONST and method == :new and first_arg.nil? and !node_type?(@exp_context.last, :iter)
|
123
|
+
return Sexp.new(:hash)
|
124
|
+
elsif exp == RAILS_TEST
|
125
|
+
return Sexp.new(:false)
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
#See if it is possible to simplify some basic cases
|
130
|
+
#of addition/concatenation.
|
131
|
+
case method
|
132
|
+
when :+
|
133
|
+
if array? target and array? first_arg
|
134
|
+
joined = join_arrays target, first_arg
|
135
|
+
joined.line(exp.line)
|
136
|
+
exp = joined
|
137
|
+
elsif string? first_arg
|
138
|
+
if string? target # "blah" + "blah"
|
139
|
+
joined = join_strings target, first_arg
|
140
|
+
joined.line(exp.line)
|
141
|
+
exp = joined
|
142
|
+
elsif call? target and target.method == :+ and string? target.first_arg
|
143
|
+
joined = join_strings target.first_arg, first_arg
|
144
|
+
joined.line(exp.line)
|
145
|
+
target.first_arg = joined
|
146
|
+
exp = target
|
147
|
+
end
|
148
|
+
elsif number? first_arg
|
149
|
+
if number? target
|
150
|
+
exp = Sexp.new(:lit, target.value + first_arg.value)
|
151
|
+
elsif call? target and target.method == :+ and number? target.first_arg
|
152
|
+
target.first_arg = Sexp.new(:lit, target.first_arg.value + first_arg.value)
|
153
|
+
exp = target
|
154
|
+
end
|
155
|
+
end
|
156
|
+
when :-
|
157
|
+
if number? target and number? first_arg
|
158
|
+
exp = Sexp.new(:lit, target.value - first_arg.value)
|
159
|
+
end
|
160
|
+
when :*
|
161
|
+
if number? target and number? first_arg
|
162
|
+
exp = Sexp.new(:lit, target.value * first_arg.value)
|
163
|
+
end
|
164
|
+
when :/
|
165
|
+
if number? target and number? first_arg
|
166
|
+
if first_arg.value == 0 and not target.value.is_a? Float
|
167
|
+
if @tracker
|
168
|
+
location = [@current_class, @current_method, "line #{first_arg.line}"].compact.join(' ')
|
169
|
+
require 'brakeman/processors/output_processor'
|
170
|
+
code = Brakeman::OutputProcessor.new.format(exp)
|
171
|
+
@tracker.error Exception.new("Potential divide by zero: #{code} (#{location})")
|
172
|
+
end
|
173
|
+
else
|
174
|
+
exp = Sexp.new(:lit, target.value / first_arg.value)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
when :[]
|
178
|
+
if array? target
|
179
|
+
temp_exp = process_array_access target, exp.args
|
180
|
+
exp = temp_exp if temp_exp
|
181
|
+
elsif hash? target
|
182
|
+
temp_exp = process_hash_access target, first_arg
|
183
|
+
exp = temp_exp if temp_exp
|
184
|
+
end
|
185
|
+
when :merge!, :update
|
186
|
+
if hash? target and hash? first_arg
|
187
|
+
target = process_hash_merge! target, first_arg
|
188
|
+
env[target_var] = target
|
189
|
+
return target
|
190
|
+
end
|
191
|
+
when :merge
|
192
|
+
if hash? target and hash? first_arg
|
193
|
+
return process_hash_merge(target, first_arg)
|
194
|
+
end
|
195
|
+
when :<<
|
196
|
+
if string? target and string? first_arg
|
197
|
+
target.value << first_arg.value
|
198
|
+
env[target_var] = target
|
199
|
+
return target
|
200
|
+
elsif string? target and string_interp? first_arg
|
201
|
+
exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg[2..-1])
|
202
|
+
env[target_var] = exp
|
203
|
+
elsif string? first_arg and string_interp? target
|
204
|
+
if string? target.last
|
205
|
+
target.last.value << first_arg.value
|
206
|
+
elsif target.last.is_a? String
|
207
|
+
target.last << first_arg.value
|
208
|
+
else
|
209
|
+
target << first_arg
|
210
|
+
end
|
211
|
+
env[target_var] = target
|
212
|
+
return first_arg
|
213
|
+
elsif array? target
|
214
|
+
target << first_arg
|
215
|
+
env[target_var] = target
|
216
|
+
return target
|
217
|
+
else
|
218
|
+
target = find_push_target(target_var)
|
219
|
+
env[target] = exp unless target.nil? # Happens in TemplateAliasProcessor
|
220
|
+
end
|
221
|
+
when :first
|
222
|
+
if array? target and first_arg.nil? and sexp? target[1]
|
223
|
+
exp = target[1]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
exp
|
228
|
+
end
|
229
|
+
|
230
|
+
def process_iter exp
|
231
|
+
@exp_context.push exp
|
232
|
+
exp[1] = process exp.block_call
|
233
|
+
if array_detect_all_literals? exp[1]
|
234
|
+
return exp.block_call.target[1]
|
235
|
+
end
|
236
|
+
|
237
|
+
@exp_context.pop
|
238
|
+
|
239
|
+
env.scope do
|
240
|
+
exp.block_args.each do |e|
|
241
|
+
#Force block arg(s) to be local
|
242
|
+
if node_type? e, :lasgn
|
243
|
+
env.current[Sexp.new(:lvar, e.lhs)] = Sexp.new(:lvar, e.lhs)
|
244
|
+
elsif node_type? e, :kwarg
|
245
|
+
env.current[Sexp.new(:lvar, e[1])] = e[2]
|
246
|
+
elsif node_type? e, :masgn, :shadow
|
247
|
+
e[1..-1].each do |var|
|
248
|
+
local = Sexp.new(:lvar, var)
|
249
|
+
env.current[local] = local
|
250
|
+
end
|
251
|
+
elsif e.is_a? Symbol
|
252
|
+
local = Sexp.new(:lvar, e)
|
253
|
+
env.current[local] = local
|
254
|
+
else
|
255
|
+
raise "Unexpected value in block args: #{e.inspect}"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
block = exp.block
|
260
|
+
|
261
|
+
if block? block
|
262
|
+
process_all! block
|
263
|
+
else
|
264
|
+
exp[3] = process block
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
exp
|
269
|
+
end
|
270
|
+
|
271
|
+
#Process a new scope.
|
272
|
+
def process_scope exp
|
273
|
+
env.scope do
|
274
|
+
process exp.block
|
275
|
+
end
|
276
|
+
exp
|
277
|
+
end
|
278
|
+
|
279
|
+
#Start new scope for block.
|
280
|
+
def process_block exp
|
281
|
+
env.scope do
|
282
|
+
process_default exp
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
#Process a method definition.
|
287
|
+
def process_defn exp
|
288
|
+
meth_env do
|
289
|
+
exp.body = process_all! exp.body
|
290
|
+
end
|
291
|
+
exp
|
292
|
+
end
|
293
|
+
|
294
|
+
def meth_env
|
295
|
+
begin
|
296
|
+
env.scope do
|
297
|
+
set_env_defaults
|
298
|
+
@meth_env = env.current
|
299
|
+
yield
|
300
|
+
end
|
301
|
+
ensure
|
302
|
+
@meth_env = nil
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
#Process a method definition on self.
|
307
|
+
def process_defs exp
|
308
|
+
env.scope do
|
309
|
+
set_env_defaults
|
310
|
+
exp.body = process_all! exp.body
|
311
|
+
end
|
312
|
+
exp
|
313
|
+
end
|
314
|
+
|
315
|
+
# Handles x = y = z = 1
|
316
|
+
def get_rhs exp
|
317
|
+
if node_type? exp, :lasgn, :iasgn, :gasgn, :attrasgn, :safe_attrasgn, :cvdecl, :cdecl
|
318
|
+
get_rhs(exp.rhs)
|
319
|
+
else
|
320
|
+
exp
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
#Local assignment
|
325
|
+
# x = 1
|
326
|
+
def process_lasgn exp
|
327
|
+
self_assign = self_assign?(exp.lhs, exp.rhs)
|
328
|
+
exp.rhs = process exp.rhs if sexp? exp.rhs
|
329
|
+
return exp if exp.rhs.nil?
|
330
|
+
|
331
|
+
local = Sexp.new(:lvar, exp.lhs).line(exp.line || -2)
|
332
|
+
|
333
|
+
if self_assign
|
334
|
+
# Skip branching
|
335
|
+
env[local] = get_rhs(exp)
|
336
|
+
else
|
337
|
+
set_value local, get_rhs(exp)
|
338
|
+
end
|
339
|
+
|
340
|
+
exp
|
341
|
+
end
|
342
|
+
|
343
|
+
#Instance variable assignment
|
344
|
+
# @x = 1
|
345
|
+
def process_iasgn exp
|
346
|
+
self_assign = self_assign?(exp.lhs, exp.rhs)
|
347
|
+
exp.rhs = process exp.rhs
|
348
|
+
ivar = Sexp.new(:ivar, exp.lhs).line(exp.line)
|
349
|
+
|
350
|
+
if self_assign
|
351
|
+
if env[ivar].nil? and @meth_env
|
352
|
+
@meth_env[ivar] = get_rhs(exp)
|
353
|
+
else
|
354
|
+
env[ivar] = get_rhs(exp)
|
355
|
+
end
|
356
|
+
else
|
357
|
+
set_value ivar, get_rhs(exp)
|
358
|
+
end
|
359
|
+
|
360
|
+
exp
|
361
|
+
end
|
362
|
+
|
363
|
+
#Global assignment
|
364
|
+
# $x = 1
|
365
|
+
def process_gasgn exp
|
366
|
+
match = Sexp.new(:gvar, exp.lhs)
|
367
|
+
exp.rhs = process(exp.rhs)
|
368
|
+
value = get_rhs(exp)
|
369
|
+
value.line = exp.line
|
370
|
+
|
371
|
+
set_value match, value
|
372
|
+
|
373
|
+
exp
|
374
|
+
end
|
375
|
+
|
376
|
+
#Class variable assignment
|
377
|
+
# @@x = 1
|
378
|
+
def process_cvdecl exp
|
379
|
+
match = Sexp.new(:cvar, exp.lhs)
|
380
|
+
exp.rhs = process(exp.rhs)
|
381
|
+
value = get_rhs(exp)
|
382
|
+
|
383
|
+
set_value match, value
|
384
|
+
|
385
|
+
exp
|
386
|
+
end
|
387
|
+
|
388
|
+
#'Attribute' assignment
|
389
|
+
# x.y = 1
|
390
|
+
#or
|
391
|
+
# x[:y] = 1
|
392
|
+
def process_attrasgn exp
|
393
|
+
tar_variable = exp.target
|
394
|
+
target = exp.target = process(exp.target)
|
395
|
+
method = exp.method
|
396
|
+
index_arg = exp.first_arg
|
397
|
+
value_arg = exp.second_arg
|
398
|
+
|
399
|
+
if method == :[]=
|
400
|
+
index = exp.first_arg = process(index_arg)
|
401
|
+
value = exp.second_arg = process(value_arg)
|
402
|
+
match = Sexp.new(:call, target, :[], index)
|
403
|
+
|
404
|
+
set_value match, value
|
405
|
+
|
406
|
+
if hash? target
|
407
|
+
env[tar_variable] = hash_insert target.deep_clone, index, value
|
408
|
+
end
|
409
|
+
elsif method.to_s[-1,1] == "="
|
410
|
+
exp.first_arg = process(index_arg)
|
411
|
+
value = get_rhs(exp)
|
412
|
+
#This is what we'll replace with the value
|
413
|
+
match = Sexp.new(:call, target, method.to_s[0..-2].to_sym)
|
414
|
+
|
415
|
+
set_value match, value
|
416
|
+
else
|
417
|
+
raise "Unrecognized assignment: #{exp}"
|
418
|
+
end
|
419
|
+
exp
|
420
|
+
end
|
421
|
+
|
422
|
+
# Multiple/parallel assignment:
|
423
|
+
#
|
424
|
+
# x, y = z, w
|
425
|
+
def process_masgn exp
|
426
|
+
unless array? exp[1] and array? exp[2] and exp[1].length == exp[2].length
|
427
|
+
return process_default(exp)
|
428
|
+
end
|
429
|
+
|
430
|
+
vars = exp[1].dup
|
431
|
+
vals = exp[2].dup
|
432
|
+
|
433
|
+
vars.shift
|
434
|
+
vals.shift
|
435
|
+
|
436
|
+
# Call each assignment as if it is normal
|
437
|
+
vars.each_with_index do |var, i|
|
438
|
+
val = vals[i]
|
439
|
+
if val
|
440
|
+
assign = var.dup
|
441
|
+
assign.rhs = val
|
442
|
+
process assign
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
exp
|
447
|
+
end
|
448
|
+
|
449
|
+
#Merge values into hash when processing
|
450
|
+
#
|
451
|
+
# h.merge! :something => "value"
|
452
|
+
def process_hash_merge! hash, args
|
453
|
+
hash = hash.deep_clone
|
454
|
+
hash_iterate args do |key, replacement|
|
455
|
+
hash_insert hash, key, replacement
|
456
|
+
match = Sexp.new(:call, hash, :[], key)
|
457
|
+
env[match] = replacement
|
458
|
+
end
|
459
|
+
hash
|
460
|
+
end
|
461
|
+
|
462
|
+
#Return a new hash Sexp with the given values merged into it.
|
463
|
+
#
|
464
|
+
#+args+ should be a hash Sexp as well.
|
465
|
+
def process_hash_merge hash, args
|
466
|
+
hash = hash.deep_clone
|
467
|
+
hash_iterate args do |key, replacement|
|
468
|
+
hash_insert hash, key, replacement
|
469
|
+
end
|
470
|
+
hash
|
471
|
+
end
|
472
|
+
|
473
|
+
#Assignments like this
|
474
|
+
# x[:y] ||= 1
|
475
|
+
def process_op_asgn1 exp
|
476
|
+
return process_default(exp) if exp[3] != :"||"
|
477
|
+
|
478
|
+
target = exp[1] = process(exp[1])
|
479
|
+
index = exp[2][1] = process(exp[2][1])
|
480
|
+
value = exp[4] = process(exp[4])
|
481
|
+
match = Sexp.new(:call, target, :[], index)
|
482
|
+
|
483
|
+
unless env[match]
|
484
|
+
if request_value? target
|
485
|
+
env[match] = match.combine(value)
|
486
|
+
else
|
487
|
+
env[match] = value
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
exp
|
492
|
+
end
|
493
|
+
|
494
|
+
#Assignments like this
|
495
|
+
# x.y ||= 1
|
496
|
+
def process_op_asgn2 exp
|
497
|
+
return process_default(exp) if exp[3] != :"||"
|
498
|
+
|
499
|
+
target = exp[1] = process(exp[1])
|
500
|
+
value = exp[4] = process(exp[4])
|
501
|
+
method = exp[2]
|
502
|
+
|
503
|
+
match = Sexp.new(:call, target, method.to_s[0..-2].to_sym)
|
504
|
+
|
505
|
+
unless env[match]
|
506
|
+
env[match] = value
|
507
|
+
end
|
508
|
+
|
509
|
+
exp
|
510
|
+
end
|
511
|
+
|
512
|
+
#This is the right hand side value of a multiple assignment,
|
513
|
+
#like `x = y, z`
|
514
|
+
def process_svalue exp
|
515
|
+
exp.value
|
516
|
+
end
|
517
|
+
|
518
|
+
#Constant assignments like
|
519
|
+
# BIG_CONSTANT = 234810983
|
520
|
+
def process_cdecl exp
|
521
|
+
if sexp? exp.rhs
|
522
|
+
exp.rhs = process exp.rhs
|
523
|
+
end
|
524
|
+
|
525
|
+
file = case
|
526
|
+
when @file_name
|
527
|
+
@file_name
|
528
|
+
when @current_class.is_a?(Brakeman::Collection)
|
529
|
+
@current_class.file
|
530
|
+
when @current_module.is_a?(Brakeman::Collection)
|
531
|
+
@current_module.file
|
532
|
+
else
|
533
|
+
nil
|
534
|
+
end
|
535
|
+
|
536
|
+
@tracker.add_constant exp.lhs, exp.rhs, :file => file if @tracker
|
537
|
+
|
538
|
+
if exp.lhs.is_a? Symbol
|
539
|
+
match = Sexp.new(:const, exp.lhs)
|
540
|
+
else
|
541
|
+
match = exp.lhs
|
542
|
+
end
|
543
|
+
|
544
|
+
env[match] = get_rhs(exp)
|
545
|
+
|
546
|
+
exp
|
547
|
+
end
|
548
|
+
|
549
|
+
# Check if exp is a call to Array#include? on an array literal
|
550
|
+
# that contains all literal values. For example:
|
551
|
+
#
|
552
|
+
# [1, 2, "a"].include? x
|
553
|
+
#
|
554
|
+
def array_include_all_literals? exp
|
555
|
+
call? exp and
|
556
|
+
exp.method == :include? and
|
557
|
+
node_type? exp.target, :array and
|
558
|
+
exp.target.length > 1 and
|
559
|
+
exp.target.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
|
560
|
+
end
|
561
|
+
|
562
|
+
def array_detect_all_literals? exp
|
563
|
+
call? exp and
|
564
|
+
[:detect, :find].include? exp.method and
|
565
|
+
node_type? exp.target, :array and
|
566
|
+
exp.target.length > 1 and
|
567
|
+
exp.first_arg.nil? and
|
568
|
+
exp.target.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
|
569
|
+
end
|
570
|
+
|
571
|
+
#Sets @inside_if = true
|
572
|
+
def process_if exp
|
573
|
+
if @ignore_ifs.nil?
|
574
|
+
@ignore_ifs = @tracker && @tracker.options[:ignore_ifs]
|
575
|
+
end
|
576
|
+
|
577
|
+
condition = exp.condition = process exp.condition
|
578
|
+
|
579
|
+
#Check if a branch is obviously going to be taken
|
580
|
+
if true? condition
|
581
|
+
no_branch = true
|
582
|
+
exps = [exp.then_clause, nil]
|
583
|
+
elsif false? condition
|
584
|
+
no_branch = true
|
585
|
+
exps = [nil, exp.else_clause]
|
586
|
+
else
|
587
|
+
no_branch = false
|
588
|
+
exps = [exp.then_clause, exp.else_clause]
|
589
|
+
end
|
590
|
+
|
591
|
+
if @ignore_ifs or no_branch
|
592
|
+
exps.each_with_index do |branch, i|
|
593
|
+
exp[2 + i] = process_if_branch branch
|
594
|
+
end
|
595
|
+
else
|
596
|
+
was_inside = @inside_if
|
597
|
+
@inside_if = true
|
598
|
+
|
599
|
+
branch_scopes = []
|
600
|
+
exps.each_with_index do |branch, i|
|
601
|
+
scope do
|
602
|
+
@branch_env = env.current
|
603
|
+
branch_index = 2 + i # s(:if, condition, then_branch, else_branch)
|
604
|
+
if i == 0 and array_include_all_literals? condition
|
605
|
+
# If the condition is ["a", "b"].include? x
|
606
|
+
# set x to "a" inside the true branch
|
607
|
+
var = condition.first_arg
|
608
|
+
previous_value = env.current[var]
|
609
|
+
env.current[var] = condition.target[1]
|
610
|
+
exp[branch_index] = process_if_branch branch
|
611
|
+
env.current[var] = previous_value
|
612
|
+
else
|
613
|
+
exp[branch_index] = process_if_branch branch
|
614
|
+
end
|
615
|
+
branch_scopes << env.current
|
616
|
+
@branch_env = nil
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
@inside_if = was_inside
|
621
|
+
|
622
|
+
branch_scopes.each do |s|
|
623
|
+
merge_if_branch s
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
exp
|
628
|
+
end
|
629
|
+
|
630
|
+
def process_if_branch exp
|
631
|
+
if sexp? exp
|
632
|
+
if block? exp
|
633
|
+
process_default exp
|
634
|
+
else
|
635
|
+
process exp
|
636
|
+
end
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
def merge_if_branch branch_env
|
641
|
+
branch_env.each do |k, v|
|
642
|
+
next if v.nil?
|
643
|
+
|
644
|
+
current_val = env[k]
|
645
|
+
|
646
|
+
if current_val
|
647
|
+
unless same_value?(current_val, v)
|
648
|
+
if too_deep? current_val
|
649
|
+
# Give up branching, start over with latest value
|
650
|
+
env[k] = v
|
651
|
+
else
|
652
|
+
env[k] = current_val.combine(v, k.line)
|
653
|
+
end
|
654
|
+
end
|
655
|
+
else
|
656
|
+
env[k] = v
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
def too_deep? exp
|
662
|
+
@or_depth_limit >= 0 and
|
663
|
+
node_type? exp, :or and
|
664
|
+
exp.or_depth and
|
665
|
+
exp.or_depth >= @or_depth_limit
|
666
|
+
end
|
667
|
+
|
668
|
+
#Process single integer access to an array.
|
669
|
+
#
|
670
|
+
#Returns the value inside the array, if possible.
|
671
|
+
def process_array_access target, args
|
672
|
+
if args.length == 1 and integer? args.first
|
673
|
+
index = args.first.value
|
674
|
+
|
675
|
+
#Have to do this because first element is :array and we have to skip it
|
676
|
+
target[1..-1][index]
|
677
|
+
else
|
678
|
+
nil
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
#Process hash access by returning the value associated
|
683
|
+
#with the given argument.
|
684
|
+
def process_hash_access target, index
|
685
|
+
hash_access(target, index)
|
686
|
+
end
|
687
|
+
|
688
|
+
#Join two array literals into one.
|
689
|
+
def join_arrays array1, array2
|
690
|
+
result = Sexp.new(:array)
|
691
|
+
result.concat array1[1..-1]
|
692
|
+
result.concat array2[1..-1]
|
693
|
+
end
|
694
|
+
|
695
|
+
#Join two string literals into one.
|
696
|
+
def join_strings string1, string2
|
697
|
+
result = Sexp.new(:str)
|
698
|
+
result.value = string1.value + string2.value
|
699
|
+
|
700
|
+
if result.value.length > 50
|
701
|
+
string1
|
702
|
+
else
|
703
|
+
result
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
# Change x.send(:y, 1) to x.y(1)
|
708
|
+
def collapse_send_call exp, first_arg
|
709
|
+
# Handle try(&:id)
|
710
|
+
if node_type? first_arg, :block_pass
|
711
|
+
first_arg = first_arg[1]
|
712
|
+
end
|
713
|
+
|
714
|
+
return unless symbol? first_arg or string? first_arg
|
715
|
+
exp.method = first_arg.value.to_sym
|
716
|
+
args = exp.args
|
717
|
+
exp.pop # remove last arg
|
718
|
+
if args.length > 1
|
719
|
+
exp.arglist = args[1..-1]
|
720
|
+
end
|
721
|
+
end
|
722
|
+
|
723
|
+
#Returns a new SexpProcessor::Environment containing only instance variables.
|
724
|
+
#This is useful, for example, when processing views.
|
725
|
+
def only_ivars include_request_vars = false, lenv = nil
|
726
|
+
lenv ||= env
|
727
|
+
res = SexpProcessor::Environment.new
|
728
|
+
|
729
|
+
if include_request_vars
|
730
|
+
lenv.all.each do |k, v|
|
731
|
+
#TODO Why would this have nil values?
|
732
|
+
if (k.node_type == :ivar or request_value? k) and not v.nil?
|
733
|
+
res[k] = v.dup
|
734
|
+
end
|
735
|
+
end
|
736
|
+
else
|
737
|
+
lenv.all.each do |k, v|
|
738
|
+
#TODO Why would this have nil values?
|
739
|
+
if k.node_type == :ivar and not v.nil?
|
740
|
+
res[k] = v.dup
|
741
|
+
end
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
res
|
746
|
+
end
|
747
|
+
|
748
|
+
def only_request_vars
|
749
|
+
res = SexpProcessor::Environment.new
|
750
|
+
|
751
|
+
env.all.each do |k, v|
|
752
|
+
if request_value? k and not v.nil?
|
753
|
+
res[k] = v.dup
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
res
|
758
|
+
end
|
759
|
+
|
760
|
+
def get_call_value call
|
761
|
+
method_name = call.method
|
762
|
+
|
763
|
+
#Look for helper methods and see if we can get a return value
|
764
|
+
if found_method = find_method(method_name, @current_class)
|
765
|
+
helper = found_method[:method]
|
766
|
+
|
767
|
+
if sexp? helper
|
768
|
+
value = process_helper_method helper, call.args
|
769
|
+
value.line(call.line)
|
770
|
+
return value
|
771
|
+
else
|
772
|
+
raise "Unexpected value for method: #{found_method}"
|
773
|
+
end
|
774
|
+
else
|
775
|
+
call
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
def process_helper_method method_exp, args
|
780
|
+
method_name = method_exp.method_name
|
781
|
+
Brakeman.debug "Processing method #{method_name}"
|
782
|
+
|
783
|
+
info = @helper_method_info[method_name]
|
784
|
+
|
785
|
+
#If method uses instance variables, then include those and request
|
786
|
+
#variables (params, etc) in the method environment. Otherwise,
|
787
|
+
#only include request variables.
|
788
|
+
if info[:uses_ivars]
|
789
|
+
meth_env = only_ivars(:include_request_vars)
|
790
|
+
else
|
791
|
+
meth_env = only_request_vars
|
792
|
+
end
|
793
|
+
|
794
|
+
#Add arguments to method environment
|
795
|
+
assign_args method_exp, args, meth_env
|
796
|
+
|
797
|
+
|
798
|
+
#Find return values if method does not depend on environment/args
|
799
|
+
values = @helper_method_cache[method_name]
|
800
|
+
|
801
|
+
unless values
|
802
|
+
#Serialize environment for cache key
|
803
|
+
meth_values = meth_env.instance_variable_get(:@env).to_a
|
804
|
+
meth_values.sort!
|
805
|
+
meth_values = meth_values.to_s
|
806
|
+
|
807
|
+
digest = Digest::SHA1.new.update(meth_values << method_name.to_s).to_s.to_sym
|
808
|
+
|
809
|
+
values = @helper_method_cache[digest]
|
810
|
+
end
|
811
|
+
|
812
|
+
if values
|
813
|
+
#Use values from cache
|
814
|
+
values[:ivar_values].each do |var, val|
|
815
|
+
env[var] = val
|
816
|
+
end
|
817
|
+
|
818
|
+
values[:return_value]
|
819
|
+
else
|
820
|
+
#Find return value for method
|
821
|
+
frv = Brakeman::FindReturnValue.new
|
822
|
+
value = frv.get_return_value(method_exp.body_list, meth_env)
|
823
|
+
|
824
|
+
ivars = {}
|
825
|
+
|
826
|
+
only_ivars(false, meth_env).all.each do |var, val|
|
827
|
+
env[var] = val
|
828
|
+
ivars[var] = val
|
829
|
+
end
|
830
|
+
|
831
|
+
if not frv.uses_ivars? and args.length == 0
|
832
|
+
#Store return value without ivars and args if they are not used
|
833
|
+
@helper_method_cache[method_exp.method_name] = { :return_value => value, :ivar_values => ivars }
|
834
|
+
else
|
835
|
+
@helper_method_cache[digest] = { :return_value => value, :ivar_values => ivars }
|
836
|
+
end
|
837
|
+
|
838
|
+
#Store information about method, just ivar usage for now
|
839
|
+
@helper_method_info[method_name] = { :uses_ivars => frv.uses_ivars? }
|
840
|
+
|
841
|
+
value
|
842
|
+
end
|
843
|
+
end
|
844
|
+
|
845
|
+
def assign_args method_exp, args, meth_env = SexpProcessor::Environment.new
|
846
|
+
formal_args = method_exp.formal_args
|
847
|
+
|
848
|
+
formal_args.each_with_index do |arg, index|
|
849
|
+
next if index == 0
|
850
|
+
|
851
|
+
if arg.is_a? Symbol and sexp? args[index - 1]
|
852
|
+
meth_env[Sexp.new(:lvar, arg)] = args[index - 1]
|
853
|
+
end
|
854
|
+
end
|
855
|
+
|
856
|
+
meth_env
|
857
|
+
end
|
858
|
+
|
859
|
+
#Finds the inner most call target which is not the target of a call to <<
|
860
|
+
def find_push_target exp
|
861
|
+
if call? exp and exp.method == :<<
|
862
|
+
find_push_target exp.target
|
863
|
+
else
|
864
|
+
exp
|
865
|
+
end
|
866
|
+
end
|
867
|
+
|
868
|
+
def duplicate? exp
|
869
|
+
@exp_context[0..-2].reverse_each do |e|
|
870
|
+
return true if exp == e
|
871
|
+
end
|
872
|
+
|
873
|
+
false
|
874
|
+
end
|
875
|
+
|
876
|
+
def find_method *args
|
877
|
+
nil
|
878
|
+
end
|
879
|
+
|
880
|
+
#Return true if lhs == rhs or lhs is an or expression and
|
881
|
+
#rhs is one of its values
|
882
|
+
def same_value? lhs, rhs
|
883
|
+
if lhs == rhs
|
884
|
+
true
|
885
|
+
elsif node_type? lhs, :or
|
886
|
+
lhs.rhs == rhs or lhs.lhs == rhs
|
887
|
+
else
|
888
|
+
false
|
889
|
+
end
|
890
|
+
end
|
891
|
+
|
892
|
+
def self_assign? var, value
|
893
|
+
self_assign_var?(var, value) or self_assign_target?(var, value)
|
894
|
+
end
|
895
|
+
|
896
|
+
#Return true if for x += blah or @x += blah
|
897
|
+
def self_assign_var? var, value
|
898
|
+
call? value and
|
899
|
+
value.method == :+ and
|
900
|
+
node_type? value.target, :lvar, :ivar and
|
901
|
+
value.target.value == var
|
902
|
+
end
|
903
|
+
|
904
|
+
#Return true for x = x.blah
|
905
|
+
def self_assign_target? var, value
|
906
|
+
target = top_target(value)
|
907
|
+
|
908
|
+
if node_type? target, :lvar, :ivar
|
909
|
+
target = target.value
|
910
|
+
end
|
911
|
+
|
912
|
+
var == target
|
913
|
+
end
|
914
|
+
|
915
|
+
#Returns last non-nil target in a call chain
|
916
|
+
def top_target exp, last = nil
|
917
|
+
if call? exp
|
918
|
+
top_target exp.target, exp
|
919
|
+
elsif node_type? exp, :iter
|
920
|
+
top_target exp.block_call, last
|
921
|
+
else
|
922
|
+
exp || last
|
923
|
+
end
|
924
|
+
end
|
925
|
+
|
926
|
+
def value_from_if exp
|
927
|
+
if block? exp.else_clause or block? exp.then_clause
|
928
|
+
#If either clause is more than a single expression, just use entire
|
929
|
+
#if expression for now
|
930
|
+
exp
|
931
|
+
elsif exp.else_clause.nil?
|
932
|
+
exp.then_clause
|
933
|
+
elsif exp.then_clause.nil?
|
934
|
+
exp.else_clause
|
935
|
+
else
|
936
|
+
condition = exp.condition
|
937
|
+
|
938
|
+
if true? condition
|
939
|
+
exp.then_clause
|
940
|
+
elsif false? condition
|
941
|
+
exp.else_clause
|
942
|
+
else
|
943
|
+
exp.then_clause.combine(exp.else_clause, exp.line)
|
944
|
+
end
|
945
|
+
end
|
946
|
+
end
|
947
|
+
|
948
|
+
#Set variable to given value.
|
949
|
+
#Creates "branched" versions of values when appropriate.
|
950
|
+
#Avoids creating multiple branched versions inside same
|
951
|
+
#if branch.
|
952
|
+
def set_value var, value
|
953
|
+
if node_type? value, :if
|
954
|
+
value = value_from_if(value)
|
955
|
+
end
|
956
|
+
|
957
|
+
if @ignore_ifs or not @inside_if
|
958
|
+
if @meth_env and node_type? var, :ivar and env[var].nil?
|
959
|
+
@meth_env[var] = value
|
960
|
+
else
|
961
|
+
env[var] = value
|
962
|
+
end
|
963
|
+
elsif env.current[var]
|
964
|
+
env.current[var] = value
|
965
|
+
elsif @branch_env and @branch_env[var]
|
966
|
+
@branch_env[var] = value
|
967
|
+
elsif @branch_env and @meth_env and node_type? var, :ivar
|
968
|
+
@branch_env[var] = value
|
969
|
+
else
|
970
|
+
env.current[var] = value
|
971
|
+
end
|
972
|
+
end
|
973
|
+
|
974
|
+
#If possible, distribute operation over both sides of an or.
|
975
|
+
#For example,
|
976
|
+
#
|
977
|
+
# (1 or 2) * 5
|
978
|
+
#
|
979
|
+
#Becomes
|
980
|
+
#
|
981
|
+
# (5 or 10)
|
982
|
+
#
|
983
|
+
#Only works for strings and numbers right now.
|
984
|
+
def process_or_simple_operation exp
|
985
|
+
arg = exp.first_arg
|
986
|
+
return nil unless string? arg or number? arg
|
987
|
+
|
988
|
+
target = exp.target
|
989
|
+
lhs = process_or_target(target.lhs, exp.dup)
|
990
|
+
rhs = process_or_target(target.rhs, exp.dup)
|
991
|
+
|
992
|
+
if lhs and rhs
|
993
|
+
if same_value? lhs, rhs
|
994
|
+
lhs
|
995
|
+
else
|
996
|
+
exp.target.lhs = lhs
|
997
|
+
exp.target.rhs = rhs
|
998
|
+
exp.target
|
999
|
+
end
|
1000
|
+
else
|
1001
|
+
nil
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
def process_or_target value, copy
|
1006
|
+
if string? value or number? value
|
1007
|
+
copy.target = value
|
1008
|
+
process copy
|
1009
|
+
else
|
1010
|
+
false
|
1011
|
+
end
|
1012
|
+
end
|
1013
|
+
end
|