Fix #5136: Regular tags are now case-sensitive.

* Fix `AST.tag` to downcase the tag name.
* Change PostQuery::Parser to use build nodes using `AST.tag`,
  `AST.metatag`, `AST.wildcard`, etc methods instead of building nodes
  directly. This way all the normalization happens in the node
  constructor methods instead of in the parser.
This commit is contained in:
evazion
2022-04-22 02:07:36 -05:00
parent 90182148aa
commit db6bb2ccac
3 changed files with 40 additions and 28 deletions

View File

@@ -68,11 +68,37 @@ class PostQuery
AST.new(:all, [])
end
def none
AST.new(:none, [])
end
def not(ast)
AST.new(:not, [ast])
end
def opt(ast)
AST.new(:opt, [ast])
end
def tag(name)
AST.new(:tag, [name])
AST.new(:tag, [name.downcase])
end
def wildcard(name)
AST.new(:wildcard, [name.downcase])
end
def metatag(name, value, quoted = false)
name = name.downcase
name = name.singularize + "_count" if name.in?(PostQueryBuilder::COUNT_METATAG_SYNONYMS)
if name == "order"
attribute, direction, _tail = value.to_s.downcase.partition(/_(asc|desc)\z/i)
if attribute.in?(PostQueryBuilder::COUNT_METATAG_SYNONYMS)
value = attribute.singularize + "_count" + direction
end
end
AST.new(:metatag, [name, value, quoted])
end
end

View File

@@ -65,7 +65,7 @@ class PostQuery
def parse
parse!
rescue Error
node(:none)
AST.none
end
# Parse the search and return the AST, or raise an error if the parse failed.
@@ -86,11 +86,11 @@ class PostQuery
space
if a.empty?
node(:all)
AST.all
elsif a.size == 1
a.first
else
node(:and, *a)
AST.new(:and, a)
end
end
@@ -101,7 +101,7 @@ class PostQuery
space
if accept(/or +/i)
b = or_clause
node(:or, a, b)
AST.new(:or, [a, b])
else
a
end
@@ -114,7 +114,7 @@ class PostQuery
space
if accept(/and +/i)
b = and_clause
node(:and, a, b)
AST.new(:and, [a, b])
else
a
end
@@ -123,7 +123,7 @@ class PostQuery
# factor_list = factor [factor_list]
def factor_list
a = one_or_more { factor }
node(:and, *a)
AST.new(:and, a)
end
# factor = "-" expr | "~" expr | expr
@@ -131,9 +131,9 @@ class PostQuery
space
if accept("-")
node(:not, expr)
AST.not(expr)
elsif accept("~")
node(:opt, expr)
AST.opt(expr)
else
expr
end
@@ -166,20 +166,10 @@ class PostQuery
# metatag = metatag_name ":" quoted_string
# metatag_name = "user" | "fav" | "pool" | "order" | ...
def metatag
name = expect(METATAG_NAME_REGEX)
name = expect(METATAG_NAME_REGEX).delete_suffix(":")
quoted, value = quoted_string
name = name.delete_suffix(":").downcase
name = name.singularize + "_count" if name.in?(PostQueryBuilder::COUNT_METATAG_SYNONYMS)
if name == "order"
attribute, direction, _tail = value.to_s.downcase.partition(/_(asc|desc)\z/i)
if attribute.in?(PostQueryBuilder::COUNT_METATAG_SYNONYMS)
value = attribute.singularize + "_count" + direction
end
end
node(:metatag, name, value, quoted)
AST.metatag(name, value, quoted)
end
def quoted_string
@@ -201,7 +191,7 @@ class PostQuery
t = string(/(?=[^ ]*\*)[^ \)~-][^ ]*/, skip_balanced_parens: true)
raise Error if t.match?(/\A#{METATAG_NAME_REGEX}/)
space
node(:wildcard, t.downcase)
AST.wildcard(t)
end
# A tag is a string that begins with a nonspace, non-')', non-'~', or non-'-' character, followed by nonspace characters.
@@ -209,7 +199,7 @@ class PostQuery
t = string(/[^ \)~-][^ ]*/, skip_balanced_parens: true)
raise Error if t.downcase.in?(%w[and or]) || t.include?("*") || t.match?(/\A#{METATAG_NAME_REGEX}/)
space
node(:tag, t.downcase)
AST.tag(t)
end
def string(pattern, skip_balanced_parens: false)
@@ -292,11 +282,6 @@ class PostQuery
raise Error, "expected one of: #{parsers}"
end
# Build an AST node of the given type.
def node(type, *args)
AST.new(type, args)
end
end
memoize :parse, :parse!

View File

@@ -85,6 +85,7 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
post3 = create(:post, tag_string: "bbb ccc")
assert_tag_match([post2, post1], "aaa")
assert_tag_match([post2, post1], "AAA")
assert_tag_match([post2, post1], " aaa ")
end