better_html 0.0.9 → 0.0.10
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.rb +7 -35
- data/lib/better_html/better_erb.rb +1 -1
- data/lib/better_html/better_erb/runtime_checks.rb +9 -8
- data/lib/better_html/config.rb +16 -0
- data/lib/better_html/node_iterator/location.rb +6 -3
- data/lib/better_html/test_helper/safe_erb_tester.rb +13 -11
- data/lib/better_html/test_helper/safe_lodash_tester.rb +6 -5
- data/lib/better_html/version.rb +1 -1
- data/test/better_html/better_erb/implementation_test.rb +52 -48
- data/test/better_html/test_helper/safe_erb_tester_test.rb +13 -7
- metadata +31 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6246bfb4cf4ae99f87a12370d99b362b9b2737b
|
4
|
+
data.tar.gz: 67a8747a32552d237bacafe3cc782dc7b1815ffc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 07c548e1027d5f582ee796df6b536bd6f895edcd42c3c2898d0eb1da61c9adffaff6683bf83cbfee3e59d0f2acb2ed70c3371606cb772b39b3a8c4df30a75d8a
|
7
|
+
data.tar.gz: d5168282725b6e5ba210eae30eefcfa88b8270063422f0127be7f53cc888dfa1470c9a82db09971d2b9ec540024a28f57337cf9d4e092afb5aa86c092f9aef0a
|
data/lib/better_html.rb
CHANGED
@@ -3,49 +3,21 @@ require 'active_support/core_ext/module/delegation'
|
|
3
3
|
require 'active_support/core_ext/class/attribute_accessors'
|
4
4
|
|
5
5
|
module BetterHtml
|
6
|
-
|
7
|
-
|
8
|
-
cattr_accessor :partial_tag_name_pattern
|
9
|
-
self.partial_tag_name_pattern = /\A[a-z0-9\-\:]+\z/
|
10
|
-
|
11
|
-
# regex to validate "bar" in "<foo bar=1>"
|
12
|
-
cattr_accessor :partial_attribute_name_pattern
|
13
|
-
self.partial_attribute_name_pattern = /\A[a-zA-Z0-9\-\:]+\z/
|
14
|
-
|
15
|
-
# true if "<foo bar='1'>" is valid syntax
|
16
|
-
cattr_accessor :allow_single_quoted_attributes
|
17
|
-
self.allow_single_quoted_attributes = false
|
18
|
-
|
19
|
-
# true if "<foo bar=1>" is valid syntax
|
20
|
-
cattr_accessor :allow_unquoted_attributes
|
21
|
-
self.allow_unquoted_attributes = false
|
22
|
-
|
23
|
-
# all methods that return "javascript-safe" strings
|
24
|
-
cattr_accessor :javascript_safe_methods
|
25
|
-
self.javascript_safe_methods = ['to_json']
|
26
|
-
|
27
|
-
# name of all html attributes that may contain javascript
|
28
|
-
cattr_accessor :javascript_attribute_names
|
29
|
-
self.javascript_attribute_names = [/\Aon/i]
|
30
|
-
|
31
|
-
cattr_accessor :template_exclusion_filter_block
|
32
|
-
|
33
|
-
def self.template_exclusion_filter(&block)
|
34
|
-
self.template_exclusion_filter_block = block
|
35
|
-
end
|
36
|
-
|
37
|
-
cattr_accessor :lodash_safe_javascript_expression
|
38
|
-
self.lodash_safe_javascript_expression = [/\AJSON\.stringify\(/]
|
6
|
+
def self.configure
|
7
|
+
yield config if block_given?
|
39
8
|
end
|
40
9
|
|
41
10
|
def self.config
|
42
11
|
@config ||= Config.new
|
43
|
-
|
44
|
-
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.config=(new_config)
|
15
|
+
@config = new_config
|
45
16
|
end
|
46
17
|
end
|
47
18
|
|
48
19
|
require 'better_html/version'
|
20
|
+
require 'better_html/config'
|
49
21
|
require 'better_html/helpers'
|
50
22
|
require 'better_html/errors'
|
51
23
|
require 'better_html/html_attributes'
|
@@ -52,7 +52,7 @@ class BetterHtml::BetterErb
|
|
52
52
|
# Always make sure we return a String in the default_internal
|
53
53
|
erb.encode!
|
54
54
|
|
55
|
-
excluded_template = !!BetterHtml
|
55
|
+
excluded_template = !!BetterHtml.config.template_exclusion_filter_block&.call(template.identifier)
|
56
56
|
klass = BetterHtml::BetterErb.content_types[exts] unless excluded_template
|
57
57
|
klass ||= self.class.erb_implementation
|
58
58
|
|
@@ -3,9 +3,10 @@ require 'action_view'
|
|
3
3
|
|
4
4
|
class BetterHtml::BetterErb
|
5
5
|
module RuntimeChecks
|
6
|
-
def initialize(
|
6
|
+
def initialize(erb, config: BetterHtml.config, **options)
|
7
7
|
@parser = HtmlTokenizer::Parser.new
|
8
|
-
|
8
|
+
@config = config
|
9
|
+
super(erb, **options)
|
9
10
|
end
|
10
11
|
|
11
12
|
def validate!
|
@@ -112,26 +113,26 @@ class BetterHtml::BetterErb
|
|
112
113
|
def check_tag_name(type, start, stop, line, column)
|
113
114
|
text = @parser.extract(start, stop)
|
114
115
|
return if text.upcase == "!DOCTYPE"
|
115
|
-
return if
|
116
|
+
return if @config.partial_tag_name_pattern === text
|
116
117
|
|
117
118
|
s = "Invalid tag name #{text.inspect} does not match "\
|
118
|
-
"regular expression #{
|
119
|
+
"regular expression #{@config.partial_tag_name_pattern.inspect}\n"
|
119
120
|
s << build_location(line, column, text.size)
|
120
121
|
raise BetterHtml::HtmlError, s
|
121
122
|
end
|
122
123
|
|
123
124
|
def check_attribute_name(type, start, stop, line, column)
|
124
125
|
text = @parser.extract(start, stop)
|
125
|
-
return if
|
126
|
+
return if @config.partial_attribute_name_pattern === text
|
126
127
|
|
127
128
|
s = "Invalid attribute name #{text.inspect} does not match "\
|
128
|
-
"regular expression #{
|
129
|
+
"regular expression #{@config.partial_attribute_name_pattern.inspect}\n"
|
129
130
|
s << build_location(line, column, text.size)
|
130
131
|
raise BetterHtml::HtmlError, s
|
131
132
|
end
|
132
133
|
|
133
134
|
def check_quoted_value(type, start, stop, line, column)
|
134
|
-
return if
|
135
|
+
return if @config.allow_single_quoted_attributes
|
135
136
|
text = @parser.extract(start, stop)
|
136
137
|
return if text == '"'
|
137
138
|
|
@@ -141,7 +142,7 @@ class BetterHtml::BetterErb
|
|
141
142
|
end
|
142
143
|
|
143
144
|
def check_unquoted_value(type, start, stop, line, column)
|
144
|
-
return if
|
145
|
+
return if @config.allow_unquoted_attributes
|
145
146
|
s = "Unquoted attribute values are not allowed\n"
|
146
147
|
s << build_location(line, column, stop-start)
|
147
148
|
raise BetterHtml::HtmlError, s
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'smart_properties'
|
2
|
+
|
3
|
+
module BetterHtml
|
4
|
+
class Config
|
5
|
+
include SmartProperties
|
6
|
+
|
7
|
+
property :partial_tag_name_pattern, default: /\A[a-z0-9\-\:]+\z/
|
8
|
+
property :partial_attribute_name_pattern, default: /\A[a-zA-Z0-9\-\:]+\z/
|
9
|
+
property :allow_single_quoted_attributes, default: true
|
10
|
+
property :allow_unquoted_attributes, default: false
|
11
|
+
property :javascript_safe_methods, default: ['to_json']
|
12
|
+
property :javascript_attribute_names, default: [/\Aon/i]
|
13
|
+
property :template_exclusion_filter
|
14
|
+
property :lodash_safe_javascript_expression, default: [/\AJSON\.stringify\(/]
|
15
|
+
end
|
16
|
+
end
|
@@ -28,10 +28,9 @@ module BetterHtml
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def line_source_with_underline
|
31
|
-
line_content =
|
32
|
-
line_content = line_content.nil? ? "" : line_content.gsub(/\n$/, '')
|
31
|
+
line_content = extract_line(line: line)
|
33
32
|
spaces = line_content.scan(/\A\s*/).first
|
34
|
-
column_without_spaces = column - spaces.length
|
33
|
+
column_without_spaces = [column - spaces.length, 0].max
|
35
34
|
underscore_length = [[stop - start, line_content.length - column_without_spaces].min, 1].max
|
36
35
|
"#{line_content.gsub(/\A\s*/, '')}\n#{' ' * column_without_spaces}#{'^' * underscore_length}"
|
37
36
|
end
|
@@ -45,6 +44,10 @@ module BetterHtml
|
|
45
44
|
def calculate_column
|
46
45
|
@document[0..start-1]&.split("\n", -1)&.last&.length || 0
|
47
46
|
end
|
47
|
+
|
48
|
+
def extract_line(line:)
|
49
|
+
@document.split("\n", -1)[line - 1]&.gsub(/\n$/, '') || ""
|
50
|
+
end
|
48
51
|
end
|
49
52
|
end
|
50
53
|
end
|
@@ -52,8 +52,9 @@ EOF
|
|
52
52
|
|
53
53
|
VALID_JAVASCRIPT_TAG_TYPES = ['text/javascript', 'text/template', 'text/html']
|
54
54
|
|
55
|
-
def initialize(data, **options)
|
55
|
+
def initialize(data, config: BetterHtml.config, **options)
|
56
56
|
@data = data
|
57
|
+
@config = config
|
57
58
|
@errors = Errors.new
|
58
59
|
@options = options.present? ? options.dup : {}
|
59
60
|
@options[:template_language] ||= :html
|
@@ -228,11 +229,11 @@ EOF
|
|
228
229
|
end
|
229
230
|
|
230
231
|
def javascript_attribute_name?(name)
|
231
|
-
|
232
|
+
@config.javascript_attribute_names.any?{ |other| other === name.to_s }
|
232
233
|
end
|
233
234
|
|
234
235
|
def javascript_safe_method?(name)
|
235
|
-
|
236
|
+
@config.javascript_safe_methods.include?(name.to_s)
|
236
237
|
end
|
237
238
|
|
238
239
|
def validate_script_tag_content(node)
|
@@ -240,19 +241,20 @@ EOF
|
|
240
241
|
case token.type
|
241
242
|
when :expr_literal, :expr_escaped
|
242
243
|
expr = RubyExpr.parse(token.code)
|
243
|
-
|
244
|
-
add_error(
|
245
|
-
"erb interpolation in javascript tag must call '(...).to_json'",
|
246
|
-
location: token.location,
|
247
|
-
)
|
248
|
-
else
|
249
|
-
validate_script_expression(node, token, expr)
|
250
|
-
end
|
244
|
+
validate_script_expression(node, token, expr)
|
251
245
|
end
|
252
246
|
end
|
253
247
|
end
|
254
248
|
|
255
249
|
def validate_script_expression(node, token, expr)
|
250
|
+
if expr.calls.empty?
|
251
|
+
add_error(
|
252
|
+
"erb interpolation in javascript tag must call '(...).to_json'",
|
253
|
+
location: token.location,
|
254
|
+
)
|
255
|
+
return
|
256
|
+
end
|
257
|
+
|
256
258
|
expr.calls.each do |call|
|
257
259
|
if call.method == :raw
|
258
260
|
call.arguments.each do |argument_node|
|
@@ -25,8 +25,8 @@ Never use <script> tags inside lodash template.
|
|
25
25
|
-----------
|
26
26
|
EOF
|
27
27
|
|
28
|
-
def assert_lodash_safety(data)
|
29
|
-
tester = Tester.new(data)
|
28
|
+
def assert_lodash_safety(data, **options)
|
29
|
+
tester = Tester.new(data, **options)
|
30
30
|
|
31
31
|
message = ""
|
32
32
|
tester.errors.each do |error|
|
@@ -47,8 +47,9 @@ EOF
|
|
47
47
|
class Tester
|
48
48
|
attr_reader :errors
|
49
49
|
|
50
|
-
def initialize(data)
|
50
|
+
def initialize(data, config: BetterHtml.config)
|
51
51
|
@data = data
|
52
|
+
@config = config
|
52
53
|
@errors = Errors.new
|
53
54
|
@nodes = BetterHtml::NodeIterator.new(data, template_language: :lodash)
|
54
55
|
validate!
|
@@ -109,11 +110,11 @@ EOF
|
|
109
110
|
end
|
110
111
|
|
111
112
|
def javascript_attribute_name?(name)
|
112
|
-
|
113
|
+
@config.javascript_attribute_names.any?{ |other| other === name }
|
113
114
|
end
|
114
115
|
|
115
116
|
def lodash_safe_javascript_expression?(code)
|
116
|
-
|
117
|
+
@config.lodash_safe_javascript_expression.any?{ |other| other === code }
|
117
118
|
end
|
118
119
|
|
119
120
|
def validate_no_statements(node)
|
data/lib/better_html/version.rb
CHANGED
@@ -6,36 +6,36 @@ require 'json'
|
|
6
6
|
class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
7
7
|
test "simple template rendering" do
|
8
8
|
assert_equal "<foo>some value<foo>",
|
9
|
-
render("<foo><%= bar %><foo>", { bar: 'some value' })
|
9
|
+
render("<foo><%= bar %><foo>", locals: { bar: 'some value' })
|
10
10
|
end
|
11
11
|
|
12
12
|
test "html_safe interpolation" do
|
13
13
|
assert_equal "<foo><bar /><foo>",
|
14
|
-
render("<foo><%= bar %><foo>", { bar: '<bar />'.html_safe })
|
14
|
+
render("<foo><%= bar %><foo>", locals: { bar: '<bar />'.html_safe })
|
15
15
|
end
|
16
16
|
|
17
17
|
test "non html_safe interpolation" do
|
18
18
|
assert_equal "<foo><bar /><foo>",
|
19
|
-
render("<foo><%= bar %><foo>", { bar: '<bar />' })
|
19
|
+
render("<foo><%= bar %><foo>", locals: { bar: '<bar />' })
|
20
20
|
end
|
21
21
|
|
22
22
|
test "interpolate non-html_safe inside attribute is escaped" do
|
23
23
|
assert_equal "<a href=\" '">x \">",
|
24
|
-
render("<a href=\"<%= value %>\">", { value: ' \'">x ' })
|
24
|
+
render("<a href=\"<%= value %>\">", locals: { value: ' \'">x ' })
|
25
25
|
end
|
26
26
|
|
27
27
|
test "interpolate html_safe inside attribute is magically force-escaped" do
|
28
28
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
29
|
-
render("<a href=\"<%= value %>\">", { value: ' \'">x '.html_safe })
|
29
|
+
render("<a href=\"<%= value %>\">", locals: { value: ' \'">x '.html_safe })
|
30
30
|
end
|
31
31
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
32
32
|
"into a quoted attribute value. The value cannot contain the character \".", e.message
|
33
33
|
end
|
34
34
|
|
35
35
|
test "interpolate html_safe inside single quoted attribute" do
|
36
|
-
|
36
|
+
config = build_config(allow_single_quoted_attributes: true)
|
37
37
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
38
|
-
render("<a href=\'<%= value %>\'>", { value: ' \'">x '.html_safe })
|
38
|
+
render("<a href=\'<%= value %>\'>", config: config, locals: { value: ' \'">x '.html_safe })
|
39
39
|
end
|
40
40
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
41
41
|
"into a quoted attribute value. The value cannot contain the character '.", e.message
|
@@ -43,12 +43,12 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
43
43
|
|
44
44
|
test "interpolate in attribute name" do
|
45
45
|
assert_equal "<a data-safe-foo>",
|
46
|
-
render("<a data-<%= value %>-foo>", { value: "safe" })
|
46
|
+
render("<a data-<%= value %>-foo>", locals: { value: "safe" })
|
47
47
|
end
|
48
48
|
|
49
49
|
test "interpolate in attribute name with unsafe value with spaces" do
|
50
50
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
51
|
-
render("<a data-<%= value %>-foo>", { value: "un safe" })
|
51
|
+
render("<a data-<%= value %>-foo>", locals: { value: "un safe" })
|
52
52
|
end
|
53
53
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
54
54
|
"into a attribute name around 'data-<%= value %>'.", e.message
|
@@ -56,7 +56,7 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
56
56
|
|
57
57
|
test "interpolate in attribute name with unsafe value with equal sign" do
|
58
58
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
59
|
-
render("<a data-<%= value %>-foo>", { value: "un=safe" })
|
59
|
+
render("<a data-<%= value %>-foo>", locals: { value: "un=safe" })
|
60
60
|
end
|
61
61
|
assert_equal "Detected invalid characters as part of the "\
|
62
62
|
"interpolation into a attribute name around 'data-<%= value %>'.", e.message
|
@@ -64,7 +64,7 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
64
64
|
|
65
65
|
test "interpolate in attribute name with unsafe value with quote" do
|
66
66
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
67
|
-
render("<a data-<%= value %>-foo>", { value: "un\"safe" })
|
67
|
+
render("<a data-<%= value %>-foo>", locals: { value: "un\"safe" })
|
68
68
|
end
|
69
69
|
assert_equal "Detected invalid characters as part of the "\
|
70
70
|
"interpolation into a attribute name around 'data-<%= value %>'.", e.message
|
@@ -76,9 +76,9 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
76
76
|
end
|
77
77
|
|
78
78
|
test "interpolate after an attribute name with equal sign" do
|
79
|
-
|
79
|
+
config = build_config(allow_unquoted_attributes: true)
|
80
80
|
e = assert_raises(BetterHtml::DontInterpolateHere) do
|
81
|
-
render("<a data-foo= <%= html_attributes(foo: 'bar') %>>")
|
81
|
+
render("<a data-foo= <%= html_attributes(foo: 'bar') %>>", config: config)
|
82
82
|
end
|
83
83
|
assert_equal "Do not interpolate without quotes after "\
|
84
84
|
"attribute around 'data-foo=<%= html_attributes(foo: 'bar') %>'.", e.message
|
@@ -94,18 +94,18 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
94
94
|
end
|
95
95
|
|
96
96
|
test "interpolate in attribute without quotes" do
|
97
|
-
|
97
|
+
config = build_config(allow_unquoted_attributes: true)
|
98
98
|
e = assert_raises(BetterHtml::DontInterpolateHere) do
|
99
|
-
render("<a href=<%= value %>>", { value: "un safe" })
|
99
|
+
render("<a href=<%= value %>>", config: config, locals: { value: "un safe" })
|
100
100
|
end
|
101
101
|
assert_equal "Do not interpolate without quotes after "\
|
102
102
|
"attribute around 'href=<%= value %>'.", e.message
|
103
103
|
end
|
104
104
|
|
105
105
|
test "interpolate in attribute after value" do
|
106
|
-
|
106
|
+
config = build_config(allow_unquoted_attributes: true)
|
107
107
|
e = assert_raises(BetterHtml::DontInterpolateHere) do
|
108
|
-
render("<a href=something<%= value %>>", { value: "" })
|
108
|
+
render("<a href=something<%= value %>>", config: config, locals: { value: "" })
|
109
109
|
end
|
110
110
|
assert_equal "Do not interpolate without quotes around this "\
|
111
111
|
"attribute value. Instead of <a href=something<%= value %>> "\
|
@@ -114,12 +114,12 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
114
114
|
|
115
115
|
test "interpolate in tag name" do
|
116
116
|
assert_equal "<tag-safe-foo>",
|
117
|
-
render("<tag-<%= value %>-foo>", { value: "safe" })
|
117
|
+
render("<tag-<%= value %>-foo>", locals: { value: "safe" })
|
118
118
|
end
|
119
119
|
|
120
120
|
test "interpolate in tag name with space" do
|
121
121
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
122
|
-
render("<tag-<%= value %>-foo>", { value: "un safe" })
|
122
|
+
render("<tag-<%= value %>-foo>", locals: { value: "un safe" })
|
123
123
|
end
|
124
124
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
125
125
|
"into a tag name around: <tag-<%= value %>>.", e.message
|
@@ -127,7 +127,7 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
127
127
|
|
128
128
|
test "interpolate in tag name with slash" do
|
129
129
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
130
|
-
render("<tag-<%= value %>-foo>", { value: "un/safe" })
|
130
|
+
render("<tag-<%= value %>-foo>", locals: { value: "un/safe" })
|
131
131
|
end
|
132
132
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
133
133
|
"into a tag name around: <tag-<%= value %>>.", e.message
|
@@ -135,7 +135,7 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
135
135
|
|
136
136
|
test "interpolate in tag name with end of tag" do
|
137
137
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
138
|
-
render("<tag-<%= value %>-foo>", { value: "><script>" })
|
138
|
+
render("<tag-<%= value %>-foo>", locals: { value: "><script>" })
|
139
139
|
end
|
140
140
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
141
141
|
"into a tag name around: <tag-<%= value %>>.", e.message
|
@@ -143,12 +143,12 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
143
143
|
|
144
144
|
test "interpolate in comment" do
|
145
145
|
assert_equal "<!-- safe -->",
|
146
|
-
render("<!-- <%= value %> -->", { value: "safe" })
|
146
|
+
render("<!-- <%= value %> -->", locals: { value: "safe" })
|
147
147
|
end
|
148
148
|
|
149
149
|
test "interpolate in comment with end-of-comment" do
|
150
150
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
151
|
-
render("<!-- <%= value %> -->", { value: "-->".html_safe })
|
151
|
+
render("<!-- <%= value %> -->", locals: { value: "-->".html_safe })
|
152
152
|
end
|
153
153
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
154
154
|
"into a html comment around: <!-- <%= value %>.", e.message
|
@@ -156,18 +156,18 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
156
156
|
|
157
157
|
test "non html_safe interpolation into comment tag" do
|
158
158
|
assert_equal "<!-- --> -->",
|
159
|
-
render("<!-- <%= value %> -->", value: '-->')
|
159
|
+
render("<!-- <%= value %> -->", locals: { value: '-->' })
|
160
160
|
end
|
161
161
|
|
162
162
|
test "interpolate in script tag" do
|
163
163
|
assert_equal "<script> foo safe bar<script>",
|
164
|
-
render("<script> foo <%= value %> bar<script>", { value: "safe" })
|
164
|
+
render("<script> foo <%= value %> bar<script>", locals: { value: "safe" })
|
165
165
|
end
|
166
166
|
|
167
167
|
test "interpolate in script tag with start of comment" do
|
168
168
|
skip "skip for now; causing problems"
|
169
169
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
170
|
-
render("<script> foo <%= value %> bar<script>", { value: "<!--".html_safe })
|
170
|
+
render("<script> foo <%= value %> bar<script>", locals: { value: "<!--".html_safe })
|
171
171
|
end
|
172
172
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
173
173
|
"into a script tag around: <script> foo <%= value %>. "\
|
@@ -176,7 +176,7 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
176
176
|
|
177
177
|
test "interpolate in script tag with start of script" do
|
178
178
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
179
|
-
render("<script> foo <%= value %> bar<script>", { value: "<script".html_safe })
|
179
|
+
render("<script> foo <%= value %> bar<script>", locals: { value: "<script".html_safe })
|
180
180
|
end
|
181
181
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
182
182
|
"into a script tag around: <script> foo <%= value %>. "\
|
@@ -185,12 +185,12 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
185
185
|
|
186
186
|
test "interpolate in script tag with raw interpolation" do
|
187
187
|
assert_equal "<script> x = \"foo\" </script>",
|
188
|
-
render("<script> x = <%== value %> </script>", { value: JSON.dump("foo") })
|
188
|
+
render("<script> x = <%== value %> </script>", locals: { value: JSON.dump("foo") })
|
189
189
|
end
|
190
190
|
|
191
191
|
test "interpolate in script tag with start of script case insensitive" do
|
192
192
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
193
|
-
render("<script> foo <%= value %> bar<script>", { value: "<ScRIpT".html_safe })
|
193
|
+
render("<script> foo <%= value %> bar<script>", locals: { value: "<ScRIpT".html_safe })
|
194
194
|
end
|
195
195
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
196
196
|
"into a script tag around: <script> foo <%= value %>. "\
|
@@ -199,7 +199,7 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
199
199
|
|
200
200
|
test "interpolate in script tag with end of script" do
|
201
201
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
202
|
-
render("<script> foo <%= value %> bar<script>", { value: "</script".html_safe })
|
202
|
+
render("<script> foo <%= value %> bar<script>", locals: { value: "</script".html_safe })
|
203
203
|
end
|
204
204
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
205
205
|
"into a script tag around: <script> foo <%= value %>. "\
|
@@ -221,17 +221,17 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
221
221
|
|
222
222
|
test "non html_safe interpolation into rawtext tag" do
|
223
223
|
assert_equal "<title></title></title>",
|
224
|
-
render("<title><%= value %></title>", value: '</title>')
|
224
|
+
render("<title><%= value %></title>", locals: { value: '</title>' })
|
225
225
|
end
|
226
226
|
|
227
227
|
test "html_safe interpolation into rawtext tag" do
|
228
228
|
assert_equal "<title><safe></title>",
|
229
|
-
render("<title><%= value %></title>", value: '<safe>'.html_safe)
|
229
|
+
render("<title><%= value %></title>", locals: { value: '<safe>'.html_safe })
|
230
230
|
end
|
231
231
|
|
232
232
|
test "html_safe interpolation terminating the current tag" do
|
233
233
|
e = assert_raises(BetterHtml::UnsafeHtmlError) do
|
234
|
-
render("<title><%= value %></title>", value: '</title>'.html_safe)
|
234
|
+
render("<title><%= value %></title>", locals: { value: '</title>'.html_safe })
|
235
235
|
end
|
236
236
|
assert_equal "Detected invalid characters as part of the interpolation "\
|
237
237
|
"into a title tag around: <title><%= value %>.", e.message
|
@@ -273,7 +273,7 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
273
273
|
|
274
274
|
test "can interpolate method calls without parenthesis" do
|
275
275
|
assert_equal "<div>foo</div>",
|
276
|
-
render("<div><%= send 'value' %></div>", value: 'foo')
|
276
|
+
render("<div><%= send 'value' %></div>", locals: { value: 'foo' })
|
277
277
|
end
|
278
278
|
|
279
279
|
test "tag names are validated against tag_name_pattern regexp" do
|
@@ -290,16 +290,16 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
290
290
|
e = assert_raises(BetterHtml::HtmlError) do
|
291
291
|
render("<foo bar_baz=\"1\">")
|
292
292
|
end
|
293
|
-
assert_equal "Invalid attribute name \"bar_baz\" does not match regular expression #{
|
293
|
+
assert_equal "Invalid attribute name \"bar_baz\" does not match regular expression #{build_config.partial_attribute_name_pattern.inspect}\n"\
|
294
294
|
"On line 1 column 5:\n"\
|
295
295
|
"<foo bar_baz=\"1\">\n"\
|
296
296
|
" ^^^^^^^", e.message
|
297
297
|
end
|
298
298
|
|
299
299
|
test "single quotes are disallowed when allow_single_quoted_attributes=false" do
|
300
|
-
|
300
|
+
config = build_config(allow_single_quoted_attributes: false)
|
301
301
|
e = assert_raises(BetterHtml::HtmlError) do
|
302
|
-
render("<foo bar='1'>")
|
302
|
+
render("<foo bar='1'>", config: config)
|
303
303
|
end
|
304
304
|
assert_equal "Single-quoted attributes are not allowed\n"\
|
305
305
|
"On line 1 column 9:\n"\
|
@@ -308,16 +308,16 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
308
308
|
end
|
309
309
|
|
310
310
|
test "single quotes are allowed when allow_single_quoted_attributes=true" do
|
311
|
-
|
311
|
+
config = build_config(allow_single_quoted_attributes: true)
|
312
312
|
assert_nothing_raised do
|
313
|
-
render("<foo bar='1'>")
|
313
|
+
render("<foo bar='1'>", config: config)
|
314
314
|
end
|
315
315
|
end
|
316
316
|
|
317
317
|
test "unquoted values are disallowed when allow_unquoted_attributes=false" do
|
318
|
-
|
318
|
+
config = build_config(allow_unquoted_attributes: false)
|
319
319
|
e = assert_raises(BetterHtml::HtmlError) do
|
320
|
-
render("<foo bar=1>")
|
320
|
+
render("<foo bar=1>", config: config)
|
321
321
|
end
|
322
322
|
assert_equal "Unquoted attribute values are not allowed\n"\
|
323
323
|
"On line 1 column 9:\n"\
|
@@ -326,9 +326,9 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
326
326
|
end
|
327
327
|
|
328
328
|
test "unquoted values are allowed when allow_unquoted_attributes=true" do
|
329
|
-
|
329
|
+
config = build_config(allow_unquoted_attributes: true)
|
330
330
|
assert_nothing_raised do
|
331
|
-
render("<foo bar=1>")
|
331
|
+
render("<foo bar=1>", config: config)
|
332
332
|
end
|
333
333
|
end
|
334
334
|
|
@@ -382,9 +382,13 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
382
382
|
end
|
383
383
|
end
|
384
384
|
|
385
|
-
def
|
385
|
+
def build_config(**options)
|
386
|
+
BetterHtml::Config.new(**options)
|
387
|
+
end
|
388
|
+
|
389
|
+
def render(source, config: build_config, locals: {})
|
386
390
|
context = ViewContext.new(locals)
|
387
|
-
impl = compile(source)
|
391
|
+
impl = compile(source, config: config)
|
388
392
|
if ActionView.version < Gem::Version.new("5.1")
|
389
393
|
impl.result(context.get_binding)
|
390
394
|
else
|
@@ -392,11 +396,11 @@ class BetterHtml::BetterErb::ImplementationTest < ActiveSupport::TestCase
|
|
392
396
|
end
|
393
397
|
end
|
394
398
|
|
395
|
-
def compile(source)
|
399
|
+
def compile(source, config: build_config)
|
396
400
|
if ActionView.version < Gem::Version.new("5.1")
|
397
|
-
BetterHtml::BetterErb::ErubisImplementation.new(source)
|
401
|
+
BetterHtml::BetterErb::ErubisImplementation.new(source, config: config)
|
398
402
|
else
|
399
|
-
BetterHtml::BetterErb::ErubiImplementation.new(source)
|
403
|
+
BetterHtml::BetterErb::ErubiImplementation.new(source, config: config)
|
400
404
|
end
|
401
405
|
end
|
402
406
|
end
|
@@ -5,12 +5,10 @@ module BetterHtml
|
|
5
5
|
module TestHelper
|
6
6
|
class SafeErbTesterTest < ActiveSupport::TestCase
|
7
7
|
setup do
|
8
|
-
BetterHtml.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
.stubs(:javascript_attribute_names)
|
13
|
-
.returns([/\Aon/i, 'data-eval'])
|
8
|
+
@config = BetterHtml::Config.new(
|
9
|
+
javascript_safe_methods: ['j', 'escape_javascript', 'to_json'],
|
10
|
+
javascript_attribute_names: [/\Aon/i, 'data-eval'],
|
11
|
+
)
|
14
12
|
end
|
15
13
|
|
16
14
|
test "string without interpolation is safe" do
|
@@ -390,9 +388,17 @@ module BetterHtml
|
|
390
388
|
assert_equal "erb interpolation in javascript attribute must call '(...).to_json'", errors.first.message
|
391
389
|
end
|
392
390
|
|
391
|
+
test "ivar missing .to_json is unsafe" do
|
392
|
+
errors = parse('<script><%= @feature.html_safe %></script>').errors
|
393
|
+
|
394
|
+
assert_equal 1, errors.size
|
395
|
+
assert_equal "<%= @feature.html_safe %>", errors.first.location.source
|
396
|
+
assert_equal "erb interpolation in javascript tag must call '(...).to_json'", errors.first.message
|
397
|
+
end
|
398
|
+
|
393
399
|
private
|
394
400
|
def parse(data, template_language: :html)
|
395
|
-
SafeErbTester::Tester.new(data, template_language: template_language)
|
401
|
+
SafeErbTester::Tester.new(data, config: @config, template_language: template_language)
|
396
402
|
end
|
397
403
|
end
|
398
404
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: better_html
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Francois Chagnon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-11-
|
11
|
+
date: 2017-11-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: erubi
|
@@ -66,6 +66,34 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '2.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: smart_properties
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: html_tokenizer
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: rake
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -96,6 +124,7 @@ files:
|
|
96
124
|
- lib/better_html/better_erb/erubis_implementation.rb
|
97
125
|
- lib/better_html/better_erb/runtime_checks.rb
|
98
126
|
- lib/better_html/better_erb/validated_output_buffer.rb
|
127
|
+
- lib/better_html/config.rb
|
99
128
|
- lib/better_html/errors.rb
|
100
129
|
- lib/better_html/helpers.rb
|
101
130
|
- lib/better_html/html_attributes.rb
|