util.rb 10.5 KB
Newer Older
J
Justin 已提交
1
require 'set'
2
require 'pathname'
J
Justin 已提交
3 4

#This is a mixin containing utility methods.
J
Justin Collins 已提交
5
module Brakeman::Util
J
Justin 已提交
6

7
  QUERY_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request), :query_parameters)
J
Justin 已提交
8

9
  PATH_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request), :path_parameters)
J
Justin 已提交
10

11
  REQUEST_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request), :request_parameters)
J
Justin 已提交
12

13
  REQUEST_PARAMS = Sexp.new(:call, Sexp.new(:call, nil, :request), :parameters)
14

15
  REQUEST_ENV = Sexp.new(:call, Sexp.new(:call, nil, :request), :env)
16

17
  PARAMETERS = Sexp.new(:call, nil, :params)
J
Justin 已提交
18

19
  COOKIES = Sexp.new(:call, nil, :cookies)
J
Justin 已提交
20

21
  SESSION = Sexp.new(:call, nil, :session)
J
Justin 已提交
22

23
  ALL_PARAMETERS = Set[PARAMETERS, QUERY_PARAMETERS, PATH_PARAMETERS, REQUEST_PARAMETERS, REQUEST_PARAMS]
J
Justin 已提交
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

  #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

43
  # stupid simple, used to delegate to ActiveSupport
J
Justin 已提交
44
  def pluralize word
45
    word + "s"
J
Justin 已提交
46 47
  end

48

J
Justin 已提交
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
  #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
    1.step(hash.length - 1, 2) do |i|
      yield hash[i], hash[i + 1]
    end
  end

  #Insert value into Hash Sexp
  def hash_insert hash, key, value
J
Justin Collins 已提交
71
    index = 1
J
Justin 已提交
72
    hash_iterate hash.dup do |k,v|
J
Justin Collins 已提交
73
      if k == key
J
Justin 已提交
74 75 76
        hash[index + 1] = value
        return hash
      end
J
Justin Collins 已提交
77
      index += 2
J
Justin 已提交
78
    end
79

J
Justin 已提交
80 81 82 83 84
    hash << key << value

    hash
  end

85 86 87 88 89 90 91 92
  #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

J
Justin Collins 已提交
93 94
    if index = hash.find_index(key) and index > 0
      return hash[index + 1]
95 96 97 98 99
    end

    nil
  end

100 101 102 103 104
  #These are never modified
  PARAMS_SEXP = Sexp.new(:params)
  SESSION_SEXP = Sexp.new(:session)
  COOKIES_SEXP = Sexp.new(:cookies)

J
Justin 已提交
105 106 107
  #Adds params, session, and cookies to environment
  #so they can be replaced by their respective Sexps.
  def set_env_defaults
108 109 110
    @env[PARAMETERS] = PARAMS_SEXP
    @env[SESSION] = SESSION_SEXP
    @env[COOKIES] = COOKIES_SEXP
J
Justin 已提交
111 112 113 114 115
  end

  #Check if _exp_ represents a hash: s(:hash, {...})
  #This also includes pseudo hashes params, session, and cookies.
  def hash? exp
116 117
    exp.is_a? Sexp and (exp.node_type == :hash or
                        exp.node_type == :params or
J
Justin 已提交
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
                        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

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

J
Justin Collins 已提交
152 153 154 155 156
  #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

J
Justin Collins 已提交
157 158 159 160 161
  #Check if _exp_ represents a result: s(:result, ...)
  def result? exp
    exp.is_a? Sexp and exp.node_type == :result
  end

J
Justin Collins 已提交
162 163 164 165 166 167 168 169 170 171 172 173 174
  #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

175 176 177 178 179 180
  #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

J
Justin 已提交
181 182
  #Check if _exp_ is a params hash
  def params? exp
J
Justin Collins 已提交
183 184 185 186 187 188 189 190 191 192 193 194 195
    if exp.is_a? Sexp
      return true if exp.node_type == :params or ALL_PARAMETERS.include? exp

      if exp.node_type == :call
        if params? exp[1]
          return true
        elsif exp[2] == :[]
          return params? exp[1]
        end
      end
    end

    false
J
Justin 已提交
196 197 198
  end

  def cookies? exp
J
Justin Collins 已提交
199 200 201 202 203 204 205 206 207 208 209 210 211
    if exp.is_a? Sexp
      return true if exp.node_type == :cookies or exp == COOKIES

      if exp.node_type == :call
        if cookies? exp[1]
          return true
        elsif exp[2] == :[]
          return cookies? exp[1]
        end
      end
    end

    false
212
  end
J
Justin Collins 已提交
213

214 215
  def request_env? exp
    call? exp and (exp == REQUEST_ENV or exp[1] == REQUEST_ENV)
J
Justin 已提交
216 217
  end

J
Justin Collins 已提交
218 219 220 221 222 223 224
  #Check if exp is params, cookies, or request_env
  def request_value? exp
    params? exp or
    cookies? exp or
    request_env? exp
  end

J
Justin 已提交
225 226 227 228
  #Check if _exp_ is a Sexp.
  def sexp? exp
    exp.is_a? Sexp
  end
229

J
Justin Collins 已提交
230 231 232 233 234
  #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

J
Justin Collins 已提交
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
  #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[1..-1].concat todo
      end
    end

    false
  end

254 255 256
  def make_call target, method, *args
    call = Sexp.new(:call, target, method)

257
    if args.empty? or args.first.empty?
258 259 260 261 262 263 264 265 266 267 268 269
      #nothing to do
    elsif node_type? args.first, :arglist
      call.concat args.first[1..-1]
    elsif args.first.node_type.is_a? Sexp #just a list of args
      call.concat args.first
    else
      call.concat args
    end

    call
  end

270 271 272 273 274 275 276 277
  #Return file name related to given warning. Uses +warning.file+ if it exists
  def file_for warning, tracker = nil
    if tracker.nil?
      tracker = @tracker || self.tracker
    end

    if warning.file
      File.expand_path warning.file, tracker.options[:app_path]
278 279
    elsif warning.template.is_a? Hash and warning.template[:file]
      warning.template[:file]
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
    else
      case warning.warning_set
      when :controller
        file_by_name warning.controller, :controller, tracker
      when :template
        file_by_name warning.template[:name], :template, tracker
      when :model
        file_by_name warning.model, :model, tracker
      when :warning
        file_by_name warning.class, nil, tracker
      else
        nil
      end
    end
  end

  #Attempt to determine path to context file based on the reported name
  #in the warning.
  #
  #For example,
  #
  #  file_by_name FileController #=> "/rails/root/app/controllers/file_controller.rb
  def file_by_name name, type, tracker = nil
    return nil unless name
    string_name = name.to_s
    name = name.to_sym

    unless type
      if string_name =~ /Controller$/
        type = :controller
310
      elsif camelize(string_name) == string_name # This is not always true
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
        type = :model
      else
        type = :template
      end
    end

    path = tracker.options[:app_path]

    case type
    when :controller
      if tracker.controllers[name] and tracker.controllers[name][:file]
        path = tracker.controllers[name][:file]
      else
        path += "/app/controllers/#{underscore(string_name)}.rb"
      end
    when :model
      if tracker.models[name] and tracker.models[name][:file]
        path = tracker.models[name][:file]
      else
330
        path += "/app/models/#{underscore(string_name)}.rb"
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
      end
    when :template
      if tracker.templates[name] and tracker.templates[name][:file]
        path = tracker.templates[name][:file]
      elsif string_name.include? " "
        name = string_name.split[0].to_sym
        path = file_for tracker, name, :template
      else
        path = nil
      end
    end

    path
  end

  #Return array of lines surrounding the warning location from the original
  #file.
348
  def context_for app_tree, warning, tracker = nil
349 350
    file = file_for warning, tracker
    context = []
351
    return context unless warning.line and file and @app_tree.path_exists? file
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376

    current_line = 0
    start_line = warning.line - 5
    end_line = warning.line + 5

    start_line = 1 if start_line < 0

    File.open file do |f|
      f.each_line do |line|
        current_line += 1

        next if line.strip == ""

        if current_line > end_line
          break
        end

        if current_line >= start_line
          context << [current_line, line]
        end
      end
    end

    context
  end
N
Neil Matatall 已提交
377

378 379 380 381 382 383 384 385
  def relative_path file
    if file and not file.empty? and file.start_with? '/'
      Pathname.new(file).relative_path_from(Pathname.new(@tracker.options[:app_path])).to_s
    else
      file
    end
  end

386
  def truncate_table str
K
Kaworu 已提交
387 388 389
    @terminal_width ||= if @tracker.options[:table_width]
                          @tracker.options[:table_width]
                        elsif $stdin && $stdin.tty?
390
                          Brakeman.load_brakeman_dependency 'highline'
391
                          ::HighLine.new.terminal_size[0]
F
fsword 已提交
392
                        else
K
Kaworu 已提交
393
                          80
F
fsword 已提交
394
                        end
N
Neil Matatall 已提交
395 396 397 398
    lines = str.lines

    lines.map do |line|
      if line.chomp.length > @terminal_width
399
        line[0..(@terminal_width - 3)] + ">>\n"
N
Neil Matatall 已提交
400 401 402 403 404 405 406 407
      else
        line
      end
    end.join
  end

  # rely on Terminal::Table to build the structure, extract the data out in CSV format
  def table_to_csv table
408
    Brakeman.load_brakeman_dependency 'terminal-table'
N
Neil Matatall 已提交
409 410 411 412 413
    output = CSV.generate_line(table.headings.cells.map{|cell| cell.to_s.strip})
    table.rows.each do |row|
      output << CSV.generate_line(row.cells.map{|cell| cell.to_s.strip})
    end
    output
414
  end
J
Justin 已提交
415
end