fix: escape wildcard list queries

This commit is contained in:
Peter Steinberger 2026-04-21 05:01:10 +01:00
parent 716e7a8496
commit 999461ce0e
No known key found for this signature in database
5 changed files with 59 additions and 8 deletions

View File

@ -27,8 +27,8 @@ func (d *DB) ListChats(query string, limit int) ([]Chat, error) {
q := `SELECT jid, kind, COALESCE(name,''), COALESCE(last_message_ts,0) FROM chats WHERE 1=1`
var args []interface{}
if strings.TrimSpace(query) != "" {
q += ` AND (LOWER(name) LIKE LOWER(?) OR LOWER(jid) LIKE LOWER(?))`
needle := "%" + query + "%"
q += ` AND (LOWER(name) LIKE LOWER(?) ESCAPE '\' OR LOWER(jid) LIKE LOWER(?) ESCAPE '\')`
needle := likeContains(query)
args = append(args, needle, needle)
}
q += ` ORDER BY last_message_ts DESC LIMIT ?`

View File

@ -21,10 +21,10 @@ func (d *DB) SearchContacts(query string, limit int) ([]Contact, error) {
c.updated_at
FROM contacts c
LEFT JOIN contact_aliases a ON a.jid = c.jid
WHERE LOWER(COALESCE(a.alias,'')) LIKE LOWER(?) OR LOWER(COALESCE(c.full_name,'')) LIKE LOWER(?) OR LOWER(COALESCE(c.push_name,'')) LIKE LOWER(?) OR LOWER(COALESCE(c.phone,'')) LIKE LOWER(?) OR LOWER(c.jid) LIKE LOWER(?)
WHERE LOWER(COALESCE(a.alias,'')) LIKE LOWER(?) ESCAPE '\' OR LOWER(COALESCE(c.full_name,'')) LIKE LOWER(?) ESCAPE '\' OR LOWER(COALESCE(c.push_name,'')) LIKE LOWER(?) ESCAPE '\' OR LOWER(COALESCE(c.phone,'')) LIKE LOWER(?) ESCAPE '\' OR LOWER(c.jid) LIKE LOWER(?) ESCAPE '\'
ORDER BY COALESCE(NULLIF(a.alias,''), NULLIF(c.full_name,''), NULLIF(c.push_name,''), c.jid)
LIMIT ?`
needle := "%" + query + "%"
needle := likeContains(query)
rows, err := d.sql.Query(q, needle, needle, needle, needle, needle, limit)
if err != nil {
return nil, err

View File

@ -59,8 +59,8 @@ func (d *DB) ListGroups(query string, limit int) ([]Group, error) {
q := `SELECT jid, COALESCE(name,''), COALESCE(owner_jid,''), COALESCE(created_ts,0), updated_at FROM groups WHERE 1=1`
var args []interface{}
if strings.TrimSpace(query) != "" {
needle := "%" + query + "%"
q += ` AND (LOWER(name) LIKE LOWER(?) OR LOWER(jid) LIKE LOWER(?))`
needle := likeContains(query)
q += ` AND (LOWER(name) LIKE LOWER(?) ESCAPE '\' OR LOWER(jid) LIKE LOWER(?) ESCAPE '\')`
args = append(args, needle, needle)
}
q += ` ORDER BY COALESCE(created_ts,0) DESC LIMIT ?`

View File

@ -39,14 +39,18 @@ func escapeLIKE(s string) string {
return s
}
func likeContains(s string) string {
return "%" + escapeLIKE(s) + "%"
}
func (d *DB) searchLIKE(p SearchMessagesParams) ([]Message, error) {
query := `
SELECT m.chat_jid, COALESCE(c.name,''), m.msg_id, COALESCE(m.sender_jid,''), m.ts, m.from_me, COALESCE(m.text,''), COALESCE(m.display_text,''), COALESCE(m.media_type,''), ''
FROM messages m
LEFT JOIN chats c ON c.jid = m.chat_jid
WHERE (LOWER(m.text) LIKE LOWER(?) ESCAPE '\' OR LOWER(m.display_text) LIKE LOWER(?) ESCAPE '\' OR LOWER(m.media_caption) LIKE LOWER(?) ESCAPE '\' OR LOWER(m.filename) LIKE LOWER(?) ESCAPE '\' OR LOWER(COALESCE(m.chat_name,'')) LIKE LOWER(?) ESCAPE '\' OR LOWER(COALESCE(m.sender_name,'')) LIKE LOWER(?) ESCAPE '\' OR LOWER(COALESCE(c.name,'')) LIKE LOWER(?) ESCAPE '\')`
WHERE (LOWER(m.text) LIKE LOWER(?) ESCAPE '\' OR LOWER(m.display_text) LIKE LOWER(?) ESCAPE '\' OR LOWER(m.media_caption) LIKE LOWER(?) ESCAPE '\' OR LOWER(m.filename) LIKE LOWER(?) ESCAPE '\' OR LOWER(COALESCE(m.chat_name,'')) LIKE LOWER(?) ESCAPE '\' OR LOWER(COALESCE(m.sender_name,'')) LIKE LOWER(?) ESCAPE '\' OR LOWER(COALESCE(c.name,'')) LIKE LOWER(?) ESCAPE '\')`
// Escape wildcards before wrapping in % so user input is literal (#56).
needle := "%" + escapeLIKE(p.Query) + "%"
needle := likeContains(p.Query)
args := []interface{}{needle, needle, needle, needle, needle, needle, needle}
query, args = applyMessageFilters(query, args, p)
query += " ORDER BY m.ts DESC LIMIT ?"

View File

@ -305,6 +305,53 @@ func TestContactsAliasTagsAndSearch(t *testing.T) {
}
}
func TestListQueriesEscapeLIKEWildcards(t *testing.T) {
db := openTestDB(t)
when := time.Date(2024, 3, 1, 0, 0, 0, 0, time.UTC)
if err := db.UpsertChat("literal_percent@s.whatsapp.net", "dm", "100% literal", when); err != nil {
t.Fatalf("UpsertChat literal: %v", err)
}
if err := db.UpsertChat("wildcard@s.whatsapp.net", "dm", "100X wildcard", when); err != nil {
t.Fatalf("UpsertChat wildcard: %v", err)
}
chats, err := db.ListChats("100%", 10)
if err != nil {
t.Fatalf("ListChats: %v", err)
}
if len(chats) != 1 || chats[0].JID != "literal_percent@s.whatsapp.net" {
t.Fatalf("ListChats wildcard leak: %+v", chats)
}
if err := db.UpsertContact("literal_under@s.whatsapp.net", "", "Ann_", "", "", ""); err != nil {
t.Fatalf("UpsertContact literal: %v", err)
}
if err := db.UpsertContact("wildcard_under@s.whatsapp.net", "", "AnnX", "", "", ""); err != nil {
t.Fatalf("UpsertContact wildcard: %v", err)
}
contacts, err := db.SearchContacts("Ann_", 10)
if err != nil {
t.Fatalf("SearchContacts: %v", err)
}
if len(contacts) != 1 || contacts[0].JID != "literal_under@s.whatsapp.net" {
t.Fatalf("SearchContacts wildcard leak: %+v", contacts)
}
if err := db.UpsertGroup("literal_group@g.us", "team_1", "", when); err != nil {
t.Fatalf("UpsertGroup literal: %v", err)
}
if err := db.UpsertGroup("wildcard_group@g.us", "teamA1", "", when); err != nil {
t.Fatalf("UpsertGroup wildcard: %v", err)
}
groups, err := db.ListGroups("team_1", 10)
if err != nil {
t.Fatalf("ListGroups: %v", err)
}
if len(groups) != 1 || groups[0].JID != "literal_group@g.us" {
t.Fatalf("ListGroups wildcard leak: %+v", groups)
}
}
func TestCountMessagesAndOldestMessageInfo(t *testing.T) {
db := openTestDB(t)