hamlit 2.9.4-java → 2.12.0-java

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.
@@ -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