better_html 1.0.15 → 2.0.1
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 +47 -56
- data/lib/better_html/config.rb +7 -4
- data/lib/better_html/errors.rb +15 -2
- data/lib/better_html/helpers.rb +7 -3
- data/lib/better_html/html_attributes.rb +6 -2
- data/lib/better_html/parser.rb +22 -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 +34 -32
- 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 +19 -15
- 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,65 @@
|
|
|
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
|
-
self.content_types = {
|
|
14
|
-
'html.erb' => BetterHtml::BetterErb::ErubisImplementation
|
|
15
|
-
}
|
|
16
|
-
else
|
|
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"
|
|
21
8
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
module BetterHtml
|
|
10
|
+
class BetterErb
|
|
11
|
+
cattr_accessor :content_types
|
|
25
12
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
self.content_types = {
|
|
14
|
+
"html.erb" => BetterHtml::BetterErb::ErubiImplementation,
|
|
15
|
+
}
|
|
29
16
|
|
|
30
|
-
|
|
31
|
-
|
|
17
|
+
class << self
|
|
18
|
+
def prepend!
|
|
19
|
+
ActionView::Template::Handlers::ERB.prepend(ConditionalImplementation)
|
|
20
|
+
end
|
|
32
21
|
end
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
module ConditionalImplementation
|
|
24
|
+
def call(template, source = nil)
|
|
25
|
+
generate(template, source)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
def generate(template, source)
|
|
31
|
+
# First, convert to BINARY, so in case the encoding is
|
|
32
|
+
# wrong, we can still find an encoding tag
|
|
33
|
+
# (<%# encoding %>) inside the String using a regular
|
|
34
|
+
# expression
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
source ||= template.source
|
|
37
|
+
filename = template.identifier.split("/").last
|
|
38
|
+
exts = filename.split(".")
|
|
39
|
+
exts = exts[1..exts.length].join(".")
|
|
40
|
+
template_source = source.dup.force_encoding(Encoding::ASCII_8BIT)
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
erb = template_source.gsub(ActionView::Template::Handlers::ERB::ENCODING_TAG, "")
|
|
43
|
+
encoding = Regexp.last_match(2)
|
|
50
44
|
|
|
51
|
-
|
|
45
|
+
erb.force_encoding(valid_encoding(source.dup, encoding))
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
# Always make sure we return a String in the default_internal
|
|
48
|
+
erb.encode!
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
excluded_template = !!BetterHtml.config.template_exclusion_filter&.call(template.identifier)
|
|
51
|
+
klass = BetterHtml::BetterErb.content_types[exts] unless excluded_template
|
|
52
|
+
klass ||= self.class.erb_implementation
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
escape = self.class.escape_ignore_list.include?(template.type)
|
|
55
|
+
generator = klass.new(
|
|
56
|
+
erb,
|
|
57
|
+
escape: escape,
|
|
58
|
+
trim: (self.class.erb_trim_mode == "-")
|
|
59
|
+
)
|
|
60
|
+
generator.validate! if generator.respond_to?(:validate!)
|
|
61
|
+
generator.src
|
|
64
62
|
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
63
|
end
|
|
73
64
|
end
|
|
74
65
|
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
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/string/output_safety"
|
|
4
|
+
require "action_view"
|
|
3
5
|
|
|
4
6
|
module BetterHtml
|
|
5
7
|
class InterpolatorError < RuntimeError; end
|
|
@@ -10,4 +12,15 @@ module BetterHtml
|
|
|
10
12
|
class Errors < Array
|
|
11
13
|
alias_method :add, :<<
|
|
12
14
|
end
|
|
15
|
+
|
|
16
|
+
class ParserError < RuntimeError
|
|
17
|
+
attr_reader :position, :line, :column
|
|
18
|
+
|
|
19
|
+
def initialize(message, position, line, column)
|
|
20
|
+
super(message)
|
|
21
|
+
@position = position
|
|
22
|
+
@line = line
|
|
23
|
+
@column = column
|
|
24
|
+
end
|
|
25
|
+
end
|
|
13
26
|
end
|
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,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "better_html_ext"
|
|
4
|
+
require_relative "tokenizer/javascript_erb"
|
|
5
|
+
require_relative "tokenizer/html_erb"
|
|
6
|
+
require_relative "tokenizer/html_lodash"
|
|
7
|
+
require_relative "tokenizer/location"
|
|
8
|
+
require_relative "tokenizer/token_array"
|
|
9
|
+
require_relative "ast/node"
|
|
10
|
+
require "parser/source/buffer"
|
|
8
11
|
|
|
9
12
|
module BetterHtml
|
|
10
13
|
class Parser
|
|
@@ -21,7 +24,8 @@ module BetterHtml
|
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
def initialize(buffer, template_language: :html)
|
|
24
|
-
raise ArgumentError,
|
|
27
|
+
raise ArgumentError, "first argument must be Parser::Source::Buffer" unless buffer.is_a?(::Parser::Source::Buffer)
|
|
28
|
+
|
|
25
29
|
@buffer = buffer
|
|
26
30
|
@template_language = template_language
|
|
27
31
|
@erb = case template_language
|
|
@@ -38,7 +42,7 @@ module BetterHtml
|
|
|
38
42
|
|
|
39
43
|
def nodes_with_type(*type)
|
|
40
44
|
types = Array.wrap(type)
|
|
41
|
-
ast.children.select{ |node| node.is_a?(::AST::Node) && types.include?(node.type) }
|
|
45
|
+
ast.children.select { |node| node.is_a?(::AST::Node) && types.include?(node.type) }
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
def ast
|
|
@@ -76,7 +80,8 @@ module BetterHtml
|
|
|
76
80
|
when :text, *INTERPOLATION_TYPES
|
|
77
81
|
children << build_text_node(tokens)
|
|
78
82
|
else
|
|
79
|
-
raise
|
|
83
|
+
raise "Unhandled token #{tokens.current.type} line #{tokens.current.loc.line} column " \
|
|
84
|
+
"#{tokens.current.loc.column}, #{children.inspect}"
|
|
80
85
|
end
|
|
81
86
|
end
|
|
82
87
|
|
|
@@ -141,6 +146,7 @@ module BetterHtml
|
|
|
141
146
|
attributes_tokens = []
|
|
142
147
|
while tokens.any?
|
|
143
148
|
break if tokens.size == 1 && tokens.last.type == :solidus
|
|
149
|
+
|
|
144
150
|
if tokens.current.type == :attribute_name
|
|
145
151
|
attributes_tokens << build_attribute_node(tokens)
|
|
146
152
|
elsif tokens.current.type == :attribute_quoted_value_start
|
|
@@ -148,7 +154,7 @@ module BetterHtml
|
|
|
148
154
|
elsif tokens.current.type == :erb_begin
|
|
149
155
|
attributes_tokens << build_erb_node(tokens)
|
|
150
156
|
else
|
|
151
|
-
#
|
|
157
|
+
# TODO: warn about ignored things
|
|
152
158
|
tokens.shift
|
|
153
159
|
end
|
|
154
160
|
end
|
|
@@ -179,8 +185,7 @@ module BetterHtml
|
|
|
179
185
|
def build_attribute_value_node(tokens)
|
|
180
186
|
children = shift_all_with_interpolation(tokens,
|
|
181
187
|
:attribute_quoted_value_start, :attribute_quoted_value,
|
|
182
|
-
:attribute_quoted_value_end, :attribute_unquoted_value
|
|
183
|
-
)
|
|
188
|
+
:attribute_quoted_value_end, :attribute_unquoted_value)
|
|
184
189
|
|
|
185
190
|
build_node(:attribute_value, children)
|
|
186
191
|
end
|
|
@@ -201,6 +206,7 @@ module BetterHtml
|
|
|
201
206
|
def build_location(enumerable)
|
|
202
207
|
enumerable = enumerable.compact
|
|
203
208
|
raise ArgumentError, "cannot build location for #{enumerable.inspect}" unless enumerable.first && enumerable.last
|
|
209
|
+
|
|
204
210
|
Tokenizer::Location.new(@buffer, enumerable.first.loc.begin_pos, enumerable.last.loc.end_pos)
|
|
205
211
|
end
|
|
206
212
|
|
|
@@ -289,9 +295,11 @@ module BetterHtml
|
|
|
289
295
|
|
|
290
296
|
def wrap_token(object)
|
|
291
297
|
return unless object
|
|
298
|
+
|
|
292
299
|
if object.is_a?(::AST::Node)
|
|
293
300
|
object
|
|
294
|
-
elsif [:text, :tag_name, :attribute_name, :attribute_quoted_value,
|
|
301
|
+
elsif [:text, :tag_name, :attribute_name, :attribute_quoted_value,
|
|
302
|
+
:attribute_unquoted_value,].include?(object.type)
|
|
295
303
|
object.loc.source
|
|
296
304
|
elsif [:attribute_quoted_value_start, :attribute_quoted_value_end].include?(object.type)
|
|
297
305
|
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 '<%=' ?",
|