/
util.rb
396 lines (320 loc) · 9.45 KB
/
util.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
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
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
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
def request_env? exp
call? exp and (exp == REQUEST_ENV or exp[1] == REQUEST_ENV)
end
#Check if exp is params, cookies, or request_env
def request_value? exp
params? exp or
cookies? exp or
request_env? 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