liquid 4.0.1 → 5.4.0

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.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +142 -0
  3. data/README.md +10 -4
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +169 -56
  6. data/lib/liquid/condition.rb +59 -23
  7. data/lib/liquid/context.rb +111 -52
  8. data/lib/liquid/document.rb +47 -9
  9. data/lib/liquid/drop.rb +4 -2
  10. data/lib/liquid/errors.rb +20 -18
  11. data/lib/liquid/expression.rb +29 -33
  12. data/lib/liquid/extensions.rb +2 -0
  13. data/lib/liquid/file_system.rb +6 -4
  14. data/lib/liquid/forloop_drop.rb +54 -4
  15. data/lib/liquid/i18n.rb +5 -3
  16. data/lib/liquid/interrupts.rb +3 -1
  17. data/lib/liquid/lexer.rb +31 -24
  18. data/lib/liquid/locales/en.yml +8 -5
  19. data/lib/liquid/parse_context.rb +20 -4
  20. data/lib/liquid/parse_tree_visitor.rb +42 -0
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +17 -3
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/range_lookup.rb +13 -3
  27. data/lib/liquid/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +616 -129
  30. data/lib/liquid/strainer_factory.rb +41 -0
  31. data/lib/liquid/strainer_template.rb +62 -0
  32. data/lib/liquid/tablerowloop_drop.rb +64 -5
  33. data/lib/liquid/tag/disableable.rb +22 -0
  34. data/lib/liquid/tag/disabler.rb +21 -0
  35. data/lib/liquid/tag.rb +28 -6
  36. data/lib/liquid/tags/assign.rb +44 -18
  37. data/lib/liquid/tags/break.rb +16 -3
  38. data/lib/liquid/tags/capture.rb +24 -18
  39. data/lib/liquid/tags/case.rb +69 -27
  40. data/lib/liquid/tags/comment.rb +18 -3
  41. data/lib/liquid/tags/continue.rb +16 -12
  42. data/lib/liquid/tags/cycle.rb +45 -25
  43. data/lib/liquid/tags/decrement.rb +22 -20
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +97 -89
  46. data/lib/liquid/tags/if.rb +61 -35
  47. data/lib/liquid/tags/ifchanged.rb +11 -10
  48. data/lib/liquid/tags/include.rb +56 -56
  49. data/lib/liquid/tags/increment.rb +23 -17
  50. data/lib/liquid/tags/inline_comment.rb +43 -0
  51. data/lib/liquid/tags/raw.rb +25 -11
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +53 -19
  54. data/lib/liquid/tags/unless.rb +38 -19
  55. data/lib/liquid/template.rb +52 -72
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +18 -10
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +13 -3
  60. data/lib/liquid/variable.rb +52 -41
  61. data/lib/liquid/variable_lookup.rb +24 -10
  62. data/lib/liquid/version.rb +3 -1
  63. data/lib/liquid.rb +19 -6
  64. metadata +21 -104
  65. data/lib/liquid/strainer.rb +0 -66
  66. data/test/fixtures/en_locale.yml +0 -9
  67. data/test/integration/assign_test.rb +0 -48
  68. data/test/integration/blank_test.rb +0 -106
  69. data/test/integration/block_test.rb +0 -12
  70. data/test/integration/capture_test.rb +0 -50
  71. data/test/integration/context_test.rb +0 -32
  72. data/test/integration/document_test.rb +0 -19
  73. data/test/integration/drop_test.rb +0 -273
  74. data/test/integration/error_handling_test.rb +0 -260
  75. data/test/integration/filter_test.rb +0 -178
  76. data/test/integration/hash_ordering_test.rb +0 -23
  77. data/test/integration/output_test.rb +0 -123
  78. data/test/integration/parsing_quirks_test.rb +0 -122
  79. data/test/integration/render_profiling_test.rb +0 -154
  80. data/test/integration/security_test.rb +0 -80
  81. data/test/integration/standard_filter_test.rb +0 -626
  82. data/test/integration/tags/break_tag_test.rb +0 -15
  83. data/test/integration/tags/continue_tag_test.rb +0 -15
  84. data/test/integration/tags/for_tag_test.rb +0 -410
  85. data/test/integration/tags/if_else_tag_test.rb +0 -188
  86. data/test/integration/tags/include_tag_test.rb +0 -245
  87. data/test/integration/tags/increment_tag_test.rb +0 -23
  88. data/test/integration/tags/raw_tag_test.rb +0 -31
  89. data/test/integration/tags/standard_tag_test.rb +0 -296
  90. data/test/integration/tags/statements_test.rb +0 -111
  91. data/test/integration/tags/table_row_test.rb +0 -64
  92. data/test/integration/tags/unless_else_tag_test.rb +0 -26
  93. data/test/integration/template_test.rb +0 -332
  94. data/test/integration/trim_mode_test.rb +0 -529
  95. data/test/integration/variable_test.rb +0 -96
  96. data/test/test_helper.rb +0 -116
  97. data/test/unit/block_unit_test.rb +0 -58
  98. data/test/unit/condition_unit_test.rb +0 -166
  99. data/test/unit/context_unit_test.rb +0 -489
  100. data/test/unit/file_system_unit_test.rb +0 -35
  101. data/test/unit/i18n_unit_test.rb +0 -37
  102. data/test/unit/lexer_unit_test.rb +0 -51
  103. data/test/unit/parser_unit_test.rb +0 -82
  104. data/test/unit/regexp_unit_test.rb +0 -44
  105. data/test/unit/strainer_unit_test.rb +0 -164
  106. data/test/unit/tag_unit_test.rb +0 -21
  107. data/test/unit/tags/case_tag_unit_test.rb +0 -10
  108. data/test/unit/tags/for_tag_unit_test.rb +0 -13
  109. data/test/unit/tags/if_tag_unit_test.rb +0 -8
  110. data/test/unit/template_unit_test.rb +0 -78
  111. data/test/unit/tokenizer_unit_test.rb +0 -55
  112. data/test/unit/variable_unit_test.rb +0 -162
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Container for liquid nodes which conveniently wraps decision making logic
3
5
  #
@@ -6,41 +8,60 @@ module Liquid
6
8
  # c = Condition.new(1, '==', 1)
7
9
  # c.evaluate #=> true
8
10
  #
9
- class Condition #:nodoc:
11
+ class Condition # :nodoc:
10
12
  @@operators = {
11
- '=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
12
- '!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
13
- '<>'.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
14
- '<'.freeze => :<,
15
- '>'.freeze => :>,
16
- '>='.freeze => :>=,
17
- '<='.freeze => :<=,
18
- 'contains'.freeze => lambda do |cond, left, right|
13
+ '==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
14
+ '!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
15
+ '<>' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
16
+ '<' => :<,
17
+ '>' => :>,
18
+ '>=' => :>=,
19
+ '<=' => :<=,
20
+ 'contains' => lambda do |_cond, left, right|
19
21
  if left && right && left.respond_to?(:include?)
20
22
  right = right.to_s if left.is_a?(String)
21
23
  left.include?(right)
22
24
  else
23
25
  false
24
26
  end
27
+ end,
28
+ }
29
+
30
+ class MethodLiteral
31
+ attr_reader :method_name, :to_s
32
+
33
+ def initialize(method_name, to_s)
34
+ @method_name = method_name
35
+ @to_s = to_s
25
36
  end
37
+ end
38
+
39
+ @@method_literals = {
40
+ 'blank' => MethodLiteral.new(:blank?, '').freeze,
41
+ 'empty' => MethodLiteral.new(:empty?, '').freeze,
26
42
  }
27
43
 
28
44
  def self.operators
29
45
  @@operators
30
46
  end
31
47
 
32
- attr_reader :attachment
48
+ def self.parse_expression(parse_context, markup)
49
+ @@method_literals[markup] || parse_context.parse_expression(markup)
50
+ end
51
+
52
+ attr_reader :attachment, :child_condition
33
53
  attr_accessor :left, :operator, :right
34
54
 
35
55
  def initialize(left = nil, operator = nil, right = nil)
36
- @left = left
56
+ @left = left
37
57
  @operator = operator
38
- @right = right
58
+ @right = right
59
+
39
60
  @child_relation = nil
40
61
  @child_condition = nil
41
62
  end
42
63
 
43
- def evaluate(context = Context.new)
64
+ def evaluate(context = deprecated_default_context)
44
65
  condition = self
45
66
  result = nil
46
67
  loop do
@@ -60,12 +81,12 @@ module Liquid
60
81
  end
61
82
 
62
83
  def or(condition)
63
- @child_relation = :or
84
+ @child_relation = :or
64
85
  @child_condition = condition
65
86
  end
66
87
 
67
88
  def and(condition)
68
- @child_relation = :and
89
+ @child_relation = :and
69
90
  @child_condition = condition
70
91
  end
71
92
 
@@ -78,17 +99,17 @@ module Liquid
78
99
  end
79
100
 
80
101
  def inspect
81
- "#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
102
+ "#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
82
103
  end
83
104
 
84
105
  protected
85
106
 
86
- attr_reader :child_relation, :child_condition
107
+ attr_reader :child_relation
87
108
 
88
109
  private
89
110
 
90
111
  def equal_variables(left, right)
91
- if left.is_a?(Liquid::Expression::MethodLiteral)
112
+ if left.is_a?(MethodLiteral)
92
113
  if right.respond_to?(left.method_name)
93
114
  return right.send(left.method_name)
94
115
  else
@@ -96,7 +117,7 @@ module Liquid
96
117
  end
97
118
  end
98
119
 
99
- if right.is_a?(Liquid::Expression::MethodLiteral)
120
+ if right.is_a?(MethodLiteral)
100
121
  if left.respond_to?(right.method_name)
101
122
  return left.send(right.method_name)
102
123
  else
@@ -113,10 +134,10 @@ module Liquid
113
134
  # return this as the result.
114
135
  return context.evaluate(left) if op.nil?
115
136
 
116
- left = context.evaluate(left)
117
- right = context.evaluate(right)
137
+ left = Liquid::Utils.to_liquid_value(context.evaluate(left))
138
+ right = Liquid::Utils.to_liquid_value(context.evaluate(right))
118
139
 
119
- operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
140
+ operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
120
141
 
121
142
  if operation.respond_to?(:call)
122
143
  operation.call(self, left, right)
@@ -124,10 +145,25 @@ module Liquid
124
145
  begin
125
146
  left.send(operation, right)
126
147
  rescue ::ArgumentError => e
127
- raise Liquid::ArgumentError.new(e.message)
148
+ raise Liquid::ArgumentError, e.message
128
149
  end
129
150
  end
130
151
  end
152
+
153
+ def deprecated_default_context
154
+ warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
155
+ " and will be removed from Liquid 6.0.0.")
156
+ Context.new
157
+ end
158
+
159
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
160
+ def children
161
+ [
162
+ @node.left, @node.right,
163
+ @node.child_condition, @node.attachment
164
+ ].compact
165
+ end
166
+ end
131
167
  end
132
168
 
133
169
  class ElseCondition < Condition
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Context keeps the variable stack and resolves variables, as well as keywords
3
5
  #
@@ -12,37 +14,53 @@ module Liquid
12
14
  #
13
15
  # context['bob'] #=> nil class Context
14
16
  class Context
15
- attr_reader :scopes, :errors, :registers, :environments, :resource_limits
17
+ attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments
16
18
  attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
17
19
 
18
- def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
19
- @environments = [environments].flatten
20
- @scopes = [(outer_scope || {})]
21
- @registers = registers
22
- @errors = []
23
- @partial = false
24
- @strict_variables = false
25
- @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
26
- squash_instance_assigns_with_environments
20
+ # rubocop:disable Metrics/ParameterLists
21
+ def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
22
+ new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, &block)
23
+ end
27
24
 
28
- @this_stack_used = false
25
+ def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
26
+ @environments = [environments]
27
+ @environments.flatten!
28
+
29
+ @static_environments = [static_environments].flat_map(&:freeze).freeze
30
+ @scopes = [(outer_scope || {})]
31
+ @registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
32
+ @errors = []
33
+ @partial = false
34
+ @strict_variables = false
35
+ @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
36
+ @base_scope_depth = 0
37
+ @interrupts = []
38
+ @filters = []
39
+ @global_filter = nil
40
+ @disabled_tags = {}
41
+
42
+ @registers.static[:cached_partials] ||= {}
43
+ @registers.static[:file_system] ||= Liquid::Template.file_system
44
+ @registers.static[:template_factory] ||= Liquid::TemplateFactory.new
29
45
 
30
46
  self.exception_renderer = Template.default_exception_renderer
31
47
  if rethrow_errors
32
- self.exception_renderer = ->(e) { raise }
48
+ self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
33
49
  end
34
50
 
35
- @interrupts = []
36
- @filters = []
37
- @global_filter = nil
51
+ yield self if block_given?
52
+
53
+ # Do this last, since it could result in this object being passed to a Proc in the environment
54
+ squash_instance_assigns_with_environments
38
55
  end
56
+ # rubocop:enable Metrics/ParameterLists
39
57
 
40
58
  def warnings
41
59
  @warnings ||= []
42
60
  end
43
61
 
44
62
  def strainer
45
- @strainer ||= Strainer.create(self, @filters)
63
+ @strainer ||= StrainerFactory.create(self, @filters)
46
64
  end
47
65
 
48
66
  # Adds filters to this context.
@@ -77,7 +95,7 @@ module Liquid
77
95
  def handle_error(e, line_number = nil)
78
96
  e = internal_error unless e.is_a?(Liquid::Error)
79
97
  e.template_name ||= template_name
80
- e.line_number ||= line_number
98
+ e.line_number ||= line_number
81
99
  errors.push(e)
82
100
  exception_renderer.call(e).to_s
83
101
  end
@@ -89,7 +107,7 @@ module Liquid
89
107
  # Push new local scope on the stack. use <tt>Context#stack</tt> instead
90
108
  def push(new_scope = {})
91
109
  @scopes.unshift(new_scope)
92
- raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH
110
+ check_overflow
93
111
  end
94
112
 
95
113
  # Merge a hash of variables in the current local scope
@@ -110,20 +128,32 @@ module Liquid
110
128
  # context['var'] = 'hi'
111
129
  # end
112
130
  #
113
- # context['var] #=> nil
114
- def stack(new_scope = nil)
115
- old_stack_used = @this_stack_used
116
- if new_scope
117
- push(new_scope)
118
- @this_stack_used = true
119
- else
120
- @this_stack_used = false
121
- end
122
-
131
+ # context['var'] #=> nil
132
+ def stack(new_scope = {})
133
+ push(new_scope)
123
134
  yield
124
135
  ensure
125
- pop if @this_stack_used
126
- @this_stack_used = old_stack_used
136
+ pop
137
+ end
138
+
139
+ # Creates a new context inheriting resource limits, filters, environment etc.,
140
+ # but with an isolated scope.
141
+ def new_isolated_subcontext
142
+ check_overflow
143
+
144
+ self.class.build(
145
+ resource_limits: resource_limits,
146
+ static_environments: static_environments,
147
+ registers: Registers.new(registers)
148
+ ).tap do |subcontext|
149
+ subcontext.base_scope_depth = base_scope_depth + 1
150
+ subcontext.exception_renderer = exception_renderer
151
+ subcontext.filters = @filters
152
+ subcontext.strainer = nil
153
+ subcontext.errors = errors
154
+ subcontext.warnings = warnings
155
+ subcontext.disabled_tags = @disabled_tags
156
+ end
127
157
  end
128
158
 
129
159
  def clear_instance_assigns
@@ -132,10 +162,6 @@ module Liquid
132
162
 
133
163
  # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
134
164
  def []=(key, value)
135
- unless @this_stack_used
136
- @this_stack_used = true
137
- push({})
138
- end
139
165
  @scopes[0][key] = value
140
166
  end
141
167
 
@@ -164,26 +190,14 @@ module Liquid
164
190
  # This was changed from find() to find_index() because this is a very hot
165
191
  # path and find_index() is optimized in MRI to reduce object allocation
166
192
  index = @scopes.find_index { |s| s.key?(key) }
167
- scope = @scopes[index] if index
168
-
169
- variable = nil
170
193
 
171
- if scope.nil?
172
- @environments.each do |e|
173
- variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found)
174
- # When lookup returned a value OR there is no value but the lookup also did not raise
175
- # then it is the value we are looking for.
176
- if !variable.nil? || @strict_variables && raise_on_not_found
177
- scope = e
178
- break
179
- end
180
- end
194
+ variable = if index
195
+ lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
196
+ else
197
+ try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
181
198
  end
182
199
 
183
- scope ||= @environments.last || @scopes.last
184
- variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found)
185
-
186
- variable = variable.to_liquid
200
+ variable = variable.to_liquid
187
201
  variable.context = self if variable.respond_to?(:context=)
188
202
 
189
203
  variable
@@ -197,14 +211,59 @@ module Liquid
197
211
  value = obj[key]
198
212
 
199
213
  if value.is_a?(Proc) && obj.respond_to?(:[]=)
200
- obj[key] = (value.arity == 0) ? value.call : value.call(self)
214
+ obj[key] = value.arity == 0 ? value.call : value.call(self)
201
215
  else
202
216
  value
203
217
  end
204
218
  end
205
219
 
220
+ def with_disabled_tags(tag_names)
221
+ tag_names.each do |name|
222
+ @disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
223
+ end
224
+ yield
225
+ ensure
226
+ tag_names.each do |name|
227
+ @disabled_tags[name] -= 1
228
+ end
229
+ end
230
+
231
+ def tag_disabled?(tag_name)
232
+ @disabled_tags.fetch(tag_name, 0) > 0
233
+ end
234
+
235
+ protected
236
+
237
+ attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
238
+
206
239
  private
207
240
 
241
+ attr_reader :base_scope_depth
242
+
243
+ def try_variable_find_in_environments(key, raise_on_not_found:)
244
+ @environments.each do |environment|
245
+ found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
246
+ if !found_variable.nil? || @strict_variables && raise_on_not_found
247
+ return found_variable
248
+ end
249
+ end
250
+ @static_environments.each do |environment|
251
+ found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
252
+ if !found_variable.nil? || @strict_variables && raise_on_not_found
253
+ return found_variable
254
+ end
255
+ end
256
+ nil
257
+ end
258
+
259
+ def check_overflow
260
+ raise StackLevelError, "Nesting too deep" if overflow?
261
+ end
262
+
263
+ def overflow?
264
+ base_scope_depth + @scopes.length > Block::MAX_DEPTH
265
+ end
266
+
208
267
  def internal_error
209
268
  # raise and catch to set backtrace and cause on exception
210
269
  raise Liquid::InternalError, 'internal'
@@ -1,26 +1,64 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
- class Document < BlockBody
4
+ class Document
3
5
  def self.parse(tokens, parse_context)
4
- doc = new
6
+ doc = new(parse_context)
5
7
  doc.parse(tokens, parse_context)
6
8
  doc
7
9
  end
8
10
 
9
- def parse(tokens, parse_context)
10
- super do |end_tag_name, end_tag_params|
11
- unknown_tag(end_tag_name, parse_context) if end_tag_name
11
+ attr_reader :parse_context, :body
12
+
13
+ def initialize(parse_context)
14
+ @parse_context = parse_context
15
+ @body = new_body
16
+ end
17
+
18
+ def nodelist
19
+ @body.nodelist
20
+ end
21
+
22
+ def parse(tokenizer, parse_context)
23
+ while parse_body(tokenizer)
12
24
  end
25
+ @body.freeze
13
26
  rescue SyntaxError => e
14
27
  e.line_number ||= parse_context.line_number
15
28
  raise
16
29
  end
17
30
 
18
- def unknown_tag(tag, parse_context)
31
+ def unknown_tag(tag, _markup, _tokenizer)
19
32
  case tag
20
- when 'else'.freeze, 'end'.freeze
21
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag))
33
+ when 'else', 'end'
34
+ raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
22
35
  else
23
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
36
+ raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
37
+ end
38
+ end
39
+
40
+ def render_to_output_buffer(context, output)
41
+ @body.render_to_output_buffer(context, output)
42
+ end
43
+
44
+ def render(context)
45
+ render_to_output_buffer(context, +'')
46
+ end
47
+
48
+ private
49
+
50
+ def new_body
51
+ parse_context.new_block_body
52
+ end
53
+
54
+ def parse_body(tokenizer)
55
+ @body.parse(tokenizer, parse_context) do |unknown_tag_name, unknown_tag_markup|
56
+ if unknown_tag_name
57
+ unknown_tag(unknown_tag_name, unknown_tag_markup, tokenizer)
58
+ true
59
+ else
60
+ false
61
+ end
24
62
  end
25
63
  end
26
64
  end
data/lib/liquid/drop.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module Liquid
@@ -25,7 +27,7 @@ module Liquid
25
27
 
26
28
  # Catch all for the method
27
29
  def liquid_method_missing(method)
28
- return nil unless @context && @context.strict_variables
30
+ return nil unless @context&.strict_variables
29
31
  raise Liquid::UndefinedDropMethod, "undefined method #{method}"
30
32
  end
31
33
 
@@ -67,7 +69,7 @@ module Liquid
67
69
 
68
70
  if include?(Enumerable)
69
71
  blacklist += Enumerable.public_instance_methods
70
- blacklist -= [:sort, :count, :first, :min, :max, :include?]
72
+ blacklist -= [:sort, :count, :first, :min, :max]
71
73
  end
72
74
 
73
75
  whitelist = [:to_liquid] + (public_instance_methods - blacklist)
data/lib/liquid/errors.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Error < ::StandardError
3
5
  attr_accessor :line_number
@@ -5,7 +7,7 @@ module Liquid
5
7
  attr_accessor :markup_context
6
8
 
7
9
  def to_s(with_prefix = true)
8
- str = ""
10
+ str = +""
9
11
  str << message_prefix if with_prefix
10
12
  str << super()
11
13
 
@@ -20,11 +22,11 @@ module Liquid
20
22
  private
21
23
 
22
24
  def message_prefix
23
- str = ""
24
- if is_a?(SyntaxError)
25
- str << "Liquid syntax error"
25
+ str = +""
26
+ str << if is_a?(SyntaxError)
27
+ "Liquid syntax error"
26
28
  else
27
- str << "Liquid error"
29
+ "Liquid error"
28
30
  end
29
31
 
30
32
  if line_number
@@ -38,19 +40,19 @@ module Liquid
38
40
  end
39
41
  end
40
42
 
41
- ArgumentError = Class.new(Error)
42
- ContextError = Class.new(Error)
43
- FileSystemError = Class.new(Error)
44
- StandardError = Class.new(Error)
45
- SyntaxError = Class.new(Error)
46
- StackLevelError = Class.new(Error)
47
- TaintedError = Class.new(Error)
48
- MemoryError = Class.new(Error)
49
- ZeroDivisionError = Class.new(Error)
50
- FloatDomainError = Class.new(Error)
51
- UndefinedVariable = Class.new(Error)
43
+ ArgumentError = Class.new(Error)
44
+ ContextError = Class.new(Error)
45
+ FileSystemError = Class.new(Error)
46
+ StandardError = Class.new(Error)
47
+ SyntaxError = Class.new(Error)
48
+ StackLevelError = Class.new(Error)
49
+ MemoryError = Class.new(Error)
50
+ ZeroDivisionError = Class.new(Error)
51
+ FloatDomainError = Class.new(Error)
52
+ UndefinedVariable = Class.new(Error)
52
53
  UndefinedDropMethod = Class.new(Error)
53
- UndefinedFilter = Class.new(Error)
54
+ UndefinedFilter = Class.new(Error)
54
55
  MethodOverrideError = Class.new(Error)
55
- InternalError = Class.new(Error)
56
+ DisabledError = Class.new(Error)
57
+ InternalError = Class.new(Error)
56
58
  end
@@ -1,45 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Expression
3
- class MethodLiteral
4
- attr_reader :method_name, :to_s
5
-
6
- def initialize(method_name, to_s)
7
- @method_name = method_name
8
- @to_s = to_s
9
- end
10
-
11
- def to_liquid
12
- to_s
13
- end
14
- end
15
-
16
5
  LITERALS = {
17
- nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
18
- 'true'.freeze => true,
19
- 'false'.freeze => false,
20
- 'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
21
- 'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
22
- }
6
+ nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
7
+ 'true' => true,
8
+ 'false' => false,
9
+ 'blank' => '',
10
+ 'empty' => ''
11
+ }.freeze
23
12
 
24
- SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
25
- DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
26
13
  INTEGERS_REGEX = /\A(-?\d+)\z/
27
14
  FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
28
- RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/
15
+
16
+ # Use an atomic group (?>...) to avoid pathological backtracing from
17
+ # malicious input as described in https://github.com/Shopify/liquid/issues/1357
18
+ RANGES_REGEX = /\A\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\z/
29
19
 
30
20
  def self.parse(markup)
31
- if LITERALS.key?(markup)
32
- LITERALS[markup]
21
+ return nil unless markup
22
+
23
+ markup = markup.strip
24
+ if (markup.start_with?('"') && markup.end_with?('"')) ||
25
+ (markup.start_with?("'") && markup.end_with?("'"))
26
+ return markup[1..-2]
27
+ end
28
+
29
+ case markup
30
+ when INTEGERS_REGEX
31
+ Regexp.last_match(1).to_i
32
+ when RANGES_REGEX
33
+ RangeLookup.parse(Regexp.last_match(1), Regexp.last_match(2))
34
+ when FLOATS_REGEX
35
+ Regexp.last_match(1).to_f
33
36
  else
34
- case markup
35
- when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
36
- $1
37
- when INTEGERS_REGEX
38
- $1.to_i
39
- when RANGES_REGEX
40
- RangeLookup.parse($1, $2)
41
- when FLOATS_REGEX
42
- $1.to_f
37
+ if LITERALS.key?(markup)
38
+ LITERALS[markup]
43
39
  else
44
40
  VariableLookup.parse(markup)
45
41
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
4
  require 'date'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
3
5
  #
@@ -44,8 +46,8 @@ module Liquid
44
46
  class LocalFileSystem
45
47
  attr_accessor :root
46
48
 
47
- def initialize(root, pattern = "_%s.liquid".freeze)
48
- @root = root
49
+ def initialize(root, pattern = "_%s.liquid")
50
+ @root = root
49
51
  @pattern = pattern
50
52
  end
51
53
 
@@ -57,9 +59,9 @@ module Liquid
57
59
  end
58
60
 
59
61
  def full_path(template_path)
60
- raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
62
+ raise FileSystemError, "Illegal template name '#{template_path}'" unless %r{\A[^./][a-zA-Z0-9_/]+\z}.match?(template_path)
61
63
 
62
- full_path = if template_path.include?('/'.freeze)
64
+ full_path = if template_path.include?('/')
63
65
  File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
64
66
  else
65
67
  File.join(root, @pattern % template_path)