diff --git a/lib/liquid.rb b/lib/liquid.rb index 09b0dd808..eba84e140 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -41,7 +41,7 @@ module Liquid AnyStartingTag = /#{TagStart}|#{VariableStart}/o PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om - VariableParser = /\[(?:[^\[\]]+|\g<0>)*\]|#{VariableSegment}+\??/o + VariableParser = /\[(?>[^\[\]]+|\g<0>)*\]|#{VariableSegment}+\??/o RAISE_EXCEPTION_LAMBDA = ->(_e) { raise } diff --git a/test/integration/variable_test.rb b/test/integration/variable_test.rb index 0cb8bddc1..a66f1a0bd 100644 --- a/test/integration/variable_test.rb +++ b/test/integration/variable_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'test_helper' +require 'timeout' class VariableTest < Minitest::Test include Liquid @@ -169,4 +170,38 @@ def test_double_nested_variable_lookup } ) end + + def test_variable_lookup_should_not_hang_with_invalid_syntax + Timeout.timeout(1) do + assert_template_result( + 'bar', + "{{['foo'}}", + { + 'foo' => 'bar', + }, + error_mode: :lax, + ) + end + + very_long_key = "1234567890" * 100 + + template_list = [ + "{{['#{very_long_key}']}}", # valid + "{{['#{very_long_key}'}}", # missing closing bracket + "{{[['#{very_long_key}']}}", # extra open bracket + ] + + template_list.each do |template| + Timeout.timeout(1) do + assert_template_result( + 'bar', + template, + { + very_long_key => 'bar', + }, + error_mode: :lax, + ) + end + end + end end diff --git a/test/unit/regexp_unit_test.rb b/test/unit/regexp_unit_test.rb index aad663c1b..877c95e45 100644 --- a/test/unit/regexp_unit_test.rb +++ b/test/unit/regexp_unit_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'test_helper' +require 'timeout' class RegexpUnitTest < Minitest::Test include Liquid @@ -37,10 +38,22 @@ def test_quoted_words_in_the_middle def test_variable_parser assert_equal(['var'], 'var'.scan(VariableParser)) + assert_equal(['[var]'], '[var]'.scan(VariableParser)) assert_equal(['var', 'method'], 'var.method'.scan(VariableParser)) assert_equal(['var', '[method]'], 'var[method]'.scan(VariableParser)) assert_equal(['var', '[method]', '[0]'], 'var[method][0]'.scan(VariableParser)) assert_equal(['var', '["method"]', '[0]'], 'var["method"][0]'.scan(VariableParser)) assert_equal(['var', '[method]', '[0]', 'method'], 'var[method][0].method'.scan(VariableParser)) end + + def test_variable_parser_with_large_input + Timeout.timeout(1) { assert_equal(['[var]'], '[var]'.scan(VariableParser)) } + + very_long_string = "foo" * 1000 + + # valid dynamic lookup + Timeout.timeout(1) { assert_equal(["[#{very_long_string}]"], "[#{very_long_string}]".scan(VariableParser)) } + # invalid dynamic lookup with missing closing bracket + Timeout.timeout(1) { assert_equal([very_long_string], "[#{very_long_string}".scan(VariableParser)) } + end end # RegexpTest