better_html 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|