spektr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yaml +32 -0
- data/.gitignore +9 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +134 -0
- data/Guardfile +45 -0
- data/LICENSE.txt +27 -0
- data/README.md +70 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/spektr +7 -0
- data/lib/spektr/app.rb +209 -0
- data/lib/spektr/checks/base.rb +151 -0
- data/lib/spektr/checks/basic_auth.rb +27 -0
- data/lib/spektr/checks/basic_auth_timing.rb +24 -0
- data/lib/spektr/checks/command_injection.rb +48 -0
- data/lib/spektr/checks/content_tag_xss.rb +54 -0
- data/lib/spektr/checks/cookie_serialization.rb +21 -0
- data/lib/spektr/checks/create_with.rb +27 -0
- data/lib/spektr/checks/csrf.rb +25 -0
- data/lib/spektr/checks/csrf_setting.rb +39 -0
- data/lib/spektr/checks/default_routes.rb +43 -0
- data/lib/spektr/checks/deserialize.rb +62 -0
- data/lib/spektr/checks/detailed_exceptions.rb +29 -0
- data/lib/spektr/checks/digest_dos.rb +28 -0
- data/lib/spektr/checks/dynamic_finders.rb +26 -0
- data/lib/spektr/checks/evaluation.rb +25 -0
- data/lib/spektr/checks/file_access.rb +38 -0
- data/lib/spektr/checks/file_disclosure.rb +25 -0
- data/lib/spektr/checks/filter_skipping.rb +29 -0
- data/lib/spektr/checks/header_dos.rb +20 -0
- data/lib/spektr/checks/i18n_xss.rb +20 -0
- data/lib/spektr/checks/json_encoding.rb +23 -0
- data/lib/spektr/checks/json_entity_escape.rb +30 -0
- data/lib/spektr/checks/json_parsing.rb +47 -0
- data/lib/spektr/checks/link_to_href.rb +35 -0
- data/lib/spektr/checks/mass_assignment.rb +42 -0
- data/lib/spektr/checks/send.rb +24 -0
- data/lib/spektr/checks/sqli.rb +52 -0
- data/lib/spektr/checks/xss.rb +49 -0
- data/lib/spektr/checks.rb +9 -0
- data/lib/spektr/cli.rb +53 -0
- data/lib/spektr/erubi.rb +78 -0
- data/lib/spektr/exp/assignment.rb +20 -0
- data/lib/spektr/exp/base.rb +32 -0
- data/lib/spektr/exp/const.rb +7 -0
- data/lib/spektr/exp/definition.rb +32 -0
- data/lib/spektr/exp/ivasign.rb +7 -0
- data/lib/spektr/exp/lvasign.rb +7 -0
- data/lib/spektr/exp/send.rb +135 -0
- data/lib/spektr/exp/xstr.rb +12 -0
- data/lib/spektr/processors/base.rb +80 -0
- data/lib/spektr/processors/class_processor.rb +25 -0
- data/lib/spektr/targets/base.rb +119 -0
- data/lib/spektr/targets/config.rb +6 -0
- data/lib/spektr/targets/controller.rb +74 -0
- data/lib/spektr/targets/model.rb +6 -0
- data/lib/spektr/targets/routes.rb +38 -0
- data/lib/spektr/targets/view.rb +34 -0
- data/lib/spektr/version.rb +3 -0
- data/lib/spektr/warning.rb +23 -0
- data/lib/spektr.rb +120 -0
- data/spektr.gemspec +49 -0
- metadata +362 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class FileDisclosure < Base
|
4
|
+
|
5
|
+
def initialize(app, target)
|
6
|
+
super
|
7
|
+
@name = "File existence disclosure"
|
8
|
+
@type = "Information Disclosure"
|
9
|
+
@targets = ["Spektr::Targets::Base"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
return unless super
|
14
|
+
config = @app.production_config.find_calls(:serve_static_assets=).first
|
15
|
+
if config && config.arguments.first.type == :true
|
16
|
+
warn! "root", self, nil, "File existence disclosure vulnerability"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def should_run?
|
21
|
+
app_version_between?("2.0.0", "2.3.18") || app_version_between?("3.0.0", "3.2.20") || app_version_between?("4.0.0", "4.0.11") || app_version_between?("4.1.0", "4.1.7")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class FilterSkipping < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "Default routes filter skipping"
|
7
|
+
@type = "Default Routes"
|
8
|
+
@targets = ["Spektr::Targets::Routes"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
return unless super
|
13
|
+
calls = %w{ match get post put delete }.inject([]) do |memo, method|
|
14
|
+
memo.concat @target.find_calls(method.to_sym)
|
15
|
+
memo
|
16
|
+
end
|
17
|
+
calls.each do |call|
|
18
|
+
if !call.arguments.empty? && (call.arguments.first.name.include?(":action") or call.arguments.first.name.include?("*action"))
|
19
|
+
warn! @target, self, call.location, "CVE-2011-2929 Rails versions before 3.0.10 have a vulnerability which allows filters to be bypassed"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def should_run?
|
25
|
+
app_version_between?("3.0.0", "3.0.9")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class HeaderDos < Base
|
4
|
+
|
5
|
+
def initialize(app, target)
|
6
|
+
super
|
7
|
+
@name = "HTTP MIME type header DoS (CVE-2013-6414)"
|
8
|
+
@type = "Denial of Service"
|
9
|
+
@targets = ["Spektr::Targets::Base"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
return unless super
|
14
|
+
if app_version_between?("3.0.0", "3.2.15")
|
15
|
+
warn! "root", self, nil, "CVE_2013_6414"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class I18nXss < Base
|
4
|
+
|
5
|
+
def initialize(app, target)
|
6
|
+
super
|
7
|
+
@name = "XSS in i18n (CVE-2013-4491)"
|
8
|
+
@type = "Cross-Site Scripting"
|
9
|
+
@targets = ["Spektr::Targets::Base"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
return unless super
|
14
|
+
if app_version_between?("3.0.6", "3.2.15") || app_version_between?("4.0.0", "4.0.1")
|
15
|
+
warn! "root", self, nil, "I18n has a Cross-Site Scripting vulnerability (CVE_2013_4491)"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class JsonEncoding < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "XSS by missing JSON encoding"
|
7
|
+
@type = "Cross-Site Scripting"
|
8
|
+
@targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::Routes", "Spektr::Targets::View"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
return unless super
|
13
|
+
if app_version_between?("4.1.0", "4.1.10") || app_version_between?("4.2.0", "4.2.1")
|
14
|
+
if calls = @target.find_calls(:to_json).any? || calls = @target.find_calls(:encode).any?
|
15
|
+
warn! @target, self, calls.first.location, "Cross-Site Scripting CVE_2015_3226"
|
16
|
+
else
|
17
|
+
warn! "root", self, nil, "Cross-Site Scripting CVE_2015_3226"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class JsonEntityEscape < Base
|
4
|
+
|
5
|
+
def initialize(app, target)
|
6
|
+
super
|
7
|
+
@name = "HTML escaping is disabled for JSON output"
|
8
|
+
@type = "Cross-Site Scripting"
|
9
|
+
@targets = ["Spektr::Targets::Config", "Spektr::Targets::Base"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
return unless super
|
14
|
+
if @app.production_config
|
15
|
+
config = @app.production_config.find_calls(:escape_html_entities_in_json=).first
|
16
|
+
end
|
17
|
+
if config and config.receiver.expanded == "config.active_support" && config.arguments.first.type == :false
|
18
|
+
warn! @app.production_config.path, self, nil, "HTML entities in JSON are not escaped by default"
|
19
|
+
end
|
20
|
+
['ActiveSupport', 'ActiveSupport.JSON.Encoding'].each do |receiver|
|
21
|
+
calls = @target.find_calls(:escape_html_entities_in_json=, receiver)
|
22
|
+
if calls.any?
|
23
|
+
warn! @target, self, calls.first.location, "HTML entities in JSON are not escaped by default"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class JsonParsing < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "JSON parsing vulnerability"
|
7
|
+
@type = "Remote Code Execution"
|
8
|
+
@targets = ["Spektr::Targets::Base"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
return unless super
|
13
|
+
check_cve_2013_0333
|
14
|
+
check_cve_2013_0269
|
15
|
+
end
|
16
|
+
|
17
|
+
def check_cve_2013_0333
|
18
|
+
return unless app_version_between?("0.0.0", "2.3.15") || app_version_between?("3.0.0", "3.0.19")
|
19
|
+
if @app.has_gem?("yajl")
|
20
|
+
warn! "root", self, nil, "Remote Code Execution CVE_2013_0333"
|
21
|
+
end
|
22
|
+
uses_json_gem?
|
23
|
+
end
|
24
|
+
|
25
|
+
def uses_json_gem?
|
26
|
+
@target.find_calls(:backend=).each do |call|
|
27
|
+
if call.receiver.expanded == "ActiveSupport.JSON" && call.arguments.first&.name == :JSONGem
|
28
|
+
warn! @target, self, call.location, "Remote Code Execution CVE_2013_0333"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_cve_2013_0269
|
34
|
+
["json", "json_pure"].each do |gem_name|
|
35
|
+
if g = @app.gem_specs&.find { |g| g.name == gem_name }
|
36
|
+
if version_between?("1.7.0", "1.7.6", g.version)
|
37
|
+
warn! "Gemfile", self, nil, "Unsafe Object Creation Vulnerability in the #{g.name} gem"
|
38
|
+
end
|
39
|
+
if version_between?("0", "1.5.4", g.version) || version_between?("1.6.0", "1.6.7", g.version)
|
40
|
+
warn! "Gemfile", self, nil, "Unsafe Object Creation Vulnerability in the #{g.name} gem"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class LinkToHref < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "XSS in href param of link_to"
|
7
|
+
@type = "Cross-Site Scripting"
|
8
|
+
@targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::View"]
|
9
|
+
end
|
10
|
+
|
11
|
+
# TODO: check for user supplied model attributes too
|
12
|
+
def run
|
13
|
+
return unless super
|
14
|
+
block_locations = []
|
15
|
+
@target.find_calls_with_block(:link_to).each do |call|
|
16
|
+
block_locations << call.location
|
17
|
+
next unless call.arguments.first
|
18
|
+
::Spektr.logger.debug "#{@target.path} #{call.location.line} #{call.arguments.first.inspect}"
|
19
|
+
if user_input? call.arguments.first.type, call.arguments.first.name, call.arguments.first.ast, call.arguments.first
|
20
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@target.find_calls(:link_to).each do |call|
|
25
|
+
next if block_locations.include? call.location
|
26
|
+
::Spektr.logger.debug "#{@target.path} #{call.location.line} #{call.arguments[1].inspect}"
|
27
|
+
next unless call.arguments[1] || call.arguments[1]&.name =~ /_url$|_path$/
|
28
|
+
if user_input? call.arguments[1].type, call.arguments[1].name, call.arguments[1].ast, call.arguments[1]
|
29
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class MassAssignment < Base
|
4
|
+
|
5
|
+
# TODO: Make this better
|
6
|
+
def initialize(app, target)
|
7
|
+
super
|
8
|
+
@name = "Mass Assignment"
|
9
|
+
@type = "Input Validation"
|
10
|
+
@targets = ["Spektr::Targets::Controller"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
return unless super
|
15
|
+
model_names = @app.models.collect(&:name)
|
16
|
+
calls = []
|
17
|
+
model_names.each do |receiver|
|
18
|
+
[:new, :build, :create].each do |method|
|
19
|
+
calls.concat @target.find_calls(method, receiver)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
calls.each do |call|
|
23
|
+
argument = call.arguments.first
|
24
|
+
next if argument.nil?
|
25
|
+
::Spektr.logger.debug "Mass assignment check at #{call.location.line}"
|
26
|
+
if user_input?(argument.type, argument.name, call.ast)
|
27
|
+
# we check for permit! separately
|
28
|
+
next if argument.ast.children[1] == :permit!
|
29
|
+
# check for permit with arguments
|
30
|
+
next if argument.ast.children[1] == :permit && argument.ast.children[2]
|
31
|
+
warn! @target, self, call.location, "Mass assignment"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@target.find_calls(:permit!).each do |call|
|
35
|
+
if call.arguments.none?
|
36
|
+
warn! @target, self, call.location, "permit! allows any keys, use it with caution!", :medium
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class Send < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "Dangerous send"
|
7
|
+
@type = "Dangerous send"
|
8
|
+
@targets = ["Spektr::Targets::Base", "Spektr::Targets::Model", "Spektr::Targets::Controller", "Spektr::Targets::Routes", "Spektr::Targets::View"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
return unless super
|
13
|
+
[:send, :try, :__send__, :public_send].each do |method|
|
14
|
+
@target.find_calls(method).each do |call|
|
15
|
+
argument = call.arguments.first
|
16
|
+
if user_input?(argument.type, argument.name, argument.ast)
|
17
|
+
warn! @target, self, call.location, "User supplied value in send"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class Sqli < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "SQL Injection"
|
7
|
+
@name = "SQL Injection"
|
8
|
+
@targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::Model"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
return unless super
|
13
|
+
|
14
|
+
[
|
15
|
+
:average, :count, :maximum, :minimum, :sum, :exists?,
|
16
|
+
:find_by, :find_by!, :find_or_create_by, :find_or_create_by!,
|
17
|
+
:find_or_initialize_by, :from, :group, :having, :join, :lock,
|
18
|
+
:where, :not, :select, :rewhere, :reselect, :update_all
|
19
|
+
|
20
|
+
].each do |m|
|
21
|
+
@target.find_calls(m).each do |call|
|
22
|
+
check_argument(call.arguments.first, m, call)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
[:calculate].each do |m|
|
26
|
+
@target.find_calls(m).each do |call|
|
27
|
+
check_argument(call.arguments[1], m, call)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
[:delete_by, :destroy_by].each do |m|
|
32
|
+
@target.find_calls(m).each do |call|
|
33
|
+
if call.arguments.first
|
34
|
+
check_argument(call.arguments.first, m, call)
|
35
|
+
end
|
36
|
+
call.options.values.each do |option|
|
37
|
+
check_argument(@target.ast_to_exp(option.key), m, call)
|
38
|
+
check_argument(@target.ast_to_exp(option.value), m, call)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_argument(argument, method, call)
|
45
|
+
return if argument.nil?
|
46
|
+
if user_input?(argument.type, argument.name, argument.ast, argument)
|
47
|
+
warn! @target, self, call.location, "Possible SQL Injection at #{method}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Spektr
|
2
|
+
class Checks
|
3
|
+
class Xss < Base
|
4
|
+
def initialize(app, target)
|
5
|
+
super
|
6
|
+
@name = "XSS"
|
7
|
+
@type = "Cross-Site Scripting"
|
8
|
+
@targets = ["Spektr::Targets::Base", "Spektr::Targets::Controller", "Spektr::Targets::View"]
|
9
|
+
end
|
10
|
+
|
11
|
+
# TODO: tests for haml, xml, js
|
12
|
+
# TODO: add check for raw calls
|
13
|
+
def run
|
14
|
+
return unless super
|
15
|
+
calls = @target.find_calls(:safe_expr_append=)
|
16
|
+
calls.concat(@target.find_calls(:raw))
|
17
|
+
calls.each do |call|
|
18
|
+
call.arguments.each do |argument|
|
19
|
+
if user_input?(argument.type, argument.name, argument.ast)
|
20
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
|
21
|
+
end
|
22
|
+
if model_attribute?(argument)
|
23
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{argument.name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
calls.each do |call|
|
28
|
+
call.arguments.each do |argument|
|
29
|
+
if user_input?(argument.type, argument.name, argument.ast)
|
30
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
|
31
|
+
end
|
32
|
+
if model_attribute?(argument)
|
33
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{argument.name}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
calls = @target.find_calls(:html_safe)
|
38
|
+
calls.each do |call|
|
39
|
+
if user_input?(call.receiver.type, call.receiver.name, call.receiver.ast, call.receiver)
|
40
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped user input"
|
41
|
+
end
|
42
|
+
if model_attribute?(call.receiver)
|
43
|
+
warn! @target, self, call.location, "Cross-Site Scripting: Unescaped model attribute #{call.receiver.name}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/spektr/cli.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'tty/option'
|
2
|
+
require 'json'
|
3
|
+
module Spektr
|
4
|
+
class Cli
|
5
|
+
include TTY::Option
|
6
|
+
usage do
|
7
|
+
program 'Spektr'
|
8
|
+
command 'scan'
|
9
|
+
desc 'Find vulnerabilities in ruby code'
|
10
|
+
end
|
11
|
+
|
12
|
+
argument :root do
|
13
|
+
optional
|
14
|
+
desc 'Path to application root'
|
15
|
+
end
|
16
|
+
|
17
|
+
flag :output_format do
|
18
|
+
long '--output_format string'
|
19
|
+
desc 'output format terminal or json'
|
20
|
+
default 'terminal'
|
21
|
+
end
|
22
|
+
|
23
|
+
flag :check do
|
24
|
+
long '--check string'
|
25
|
+
desc 'run this single check'
|
26
|
+
end
|
27
|
+
|
28
|
+
flag :debug do
|
29
|
+
long '--debug'
|
30
|
+
short '-d'
|
31
|
+
desc 'output debug logs to STDOUT'
|
32
|
+
end
|
33
|
+
|
34
|
+
flag :help do
|
35
|
+
short '-h'
|
36
|
+
long '--help'
|
37
|
+
desc 'Print usage'
|
38
|
+
end
|
39
|
+
|
40
|
+
def scan
|
41
|
+
if params[:help]
|
42
|
+
print help
|
43
|
+
exit
|
44
|
+
else
|
45
|
+
report = Spektr.run(params[:root], params[:output_format], params[:debug], params[:check])
|
46
|
+
case params[:output_format]
|
47
|
+
when 'json'
|
48
|
+
puts JSON.pretty_generate report
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/spektr/erubi.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require "erubi"
|
2
|
+
# This is a copy of module ActionView::Template::Handlers::ERB::Erubi
|
3
|
+
module Spektr
|
4
|
+
class Erubi < ::Erubi::Engine
|
5
|
+
def initialize(input, properties = {})
|
6
|
+
@newline_pending = 0
|
7
|
+
|
8
|
+
# Dup properties so that we don't modify argument
|
9
|
+
properties = Hash[properties]
|
10
|
+
properties[:preamble] = ""
|
11
|
+
properties[:postamble] = "@output_buffer.to_s"
|
12
|
+
properties[:bufvar] = "@output_buffer"
|
13
|
+
properties[:escapefunc] = ""
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def evaluate(action_view_erb_handler_context)
|
19
|
+
src = @src
|
20
|
+
view = Class.new(ActionView::Base) {
|
21
|
+
include action_view_erb_handler_context._routes.url_helpers
|
22
|
+
class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
|
23
|
+
}.empty
|
24
|
+
view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def add_text(text)
|
29
|
+
return if text.empty?
|
30
|
+
|
31
|
+
if text == "\n"
|
32
|
+
@newline_pending += 1
|
33
|
+
else
|
34
|
+
src << "@output_buffer.safe_append='"
|
35
|
+
src << "\n" * @newline_pending if @newline_pending > 0
|
36
|
+
src << text.gsub(/['\\]/, '\\\\\&')
|
37
|
+
src << "'.freeze;"
|
38
|
+
|
39
|
+
@newline_pending = 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
44
|
+
|
45
|
+
def add_expression(indicator, code)
|
46
|
+
flush_newline_if_pending(src)
|
47
|
+
|
48
|
+
if (indicator == "==") || @escape
|
49
|
+
src << "@output_buffer.safe_expr_append="
|
50
|
+
else
|
51
|
+
src << "@output_buffer.append="
|
52
|
+
end
|
53
|
+
|
54
|
+
if BLOCK_EXPR.match?(code)
|
55
|
+
src << " " << code
|
56
|
+
else
|
57
|
+
src << "(" << code << ");"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_code(code)
|
62
|
+
flush_newline_if_pending(src)
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_postamble(_)
|
67
|
+
flush_newline_if_pending(src)
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
def flush_newline_if_pending(src)
|
72
|
+
if @newline_pending > 0
|
73
|
+
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
|
74
|
+
@newline_pending = 0
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Spektr
|
2
|
+
module Exp
|
3
|
+
module Assignment
|
4
|
+
def user_input?
|
5
|
+
if ast.children[1].type == :send
|
6
|
+
_send = Send.new(ast.children[1])
|
7
|
+
name = if _send.receiver && _send.receiver.type == :send
|
8
|
+
_send.receiver.name
|
9
|
+
else
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
if [:params, :cookies, :request].include? name
|
13
|
+
return true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Spektr
|
2
|
+
module Exp
|
3
|
+
class Base
|
4
|
+
attr_accessor :ast, :name, :type, :options, :arguments, :body, :location
|
5
|
+
|
6
|
+
def initialize(ast)
|
7
|
+
@ast = ast
|
8
|
+
@type = ast.type
|
9
|
+
@location = ast.location
|
10
|
+
@name = ast.children.first
|
11
|
+
@options = {}
|
12
|
+
@arguments = []
|
13
|
+
@body = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def send?
|
17
|
+
is_a? Send
|
18
|
+
end
|
19
|
+
|
20
|
+
include AST::Processor::Mixin
|
21
|
+
|
22
|
+
def process(ast)
|
23
|
+
return unless ast.respond_to?(:to_ast)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def handler_missing(node)
|
28
|
+
# puts "handler missing for #{node.type}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Spektr
|
2
|
+
module Exp
|
3
|
+
class Definition < Base
|
4
|
+
attr_accessor :private, :protected
|
5
|
+
|
6
|
+
def initialize(ast)
|
7
|
+
super
|
8
|
+
process @ast.children
|
9
|
+
end
|
10
|
+
|
11
|
+
def process(ast)
|
12
|
+
ast&.each do |node|
|
13
|
+
next unless Parser::AST::Node === node
|
14
|
+
case node.type
|
15
|
+
when :args
|
16
|
+
node.children.each do |argument|
|
17
|
+
@arguments << argument.children.first
|
18
|
+
end
|
19
|
+
when :begin
|
20
|
+
process(node.children)
|
21
|
+
when :lvasgn
|
22
|
+
@body << Lvasign.new(node)
|
23
|
+
when :ivasgn
|
24
|
+
@body << Ivasign.new(node)
|
25
|
+
when :send
|
26
|
+
@body << Send.new(node)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|