Search treats % and _ as themselves (#296)
The changelog lines for #292, #293 and #295 ride along in this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,10 +40,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Artwork a publisher sends without an image type, or as `application/octet-stream`, is shown when it is a JPEG, PNG, GIF or WebP, instead of initials (#279).
|
||||
- A 429 from TypeSafe holds every call back as it asks, instead of the pass going on asking (#277).
|
||||
- Looking for items to read for Signals read every item on the server on each scan; it now reads only recent ones (#274).
|
||||
- With the downloads folder out of reach during a clean-up, as when the array stops, every downloaded episode was taken as deleted by hand and forgotten; the clean-up now leaves them alone until the folder is back (#293).
|
||||
- A feed address that serves something larger than 50 MB, such as an episode's file given as a feed, is refused as too large, instead of read whole into memory at every check (#295).
|
||||
- Searching items for `%` or `_` finds those characters, instead of treating them as wildcards that matched nearly everything (#296).
|
||||
|
||||
### Security
|
||||
|
||||
- ammonia, which cleans publishers' HTML, updated to 4.2.4 for three advisories, among them XSS through SVG animation tags; rustls to 0.23.46 for five and h2 to 0.4.20 for one, in every connection ipx makes (#281).
|
||||
- The address of a file being downloaded, which for Patreon carries its signature, is sent to open pages and written to the log without its query, as other addresses already were (#292).
|
||||
|
||||
## [1.3.0] - 2026-10-16
|
||||
|
||||
|
||||
@@ -1222,8 +1222,13 @@ fn entries_from(
|
||||
}
|
||||
let search = match search.map(|q| q.trim().to_lowercase()).filter(|q| !q.is_empty()) {
|
||||
Some(q) => {
|
||||
// % and _ as typed, not as wildcards: "100%" matched anything starting 100 (#296).
|
||||
let q = q.replace('\\', "\\\\").replace('%', "\\%").replace('_', "\\_");
|
||||
let like = a.p(format!("%{q}%"));
|
||||
format!("(lower(coalesce(e.title, '')) LIKE {like} OR lower(coalesce(e.description, '')) LIKE {like})")
|
||||
format!(
|
||||
"(lower(coalesce(e.title, '')) LIKE {like} ESCAPE '\\' \
|
||||
OR lower(coalesce(e.description, '')) LIKE {like} ESCAPE '\\')"
|
||||
)
|
||||
}
|
||||
None => "true".into(),
|
||||
};
|
||||
@@ -3614,6 +3619,9 @@ mod tests {
|
||||
assert_eq!(db.count_in(7, Some("f"), Filter::Downloaded, None).await.unwrap(), 1);
|
||||
assert_eq!(db.count_in(7, Some("f"), Filter::Flagged, None).await.unwrap(), 1);
|
||||
assert_eq!(db.count_in(7, Some("f"), Filter::All, Some("dive")).await.unwrap(), 2);
|
||||
for wild in ["%", "_", "\\"] {
|
||||
assert_eq!(db.count_in(7, Some("f"), Filter::All, Some(wild)).await.unwrap(), 0, "{wild} is only itself");
|
||||
}
|
||||
assert_eq!(
|
||||
db.count_in(7, Some("f"), Filter::All, Some("NOTES two")).await.unwrap(),
|
||||
1,
|
||||
|
||||
Reference in New Issue
Block a user