// SPDX-License-Identifier: GPL-3.0-or-later // // This file is part of CProof. // See LICENSE for the full GPLv3 text and the special OpenSSL linking exception. /* * database_flatfile.h * vim: expandtab:ts=4:sts=4:sw=4 * * Internal header shared between database_flatfile*.c modules. * Not part of the public API — do not include from outside the flatfile backend. */ #ifndef DATABASE_FLATFILE_H #define DATABASE_FLATFILE_H #include #include #include #include "database.h" #include "xmpp/xmpp.h" #include "xmpp/message.h" // --- Constants --- #define DIR_FLATLOG "flatlog" #define FLATFILE_FORMAT_VERSION 1 #define FF_VERSION_MARKER "format-version: " #define FF_STRINGIFY_(x) #x #define FF_STRINGIFY(x) FF_STRINGIFY_(x) #define FLATFILE_HEADER "# profanity chat log — UTF-8, LF line endings, " FF_VERSION_MARKER FF_STRINGIFY(FLATFILE_FORMAT_VERSION) "\n" #define FF_MAX_LINE_LEN (10 * 1024 * 1024) /* 10 MB — reject lines longer than this */ #define FF_MAX_LMC_DEPTH 100 /* max correction chain depth */ // --- Shared global --- // Account JID stored during init for path construction. // Defined in database_flatfile.c, used by all flatfile modules. extern char* g_flatfile_account_jid; // --- Parsed line structure --- typedef struct { char* timestamp_str; GDateTime* timestamp; char* type; char* enc; char* stanza_id; char* archive_id; char* replace_id; char* from_jid; char* from_resource; char* to_jid; char* to_resource; int marked_read; // -1 = unset (NULL in DB), 0 = unread, 1 = read char* message; off_t file_offset; } ff_parsed_line_t; // --- Sparse index for single-file lookup --- // Sample every N-th log line for the sparse index. // 500 lines ≈ one index entry per ~50 KB of log data, so a 100K-message // contact requires only ~200 entries (~3 KB) for O(log n) time-range lookup. #define FF_INDEX_STEP 500 // --- Metadata field prefixes (used in _ff_cache_line_ids and ff_parse_line) --- #define FF_META_PREFIX_ID "id:" #define FF_META_PREFIX_AID "aid:" typedef struct { off_t byte_offset; gint64 timestamp_epoch; } ff_index_entry_t; typedef struct { time_t mtime; off_t size; ino_t inode; } ff_file_stamp_t; typedef struct { char* filepath; ff_index_entry_t* entries; size_t n_entries; size_t cap_entries; size_t total_lines; ff_file_stamp_t stamp; int bom_len; GHashTable* archive_ids; // set: archive_id -> NULL (MAM dedup, O(1)) GHashTable* stanza_senders; // map: stanza_id -> from_jid (LMC validation, O(1)) } ff_contact_state_t; // State management ff_contact_state_t* ff_state_new(const char* filepath); void ff_state_free(ff_contact_state_t* state); gboolean ff_state_ensure_fresh(ff_contact_state_t* state); off_t ff_state_offset_for_time(ff_contact_state_t* state, const char* iso_time); // --- Type conversion helpers --- const char* ff_get_message_type_str(prof_msg_type_t type); prof_msg_type_t ff_get_message_type_type(const char* const type); const char* ff_get_message_enc_str(prof_enc_t enc); prof_enc_t ff_get_message_enc_type(const char* const encstr); // --- Path helpers --- char* ff_jid_to_dir(const char* jid); char* ff_get_contact_dir(const char* contact_barejid); char* ff_get_log_path(const char* contact_barejid); gboolean ff_ensure_dir(const char* path); // --- Escape / unescape --- char* ff_escape_message(const char* text); char* ff_unescape_message(const char* text); char* ff_escape_meta_value(const char* val); char* ff_unescape_meta_value(const char* val); // --- I/O --- // Skip UTF-8 BOM if present; returns 3 if skipped, 0 otherwise. int ff_skip_bom(FILE* fp); // Scan leading '#' comment lines for the format-version marker. Returns // the version found, 0 if no marker is present, or -1 on read error. // File position is rewound to the start of the comment block on entry. int ff_read_format_version(FILE* fp); char* ff_readline(FILE* fp, gboolean* truncated); void ff_write_line(FILE* fp, const char* timestamp, const char* type, const char* enc, const char* stanza_id, const char* archive_id, const char* replace_id, const char* from_jid, const char* from_resource, const char* to_jid, const char* to_resource, int marked_read, const char* message_text); // --- Parser helpers --- const char* ff_find_unescaped_char(const char* str, char ch); char** ff_split_meta(const char* meta); const char* ff_find_unescaped_colonspace(const char* str); char* ff_unescape_sender_resource(const char* res); // --- Parser --- void ff_parsed_line_free(ff_parsed_line_t* pl); ff_parsed_line_t* ff_parse_line(const char* line); ProfMessage* ff_parsed_to_profmessage(ff_parsed_line_t* pl); // --- Integrity verification (database_flatfile_verify.c) --- // Run integrity checks against one contact's history.log (or every contact // when contact_barejid == NULL). // // Returns a freshly allocated GSList reporting: // - File-level: missing log, BOM, CRLF, empty, wrong permissions // - Line-level: invalid UTF-8, control characters, unparsable lines, // timestamps out of order, duplicate stanza-id / // archive-id (tracked separately) // - Cross-line: broken `corrects:` LMC references // // Callers must free with g_slist_free_full(issues, integrity_issue_free). GSList* ff_verify_integrity(const gchar* const contact_barejid); #endif