From 68fc2a18b1cdd812adbe01ec04e561720a891c97 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Mon, 19 Oct 2020 14:18:04 +0800 Subject: [PATCH] FIX: Properly handle quotes and backslash in `Search.set_tsquery_weight_filter` --- lib/search.rb | 15 +++++++++------ spec/components/search_spec.rb | 28 ++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/lib/search.rb b/lib/search.rb index 3daf57c5e76..e9e226d4482 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -188,7 +188,7 @@ class Search if term.present? @term = Search.prepare_data(term, Topic === @search_context ? :topic : nil) - @original_term = PG::Connection.escape_string(@term) + @original_term = Search.escape_string(@term) end if @search_pms || @opts[:type_filter] == 'private_messages' @@ -1100,14 +1100,17 @@ class Search def self.to_tsquery(ts_config: nil, term:, joiner: nil) ts_config = ActiveRecord::Base.connection.quote(ts_config) if ts_config - tsquery = "TO_TSQUERY(#{ts_config || default_ts_config}, '#{term}')" - tsquery = "REPLACE(#{tsquery}::text, '&', '#{PG::Connection.escape_string(joiner)}')::tsquery" if joiner + tsquery = "TO_TSQUERY(#{ts_config || default_ts_config}, '#{self.escape_string(term)}')" + tsquery = "REPLACE(#{tsquery}::text, '&', '#{self.escape_string(joiner)}')::tsquery" if joiner tsquery end def self.set_tsquery_weight_filter(term, weight_filter) - term = term.gsub("'", "''") - "''#{PG::Connection.escape_string(term)}'':*#{weight_filter}" + "'#{self.escape_string(term)}':*#{weight_filter}" + end + + def self.escape_string(term) + PG::Connection.escape_string(term).gsub('\\', '\\\\\\') end def ts_query(ts_config = nil, weight_filter: nil) @@ -1237,7 +1240,7 @@ class Search def posts_scope(default_scope = Post.all) if SiteSetting.use_pg_headlines_for_excerpt - search_term = @term.present? ? PG::Connection.escape_string(@term) : nil + search_term = @term.present? ? Search.escape_string(@term) : nil ts_config = default_ts_config default_scope diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 12e8b92624d..7e9b0cb7976 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -134,12 +134,20 @@ describe Search do expect(search.term).to eq('a b c okaylength') end - it 'escapes non alphanumeric characters' do - expect(Search.execute('foo :!$);}]>@\#\"\'').posts.length).to eq(0) # There are at least three levels of sanitation for Search.query! - end + context 'query sanitizaton' do + let!(:post) { Fabricate(:post, raw: 'hello world') } - it "doesn't raise an error when single quotes are present" do - expect(Search.execute("'hello' world").posts.length).to eq(0) # There are at least three levels of sanitation for Search.query! + it 'escapes backslash' do + expect(Search.execute('hello\\').posts).to contain_exactly(post) + end + + it 'escapes single quote' do + expect(Search.execute("hello'").posts).to contain_exactly(post) + end + + it 'escapes non-alphanumeric characters' do + expect(Search.execute('hello :!$);}]>@\#\"\'').posts).to contain_exactly(post) + end end it 'works when given two terms with spaces' do @@ -755,7 +763,7 @@ describe Search do let(:result) { Search.execute('запись') } it 'finds something when given cyrillic query' do - expect(result.posts).to be_present + expect(result.posts).to contain_exactly(post) end end @@ -1622,6 +1630,14 @@ describe Search do expect { DB.exec(+"SELECT to_tsvector('bbb') @@ " << ts_query) }.to_not raise_error expect(ts_query).to include("baz") end + + it 'esacpes the term correctly' do + expect(Search.ts_query(term: 'Title with trailing backslash\\')) + .to eq("TO_TSQUERY('english', '''Title with trailing backslash\\\\\\\\'':*')") + + expect(Search.ts_query(term: "Title with trailing quote'")) + .to eq("TO_TSQUERY('english', '''Title with trailing quote'''''':*')") + end end context '#word_to_date' do