better_html 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/better_html/ast/iterator.rb +3 -3
- data/lib/better_html/ast/node.rb +6 -2
- data/lib/better_html/errors.rb +2 -11
- data/lib/better_html/test_helper/ruby_node.rb +103 -0
- data/lib/better_html/test_helper/safe_erb/allowed_script_type.rb +29 -0
- data/lib/better_html/test_helper/safe_erb/base.rb +56 -0
- data/lib/better_html/test_helper/safe_erb/no_javascript_tag_helper.rb +34 -0
- data/lib/better_html/test_helper/safe_erb/no_statements.rb +40 -0
- data/lib/better_html/test_helper/safe_erb/script_interpolation.rb +65 -0
- data/lib/better_html/test_helper/safe_erb/tag_interpolation.rb +163 -0
- data/lib/better_html/test_helper/safe_erb_tester.rb +32 -285
- data/lib/better_html/tokenizer/location.rb +1 -1
- data/lib/better_html/version.rb +1 -1
- data/test/better_html/errors_test.rb +13 -0
- data/test/better_html/test_helper/ruby_node_test.rb +288 -0
- data/test/better_html/test_helper/safe_erb/allowed_script_type_test.rb +45 -0
- data/test/better_html/test_helper/safe_erb/no_javascript_tag_helper_test.rb +37 -0
- data/test/better_html/test_helper/safe_erb/no_statements_test.rb +128 -0
- data/test/better_html/test_helper/safe_erb/script_interpolation_test.rb +149 -0
- data/test/better_html/test_helper/safe_erb/tag_interpolation_test.rb +295 -0
- data/test/test_helper.rb +1 -0
- metadata +23 -7
- data/lib/better_html/test_helper/ruby_expr.rb +0 -117
- data/test/better_html/test_helper/ruby_expr_test.rb +0 -283
- data/test/better_html/test_helper/safe_erb_tester_test.rb +0 -450
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9b3e15933b5788d810377d49068228464e17fa3
|
4
|
+
data.tar.gz: 18c03ea104454fec79e3a9dd8cf29d5ec83f8adb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f93049ef7e686b1c1419d05f86c6240bed8e18016b2e186b6b813d9760fd0662b6db83de4ba5cf18e9b1d4c0716ae358cf25d49b4e0f695ac9866b0506e527e4
|
7
|
+
data.tar.gz: f6c7e9af3e8f72269321f954982313fcf907b44a40a97fd3509239d73252b0c87d418e677cb8e7392789b7f658bb73f556a43ffdc18ab4a76d83fc38cbf8f8dd
|
@@ -5,13 +5,13 @@ module BetterHtml
|
|
5
5
|
module AST
|
6
6
|
class Iterator
|
7
7
|
def initialize(types, &block)
|
8
|
-
@types = Array.wrap(types)
|
8
|
+
@types = types.nil? ? nil : Array.wrap(types)
|
9
9
|
@block = block
|
10
10
|
end
|
11
11
|
|
12
12
|
def traverse(node)
|
13
13
|
return unless node.is_a?(::AST::Node)
|
14
|
-
@block.call(node) if @types.include?(node.type)
|
14
|
+
@block.call(node) if @types.nil? || @types.include?(node.type)
|
15
15
|
traverse_all(node)
|
16
16
|
end
|
17
17
|
|
@@ -21,7 +21,7 @@ module BetterHtml
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
def self.descendants(root_node, type
|
24
|
+
def self.descendants(root_node, type)
|
25
25
|
Enumerator.new do |yielder|
|
26
26
|
t = new(type) { |node| yielder << node }
|
27
27
|
t.traverse(root_node)
|
data/lib/better_html/ast/node.rb
CHANGED
@@ -6,8 +6,12 @@ module BetterHtml
|
|
6
6
|
class Node < ::AST::Node
|
7
7
|
attr_reader :loc
|
8
8
|
|
9
|
-
def descendants(
|
10
|
-
AST::Iterator.descendants(self,
|
9
|
+
def descendants(*types)
|
10
|
+
AST::Iterator.descendants(self, types)
|
11
|
+
end
|
12
|
+
|
13
|
+
def location
|
14
|
+
loc
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|
data/lib/better_html/errors.rb
CHANGED
@@ -7,16 +7,7 @@ module BetterHtml
|
|
7
7
|
class UnsafeHtmlError < InterpolatorError; end
|
8
8
|
class HtmlError < RuntimeError; end
|
9
9
|
|
10
|
-
class Errors
|
11
|
-
|
12
|
-
:empty?, :any?, :present?, to: :@errors
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
@errors = []
|
16
|
-
end
|
17
|
-
|
18
|
-
def add(error)
|
19
|
-
@errors << error
|
20
|
-
end
|
10
|
+
class Errors < Array
|
11
|
+
alias_method :add, :<<
|
21
12
|
end
|
22
13
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
|
3
|
+
module BetterHtml
|
4
|
+
module TestHelper
|
5
|
+
class RubyNode < BetterHtml::AST::Node
|
6
|
+
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
7
|
+
|
8
|
+
class ParseError < RuntimeError; end
|
9
|
+
|
10
|
+
class Builder < ::Parser::Builders::Default
|
11
|
+
def n(type, children, source_map)
|
12
|
+
BetterHtml::TestHelper::RubyNode.new(type, children, loc: source_map)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse(code)
|
17
|
+
parser = ::Parser::CurrentRuby.new(Builder.new)
|
18
|
+
parser.diagnostics.ignore_warnings = true
|
19
|
+
parser.diagnostics.all_errors_are_fatal = false
|
20
|
+
parser.diagnostics.consumer = nil
|
21
|
+
|
22
|
+
buf = ::Parser::Source::Buffer.new('(string)')
|
23
|
+
buf.source = code.sub(BLOCK_EXPR, '')
|
24
|
+
parser.parse(buf)
|
25
|
+
end
|
26
|
+
|
27
|
+
def child_nodes
|
28
|
+
children.select { |child| node?(child) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def node?(current)
|
32
|
+
current.is_a?(self.class)
|
33
|
+
end
|
34
|
+
|
35
|
+
def type?(wanted_type)
|
36
|
+
Array.wrap(wanted_type).include?(type)
|
37
|
+
end
|
38
|
+
|
39
|
+
STATIC_TYPES = [:str, :int, :true, :false, :nil]
|
40
|
+
|
41
|
+
def static_value?
|
42
|
+
type?(STATIC_TYPES) ||
|
43
|
+
(type?(:dstr) && !children.any? { |child| !child.type?(:str) })
|
44
|
+
end
|
45
|
+
|
46
|
+
def return_values
|
47
|
+
Enumerator.new do |yielder|
|
48
|
+
case type
|
49
|
+
when :send, :csend, :ivar, *STATIC_TYPES
|
50
|
+
yielder.yield(self)
|
51
|
+
when :if, :masgn, :lvasgn
|
52
|
+
# first child is ignored as it does not contain return values
|
53
|
+
# for example, in `foo ? x : y` we only care about x and y, not foo
|
54
|
+
children[1..-1].each do |child|
|
55
|
+
child.return_values.each { |v| yielder.yield(v) } if node?(child)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
child_nodes.each do |child|
|
59
|
+
child.return_values.each { |v| yielder.yield(v) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def static_return_value?
|
66
|
+
return false if (possible_values = return_values.to_a).empty?
|
67
|
+
possible_values.all?(&:static_value?)
|
68
|
+
end
|
69
|
+
|
70
|
+
def method_call?
|
71
|
+
[:send, :csend].include?(type)
|
72
|
+
end
|
73
|
+
|
74
|
+
def hash?
|
75
|
+
type?(:hash)
|
76
|
+
end
|
77
|
+
|
78
|
+
def pair?
|
79
|
+
type?(:pair)
|
80
|
+
end
|
81
|
+
|
82
|
+
def begin?
|
83
|
+
type?(:begin)
|
84
|
+
end
|
85
|
+
|
86
|
+
def method_name
|
87
|
+
children[1] if method_call?
|
88
|
+
end
|
89
|
+
|
90
|
+
def arguments
|
91
|
+
children[2..-1] if method_call?
|
92
|
+
end
|
93
|
+
|
94
|
+
def receiver
|
95
|
+
children[0] if method_call?
|
96
|
+
end
|
97
|
+
|
98
|
+
def method_name?(name)
|
99
|
+
method_call? && method_name == name
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module BetterHtml
|
4
|
+
module TestHelper
|
5
|
+
module SafeErb
|
6
|
+
class AllowedScriptType < Base
|
7
|
+
VALID_JAVASCRIPT_TAG_TYPES = ['text/javascript', 'text/template', 'text/html']
|
8
|
+
|
9
|
+
def validate
|
10
|
+
script_tags.each do |tag, _|
|
11
|
+
validate_type(tag)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validate_type(tag)
|
18
|
+
return unless type_attribute = tag.attributes['type']
|
19
|
+
return if VALID_JAVASCRIPT_TAG_TYPES.include?(type_attribute.value)
|
20
|
+
|
21
|
+
add_error(
|
22
|
+
"#{type_attribute.value} is not a valid type, valid types are #{VALID_JAVASCRIPT_TAG_TYPES.join(', ')}",
|
23
|
+
location: type_attribute.loc
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'better_html/errors'
|
2
|
+
require 'better_html/tree/tag'
|
3
|
+
require 'better_html/test_helper/safety_error'
|
4
|
+
require 'ast'
|
5
|
+
|
6
|
+
module BetterHtml
|
7
|
+
module TestHelper
|
8
|
+
module SafeErb
|
9
|
+
class Base
|
10
|
+
attr_reader :errors
|
11
|
+
|
12
|
+
def initialize(parser, config: BetterHtml.config)
|
13
|
+
@parser = parser
|
14
|
+
@config = config
|
15
|
+
@errors = BetterHtml::Errors.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_error(message, location:)
|
19
|
+
@errors.add(SafetyError.new(message, location: location))
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def erb_nodes(root_node)
|
25
|
+
Enumerator.new do |yielder|
|
26
|
+
next if root_node.nil?
|
27
|
+
root_node.descendants(:erb).each do |erb_node|
|
28
|
+
indicator_node, _, code_node, _ = *erb_node
|
29
|
+
yielder.yield(erb_node, indicator_node, code_node)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def script_tags
|
35
|
+
Enumerator.new do |yielder|
|
36
|
+
@parser.nodes_with_type(:tag).each do |tag_node|
|
37
|
+
tag = Tree::Tag.from_node(tag_node)
|
38
|
+
next if tag.closing?
|
39
|
+
|
40
|
+
if tag.name == 'script'
|
41
|
+
index = ast.to_a.find_index(tag_node)
|
42
|
+
next_node = ast.to_a[index + 1]
|
43
|
+
|
44
|
+
yielder.yield(tag, next_node.type == :text ? next_node : nil)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def ast
|
51
|
+
@parser.ast
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require 'better_html/test_helper/ruby_node'
|
3
|
+
|
4
|
+
module BetterHtml
|
5
|
+
module TestHelper
|
6
|
+
module SafeErb
|
7
|
+
class NoJavascriptTagHelper < Base
|
8
|
+
def validate
|
9
|
+
no_javascript_tag_helper(ast)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def no_javascript_tag_helper(node)
|
15
|
+
erb_nodes(node).each do |erb_node, indicator_node, code_node|
|
16
|
+
indicator = indicator_node&.loc&.source
|
17
|
+
next if indicator == '#'
|
18
|
+
source = code_node.loc.source
|
19
|
+
|
20
|
+
next unless (ruby_node = RubyNode.parse(source))
|
21
|
+
ruby_node.descendants(:send, :csend).each do |send_node|
|
22
|
+
next unless send_node.method_name?(:javascript_tag)
|
23
|
+
|
24
|
+
add_error(
|
25
|
+
"'javascript_tag do' syntax is deprecated; use inline <script> instead",
|
26
|
+
location: erb_node.loc,
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module BetterHtml
|
4
|
+
module TestHelper
|
5
|
+
module SafeErb
|
6
|
+
class NoStatements < Base
|
7
|
+
def validate
|
8
|
+
script_tags.each do |tag, content_node|
|
9
|
+
no_statements(content_node) unless content_node.present? && tag.attributes['type']&.value == "text/html"
|
10
|
+
end
|
11
|
+
|
12
|
+
if @parser.template_language == :javascript
|
13
|
+
@parser.nodes_with_type(:text).each do |node|
|
14
|
+
no_statements(node)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
@parser.nodes_with_type(:cdata, :comment).each do |node|
|
19
|
+
no_statements(node)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def no_statements(node)
|
26
|
+
erb_nodes(node).each do |erb_node, indicator_node, code_node|
|
27
|
+
next unless indicator_node.nil?
|
28
|
+
source = code_node.loc.source
|
29
|
+
next if /\A\s*end/m === source
|
30
|
+
|
31
|
+
add_error(
|
32
|
+
"erb statement not allowed here; did you mean '<%=' ?",
|
33
|
+
location: erb_node.loc,
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require 'better_html/test_helper/ruby_node'
|
3
|
+
|
4
|
+
module BetterHtml
|
5
|
+
module TestHelper
|
6
|
+
module SafeErb
|
7
|
+
class ScriptInterpolation < Base
|
8
|
+
def validate
|
9
|
+
script_tags.each do |tag, content_node|
|
10
|
+
if content_node.present? && (tag.attributes['type']&.value || "text/javascript") == "text/javascript"
|
11
|
+
validate_script(content_node)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if @parser.template_language == :javascript
|
16
|
+
@parser.nodes_with_type(:text).each do |node|
|
17
|
+
validate_script(node)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def validate_script(node)
|
25
|
+
erb_nodes(node).each do |erb_node, indicator_node, code_node|
|
26
|
+
next unless indicator_node.present?
|
27
|
+
indicator = indicator_node.loc.source
|
28
|
+
next if indicator == '#'
|
29
|
+
source = code_node.loc.source
|
30
|
+
|
31
|
+
next unless (ruby_node = RubyNode.parse(source))
|
32
|
+
validate_script_interpolation(erb_node, ruby_node)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_script_interpolation(parent_node, ruby_node)
|
37
|
+
method_calls = ruby_node.return_values.select(&:method_call?)
|
38
|
+
|
39
|
+
if method_calls.empty?
|
40
|
+
add_error(
|
41
|
+
"erb interpolation in javascript tag must call '(...).to_json'",
|
42
|
+
location: parent_node.loc,
|
43
|
+
)
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
method_calls.each do |call_node|
|
48
|
+
if call_node.method_name?(:raw)
|
49
|
+
call_node.arguments.each do |argument_node|
|
50
|
+
validate_script_interpolation(parent_node, argument_node)
|
51
|
+
end
|
52
|
+
elsif call_node.method_name?(:html_safe)
|
53
|
+
validate_script_interpolation(parent_node, call_node.receiver)
|
54
|
+
elsif !@config.javascript_safe_method?(call_node.method_name)
|
55
|
+
add_error(
|
56
|
+
"erb interpolation in javascript tag must call '(...).to_json'",
|
57
|
+
location: parent_node.loc,
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require 'better_html/test_helper/ruby_node'
|
3
|
+
|
4
|
+
module BetterHtml
|
5
|
+
module TestHelper
|
6
|
+
module SafeErb
|
7
|
+
class TagInterpolation < Base
|
8
|
+
def validate
|
9
|
+
@parser.nodes_with_type(:tag).each do |tag_node|
|
10
|
+
tag = Tree::Tag.from_node(tag_node)
|
11
|
+
tag.attributes.each do |attribute|
|
12
|
+
validate_attribute(attribute)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@parser.nodes_with_type(:text).each do |node|
|
17
|
+
validate_text_node(node) unless in_script_tag?(node)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def in_script_tag?(node)
|
24
|
+
ast = @parser.ast.to_a
|
25
|
+
index = ast.find_index(node)
|
26
|
+
return unless (previous_node = ast[index - 1])
|
27
|
+
return unless previous_node.type == :tag
|
28
|
+
|
29
|
+
tag = BetterHtml::Tree::Tag.from_node(previous_node)
|
30
|
+
tag.name == "script" && !tag.closing?
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_attribute(attribute)
|
34
|
+
erb_nodes(attribute.value_node).each do |erb_node, indicator_node, code_node|
|
35
|
+
next if indicator_node.nil?
|
36
|
+
|
37
|
+
indicator = indicator_node.loc.source
|
38
|
+
source = code_node.loc.source
|
39
|
+
|
40
|
+
if indicator == '='
|
41
|
+
if (ruby_node = RubyNode.parse(source))
|
42
|
+
no_unsafe_calls(code_node, ruby_node)
|
43
|
+
unless ruby_node.static_return_value?
|
44
|
+
handle_missing_safe_wrapper(code_node, ruby_node, attribute.name)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
elsif indicator == '=='
|
48
|
+
add_error(
|
49
|
+
"erb interpolation with '<%==' inside html attribute is never safe",
|
50
|
+
location: erb_node.loc
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_text_node(text_node)
|
57
|
+
erb_nodes(text_node).each do |erb_node, indicator_node, code_node|
|
58
|
+
indicator = indicator_node&.loc&.source
|
59
|
+
next if indicator == '#'
|
60
|
+
source = code_node.loc.source
|
61
|
+
|
62
|
+
next unless (ruby_node = RubyNode.parse(source))
|
63
|
+
no_unsafe_calls(code_node, ruby_node)
|
64
|
+
validate_ruby_helper(code_node, ruby_node)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_ruby_helper(parent_node, ruby_node)
|
69
|
+
ruby_node.descendants(:send, :csend).each do |send_node|
|
70
|
+
send_node.descendants(:hash).each do |hash_node|
|
71
|
+
hash_node.child_nodes.select(&:pair?).each do |pair_node|
|
72
|
+
validate_ruby_helper_hash_entry(parent_node, ruby_node, nil, *pair_node.children)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_ruby_helper_hash_entry(parent_node, ruby_node, key_prefix, key_node, value_node)
|
79
|
+
return unless [:sym, :str].include?(key_node.type)
|
80
|
+
key = [key_prefix, key_node.children.first.to_s].compact.join('-').dasherize
|
81
|
+
case value_node.type
|
82
|
+
when :dstr
|
83
|
+
validate_ruby_helper_hash_value(parent_node, ruby_node, key, value_node)
|
84
|
+
when :hash
|
85
|
+
if key == 'data'
|
86
|
+
value_node.child_nodes.select(&:pair?).each do |pair_node|
|
87
|
+
validate_ruby_helper_hash_entry(parent_node, ruby_node, key, *pair_node.children)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate_ruby_helper_hash_value(parent_node, ruby_node, attr_name, hash_value)
|
94
|
+
hash_value.child_nodes.select(&:begin?).each do |begin_node|
|
95
|
+
validate_tag_interpolation(parent_node, begin_node, attr_name)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def handle_missing_safe_wrapper(parent_node, ruby_node, attr_name)
|
100
|
+
return unless @config.javascript_attribute_name?(attr_name)
|
101
|
+
method_calls = ruby_node.return_values.select(&:method_call?)
|
102
|
+
unsafe_calls = method_calls.select { |node| !@config.javascript_safe_method?(node.method_name) }
|
103
|
+
if method_calls.empty?
|
104
|
+
add_error(
|
105
|
+
"erb interpolation in javascript attribute must be wrapped in safe helper such as '(...).to_json'",
|
106
|
+
location: nested_location(parent_node, ruby_node)
|
107
|
+
)
|
108
|
+
true
|
109
|
+
elsif unsafe_calls.any?
|
110
|
+
unsafe_calls.each do |call_node|
|
111
|
+
add_error(
|
112
|
+
"erb interpolation in javascript attribute must be wrapped in safe helper such as '(...).to_json'",
|
113
|
+
location: nested_location(parent_node, call_node)
|
114
|
+
)
|
115
|
+
end
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate_tag_interpolation(parent_node, ruby_node, attr_name)
|
121
|
+
return if ruby_node.static_return_value?
|
122
|
+
return if handle_missing_safe_wrapper(parent_node, ruby_node, attr_name)
|
123
|
+
|
124
|
+
ruby_node.return_values.each do |call_node|
|
125
|
+
next if call_node.static_return_value?
|
126
|
+
|
127
|
+
if @config.javascript_attribute_name?(attr_name) &&
|
128
|
+
!@config.javascript_safe_method?(call_node.method_name)
|
129
|
+
add_error(
|
130
|
+
"erb interpolation in javascript attribute must be wrapped in safe helper such as '(...).to_json'",
|
131
|
+
location: nested_location(parent_node, ruby_node)
|
132
|
+
)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def no_unsafe_calls(parent_node, ruby_node)
|
138
|
+
ruby_node.descendants(:send, :csend).each do |call|
|
139
|
+
if call.method_name?(:raw)
|
140
|
+
add_error(
|
141
|
+
"erb interpolation with '<%= raw(...) %>' in this context is never safe",
|
142
|
+
location: nested_location(parent_node, ruby_node)
|
143
|
+
)
|
144
|
+
elsif call.method_name?(:html_safe)
|
145
|
+
add_error(
|
146
|
+
"erb interpolation with '<%= (...).html_safe %>' in this context is never safe",
|
147
|
+
location: nested_location(parent_node, ruby_node)
|
148
|
+
)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def nested_location(parent_node, ruby_node)
|
154
|
+
Tokenizer::Location.new(
|
155
|
+
parent_node.loc.document,
|
156
|
+
parent_node.loc.start + ruby_node.loc.expression.begin_pos,
|
157
|
+
parent_node.loc.start + ruby_node.loc.expression.end_pos - 1
|
158
|
+
)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|