hamlit 2.11.1 → 2.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile +0 -4
- data/ext/hamlit/hamlit.c +0 -1
- data/lib/hamlit/attribute_builder.rb +2 -2
- data/lib/hamlit/attribute_compiler.rb +3 -3
- data/lib/hamlit/compiler/children_compiler.rb +1 -1
- data/lib/hamlit/parser/haml_attribute_builder.rb +164 -0
- data/lib/hamlit/parser/haml_helpers.rb +6 -0
- data/lib/hamlit/parser/haml_parser.rb +32 -9
- data/lib/hamlit/parser/haml_xss_mods.rb +6 -3
- data/lib/hamlit/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8a587e57cbdc02c90d18591926efb8bf4fd990d60c31969501418a1eed0d2d9
|
4
|
+
data.tar.gz: 7f97e39c33bad82a1cc5b4056b8ac0765a2939f2a9c0a6fecfd156daefa98fa3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dba41938cf6697aeef29f404178de0ccf46cb438402510e989af63593e5ea853627f3f0594fd0359e4b10c68b3c53a7ae64b9c3e2d2f717707e5996006b49721
|
7
|
+
data.tar.gz: 61ce345a747e0fffee535ecab7caee9b0320858f0243b405643115b97cef012039ed9d72766f5bbd4f1b0cde8fa2ffca7b7cea74a8b9ce2a875106c680f5274d
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. This
|
|
4
4
|
project adheres to [Semantic Versioning](http://semver.org/). This change log is based upon
|
5
5
|
[keep-a-changelog](https://github.com/olivierlacan/keep-a-changelog).
|
6
6
|
|
7
|
+
## [2.12.0](https://github.com/k0kubun/hamlit/compare/v2.11.1...v2.12.0) - 2020-09-30
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
|
11
|
+
- Class names are no longer ordered alphabetically
|
12
|
+
*Thanks to @aliismayilov*
|
13
|
+
- This is compatible with [Haml 5.2](https://github.com/haml/haml/blob/v5.2.0/CHANGELOG.md#52)
|
14
|
+
|
7
15
|
## [2.11.1](https://github.com/k0kubun/hamlit/compare/v2.11.0...v2.11.1) - 2020-08-25
|
8
16
|
|
9
17
|
### Fixed
|
data/Gemfile
CHANGED
data/ext/hamlit/hamlit.c
CHANGED
@@ -47,7 +47,7 @@ module Hamlit::AttributeBuilder
|
|
47
47
|
when value.is_a?(String)
|
48
48
|
# noop
|
49
49
|
when value.is_a?(Array)
|
50
|
-
value = value.flatten.select { |v| v }.map(&:to_s).
|
50
|
+
value = value.flatten.select { |v| v }.map(&:to_s).uniq.join(' ')
|
51
51
|
when value
|
52
52
|
value = value.to_s
|
53
53
|
else
|
@@ -67,7 +67,7 @@ module Hamlit::AttributeBuilder
|
|
67
67
|
classes << value.to_s
|
68
68
|
end
|
69
69
|
end
|
70
|
-
escape_html(escape_attrs, classes.map(&:to_s).
|
70
|
+
escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
|
71
71
|
end
|
72
72
|
|
73
73
|
def build_data(escape_attrs, quote, *hashes)
|
@@ -17,7 +17,7 @@ module Hamlit
|
|
17
17
|
if node.value[:object_ref] != :nil || !Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
|
18
18
|
return runtime_compile(node)
|
19
19
|
end
|
20
|
-
node.value[:
|
20
|
+
[node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
|
21
21
|
hash = AttributeParser.parse(attribute_str)
|
22
22
|
return runtime_compile(node) unless hash
|
23
23
|
hashes << hash
|
@@ -28,11 +28,11 @@ module Hamlit
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def runtime_compile(node)
|
31
|
-
attrs =
|
31
|
+
attrs = []
|
32
32
|
attrs.unshift(node.value[:attributes].inspect) if node.value[:attributes] != {}
|
33
33
|
|
34
34
|
args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect].push(node.value[:object_ref]) + attrs
|
35
|
-
[:html, :attrs, [:dynamic, "::Hamlit::AttributeBuilder.build(#{args.join(', ')})"]]
|
35
|
+
[:html, :attrs, [:dynamic, "::Hamlit::AttributeBuilder.build(#{args.join(', ')}, #{node.value[:dynamic_attributes].to_literal})"]]
|
36
36
|
end
|
37
37
|
|
38
38
|
def static_compile(static_hash, dynamic_hashes)
|
@@ -39,7 +39,7 @@ module Hamlit
|
|
39
39
|
when :script, :silent_script
|
40
40
|
@lineno += 1
|
41
41
|
when :tag
|
42
|
-
node.value[:
|
42
|
+
[node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_hash|
|
43
43
|
@lineno += attribute_hash.count("\n")
|
44
44
|
end
|
45
45
|
@lineno += 1 if node.children.empty? && node.value[:parse]
|
@@ -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
|
-
|
432
|
+
dynamic_attributes = DynamicAttributes.new
|
407
433
|
|
408
434
|
if attributes_hashes[:new]
|
409
435
|
static_attributes, attributes_hash = attributes_hashes[:new]
|
410
|
-
|
411
|
-
|
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
|
-
|
417
|
-
|
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
|
-
:
|
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[
|
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,
|
data/lib/hamlit/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hamlit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Takashi Kokubun
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: temple
|
@@ -350,6 +350,7 @@ files:
|
|
350
350
|
- lib/hamlit/parser.rb
|
351
351
|
- lib/hamlit/parser/MIT-LICENSE
|
352
352
|
- lib/hamlit/parser/README.md
|
353
|
+
- lib/hamlit/parser/haml_attribute_builder.rb
|
353
354
|
- lib/hamlit/parser/haml_buffer.rb
|
354
355
|
- lib/hamlit/parser/haml_compiler.rb
|
355
356
|
- lib/hamlit/parser/haml_error.rb
|