Commit Graph

11 Commits

Author SHA1 Message Date
1fe3808b23 style(export): gchar*/g_free consistency + auto_char in read loop
All checks were successful
CI Code / Check spelling (pull_request) Successful in 2m12s
CI Code / Check coding style (pull_request) Successful in 2m23s
CI Code / Linux (arch) (pull_request) Successful in 5m35s
CI Code / Linux (debian) (pull_request) Successful in 6m59s
CI Code / Linux (ubuntu) (pull_request) Successful in 7m41s
CI Code / Code Coverage (pull_request) Successful in 6m59s
2026-05-02 16:18:48 +03:00
8868d58920 fix(review): address PR #94 review #37/#38 reviewer comments
Parser (src/database_flatfile_parser.c):
- ff_jid_to_dir: drop GString+step1, in-place mutate str_replace result
- ff_ensure_dir: g_file_test(G_FILE_TEST_IS_SYMLINK) instead of g_lstat
- ff_unescape_*: early-continue for non-escape branch (less nesting)
- ff_readline: strdup("") instead of malloc(1)+'\0' for skip-line return
- ff_write_line: auto_char safe_msg + single fprintf (drop GString full_line)
- ff_parse_line: early return on missing closing bracket;
  extract metadata-parts loop into _ff_parse_meta_parts helper

Verify (src/database_flatfile_verify.c):
- Split monolithic ff_verify_integrity into _collect_contact_dirs,
  _check_permissions, _first_pass, _lmc_pass, _verify_contact_dir
- _issue_new constructor; g_new0 throughout
- Separate seen_stanza_ids and seen_archive_ids tables (no cross-kind
  duplicate confusion)
- ff_skip_bom in second pass (drop manual fgetc x3)
- log_debug when skipping a contact dir without history.log
- Doc comment on ff_verify_integrity in header

SQLite (src/database_sqlite.c):
- Doc block on ChatLogs schema (replaces_db_id <-> replaced_by_db_id
  invariant maintained by AFTER INSERT trigger)
- log_error when _get_db_filename returns NULL during init

Flatfile dispatch (src/database_flatfile.c):
- Replace if/else with is_outgoing ternary for log-path contact pick

Tests:
- 6 new invalid-date parser tests: empty / partial ISO / garbage /
  impossible calendar / negative year / far future timestamp
- New functional test message_db_history_multi_resource verifying
  same-JID-different-resource history rehydration preserves resources
- test_database_stress.c LMC chain loop: auto_char buf, drop redundant
  comment/empty pre-filter (ff_parse_line handles those)
2026-04-30 10:53:03 +03:00
7ba7e53291 refactor: extract helpers for verify print, scan loop, and per-contact export
- extract _show_integrity_issues() from cmd_history verify block
- extract _ff_scan_lines() shared by _ff_state_build and _ff_state_extend
- extract _export_one_contact() from log_database_export_to_flatfile loop
2026-03-28 12:58:27 +03:00
a96a4ad41e fix(review): address PR #94 reviewer comments
Blockers:
- SPDX/CProof copyright headers (7 files)
- stanza-id non-uniqueness: warn-only, don't skip (matches SQLite)
- fopen → open(O_EXCL|O_NOFOLLOW, 0600) + fdopen in export

Medium:
- log_info → log_warning for SQLite fallback
- remove prefs_save() auto-save from backend switch
- vtable null → log_warning in 4 dispatch functions
- log_debug → log_warning + null error handling in export
- rename failure → cons_show_error to user
- remove unused total_skipped variable
- LMC respects PREF_CORRECTION_ALLOW

Low nits:
- man page .SS fix + jabber.space URL
- profrc.example simplified
- show flatfile path on /logging flatfile
- EXPORT_PROGRESS_INTERVAL constant (magic 500)
- FF_META_PREFIX_ID/AID constants + FF_INDEX_STEP comment
- buffer overflow guard on close[1]/close[2]
- for-loop → memchr for JID resource scan
- BOM check extracted to ff_skip_bom() (DRY)
- index building extracted to _ff_maybe_index_line()
- O_APPEND+O_EXCL comment rewritten
- Makefile.am spaces → tabs
2026-03-28 12:38:39 +03:00
71f9ab5007 perf: O(1) dedup/LMC cache, g_slist_append → prepend+reverse
- Add archive_ids hash set and stanza_senders hash map to
  ff_contact_state_t, populated during index build/extend via
  lightweight _ff_cache_line_ids() (no full parse overhead).
- Replace O(n) file scans in _ff_has_archive_id() and
  _ff_find_original_sender() with O(1) hash table lookups.
- Replace g_slist_append with g_slist_prepend + g_slist_reverse
  in _ff_apply_lmc, _flatfile_get_previous_chat, and
  ff_verify_integrity to avoid O(n²) list building.
- Add db_sqlite_last_changes() declaration to database.h.
2026-03-12 18:52:00 +03:00
507ef91b5f feat: complete field parity, harden export/import, add tests
Export/Import improvements:
- Replace pagination with direct SQL query (db_sqlite_get_all_chat)
- Wrap import in SQL transaction with rollback on error
- Add fsync before fclose in export for data safety
- Sort merged output by timestamp with secondary key (stanza_id, from_jid)
- Export archive_id and marked_read from SQLite (lossless migration)
- Add progress indication every 500 messages during write/import
- Expand dedup key body prefix from 64 to 256 chars
- Fix g_slist_append O(n²) → g_slist_prepend + g_slist_reverse O(n)

Field parity (to_jid, to_resource, marked_read):
- Add fields to ff_parsed_line_t struct
- Write/parse to:|to_res:|read: metadata tags in flatfile format
- Pass to_resource through _ff_add_message and all callers
- Add marked_read to ProfMessage struct with -1 default (unset)
- Preserve fields across export/import round-trips

Tests (19 new: 11 unit + 8 functional):
- Unit: to_jid_and_marked_read, bracket_in_stanza_id, backslash_in_resource,
  mucpm_type, all_enc_types, crlf_handling, to_jid_special_chars,
  multiple_lines, parsed_line_free_null_safe, no_space_rejected,
  unclosed_bracket
- Functional: export_idempotent_no_duplicates, export_lmc_correction_survives,
  switch_preserves_old_backend_data, export_all_contacts,
  import_double_dedup, verify_after_export,
  switch_backends_independent_messages, export_empty_contact
- Rebalance test groups: move Chat Session from Group 3 to Group 4
  (25/33/30/27 instead of 25/33/36/21)
- Remove hardcoded test counts from group comments

Man page:
- Document /history switch sqlite|flatfile
2026-03-12 18:46:42 +03:00
23723376c6 fix: harden flatfile backend, make SQLite optional
Build system:
- Add --without-sqlite configure flag (AM_CONDITIONAL BUILD_SQLITE)
- Guard database_sqlite.c with HAVE_SQLITE in Makefile.am, database.c,
  database.h and stub_database.c
- Fall back to flatfile when SQLite not compiled in

Security (database_flatfile.c):
- MAM dedup: skip incoming messages with duplicate stanza-id (archive_id)
- LMC sender validation: reject corrections from mismatched JIDs
- Add flock() advisory locking to prevent interleaved writes from
  concurrent instances
- Check fprintf return when writing file header

Autocomplete (cmd_ac.c):
- Add 'flatfile' to /privacy logging autocomplete
- Add dedicated /history autocomplete with on/off/verify (was boolean-only)

Code quality:
- Fix fwrite return type: size_t not ssize_t (database_flatfile_parser.c)
- Fix mixed allocators in ff_readline: use malloc() consistently for
  overlength-line fallback instead of g_strdup()
- Fix index alignment in _ff_state_extend: use local counter so index
  step doesn't depend on total_lines from initial build

Documentation:
- Fix page: correct directory structure (history.log not per-day files)
- Fix page: add aid:{archive_id} to line format example
- Fix page: missing newline before .SH BUGS
2026-03-12 18:46:42 +03:00
16ae7fb85c database_flatfile: single-file storage with sparse index
Replace per-contact directory structure with a single flat file per contact.
Each file uses pipe-delimited records with a sparse byte-offset index
that is rebuilt on demand and cached in memory.

- Rewrite _ff_write_record / _ff_read_records for single-file format
- Add sparse index (every Nth record) for O(log N) seek on history read
- Cursor-based pagination: _ff_get_previous_chat uses saved byte offset
- LMC corrections applied in-place during read (_ff_apply_lmc)
- Newline escaping (\n) in message bodies for line-based storage
- Simplify verify: parse + timestamp order + LMC reference check
- Update parser helpers for new field layout
2026-03-12 18:46:42 +03:00
d25f90899c refactor: split database_flatfile.c into functional modules
Split the monolithic database_flatfile.c into:
- database_flatfile.h: internal header with shared types and prototypes
- database_flatfile_parser.c: parsing, escaping, IO helpers
- database_flatfile_verify.c: integrity verification logic
- database_flatfile.c: core backend (init, write, read, LMC, vtable)
2026-03-12 18:46:42 +03:00
89b90e5cf2 fix(flatfile): harden flat-file backend against injection and traversal attacks
Security fixes for 7 vulnerabilities in database_flatfile.c:

1. Path traversal via crafted JID (HIGH):
   _ff_jid_to_dir() now strips '/', '\' -> '_' and collapses '..' -> '__',
   preventing a malicious federated JID from escaping the log directory.

2. Log injection via unescaped message body (HIGH):
   Add _ff_escape_message()/_ff_unescape_message() -- escape \n, \r, \\
   in message text on write, unescape on read. Prevents remote contacts
   from injecting fake log lines with forged sender/timestamp/encryption.

3. Metadata field injection (HIGH):
   Add _ff_escape_meta_value()/_ff_unescape_meta_value() -- escape |, ],
   \\, \n, \r in stanza_id/archive_id/replace_id. Parser uses escape-aware
   _ff_find_unescaped_char() and _ff_split_meta() instead of strchr/strsplit.

4. Sender ": " parsing confusion (MEDIUM):
   Escape ": " -> "\: " in XMPP resource on write. Parser uses
   _ff_find_unescaped_colonspace() + _ff_unescape_sender_resource().

5. LMC correction chain cycle -> infinite loop (MEDIUM):
   Add visited hash-set and FF_MAX_LMC_DEPTH (100) limit to chain walker.

6. Unbounded getline() -> OOM (MEDIUM):
   Add FF_MAX_LINE_LEN (10 MB) cap in _ff_readline(); overlength lines
   are skipped with a warning. Replaces all char buf[8192]/fgets() sites
   with dynamic getline() + truncated-line detection at EOF.

7. Symlink attack + TOCTOU on file creation (MEDIUM):
   Replace fopen("a") with open(O_WRONLY|O_APPEND|O_CREAT|O_EXCL|O_NOFOLLOW)
   + fdopen() -- atomic new-file detection, symlink rejection via O_NOFOLLOW.
   _ff_ensure_dir() checks g_lstat() for symlinks. File permissions 0600
   set via open() mode, not post-hoc g_chmod().
2026-03-12 18:46:42 +03:00
31e8c30c34 feat: add flat-file database backend for message history
Add pluggable storage backend abstraction (vtable) to the database layer,
allowing selection between SQLite (default) and a new flat-file backend
that stores messages as human-readable plain text files.

New files:
- database_sqlite.c: extracted SQLite backend from database.c
- database_flatfile.c: plain text backend with tolerant parser,
  LMC correction chains, UTF-8/BOM/CRLF handling, integrity checks

Commands:
- /privacy logging flatfile — switch to flat-file backend
- /history verify [<jid>] — check integrity of stored history

Fixes:
- Add missing 'off' guard in flatfile backend
- Enable CHLOG/HISTORY prefs when switching to flatfile mode

Logs stored in ~/.local/share/profanity/flatlog/{account}/{contact}/{date}.log
Format: {ISO8601} [{type}|{enc}|id:{id}|corrects:{id}] {sender}: {message}

Updated: CHANGELOG, CONTRIBUTING.md, profrc.example, man page, cmd_defs,
Makefile.am, test stubs, functional test support (PROF_FLATFILE=1)
2026-03-12 18:46:42 +03:00