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, []) AST.new(:all, [])
end 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) def tag(name)
AST.new(:tag, [name]) AST.new(:tag, [name.downcase])
end
def wildcard(name)
AST.new(:wildcard, [name.downcase])
end end
def metatag(name, value, quoted = false) 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]) AST.new(:metatag, [name, value, quoted])
end end
end end

View File

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

View File

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