require 'set' require 'pathname' #This is a mixin containing utility methods. module Brakeman::Util QUERY_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request), :query_parameters) PATH_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request), :path_parameters) REQUEST_REQUEST_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request), :request_parameters) REQUEST_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request), :parameters) REQUEST_PARAMS = Sexp.new(:call, Sexp.new(:call, nil, :request), :params) REQUEST_ENV = Sexp.new(:call, Sexp.new(:call, nil, :request), :env) PARAMETERS = Sexp.new(:call, nil, :params) COOKIES = Sexp.new(:call, nil, :cookies) REQUEST_COOKIES = s(:call, s(:call, nil, :request), :cookies) SESSION = Sexp.new(:call, nil, :session) ALL_PARAMETERS = Set[PARAMETERS, QUERY_PARAMETERS, PATH_PARAMETERS, REQUEST_REQUEST_PARAMETERS, REQUEST_PARAMETERS, REQUEST_PARAMS] ALL_COOKIES = Set[COOKIES, REQUEST_COOKIES] SAFE_LITERAL = s(:lit, :BRAKEMAN_SAFE_LITERAL) #Convert a string from "something_like_this" to "SomethingLikeThis" # #Taken from ActiveSupport. def camelize lower_case_and_underscored_word lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } end #Convert a string from "Something::LikeThis" to "something/like_this" # #Taken from ActiveSupport. def underscore camel_cased_word camel_cased_word.to_s.gsub(/::/, '/'). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). tr("-", "_"). downcase end # stupid simple, used to delegate to ActiveSupport def pluralize word if word.end_with? 's' word + 'es' else word + 's' end end #Returns a class name as a Symbol. #If class name cannot be determined, returns _exp_. def class_name exp case exp when Sexp case exp.node_type when :const exp.value when :lvar exp.value.to_sym when :colon2 "#{class_name(exp.lhs)}::#{exp.rhs}".to_sym when :colon3 "::#{exp.value}".to_sym when :self @current_class || @current_module || nil else exp end when Symbol exp when nil nil else exp end end #Takes an Sexp like # (:hash, (:lit, :key), (:str, "value")) #and yields the key and value pairs to the given block. # #For example: # # h = Sexp.new(:hash, (:lit, :name), (:str, "bob"), (:lit, :name), (:str, "jane")) # names = [] # hash_iterate(h) do |key, value| # if symbol? key and key[1] == :name # names << value[1] # end # end # names #["bob"] def hash_iterate hash hash = remove_kwsplat(hash) 1.step(hash.length - 1, 2) do |i| yield hash[i], hash[i + 1] end end def remove_kwsplat exp if exp.any? { |e| node_type? e, :kwsplat } exp.reject { |e| node_type? e, :kwsplat } else exp end end #Insert value into Hash Sexp def hash_insert hash, key, value index = 1 hash_iterate hash.dup do |k,v| if k == key hash[index + 1] = value return hash end index += 2 end hash << key << value hash end #Get value from hash using key. # #If _key_ is a Symbol, it will be converted to a Sexp(:lit, key). def hash_access hash, key if key.is_a? Symbol key = Sexp.new(:lit, key) end if index = hash.find_index(key) and index > 0 return hash[index + 1] end nil end def hash_values hash values = hash.each_sexp.each_slice(2).map do |_, value| value end Sexp.new(:array).concat(values).line(hash.line) end #These are never modified PARAMS_SEXP = Sexp.new(:params) SESSION_SEXP = Sexp.new(:session) COOKIES_SEXP = Sexp.new(:cookies) #Adds params, session, and cookies to environment #so they can be replaced by their respective Sexps. def set_env_defaults @env[PARAMETERS] = PARAMS_SEXP @env[SESSION] = SESSION_SEXP @env[COOKIES] = COOKIES_SEXP end #Check if _exp_ represents a hash: s(:hash, {...}) #This also includes pseudo hashes params, session, and cookies. def hash? exp exp.is_a? Sexp and (exp.node_type == :hash or exp.node_type == :params or exp.node_type == :session or exp.node_type == :cookies) end #Check if _exp_ represents an array: s(:array, [...]) def array? exp exp.is_a? Sexp and exp.node_type == :array end #Check if _exp_ represents a String: s(:str, "...") def string? exp exp.is_a? Sexp and exp.node_type == :str end def string_interp? exp exp.is_a? Sexp and exp.node_type == :dstr end #Check if _exp_ represents a Symbol: s(:lit, :...) def symbol? exp exp.is_a? Sexp and exp.node_type == :lit and exp[1].is_a? Symbol end #Check if _exp_ represents a method call: s(:call, ...) def call? exp exp.is_a? Sexp and (exp.node_type == :call or exp.node_type == :safe_call) end #Check if _exp_ represents a Regexp: s(:lit, /.../) def regexp? exp exp.is_a? Sexp and exp.node_type == :lit and exp[1].is_a? Regexp end #Check if _exp_ represents an Integer: s(:lit, ...) def integer? exp exp.is_a? Sexp and exp.node_type == :lit and exp[1].is_a? Integer end #Check if _exp_ represents a number: s(:lit, ...) def number? exp exp.is_a? Sexp and exp.node_type == :lit and exp[1].is_a? Numeric end #Check if _exp_ represents a result: s(:result, ...) def result? exp exp.is_a? Sexp and exp.node_type == :result end #Check if _exp_ represents a :true, :lit, or :string node def true? exp exp.is_a? Sexp and (exp.node_type == :true or exp.node_type == :lit or exp.node_type == :string) end #Check if _exp_ represents a :false or :nil node def false? exp exp.is_a? Sexp and (exp.node_type == :false or exp.node_type == :nil) end #Check if _exp_ represents a block of code def block? exp exp.is_a? Sexp and (exp.node_type == :block or exp.node_type == :rlist) end #Check if _exp_ is a params hash def params? exp recurse_check?(exp) { |child| child.node_type == :params or ALL_PARAMETERS.include? child } end def cookies? exp recurse_check?(exp) { |child| child.node_type == :cookies or ALL_COOKIES.include? child } end def recurse_check? exp, &check if exp.is_a? Sexp return true if yield(exp) if call? exp if recurse_check? exp[1], &check return true elsif exp[2] == :[] return recurse_check? exp[1], &check end end end false end # Only return true when accessing request headers via request.env[...] def request_headers? exp return unless sexp? exp if exp[1] == REQUEST_ENV if exp.method == :[] if string? exp.first_arg # Only care about HTTP headers, which are prefixed by 'HTTP_' exp.first_arg.value.start_with?('HTTP_'.freeze) else true # request.env[something] end else false # request.env.something end else false end end #Check if exp is params, cookies, or request_headers def request_value? exp params? exp or cookies? exp or request_headers? exp end def constant? exp node_type? exp, :const, :colon2, :colon3 end def kwsplat? exp exp.is_a? Sexp and exp.node_type == :hash and exp[1].is_a? Sexp and exp[1].node_type == :kwsplat end #Check if _exp_ is a Sexp. def sexp? exp exp.is_a? Sexp end #Check if _exp_ is a Sexp and the node type matches one of the given types. def node_type? exp, *types exp.is_a? Sexp and types.include? exp.node_type end SIMPLE_LITERALS = [:lit, :false, :str, :true] def simple_literal? exp exp.is_a? Sexp and SIMPLE_LITERALS.include? exp.node_type end LITERALS = [*SIMPLE_LITERALS, :array, :hash] def literal? exp exp.is_a? Sexp and LITERALS.include? exp.node_type end def all_literals? exp, expected_type = :array node_type? exp, expected_type and exp.length > 1 and exp.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str } end DIR_CONST = s(:const, :Dir) # Dir.glob(...).whatever def dir_glob? exp exp = exp.block_call if node_type? exp, :iter return unless call? exp (exp.target == DIR_CONST and exp.method == :glob) or dir_glob? exp.target end #Returns true if the given _exp_ contains a :class node. # #Useful for checking if a module is just a module or if it is a namespace. def contains_class? exp todo = [exp] until todo.empty? current = todo.shift if node_type? current, :class return true elsif sexp? current todo = current.sexp_body.concat todo end end false end def make_call target, method, *args call = Sexp.new(:call, target, method) if args.empty? or args.first.empty? #nothing to do elsif node_type? args.first, :arglist call.concat args.first.sexp_body elsif args.first.node_type.is_a? Sexp #just a list of args call.concat args.first else call.concat args end call end def safe_literal line = nil s(:lit, :BRAKEMAN_SAFE_LITERAL).line(line || 0) end def safe_literal? exp exp == SAFE_LITERAL end def safe_literal_target? exp if call? exp safe_literal_target? exp.target else safe_literal? exp end end def rails_version @tracker.config.rails_version end #Convert path/filename to view name # # views/test/something.html.erb -> test/something def template_path_to_name path names = path.relative.split('/') names.last.gsub!(/(\.(html|js)\..*|\.(rhtml|haml|erb|slim))$/, '') if names.include? 'views' names[(names.index('views') + 1)..-1] else names end.join('/').to_sym end end