- Add escape_like() helper to escape % and _ wildcards in LIKE queries - Update fail2ban_db_repo.get_history_page() to use escaping - Update history_archive_repo.get_archived_history() to use escaping - Add ESCAPE clause to all LIKE queries - Add comprehensive unit tests for escape_like function - Add integration tests for LIKE wildcard handling - Document LIKE escaping best practices in Backend-Development.md Fixes TASK-017: Prevent unintended LIKE matches when IP filter contains special characters like underscore or percent sign. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
57 lines
1.6 KiB
Python
57 lines
1.6 KiB
Python
"""Utilities shared by fail2ban-related services."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import UTC, datetime
|
|
|
|
|
|
def escape_like(s: str) -> str:
|
|
"""Escape SQLite LIKE wildcard characters in a string.
|
|
|
|
SQLite's LIKE operator treats % (any sequence) and _ (any single char) as
|
|
wildcards. This function escapes them to prevent unintended matches.
|
|
|
|
Args:
|
|
s: The string to escape.
|
|
|
|
Returns:
|
|
The escaped string where backslashes, %, and _ are escaped.
|
|
"""
|
|
return s.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
|
|
|
|
|
def ts_to_iso(unix_ts: int) -> str:
|
|
"""Convert a Unix timestamp to an ISO 8601 UTC string."""
|
|
return datetime.fromtimestamp(unix_ts, tz=UTC).isoformat()
|
|
|
|
|
|
def parse_data_json(raw: object) -> tuple[list[str], int]:
|
|
"""Extract matches and failure count from the fail2ban bans.data value."""
|
|
if raw is None:
|
|
return [], 0
|
|
|
|
obj: dict[str, object] = {}
|
|
if isinstance(raw, str):
|
|
try:
|
|
parsed = json.loads(raw)
|
|
if isinstance(parsed, dict):
|
|
obj = parsed
|
|
except json.JSONDecodeError:
|
|
return [], 0
|
|
elif isinstance(raw, dict):
|
|
obj = raw
|
|
|
|
raw_matches = obj.get("matches")
|
|
matches = [str(m) for m in raw_matches] if isinstance(raw_matches, list) else []
|
|
|
|
raw_failures = obj.get("failures")
|
|
failures = 0
|
|
if isinstance(raw_failures, (int, float, str)):
|
|
try:
|
|
failures = int(raw_failures)
|
|
except (ValueError, TypeError):
|
|
failures = 0
|
|
|
|
return matches, failures
|