fix: escape LIKE wildcards in user-search ILIKE queries
A `%` or `_` in a search term silently acted as a LIKE wildcard rather than
matching literally (`50%` matched everything, `a_b` matched `axb`). Not
injection — terms are bound — but a search-correctness bug across every
user-facing ILIKE site.
- repo::escape_like: shared pub(crate) escaper (promoted from page_tag), unit-tested.
- Trigram-entangled sites (manga, tag, author) append a separate escaped param
used only by the ILIKE branch; the trigram/similarity branches keep the raw term.
- Pattern-built sites (admin manga list, admin users, analysis coverage + history,
crawler search incl. JSONB payload title) escape inside format!("%{}%", ..) and
pair each ILIKE with ESCAPE '\'.
Tests: escape_like unit tests + integration tests on public manga search, author
autocomplete, and admin user search proving `_` matches literally.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -135,6 +135,30 @@ async fn list_total_is_computed_only_on_the_first_page(pool: PgPool) {
|
||||
assert_eq!(body1["items"].as_array().unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn search_treats_like_wildcards_literally(pool: PgPool) {
|
||||
let h = common::harness(pool);
|
||||
let (_, cookie) = common::register_user(&h.app).await;
|
||||
// Two long titles differing only at one position. `_` is a LIKE single-char
|
||||
// wildcard: unescaped, `%a_b%` matches BOTH ("axb" and "a_b"); escaped, only
|
||||
// the literal "a_b" title matches. The titles are long and the term short so
|
||||
// trigram similarity stays under threshold — the ILIKE branch decides.
|
||||
seed(&h.app, &cookie, "The Quick Brown Fox Jumps axb Over The Lazy Dog").await;
|
||||
seed(&h.app, &cookie, "The Quick Brown Fox Jumps a_b Over The Lazy Dog").await;
|
||||
|
||||
let resp = h
|
||||
.app
|
||||
.oneshot(common::get("/api/v1/mangas?search=a_b"))
|
||||
.await
|
||||
.unwrap();
|
||||
let body = common::body_json(resp).await;
|
||||
assert_eq!(
|
||||
title_list(&body),
|
||||
vec!["The Quick Brown Fox Jumps a_b Over The Lazy Dog"],
|
||||
"the `_` in the search term must match literally, not as a wildcard"
|
||||
);
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn search_via_trigram_tolerates_typos(pool: PgPool) {
|
||||
let h = common::harness(pool);
|
||||
|
||||
Reference in New Issue
Block a user