Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't parse nodes inside a comment tag #1755

Merged
merged 8 commits into from
Nov 29, 2023
1 change: 1 addition & 0 deletions lib/liquid/block_body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Liquid
class BlockBody
LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*)?#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%"
Expand Down
67 changes: 67 additions & 0 deletions lib/liquid/tags/comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ module Liquid
# {% endcomment %}
# @liquid_syntax_keyword content The content of the comment.
class Comment < Block
TAG_DELIMITER = /\A(.*)#{TagStart}#{WhitespaceControl}?\s*(endcomment)\s*(.*)?#{WhitespaceControl}?#{TagEnd}\z/om

def render_to_output_buffer(_context, output)
output
end
Expand All @@ -25,6 +27,71 @@ def unknown_tag(_tag, _markup, _tokens)
def blank?
true
end

private

def parse_body(body, tokenizer)
if parse_context.depth >= MAX_DEPTH
raise StackLevelError, "Nesting too deep"
end

parse_context.depth += 1
comment_tag_depth = 1

begin
# Consume tokens without creating child nodes.
# The children tag doesn't require to be a valid Liquid except the comment and raw tag.
# The child comment and raw tag must be closed.
while (token = tokenizer.send(:shift))
tag_name = if tokenizer.for_liquid_tag
next if token.empty? || token.match?(BlockBody::WhitespaceOrNothing)

tag_name_match = BlockBody::LiquidTagToken.match(token)

next if tag_name_match.nil?

tag_name_match[1]
elsif TAG_DELIMITER.match?(token)
# aggressively match comment delimiter
"endcomment"
elsif token =~ BlockBody::FullToken && Regexp.last_match(2) == "comment"
# aggressively match comment tag
"comment"
else
tag_name_match = BlockBody::FullTokenPossiblyInvalid.match(token)

next if tag_name_match.nil?

tag_name_match[2]
end

case tag_name
when "raw"
parse_raw_tag_body(tokenizer)
when "comment"
comment_tag_depth += 1
when "endcomment"
comment_tag_depth -= 1

return false if comment_tag_depth.zero?
end
end

raise_tag_never_closed(block_name)
ensure
parse_context.depth -= 1
end

false
end

def parse_raw_tag_body(tokenizer)
while (token = tokenizer.send(:shift))
return if token =~ BlockBody::FullTokenPossiblyInvalid && "endraw" == Regexp.last_match(2)
end

raise_tag_never_closed("raw")
end
end

Template.register_tag('comment', Comment)
Expand Down
3 changes: 1 addition & 2 deletions lib/liquid/tags/raw.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ module Liquid
# @liquid_syntax_keyword expression The expression to be output without being rendered.
class Raw < Block
Syntax = /\A\s*\z/
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*)?#{WhitespaceControl}?#{TagEnd}\z/om

def initialize(tag_name, markup, parse_context)
super
Expand All @@ -25,7 +24,7 @@ def initialize(tag_name, markup, parse_context)
def parse(tokens)
@body = +''
while (token = tokens.shift)
if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
if token =~ BlockBody::FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
return
Expand Down
138 changes: 138 additions & 0 deletions test/unit/tags/comment_tag_unit_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# frozen_string_literal: true

require 'test_helper'

class CommentTagUnitTest < Minitest::Test
def test_comment_inside_liquid_tag
assert_template_result("", <<~LIQUID.chomp)
{% liquid
if 1 != 1
comment
else
echo 123
endcomment
endif
%}
LIQUID
end

def test_does_not_parse_nodes_inside_a_comment
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% if true %}
{% if ... %}
{%- for ? -%}
{% while true %}
{%
unless if
%}
{% endcase %}
{% endcomment %}
LIQUID
end

def test_allows_incomplete_tags_inside_a_comment
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% assign foo = "1"
{% endcomment %}
LIQUID

assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% comment %}
{% invalid
{% endcomment %}
{% endcomment %}
LIQUID

assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% {{ {%- endcomment %}
LIQUID
end

def test_child_comment_tags_need_to_be_closed
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% comment %}
{% comment %}{% endcomment %}
{% endcomment %}
{% endcomment %}
LIQUID

assert_raises(Liquid::SyntaxError) do
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% comment %}
{% comment %}
{% endcomment %}
{% endcomment %}
LIQUID
end
end

def test_child_raw_tags_need_to_be_closed
assert_template_result("", <<~LIQUID.chomp)
{% comment %}
{% raw %}
{% endcomment %}
{% endraw %}
{% endcomment %}
LIQUID

assert_raises(Liquid::SyntaxError) do
Liquid::Template.parse(<<~LIQUID.chomp)
{% comment %}
{% raw %}
{% endcomment %}
{% endcomment %}
LIQUID
end
end

def test_error_line_number_is_correct
template = Liquid::Template.parse(<<~LIQUID.chomp, line_numbers: true)
{% comment %}
{% if true %}
{% endcomment %}
{{ errors.standard_error }}
LIQUID

output = template.render('errors' => ErrorDrop.new)
expected = <<~TEXT.chomp

Liquid error (line 4): standard error
TEXT

assert_equal(expected, output)
end

def test_comment_tag_delimiter_with_extra_strings
assert_template_result(
'',
<<~LIQUID.chomp,
{% comment %}
{% comment %}
{% endcomment
{% if true %}
{% endif %}
{% endcomment %}
LIQUID
)
end

def test_nested_comment_tag_with_extra_strings
assert_template_result(
'',
<<~LIQUID.chomp,
{% comment %}
{% comment
{% assign foo = 1 %}
{% endcomment
{% assign foo = 1 %}
{% endcomment %}
LIQUID
)
end
end