better_html 1.0.16 → 2.0.0
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/MIT-LICENSE +1 -0
- data/Rakefile +19 -14
- data/ext/better_html_ext/better_html.h +1 -0
- data/ext/better_html_ext/extconf.rb +16 -0
- data/ext/better_html_ext/html_tokenizer.c +12 -0
- data/ext/better_html_ext/html_tokenizer.h +7 -0
- data/ext/better_html_ext/parser.c +793 -0
- data/ext/better_html_ext/parser.h +93 -0
- data/ext/better_html_ext/tokenizer.c +717 -0
- data/ext/better_html_ext/tokenizer.h +80 -0
- data/lib/better_html/ast/iterator.rb +14 -9
- data/lib/better_html/ast/node.rb +4 -2
- data/lib/better_html/better_erb/erubi_implementation.rb +43 -39
- data/lib/better_html/better_erb/runtime_checks.rb +140 -133
- data/lib/better_html/better_erb/validated_output_buffer.rb +30 -22
- data/lib/better_html/better_erb.rb +58 -54
- data/lib/better_html/config.rb +7 -4
- data/lib/better_html/errors.rb +4 -2
- data/lib/better_html/helpers.rb +7 -3
- data/lib/better_html/html_attributes.rb +6 -2
- data/lib/better_html/parser.rb +21 -14
- data/lib/better_html/railtie.rb +8 -4
- data/lib/better_html/test_helper/ruby_node.rb +15 -10
- data/lib/better_html/test_helper/safe_erb/allowed_script_type.rb +8 -4
- data/lib/better_html/test_helper/safe_erb/base.rb +12 -9
- data/lib/better_html/test_helper/safe_erb/no_javascript_tag_helper.rb +7 -3
- data/lib/better_html/test_helper/safe_erb/no_statements.rb +7 -3
- data/lib/better_html/test_helper/safe_erb/script_interpolation.rb +9 -4
- data/lib/better_html/test_helper/safe_erb/tag_interpolation.rb +23 -20
- data/lib/better_html/test_helper/safe_erb_tester.rb +33 -31
- data/lib/better_html/test_helper/safe_lodash_tester.rb +36 -35
- data/lib/better_html/test_helper/safety_error.rb +2 -0
- data/lib/better_html/tokenizer/base_erb.rb +14 -10
- data/lib/better_html/tokenizer/html_erb.rb +3 -2
- data/lib/better_html/tokenizer/html_lodash.rb +22 -14
- data/lib/better_html/tokenizer/javascript_erb.rb +3 -1
- data/lib/better_html/tokenizer/location.rb +17 -6
- data/lib/better_html/tokenizer/token.rb +2 -0
- data/lib/better_html/tokenizer/token_array.rb +8 -8
- data/lib/better_html/tree/attribute.rb +10 -6
- data/lib/better_html/tree/attributes_list.rb +9 -5
- data/lib/better_html/tree/tag.rb +10 -6
- data/lib/better_html/version.rb +3 -1
- data/lib/better_html.rb +19 -17
- data/lib/tasks/better_html_tasks.rake +1 -0
- metadata +39 -147
- data/lib/better_html/better_erb/erubis_implementation.rb +0 -44
- data/test/better_html/better_erb/implementation_test.rb +0 -406
- data/test/better_html/errors_test.rb +0 -13
- data/test/better_html/helpers_test.rb +0 -49
- data/test/better_html/parser_test.rb +0 -314
- data/test/better_html/test_helper/ruby_node_test.rb +0 -288
- data/test/better_html/test_helper/safe_erb/allowed_script_type_test.rb +0 -46
- data/test/better_html/test_helper/safe_erb/no_javascript_tag_helper_test.rb +0 -37
- data/test/better_html/test_helper/safe_erb/no_statements_test.rb +0 -129
- data/test/better_html/test_helper/safe_erb/script_interpolation_test.rb +0 -149
- data/test/better_html/test_helper/safe_erb/tag_interpolation_test.rb +0 -303
- data/test/better_html/test_helper/safe_lodash_tester_test.rb +0 -90
- data/test/better_html/tokenizer/html_erb_test.rb +0 -180
- data/test/better_html/tokenizer/html_lodash_test.rb +0 -98
- data/test/better_html/tokenizer/location_test.rb +0 -75
- data/test/better_html/tokenizer/token_array_test.rb +0 -146
- data/test/better_html/tokenizer/token_test.rb +0 -15
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/javascripts/application.js +0 -13
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/controllers/application_controller.rb +0 -5
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/bin/bundle +0 -3
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/bin/setup +0 -29
- data/test/dummy/config/application.rb +0 -26
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/database.yml +0 -25
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -41
- data/test/dummy/config/environments/production.rb +0 -79
- data/test/dummy/config/environments/test.rb +0 -42
- data/test/dummy/config/initializers/assets.rb +0 -11
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
- data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/initializers/mime_types.rb +0 -4
- data/test/dummy/config/initializers/session_store.rb +0 -3
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -23
- data/test/dummy/config/routes.rb +0 -56
- data/test/dummy/config/secrets.yml +0 -22
- data/test/dummy/config.ru +0 -4
- data/test/dummy/public/404.html +0 -67
- data/test/dummy/public/422.html +0 -67
- data/test/dummy/public/500.html +0 -66
- data/test/dummy/public/favicon.ico +0 -0
- data/test/test_helper.rb +0 -29
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module BetterHtml
|
|
2
4
|
class BetterErb
|
|
3
5
|
class ValidatedOutputBuffer
|
|
4
|
-
def self.wrap(output, context, code, auto_escape)
|
|
5
|
-
Context.new(output, context, code, auto_escape)
|
|
6
|
-
end
|
|
7
|
-
|
|
8
6
|
class Context
|
|
9
7
|
def initialize(output, context, code, auto_escape)
|
|
10
8
|
@output = output
|
|
@@ -15,6 +13,7 @@ module BetterHtml
|
|
|
15
13
|
|
|
16
14
|
def safe_quoted_value_append=(value)
|
|
17
15
|
return if value.nil?
|
|
16
|
+
|
|
18
17
|
value = properly_escaped(value)
|
|
19
18
|
|
|
20
19
|
if value.include?(@context[:quote_character])
|
|
@@ -22,7 +21,7 @@ module BetterHtml
|
|
|
22
21
|
"into a quoted attribute value. The value cannot contain the character #{@context[:quote_character]}."
|
|
23
22
|
end
|
|
24
23
|
|
|
25
|
-
@output.safe_append= value
|
|
24
|
+
@output.safe_append = value
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def safe_unquoted_value_append=(value)
|
|
@@ -40,6 +39,7 @@ module BetterHtml
|
|
|
40
39
|
|
|
41
40
|
def safe_attribute_name_append=(value)
|
|
42
41
|
return if value.nil?
|
|
42
|
+
|
|
43
43
|
value = value.to_s
|
|
44
44
|
|
|
45
45
|
unless value =~ /\A[a-z0-9\-]*\z/
|
|
@@ -47,7 +47,7 @@ module BetterHtml
|
|
|
47
47
|
"into a attribute name around '#{@context[:attribute_name]}<%=#{@code}%>'."
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
@output.safe_append= value
|
|
50
|
+
@output.safe_append = value
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def safe_after_attribute_name_append=(value)
|
|
@@ -59,7 +59,7 @@ module BetterHtml
|
|
|
59
59
|
"try <#{@context[:tag_name]} <%= html_attributes(attr: value) %>>."
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
@output.safe_append= value.to_s
|
|
62
|
+
@output.safe_append = value.to_s
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def safe_after_equal_append=(value)
|
|
@@ -76,11 +76,12 @@ module BetterHtml
|
|
|
76
76
|
"try <#{@context[:tag_name]} <%= html_attributes(attr: value) %>>."
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
@output.safe_append= value.to_s
|
|
79
|
+
@output.safe_append = value.to_s
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def safe_tag_name_append=(value)
|
|
83
83
|
return if value.nil?
|
|
84
|
+
|
|
84
85
|
value = value.to_s
|
|
85
86
|
|
|
86
87
|
unless value =~ /\A[a-z0-9\:\-]*\z/
|
|
@@ -88,7 +89,7 @@ module BetterHtml
|
|
|
88
89
|
"into a tag name around: <#{@context[:tag_name]}<%=#{@code}%>>."
|
|
89
90
|
end
|
|
90
91
|
|
|
91
|
-
@output.safe_append= value
|
|
92
|
+
@output.safe_append = value
|
|
92
93
|
end
|
|
93
94
|
|
|
94
95
|
def safe_rawtext_append=(value)
|
|
@@ -96,23 +97,25 @@ module BetterHtml
|
|
|
96
97
|
|
|
97
98
|
value = properly_escaped(value)
|
|
98
99
|
|
|
99
|
-
if @context[:tag_name].downcase ==
|
|
100
|
-
(value =~ /<script/i || value =~
|
|
100
|
+
if @context[:tag_name].downcase == "script" &&
|
|
101
|
+
(value =~ /<script/i || value =~ %r{</script}i)
|
|
101
102
|
# https://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements
|
|
102
103
|
raise UnsafeHtmlError, "Detected invalid characters as part of the interpolation "\
|
|
103
104
|
"into a script tag around: <#{@context[:tag_name]}>#{@context[:rawtext_text]}<%=#{@code}%>. "\
|
|
104
105
|
"A script tag cannot contain <script or </script anywhere inside of it."
|
|
105
106
|
elsif value =~ /<#{Regexp.escape(@context[:tag_name].downcase)}/i ||
|
|
106
|
-
value =~
|
|
107
|
+
value =~ %r{</#{Regexp.escape(@context[:tag_name].downcase)}}i
|
|
107
108
|
raise UnsafeHtmlError, "Detected invalid characters as part of the interpolation "\
|
|
108
|
-
"into a #{@context[:tag_name].downcase} tag around:
|
|
109
|
+
"into a #{@context[:tag_name].downcase} tag around: " \
|
|
110
|
+
"<#{@context[:tag_name]}>#{@context[:rawtext_text]}<%=#{@code}%>."
|
|
109
111
|
end
|
|
110
112
|
|
|
111
|
-
@output.safe_append= value
|
|
113
|
+
@output.safe_append = value
|
|
112
114
|
end
|
|
113
115
|
|
|
114
116
|
def safe_comment_append=(value)
|
|
115
117
|
return if value.nil?
|
|
118
|
+
|
|
116
119
|
value = properly_escaped(value)
|
|
117
120
|
|
|
118
121
|
# in a <!-- ...here --> we disallow -->
|
|
@@ -121,12 +124,13 @@ module BetterHtml
|
|
|
121
124
|
"into a html comment around: <!--#{@context[:comment_text]}<%=#{@code}%>."
|
|
122
125
|
end
|
|
123
126
|
|
|
124
|
-
@output.safe_append= value
|
|
127
|
+
@output.safe_append = value
|
|
125
128
|
end
|
|
126
129
|
|
|
127
130
|
def safe_none_append=(value)
|
|
128
131
|
return if value.nil?
|
|
129
|
-
|
|
132
|
+
|
|
133
|
+
@output.safe_append = properly_escaped(value)
|
|
130
134
|
end
|
|
131
135
|
|
|
132
136
|
private
|
|
@@ -135,13 +139,11 @@ module BetterHtml
|
|
|
135
139
|
if value.is_a?(ValidatedOutputBuffer)
|
|
136
140
|
# in html context, never escape a ValidatedOutputBuffer
|
|
137
141
|
value.to_s
|
|
138
|
-
|
|
142
|
+
elsif @auto_escape
|
|
139
143
|
# in html context, follow auto_escape rule
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
value.to_s
|
|
144
|
-
end
|
|
144
|
+
auto_escape_html_safe_value(value.to_s)
|
|
145
|
+
else
|
|
146
|
+
value.to_s
|
|
145
147
|
end
|
|
146
148
|
end
|
|
147
149
|
|
|
@@ -150,6 +152,12 @@ module BetterHtml
|
|
|
150
152
|
end
|
|
151
153
|
end
|
|
152
154
|
|
|
155
|
+
class << self
|
|
156
|
+
def wrap(output, context, code, auto_escape)
|
|
157
|
+
Context.new(output, context, code, auto_escape)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
153
161
|
def html_safe?
|
|
154
162
|
true
|
|
155
163
|
end
|
|
@@ -1,74 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
if ActionView.version < Gem::Version.new("5.1")
|
|
3
|
-
require 'better_html/better_erb/erubis_implementation'
|
|
4
|
-
else
|
|
5
|
-
require 'better_html/better_erb/erubi_implementation'
|
|
6
|
-
end
|
|
7
|
-
require 'better_html/better_erb/validated_output_buffer'
|
|
1
|
+
# frozen_string_literal: true
|
|
8
2
|
|
|
3
|
+
require "action_view"
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
self.content_types = {
|
|
18
|
-
'html.erb' => BetterHtml::BetterErb::ErubiImplementation
|
|
19
|
-
}
|
|
20
|
-
end
|
|
5
|
+
require "better_html_ext"
|
|
6
|
+
require "better_html/better_erb/erubi_implementation"
|
|
7
|
+
require "better_html/better_erb/validated_output_buffer"
|
|
8
|
+
|
|
9
|
+
module BetterHtml
|
|
10
|
+
class ParserError < RuntimeError
|
|
11
|
+
attr_reader :position, :line, :column
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
def initialize(message, position, line, column)
|
|
14
|
+
super(message)
|
|
15
|
+
@position = position
|
|
16
|
+
@line = line
|
|
17
|
+
@column = column
|
|
18
|
+
end
|
|
24
19
|
end
|
|
20
|
+
end
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
module BetterHtml
|
|
23
|
+
class BetterErb
|
|
24
|
+
cattr_accessor :content_types
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
self.content_types = {
|
|
27
|
+
"html.erb" => BetterHtml::BetterErb::ErubiImplementation,
|
|
28
|
+
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
class << self
|
|
31
|
+
def prepend!
|
|
32
|
+
ActionView::Template::Handlers::ERB.prepend(ConditionalImplementation)
|
|
33
|
+
end
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
module ConditionalImplementation
|
|
37
|
+
def call(template, source = nil)
|
|
38
|
+
generate(template, source)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
def generate(template, source)
|
|
44
|
+
# First, convert to BINARY, so in case the encoding is
|
|
45
|
+
# wrong, we can still find an encoding tag
|
|
46
|
+
# (<%# encoding %>) inside the String using a regular
|
|
47
|
+
# expression
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
source ||= template.source
|
|
50
|
+
filename = template.identifier.split("/").last
|
|
51
|
+
exts = filename.split(".")
|
|
52
|
+
exts = exts[1..exts.length].join(".")
|
|
53
|
+
template_source = source.dup.force_encoding(Encoding::ASCII_8BIT)
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
erb = template_source.gsub(ActionView::Template::Handlers::ERB::ENCODING_TAG, "")
|
|
56
|
+
encoding = Regexp.last_match(2)
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
erb.force_encoding(valid_encoding(source.dup, encoding))
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
# Always make sure we return a String in the default_internal
|
|
61
|
+
erb.encode!
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
excluded_template = !!BetterHtml.config.template_exclusion_filter&.call(template.identifier)
|
|
64
|
+
klass = BetterHtml::BetterErb.content_types[exts] unless excluded_template
|
|
65
|
+
klass ||= self.class.erb_implementation
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
escape = self.class.escape_ignore_list.include?(template.type)
|
|
68
|
+
generator = klass.new(
|
|
69
|
+
erb,
|
|
70
|
+
escape: escape,
|
|
71
|
+
trim: (self.class.erb_trim_mode == "-")
|
|
72
|
+
)
|
|
73
|
+
generator.validate! if generator.respond_to?(:validate!)
|
|
74
|
+
generator.src
|
|
64
75
|
end
|
|
65
|
-
generator = klass.new(
|
|
66
|
-
erb,
|
|
67
|
-
:escape => escape,
|
|
68
|
-
:trim => (self.class.erb_trim_mode == "-")
|
|
69
|
-
)
|
|
70
|
-
generator.validate! if generator.respond_to?(:validate!)
|
|
71
|
-
generator.src
|
|
72
76
|
end
|
|
73
77
|
end
|
|
74
78
|
end
|
data/lib/better_html/config.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "smart_properties"
|
|
2
4
|
|
|
3
5
|
module BetterHtml
|
|
4
6
|
class Config
|
|
@@ -8,17 +10,18 @@ module BetterHtml
|
|
|
8
10
|
property :partial_attribute_name_pattern, default: -> { /\A[a-zA-Z0-9\-\:]+\z/ }
|
|
9
11
|
property :allow_single_quoted_attributes, default: true
|
|
10
12
|
property :allow_unquoted_attributes, default: false
|
|
11
|
-
property :javascript_safe_methods, default: -> { [
|
|
13
|
+
property :javascript_safe_methods, default: -> { ["to_json"] }
|
|
12
14
|
property :javascript_attribute_names, default: -> { [/\Aon/i] }
|
|
13
15
|
property :template_exclusion_filter
|
|
14
16
|
property :lodash_safe_javascript_expression, default: -> { [/\AJSON\.stringify\(/] }
|
|
17
|
+
property :disable_parser_validation, default: false
|
|
15
18
|
|
|
16
19
|
def javascript_attribute_name?(name)
|
|
17
|
-
javascript_attribute_names.any?{ |other| other === name.to_s }
|
|
20
|
+
javascript_attribute_names.any? { |other| other === name.to_s } # rubocop:disable Style/CaseEquality
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def lodash_safe_javascript_expression?(code)
|
|
21
|
-
lodash_safe_javascript_expression.any?{ |other| other === code }
|
|
24
|
+
lodash_safe_javascript_expression.any? { |other| other === code } # rubocop:disable Style/CaseEquality
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
def javascript_safe_method?(name)
|
data/lib/better_html/errors.rb
CHANGED
data/lib/better_html/helpers.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module BetterHtml
|
|
2
4
|
class HtmlAttributes
|
|
3
5
|
def initialize(data)
|
|
@@ -7,10 +9,12 @@ module BetterHtml
|
|
|
7
9
|
def to_s
|
|
8
10
|
@data.map do |key, value|
|
|
9
11
|
unless key =~ BetterHtml.config.partial_attribute_name_pattern
|
|
10
|
-
raise ArgumentError,
|
|
12
|
+
raise ArgumentError,
|
|
13
|
+
"Attribute names must match the pattern #{BetterHtml.config.partial_attribute_name_pattern.inspect}"
|
|
11
14
|
end
|
|
15
|
+
|
|
12
16
|
if value.nil?
|
|
13
|
-
|
|
17
|
+
key.to_s
|
|
14
18
|
else
|
|
15
19
|
value = value.to_s
|
|
16
20
|
escaped_value = value.html_safe? ? value : CGI.escapeHTML(value)
|
data/lib/better_html/parser.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tokenizer/javascript_erb"
|
|
4
|
+
require_relative "tokenizer/html_erb"
|
|
5
|
+
require_relative "tokenizer/html_lodash"
|
|
6
|
+
require_relative "tokenizer/location"
|
|
7
|
+
require_relative "tokenizer/token_array"
|
|
8
|
+
require_relative "ast/node"
|
|
9
|
+
require "parser/source/buffer"
|
|
8
10
|
|
|
9
11
|
module BetterHtml
|
|
10
12
|
class Parser
|
|
@@ -21,7 +23,8 @@ module BetterHtml
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def initialize(buffer, template_language: :html)
|
|
24
|
-
raise ArgumentError,
|
|
26
|
+
raise ArgumentError, "first argument must be Parser::Source::Buffer" unless buffer.is_a?(::Parser::Source::Buffer)
|
|
27
|
+
|
|
25
28
|
@buffer = buffer
|
|
26
29
|
@template_language = template_language
|
|
27
30
|
@erb = case template_language
|
|
@@ -38,7 +41,7 @@ module BetterHtml
|
|
|
38
41
|
|
|
39
42
|
def nodes_with_type(*type)
|
|
40
43
|
types = Array.wrap(type)
|
|
41
|
-
ast.children.select{ |node| node.is_a?(::AST::Node) && types.include?(node.type) }
|
|
44
|
+
ast.children.select { |node| node.is_a?(::AST::Node) && types.include?(node.type) }
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
def ast
|
|
@@ -76,7 +79,8 @@ module BetterHtml
|
|
|
76
79
|
when :text, *INTERPOLATION_TYPES
|
|
77
80
|
children << build_text_node(tokens)
|
|
78
81
|
else
|
|
79
|
-
raise
|
|
82
|
+
raise "Unhandled token #{tokens.current.type} line #{tokens.current.loc.line} column " \
|
|
83
|
+
"#{tokens.current.loc.column}, #{children.inspect}"
|
|
80
84
|
end
|
|
81
85
|
end
|
|
82
86
|
|
|
@@ -141,6 +145,7 @@ module BetterHtml
|
|
|
141
145
|
attributes_tokens = []
|
|
142
146
|
while tokens.any?
|
|
143
147
|
break if tokens.size == 1 && tokens.last.type == :solidus
|
|
148
|
+
|
|
144
149
|
if tokens.current.type == :attribute_name
|
|
145
150
|
attributes_tokens << build_attribute_node(tokens)
|
|
146
151
|
elsif tokens.current.type == :attribute_quoted_value_start
|
|
@@ -148,7 +153,7 @@ module BetterHtml
|
|
|
148
153
|
elsif tokens.current.type == :erb_begin
|
|
149
154
|
attributes_tokens << build_erb_node(tokens)
|
|
150
155
|
else
|
|
151
|
-
#
|
|
156
|
+
# TODO: warn about ignored things
|
|
152
157
|
tokens.shift
|
|
153
158
|
end
|
|
154
159
|
end
|
|
@@ -179,8 +184,7 @@ module BetterHtml
|
|
|
179
184
|
def build_attribute_value_node(tokens)
|
|
180
185
|
children = shift_all_with_interpolation(tokens,
|
|
181
186
|
:attribute_quoted_value_start, :attribute_quoted_value,
|
|
182
|
-
:attribute_quoted_value_end, :attribute_unquoted_value
|
|
183
|
-
)
|
|
187
|
+
:attribute_quoted_value_end, :attribute_unquoted_value)
|
|
184
188
|
|
|
185
189
|
build_node(:attribute_value, children)
|
|
186
190
|
end
|
|
@@ -201,6 +205,7 @@ module BetterHtml
|
|
|
201
205
|
def build_location(enumerable)
|
|
202
206
|
enumerable = enumerable.compact
|
|
203
207
|
raise ArgumentError, "cannot build location for #{enumerable.inspect}" unless enumerable.first && enumerable.last
|
|
208
|
+
|
|
204
209
|
Tokenizer::Location.new(@buffer, enumerable.first.loc.begin_pos, enumerable.last.loc.end_pos)
|
|
205
210
|
end
|
|
206
211
|
|
|
@@ -289,9 +294,11 @@ module BetterHtml
|
|
|
289
294
|
|
|
290
295
|
def wrap_token(object)
|
|
291
296
|
return unless object
|
|
297
|
+
|
|
292
298
|
if object.is_a?(::AST::Node)
|
|
293
299
|
object
|
|
294
|
-
elsif [:text, :tag_name, :attribute_name, :attribute_quoted_value,
|
|
300
|
+
elsif [:text, :tag_name, :attribute_name, :attribute_quoted_value,
|
|
301
|
+
:attribute_unquoted_value,].include?(object.type)
|
|
295
302
|
object.loc.source
|
|
296
303
|
elsif [:attribute_quoted_value_start, :attribute_quoted_value_end].include?(object.type)
|
|
297
304
|
BetterHtml::AST::Node.new(:quote, [object.loc.source], loc: object.loc)
|
data/lib/better_html/railtie.rb
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
require "better_html/better_erb"
|
|
4
|
+
|
|
5
|
+
module BetterHtml
|
|
6
|
+
class Railtie < Rails::Railtie
|
|
7
|
+
initializer "better_html.better_erb.initialization" do
|
|
8
|
+
BetterHtml::BetterErb.prepend!
|
|
9
|
+
end
|
|
6
10
|
end
|
|
7
11
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "better_html/parser"
|
|
4
|
+
require "parser/current"
|
|
3
5
|
|
|
4
6
|
module BetterHtml
|
|
5
7
|
module TestHelper
|
|
@@ -14,15 +16,17 @@ module BetterHtml
|
|
|
14
16
|
end
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
class << self
|
|
20
|
+
def parse(code)
|
|
21
|
+
parser = ::Parser::CurrentRuby.new(Builder.new)
|
|
22
|
+
parser.diagnostics.ignore_warnings = true
|
|
23
|
+
parser.diagnostics.all_errors_are_fatal = false
|
|
24
|
+
parser.diagnostics.consumer = nil
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
buf = ::Parser::Source::Buffer.new("(string)")
|
|
27
|
+
buf.source = code.sub(BLOCK_EXPR, "")
|
|
28
|
+
parser.parse(buf)
|
|
29
|
+
end
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
def child_nodes
|
|
@@ -65,6 +69,7 @@ module BetterHtml
|
|
|
65
69
|
|
|
66
70
|
def static_return_value?
|
|
67
71
|
return false if (possible_values = return_values.to_a).empty?
|
|
72
|
+
|
|
68
73
|
possible_values.all?(&:static_value?)
|
|
69
74
|
end
|
|
70
75
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
2
4
|
|
|
3
5
|
module BetterHtml
|
|
4
6
|
module TestHelper
|
|
5
7
|
module SafeErb
|
|
6
8
|
class AllowedScriptType < Base
|
|
7
|
-
VALID_JAVASCRIPT_TAG_TYPES = [
|
|
9
|
+
VALID_JAVASCRIPT_TAG_TYPES = ["application/ld+json", "text/javascript", "text/template", "text/html"]
|
|
8
10
|
|
|
9
11
|
def validate
|
|
10
12
|
script_tags.each do |tag, _|
|
|
@@ -15,11 +17,13 @@ module BetterHtml
|
|
|
15
17
|
private
|
|
16
18
|
|
|
17
19
|
def validate_type(tag)
|
|
18
|
-
|
|
20
|
+
type_attribute = tag.attributes["type"]
|
|
21
|
+
|
|
22
|
+
return unless type_attribute
|
|
19
23
|
return if VALID_JAVASCRIPT_TAG_TYPES.include?(type_attribute.value)
|
|
20
24
|
|
|
21
25
|
add_error(
|
|
22
|
-
"#{type_attribute.value} is not a valid type, valid types are #{VALID_JAVASCRIPT_TAG_TYPES.join(
|
|
26
|
+
"#{type_attribute.value} is not a valid type, valid types are #{VALID_JAVASCRIPT_TAG_TYPES.join(", ")}",
|
|
23
27
|
location: type_attribute.loc
|
|
24
28
|
)
|
|
25
29
|
end
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "better_html/errors"
|
|
4
|
+
require "better_html/tree/tag"
|
|
5
|
+
require "better_html/test_helper/safety_error"
|
|
6
|
+
require "ast"
|
|
5
7
|
|
|
6
8
|
module BetterHtml
|
|
7
9
|
module TestHelper
|
|
@@ -24,6 +26,7 @@ module BetterHtml
|
|
|
24
26
|
def erb_nodes(root_node)
|
|
25
27
|
Enumerator.new do |yielder|
|
|
26
28
|
next if root_node.nil?
|
|
29
|
+
|
|
27
30
|
root_node.descendants(:erb).each do |erb_node|
|
|
28
31
|
indicator_node, _, code_node, _ = *erb_node
|
|
29
32
|
yielder.yield(erb_node, indicator_node, code_node)
|
|
@@ -37,12 +40,12 @@ module BetterHtml
|
|
|
37
40
|
tag = Tree::Tag.from_node(tag_node)
|
|
38
41
|
next if tag.closing?
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
next unless tag.name == "script"
|
|
44
|
+
|
|
45
|
+
index = ast.to_a.find_index(tag_node)
|
|
46
|
+
next_node = ast.to_a[index + 1]
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
end
|
|
48
|
+
yielder.yield(tag, next_node&.type == :text ? next_node : nil)
|
|
46
49
|
end
|
|
47
50
|
end
|
|
48
51
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require "better_html/test_helper/ruby_node"
|
|
3
5
|
|
|
4
6
|
module BetterHtml
|
|
5
7
|
module TestHelper
|
|
@@ -14,7 +16,8 @@ module BetterHtml
|
|
|
14
16
|
def no_javascript_tag_helper(node)
|
|
15
17
|
erb_nodes(node).each do |erb_node, indicator_node, code_node|
|
|
16
18
|
indicator = indicator_node&.loc&.source
|
|
17
|
-
next if indicator ==
|
|
19
|
+
next if indicator == "#" || indicator == "%"
|
|
20
|
+
|
|
18
21
|
source = code_node.loc.source
|
|
19
22
|
|
|
20
23
|
ruby_node = begin
|
|
@@ -23,6 +26,7 @@ module BetterHtml
|
|
|
23
26
|
nil
|
|
24
27
|
end
|
|
25
28
|
next unless ruby_node
|
|
29
|
+
|
|
26
30
|
ruby_node.descendants(:send, :csend).each do |send_node|
|
|
27
31
|
next unless send_node.method_name?(:javascript_tag)
|
|
28
32
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
2
4
|
|
|
3
5
|
module BetterHtml
|
|
4
6
|
module TestHelper
|
|
@@ -6,7 +8,7 @@ module BetterHtml
|
|
|
6
8
|
class NoStatements < Base
|
|
7
9
|
def validate
|
|
8
10
|
script_tags.each do |tag, content_node|
|
|
9
|
-
no_statements(content_node) unless content_node.present? && tag.attributes[
|
|
11
|
+
no_statements(content_node) unless content_node.present? && tag.attributes["type"]&.value == "text/html"
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
if @parser.template_language == :javascript
|
|
@@ -25,8 +27,10 @@ module BetterHtml
|
|
|
25
27
|
def no_statements(node)
|
|
26
28
|
erb_nodes(node).each do |erb_node, indicator_node, code_node|
|
|
27
29
|
next unless indicator_node.nil?
|
|
30
|
+
|
|
28
31
|
source = code_node.loc.source
|
|
29
|
-
|
|
32
|
+
|
|
33
|
+
next if /\A\s*end/m.match?(source)
|
|
30
34
|
|
|
31
35
|
add_error(
|
|
32
36
|
"erb statement not allowed here; did you mean '<%=' ?",
|