hamlit 2.9.4-java → 2.12.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,11 +25,13 @@ module Hamlit
25
25
  condition = $1
26
26
  end
27
27
 
28
- if node.children.empty?
29
- [:html, :condcomment, condition, [:static, " #{node.value[:text]} "]]
30
- else
31
- [:html, :condcomment, condition, yield(node)]
32
- end
28
+ content =
29
+ if node.children.empty?
30
+ [:static, " #{node.value[:text]} "]
31
+ else
32
+ yield(node)
33
+ end
34
+ [:html, :condcomment, condition, content, node.value[:revealed]]
33
35
  end
34
36
  end
35
37
  end
@@ -55,10 +55,6 @@ module Hamlit
55
55
 
56
56
  # We should handle interpolation here to escape only interpolated values.
57
57
  def compile_interpolated_plain(node)
58
- unless Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
59
- return [:multi, [:escape, node.value[:escape_interpolation], [:dynamic, "%Q[#{node.value[:value]}]"]], [:newline]]
60
- end
61
-
62
58
  temple = [:multi]
63
59
  StringSplitter.compile(node.value[:value]).each do |type, value|
64
60
  case type
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ module Hamlit
3
+ # Compile [:multi, [:static, 'foo'], [:dynamic, 'bar']] to [:dynamic, '"foo#{bar}"']
4
+ class DynamicMerger < Temple::Filter
5
+ def on_multi(*exps)
6
+ exps = exps.dup
7
+ result = [:multi]
8
+ buffer = []
9
+
10
+ until exps.empty?
11
+ type, arg = exps.first
12
+ if type == :dynamic && arg.count("\n") == 0
13
+ buffer << exps.shift
14
+ elsif type == :static && exps.size > (count = arg.count("\n")) &&
15
+ exps[1, count].all? { |e| e == [:newline] }
16
+ (1 + count).times { buffer << exps.shift }
17
+ elsif type == :newline && exps.size > (count = count_newline(exps)) &&
18
+ exps[count].first == :static && count == exps[count].last.count("\n")
19
+ (count + 1).times { buffer << exps.shift }
20
+ else
21
+ result.concat(merge_dynamic(buffer))
22
+ buffer = []
23
+ result << compile(exps.shift)
24
+ end
25
+ end
26
+ result.concat(merge_dynamic(buffer))
27
+
28
+ result.size == 2 ? result[1] : result
29
+ end
30
+
31
+ private
32
+
33
+ def merge_dynamic(exps)
34
+ # Merge exps only when they have both :static and :dynamic
35
+ unless exps.any? { |type,| type == :static } && exps.any? { |type,| type == :dynamic }
36
+ return exps
37
+ end
38
+
39
+ strlit_body = String.new
40
+ exps.each do |type, arg|
41
+ case type
42
+ when :static
43
+ strlit_body << arg.dump.sub!(/\A"/, '').sub!(/"\z/, '').gsub('\n', "\n")
44
+ when :dynamic
45
+ strlit_body << "\#{#{arg}}"
46
+ when :newline
47
+ # newline is added by `gsub('\n', "\n")`
48
+ else
49
+ raise "unexpected type #{type.inspect} is given to #merge_dynamic"
50
+ end
51
+ end
52
+ [[:dynamic, "%Q\0#{strlit_body}\0"]]
53
+ end
54
+
55
+ def count_newline(exps)
56
+ count = 0
57
+ exps.each do |exp|
58
+ if exp == [:newline]
59
+ count += 1
60
+ else
61
+ return count
62
+ end
63
+ end
64
+ return count
65
+ end
66
+ end
67
+ end
@@ -2,10 +2,10 @@
2
2
  require 'temple'
3
3
  require 'hamlit/parser'
4
4
  require 'hamlit/compiler'
5
+ require 'hamlit/html'
5
6
  require 'hamlit/escapable'
6
7
  require 'hamlit/force_escapable'
7
- require 'hamlit/html'
8
- require 'hamlit/string_splitter'
8
+ require 'hamlit/dynamic_merger'
9
9
 
10
10
  module Hamlit
11
11
  class Engine < Temple::Engine
@@ -25,15 +25,14 @@ module Hamlit
25
25
  use Parser
26
26
  use Compiler
27
27
  use HTML
28
- if Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
29
- use StringSplitter
30
- filter :StaticAnalyzer
31
- end
28
+ filter :StringSplitter
29
+ filter :StaticAnalyzer
32
30
  use Escapable
33
31
  use ForceEscapable
34
32
  filter :ControlFlow
35
33
  filter :MultiFlattener
36
34
  filter :StaticMerger
35
+ use DynamicMerger
37
36
  use :Generator, -> { options[:generator] }
38
37
  end
39
38
  end
@@ -5,9 +5,6 @@ module Hamlit
5
5
  class Filters
6
6
  class Plain < Base
7
7
  def compile(node)
8
- unless Ripper.respond_to?(:lex)
9
- raise NotImplementedError.new('This platform does not have Ripper.lex required for :plain filter')
10
- end
11
8
  text = node.value[:text]
12
9
  text = text.rstrip unless ::Hamlit::HamlUtil.contains_interpolation?(text) # for compatibility
13
10
  [:multi, *compile_plain(text)]
@@ -10,5 +10,13 @@ module Hamlit
10
10
  end
11
11
  super(opts)
12
12
  end
13
+
14
+ # This dispatcher supports Haml's "revealed" conditional comment.
15
+ def on_html_condcomment(condition, content, revealed = false)
16
+ on_html_comment [:multi,
17
+ [:static, "[#{condition}]>#{'<!-->' if revealed}"],
18
+ content,
19
+ [:static, "#{'<!--' if revealed}<![endif]"]]
20
+ end
13
21
  end
14
22
  end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hamlit
4
+ module HamlAttributeBuilder
5
+ # https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
6
+ INVALID_ATTRIBUTE_NAME_REGEX = /[ \0"'>\/=]/
7
+
8
+ class << self
9
+ def build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
10
+ # @TODO this is an absolutely ridiculous amount of arguments. At least
11
+ # some of this needs to be moved into an instance method.
12
+ join_char = hyphenate_data_attrs ? '-' : '_'
13
+
14
+ attributes.each do |key, value|
15
+ if value.is_a?(Hash)
16
+ data_attributes = attributes.delete(key)
17
+ data_attributes = flatten_data_attributes(data_attributes, '', join_char)
18
+ data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
19
+ verify_attribute_names!(data_attributes.keys)
20
+ attributes = data_attributes.merge(attributes)
21
+ end
22
+ end
23
+
24
+ result = attributes.collect do |attr, value|
25
+ next if value.nil?
26
+
27
+ value = filter_and_join(value, ' ') if attr == 'class'
28
+ value = filter_and_join(value, '_') if attr == 'id'
29
+
30
+ if value == true
31
+ next " #{attr}" if is_html
32
+ next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
33
+ elsif value == false
34
+ next
35
+ end
36
+
37
+ value =
38
+ if escape_attrs == :once
39
+ Hamlit::HamlHelpers.escape_once_without_haml_xss(value.to_s)
40
+ elsif escape_attrs
41
+ Hamlit::HamlHelpers.html_escape_without_haml_xss(value.to_s)
42
+ else
43
+ value.to_s
44
+ end
45
+ " #{attr}=#{attr_wrapper}#{value}#{attr_wrapper}"
46
+ end
47
+ result.compact!
48
+ result.sort!
49
+ result.join
50
+ end
51
+
52
+ # @return [String, nil]
53
+ def filter_and_join(value, separator)
54
+ return '' if (value.respond_to?(:empty?) && value.empty?)
55
+
56
+ if value.is_a?(Array)
57
+ value = value.flatten
58
+ value.map! {|item| item ? item.to_s : nil}
59
+ value.compact!
60
+ value = value.join(separator)
61
+ else
62
+ value = value ? value.to_s : nil
63
+ end
64
+ !value.nil? && !value.empty? && value
65
+ end
66
+
67
+ # Merges two attribute hashes.
68
+ # This is the same as `to.merge!(from)`,
69
+ # except that it merges id, class, and data attributes.
70
+ #
71
+ # ids are concatenated with `"_"`,
72
+ # and classes are concatenated with `" "`.
73
+ # data hashes are simply merged.
74
+ #
75
+ # Destructively modifies `to`.
76
+ #
77
+ # @param to [{String => String,Hash}] The attribute hash to merge into
78
+ # @param from [{String => Object}] The attribute hash to merge from
79
+ # @return [{String => String,Hash}] `to`, after being merged
80
+ def merge_attributes!(to, from)
81
+ from.keys.each do |key|
82
+ to[key] = merge_value(key, to[key], from[key])
83
+ end
84
+ to
85
+ end
86
+
87
+ # Merge multiple values to one attribute value. No destructive operation.
88
+ #
89
+ # @param key [String]
90
+ # @param values [Array<Object>]
91
+ # @return [String,Hash]
92
+ def merge_values(key, *values)
93
+ values.inject(nil) do |to, from|
94
+ merge_value(key, to, from)
95
+ end
96
+ end
97
+
98
+ def verify_attribute_names!(attribute_names)
99
+ attribute_names.each do |attribute_name|
100
+ if attribute_name =~ INVALID_ATTRIBUTE_NAME_REGEX
101
+ raise InvalidAttributeNameError.new("Invalid attribute name '#{attribute_name}' was rendered")
102
+ end
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # Merge a couple of values to one attribute value. No destructive operation.
109
+ #
110
+ # @param to [String,Hash,nil]
111
+ # @param from [Object]
112
+ # @return [String,Hash]
113
+ def merge_value(key, to, from)
114
+ if from.kind_of?(Hash) || to.kind_of?(Hash)
115
+ from = { nil => from } if !from.is_a?(Hash)
116
+ to = { nil => to } if !to.is_a?(Hash)
117
+ to.merge(from)
118
+ elsif key == 'id'
119
+ merged_id = filter_and_join(from, '_')
120
+ if to && merged_id
121
+ merged_id = "#{to}_#{merged_id}"
122
+ elsif to || merged_id
123
+ merged_id ||= to
124
+ end
125
+ merged_id
126
+ elsif key == 'class'
127
+ merged_class = filter_and_join(from, ' ')
128
+ if to && merged_class
129
+ merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
130
+ elsif to || merged_class
131
+ merged_class ||= to
132
+ end
133
+ merged_class
134
+ else
135
+ from
136
+ end
137
+ end
138
+
139
+ def build_data_keys(data_hash, hyphenate, attr_name="data")
140
+ Hash[data_hash.map do |name, value|
141
+ if name == nil
142
+ [attr_name, value]
143
+ elsif hyphenate
144
+ ["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
145
+ else
146
+ ["#{attr_name}-#{name}", value]
147
+ end
148
+ end]
149
+ end
150
+
151
+ def flatten_data_attributes(data, key, join_char, seen = [])
152
+ return {key => data} unless data.is_a?(Hash)
153
+
154
+ return {key => nil} if seen.include? data.object_id
155
+ seen << data.object_id
156
+
157
+ data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
158
+ joined = key == '' ? k : [key, k].join(join_char)
159
+ hash.merge! flatten_data_attributes(v, joined, join_char, seen)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -617,6 +617,9 @@ MESSAGE
617
617
  text.gsub(HTML_ESCAPE_REGEX, HTML_ESCAPE)
618
618
  end
619
619
 
620
+ # Always escape text regardless of html_safe?
621
+ alias_method :html_escape_without_haml_xss, :html_escape
622
+
620
623
  HTML_ESCAPE_ONCE_REGEX = /[\"><]|&(?!(?:[a-zA-Z]+|#(?:\d+|[xX][0-9a-fA-F]+));)/
621
624
 
622
625
  # Escapes HTML entities in `text`, but without escaping an ampersand
@@ -629,6 +632,9 @@ MESSAGE
629
632
  text.gsub(HTML_ESCAPE_ONCE_REGEX, HTML_ESCAPE)
630
633
  end
631
634
 
635
+ # Always escape text once regardless of html_safe?
636
+ alias_method :escape_once_without_haml_xss, :escape_once
637
+
632
638
  # Returns whether or not the current template is a Haml template.
633
639
  #
634
640
  # This function, unlike other {Haml::Helpers} functions,
@@ -1,6 +1,7 @@
1
1
  require 'strscan'
2
2
  require 'hamlit/parser/haml_util'
3
3
  require 'hamlit/parser/haml_error'
4
+ require 'hamlit/parser/haml_attribute_builder'
4
5
 
5
6
  module Hamlit
6
7
  class HamlParser
@@ -206,6 +207,31 @@ module Hamlit
206
207
  end
207
208
  end
208
209
 
210
+ # @param [String] new - Hash literal including dynamic values.
211
+ # @param [String] old - Hash literal including dynamic values or Ruby literal of multiple Hashes which MUST be interpreted as method's last arguments.
212
+ DynamicAttributes = Struct.new(:new, :old) do
213
+ undef :old=
214
+ def old=(value)
215
+ unless value =~ /\A{.*}\z/m
216
+ raise ArgumentError.new('Old attributes must start with "{" and end with "}"')
217
+ end
218
+ self[:old] = value
219
+ end
220
+
221
+ # This will be a literal for Haml::Buffer#attributes's last argument, `attributes_hashes`.
222
+ def to_literal
223
+ [new, stripped_old].compact.join(', ')
224
+ end
225
+
226
+ private
227
+
228
+ # For `%foo{ { foo: 1 }, bar: 2 }`, :old is "{ { foo: 1 }, bar: 2 }" and this method returns " { foo: 1 }, bar: 2 " for last argument.
229
+ def stripped_old
230
+ return nil if old.nil?
231
+ old.sub!(/\A{/, '').sub!(/}\z/m, '')
232
+ end
233
+ end
234
+
209
235
  # Processes and deals with lowering indentation.
210
236
  def process_indent(line)
211
237
  return unless line.tabs <= @template_tabs && @template_tabs > 0
@@ -403,22 +429,20 @@ module Hamlit
403
429
  end
404
430
 
405
431
  attributes = ::Hamlit::HamlParser.parse_class_and_id(attributes)
406
- attributes_list = []
432
+ dynamic_attributes = DynamicAttributes.new
407
433
 
408
434
  if attributes_hashes[:new]
409
435
  static_attributes, attributes_hash = attributes_hashes[:new]
410
- ::Hamlit::HamlBuffer.merge_attrs(attributes, static_attributes) if static_attributes
411
- attributes_list << attributes_hash
436
+ HamlAttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
437
+ dynamic_attributes.new = attributes_hash
412
438
  end
413
439
 
414
440
  if attributes_hashes[:old]
415
441
  static_attributes = parse_static_hash(attributes_hashes[:old])
416
- ::Hamlit::HamlBuffer.merge_attrs(attributes, static_attributes) if static_attributes
417
- attributes_list << attributes_hashes[:old] unless static_attributes || @options.suppress_eval
442
+ HamlAttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
443
+ dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval
418
444
  end
419
445
 
420
- attributes_list.compact!
421
-
422
446
  raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
423
447
  raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
424
448
  raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
@@ -433,7 +457,7 @@ module Hamlit
433
457
  line = handle_ruby_multiline(line) if parse
434
458
 
435
459
  ParseNode.new(:tag, line.index + 1, :name => tag_name, :attributes => attributes,
436
- :attributes_hashes => attributes_list, :self_closing => self_closing,
460
+ :dynamic_attributes => dynamic_attributes, :self_closing => self_closing,
437
461
  :nuke_inner_whitespace => nuke_inner_whitespace,
438
462
  :nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
439
463
  :escape_html => escape_html, :preserve_tag => preserve_tag,
@@ -641,7 +665,6 @@ module Hamlit
641
665
  raise e
642
666
  end
643
667
 
644
- attributes_hash = attributes_hash[1...-1] if attributes_hash
645
668
  return attributes_hash, rest, last_line
646
669
  end
647
670
 
@@ -6,12 +6,15 @@ module Hamlit
6
6
  # to work with Rails' XSS protection methods.
7
7
  module XssMods
8
8
  def self.included(base)
9
- %w[html_escape find_and_preserve preserve list_of surround
10
- precede succeed capture_haml haml_concat haml_internal_concat haml_indent
11
- escape_once].each do |name|
9
+ %w[find_and_preserve preserve list_of surround
10
+ precede succeed capture_haml haml_concat haml_internal_concat haml_indent].each do |name|
12
11
  base.send(:alias_method, "#{name}_without_haml_xss", name)
13
12
  base.send(:alias_method, name, "#{name}_with_haml_xss")
14
13
  end
14
+ # Those two always have _without_haml_xss
15
+ %w[html_escape escape_once].each do |name|
16
+ base.send(:alias_method, name, "#{name}_with_haml_xss")
17
+ end
15
18
  end
16
19
 
17
20
  # Don't escape text that's already safe,
@@ -2,87 +2,18 @@ require 'ripper'
2
2
  require 'hamlit/ruby_expression'
3
3
 
4
4
  module Hamlit
5
- class StringSplitter < Temple::Filter
6
- class << self
7
- # `code` param must be valid string literal
8
- def compile(code)
9
- [].tap do |exps|
10
- tokens = Ripper.lex(code.strip)
11
- tokens.pop while tokens.last && %i[on_comment on_sp].include?(tokens.last[1])
12
-
13
- if tokens.size < 2
14
- raise Hamlit::InternalError.new("Expected token size >= 2 but got: #{tokens.size}")
15
- end
16
- compile_tokens!(exps, tokens)
17
- end
18
- end
19
-
20
- private
21
-
22
- def strip_quotes!(tokens)
23
- _, type, beg_str = tokens.shift
24
- if type != :on_tstring_beg
25
- raise Hamlit::InternalError.new("Expected :on_tstring_beg but got: #{type}")
26
- end
27
-
28
- _, type, end_str = tokens.pop
29
- if type != :on_tstring_end
30
- raise Hamlit::InternalError.new("Expected :on_tstring_end but got: #{type}")
31
- end
32
-
33
- [beg_str, end_str]
34
- end
35
-
36
- def compile_tokens!(exps, tokens)
37
- beg_str, end_str = strip_quotes!(tokens)
38
-
39
- until tokens.empty?
40
- _, type, str = tokens.shift
41
-
42
- case type
43
- when :on_tstring_content
44
- exps << [:static, eval("#{beg_str}#{str}#{end_str}")]
45
- when :on_embexpr_beg
46
- embedded = shift_balanced_embexpr(tokens)
47
- exps << [:dynamic, embedded] unless embedded.empty?
48
- end
49
- end
50
- end
51
-
52
- def shift_balanced_embexpr(tokens)
53
- String.new.tap do |embedded|
54
- embexpr_open = 1
55
-
56
- until tokens.empty?
57
- _, type, str = tokens.shift
58
- case type
59
- when :on_embexpr_beg
60
- embexpr_open += 1
61
- when :on_embexpr_end
62
- embexpr_open -= 1
63
- break if embexpr_open == 0
64
- end
65
-
66
- embedded << str
67
- end
68
- end
5
+ module StringSplitter
6
+ # `code` param must be valid string literal
7
+ def self.compile(code)
8
+ unless Ripper.respond_to?(:lex) # truffleruby doesn't have Ripper.lex
9
+ return [[:dynamic, code]]
69
10
  end
70
- end
71
-
72
- def on_dynamic(code)
73
- return [:dynamic, code] unless RubyExpression.string_literal?(code)
74
- return [:dynamic, code] if code.include?("\n")
75
11
 
76
- temple = [:multi]
77
- StringSplitter.compile(code).each do |type, content|
78
- case type
79
- when :static
80
- temple << [:static, content]
81
- when :dynamic
82
- temple << on_dynamic(content)
83
- end
12
+ begin
13
+ Temple::Filters::StringSplitter.compile(code)
14
+ rescue Temple::FilterError => e
15
+ raise Hamlit::InternalError.new(e.message)
84
16
  end
85
- temple
86
17
  end
87
18
  end
88
19
  end