better_html 1.0.15 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 '<%=' ?",
|