|
|
|
|
@@ -42,9 +42,9 @@
|
|
|
|
|
#include "xmpp/xmpp.h"
|
|
|
|
|
#include "xmpp/message.h"
|
|
|
|
|
|
|
|
|
|
#define DIR_FLATLOG "flatlog"
|
|
|
|
|
#define FLATFILE_HEADER "# profanity chat log — UTF-8, LF line endings\n# vim: set fileencoding=utf-8 fileformat=unix :\n"
|
|
|
|
|
#define FF_MAX_LINE_LEN (10 * 1024 * 1024) /* 10 MB — reject lines longer than this */
|
|
|
|
|
#define DIR_FLATLOG "flatlog"
|
|
|
|
|
#define FLATFILE_HEADER "# profanity chat log — UTF-8, LF line endings\n# vim: set fileencoding=utf-8 fileformat=unix :\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 */
|
|
|
|
|
|
|
|
|
|
// Account JID stored during init for path construction
|
|
|
|
|
@@ -124,12 +124,14 @@ _ff_get_message_enc_type(const char* const encstr)
|
|
|
|
|
static char*
|
|
|
|
|
_ff_jid_to_dir(const char* jid)
|
|
|
|
|
{
|
|
|
|
|
if (!jid || jid[0] == '\0') return NULL;
|
|
|
|
|
if (!jid || jid[0] == '\0')
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
// Reject embedded null bytes (strlen already stops, but be explicit)
|
|
|
|
|
// Replace '@' first
|
|
|
|
|
char* step1 = str_replace(jid, "@", "_at_");
|
|
|
|
|
if (!step1) return NULL;
|
|
|
|
|
if (!step1)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
// Replace '/' and '\\' with '_' to prevent path traversal
|
|
|
|
|
GString* out = g_string_sized_new(strlen(step1));
|
|
|
|
|
@@ -163,7 +165,8 @@ _ff_jid_to_dir(const char* jid)
|
|
|
|
|
static char*
|
|
|
|
|
_ff_get_contact_dir(const char* contact_barejid)
|
|
|
|
|
{
|
|
|
|
|
if (!g_flatfile_account_jid || !contact_barejid) return NULL;
|
|
|
|
|
if (!g_flatfile_account_jid || !contact_barejid)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
auto_gchar gchar* data_path = files_get_data_path(DIR_FLATLOG);
|
|
|
|
|
auto_gchar gchar* my_dir = _ff_jid_to_dir(g_flatfile_account_jid);
|
|
|
|
|
@@ -179,7 +182,8 @@ static char*
|
|
|
|
|
_ff_get_log_path(const char* contact_barejid, GDateTime* dt)
|
|
|
|
|
{
|
|
|
|
|
auto_gchar gchar* contact_dir = _ff_get_contact_dir(contact_barejid);
|
|
|
|
|
if (!contact_dir) return NULL;
|
|
|
|
|
if (!contact_dir)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
auto_gchar gchar* date_str = g_date_time_format(dt, "%Y_%m_%d");
|
|
|
|
|
char* result = g_strdup_printf("%s/%s.log", contact_dir, date_str);
|
|
|
|
|
@@ -218,14 +222,23 @@ _ff_ensure_dir(const char* path)
|
|
|
|
|
static char*
|
|
|
|
|
_ff_escape_message(const char* text)
|
|
|
|
|
{
|
|
|
|
|
if (!text) return g_strdup("");
|
|
|
|
|
if (!text)
|
|
|
|
|
return g_strdup("");
|
|
|
|
|
GString* out = g_string_sized_new(strlen(text));
|
|
|
|
|
for (const char* p = text; *p; p++) {
|
|
|
|
|
switch (*p) {
|
|
|
|
|
case '\\': g_string_append(out, "\\\\"); break;
|
|
|
|
|
case '\n': g_string_append(out, "\\n"); break;
|
|
|
|
|
case '\r': g_string_append(out, "\\r"); break;
|
|
|
|
|
default: g_string_append_c(out, *p); break;
|
|
|
|
|
case '\\':
|
|
|
|
|
g_string_append(out, "\\\\");
|
|
|
|
|
break;
|
|
|
|
|
case '\n':
|
|
|
|
|
g_string_append(out, "\\n");
|
|
|
|
|
break;
|
|
|
|
|
case '\r':
|
|
|
|
|
g_string_append(out, "\\r");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
g_string_append_c(out, *p);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return g_string_free(out, FALSE);
|
|
|
|
|
@@ -235,15 +248,22 @@ _ff_escape_message(const char* text)
|
|
|
|
|
static char*
|
|
|
|
|
_ff_unescape_message(const char* text)
|
|
|
|
|
{
|
|
|
|
|
if (!text) return g_strdup("");
|
|
|
|
|
if (!text)
|
|
|
|
|
return g_strdup("");
|
|
|
|
|
GString* out = g_string_sized_new(strlen(text));
|
|
|
|
|
for (const char* p = text; *p; p++) {
|
|
|
|
|
if (*p == '\\' && *(p + 1)) {
|
|
|
|
|
p++;
|
|
|
|
|
switch (*p) {
|
|
|
|
|
case '\\': g_string_append_c(out, '\\'); break;
|
|
|
|
|
case 'n': g_string_append_c(out, '\n'); break;
|
|
|
|
|
case 'r': g_string_append_c(out, '\r'); break;
|
|
|
|
|
case '\\':
|
|
|
|
|
g_string_append_c(out, '\\');
|
|
|
|
|
break;
|
|
|
|
|
case 'n':
|
|
|
|
|
g_string_append_c(out, '\n');
|
|
|
|
|
break;
|
|
|
|
|
case 'r':
|
|
|
|
|
g_string_append_c(out, '\r');
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
g_string_append_c(out, '\\');
|
|
|
|
|
g_string_append_c(out, *p);
|
|
|
|
|
@@ -261,16 +281,29 @@ _ff_unescape_message(const char* text)
|
|
|
|
|
static char*
|
|
|
|
|
_ff_escape_meta_value(const char* val)
|
|
|
|
|
{
|
|
|
|
|
if (!val || strlen(val) == 0) return NULL;
|
|
|
|
|
if (!val || strlen(val) == 0)
|
|
|
|
|
return NULL;
|
|
|
|
|
GString* out = g_string_sized_new(strlen(val));
|
|
|
|
|
for (const char* p = val; *p; p++) {
|
|
|
|
|
switch (*p) {
|
|
|
|
|
case '|': g_string_append(out, "\\|"); break;
|
|
|
|
|
case ']': g_string_append(out, "\\]"); break;
|
|
|
|
|
case '\\': g_string_append(out, "\\\\"); break;
|
|
|
|
|
case '\n': g_string_append(out, "\\n"); break;
|
|
|
|
|
case '\r': g_string_append(out, "\\r"); break;
|
|
|
|
|
default: g_string_append_c(out, *p); break;
|
|
|
|
|
case '|':
|
|
|
|
|
g_string_append(out, "\\|");
|
|
|
|
|
break;
|
|
|
|
|
case ']':
|
|
|
|
|
g_string_append(out, "\\]");
|
|
|
|
|
break;
|
|
|
|
|
case '\\':
|
|
|
|
|
g_string_append(out, "\\\\");
|
|
|
|
|
break;
|
|
|
|
|
case '\n':
|
|
|
|
|
g_string_append(out, "\\n");
|
|
|
|
|
break;
|
|
|
|
|
case '\r':
|
|
|
|
|
g_string_append(out, "\\r");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
g_string_append_c(out, *p);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return g_string_free(out, FALSE);
|
|
|
|
|
@@ -280,17 +313,28 @@ _ff_escape_meta_value(const char* val)
|
|
|
|
|
static char*
|
|
|
|
|
_ff_unescape_meta_value(const char* val)
|
|
|
|
|
{
|
|
|
|
|
if (!val) return NULL;
|
|
|
|
|
if (!val)
|
|
|
|
|
return NULL;
|
|
|
|
|
GString* out = g_string_sized_new(strlen(val));
|
|
|
|
|
for (const char* p = val; *p; p++) {
|
|
|
|
|
if (*p == '\\' && *(p + 1)) {
|
|
|
|
|
p++;
|
|
|
|
|
switch (*p) {
|
|
|
|
|
case '|': g_string_append_c(out, '|'); break;
|
|
|
|
|
case ']': g_string_append_c(out, ']'); break;
|
|
|
|
|
case '\\': g_string_append_c(out, '\\'); break;
|
|
|
|
|
case 'n': g_string_append_c(out, '\n'); break;
|
|
|
|
|
case 'r': g_string_append_c(out, '\r'); break;
|
|
|
|
|
case '|':
|
|
|
|
|
g_string_append_c(out, '|');
|
|
|
|
|
break;
|
|
|
|
|
case ']':
|
|
|
|
|
g_string_append_c(out, ']');
|
|
|
|
|
break;
|
|
|
|
|
case '\\':
|
|
|
|
|
g_string_append_c(out, '\\');
|
|
|
|
|
break;
|
|
|
|
|
case 'n':
|
|
|
|
|
g_string_append_c(out, '\n');
|
|
|
|
|
break;
|
|
|
|
|
case 'r':
|
|
|
|
|
g_string_append_c(out, '\r');
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
g_string_append_c(out, '\\');
|
|
|
|
|
g_string_append_c(out, *p);
|
|
|
|
|
@@ -330,16 +374,19 @@ _ff_readline(FILE* fp, gboolean* truncated)
|
|
|
|
|
int ch;
|
|
|
|
|
while ((ch = fgetc(fp)) != EOF && ch != '\n') {}
|
|
|
|
|
}
|
|
|
|
|
if (truncated) *truncated = FALSE;
|
|
|
|
|
if (truncated)
|
|
|
|
|
*truncated = FALSE;
|
|
|
|
|
// Return empty string so caller's loop continues (parse will reject it)
|
|
|
|
|
return g_strdup("");
|
|
|
|
|
}
|
|
|
|
|
if (truncated) *truncated = FALSE;
|
|
|
|
|
if (truncated)
|
|
|
|
|
*truncated = FALSE;
|
|
|
|
|
if (nread > 0 && line[nread - 1] == '\n') {
|
|
|
|
|
line[--nread] = '\0';
|
|
|
|
|
} else if (feof(fp)) {
|
|
|
|
|
// Line without trailing newline at EOF — likely a partial write
|
|
|
|
|
if (truncated) *truncated = TRUE;
|
|
|
|
|
if (truncated)
|
|
|
|
|
*truncated = TRUE;
|
|
|
|
|
}
|
|
|
|
|
return line;
|
|
|
|
|
}
|
|
|
|
|
@@ -428,13 +475,15 @@ _ff_write_line(FILE* fp, const char* timestamp, const char* type, const char* en
|
|
|
|
|
static const char*
|
|
|
|
|
_ff_find_unescaped_char(const char* str, char ch)
|
|
|
|
|
{
|
|
|
|
|
if (!str) return NULL;
|
|
|
|
|
if (!str)
|
|
|
|
|
return NULL;
|
|
|
|
|
for (const char* p = str; *p; p++) {
|
|
|
|
|
if (*p == '\\' && *(p + 1)) {
|
|
|
|
|
p++; // skip escaped character
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (*p == ch) return p;
|
|
|
|
|
if (*p == ch)
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
@@ -446,14 +495,15 @@ _ff_split_meta(const char* meta)
|
|
|
|
|
{
|
|
|
|
|
GPtrArray* arr = g_ptr_array_new();
|
|
|
|
|
const char* start = meta;
|
|
|
|
|
for (const char* p = meta; ; p++) {
|
|
|
|
|
for (const char* p = meta;; p++) {
|
|
|
|
|
if (*p == '\\' && *(p + 1)) {
|
|
|
|
|
p++; // skip escaped char
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (*p == '|' || *p == '\0') {
|
|
|
|
|
g_ptr_array_add(arr, g_strndup(start, p - start));
|
|
|
|
|
if (*p == '\0') break;
|
|
|
|
|
if (*p == '\0')
|
|
|
|
|
break;
|
|
|
|
|
start = p + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -466,7 +516,8 @@ _ff_split_meta(const char* meta)
|
|
|
|
|
static const char*
|
|
|
|
|
_ff_find_unescaped_colonspace(const char* str)
|
|
|
|
|
{
|
|
|
|
|
if (!str) return NULL;
|
|
|
|
|
if (!str)
|
|
|
|
|
return NULL;
|
|
|
|
|
for (const char* p = str; *p; p++) {
|
|
|
|
|
if (*p == '\\' && *(p + 1)) {
|
|
|
|
|
p++; // skip escaped character
|
|
|
|
|
@@ -483,7 +534,8 @@ _ff_find_unescaped_colonspace(const char* str)
|
|
|
|
|
static char*
|
|
|
|
|
_ff_unescape_sender_resource(const char* res)
|
|
|
|
|
{
|
|
|
|
|
if (!res) return NULL;
|
|
|
|
|
if (!res)
|
|
|
|
|
return NULL;
|
|
|
|
|
GString* out = g_string_sized_new(strlen(res));
|
|
|
|
|
for (const char* p = res; *p; p++) {
|
|
|
|
|
if (*p == '\\' && *(p + 1)) {
|
|
|
|
|
@@ -507,7 +559,8 @@ _ff_unescape_sender_resource(const char* res)
|
|
|
|
|
|
|
|
|
|
// --- Line parser ---
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
char* timestamp_str;
|
|
|
|
|
GDateTime* timestamp;
|
|
|
|
|
char* type;
|
|
|
|
|
@@ -523,9 +576,11 @@ typedef struct {
|
|
|
|
|
static void
|
|
|
|
|
_ff_parsed_line_free(flatfile_parsed_line_t* pl)
|
|
|
|
|
{
|
|
|
|
|
if (!pl) return;
|
|
|
|
|
if (!pl)
|
|
|
|
|
return;
|
|
|
|
|
g_free(pl->timestamp_str);
|
|
|
|
|
if (pl->timestamp) g_date_time_unref(pl->timestamp);
|
|
|
|
|
if (pl->timestamp)
|
|
|
|
|
g_date_time_unref(pl->timestamp);
|
|
|
|
|
g_free(pl->type);
|
|
|
|
|
g_free(pl->enc);
|
|
|
|
|
g_free(pl->stanza_id);
|
|
|
|
|
@@ -570,7 +625,8 @@ _ff_parse_line(const char* line)
|
|
|
|
|
g_free(work);
|
|
|
|
|
work = converted;
|
|
|
|
|
} else {
|
|
|
|
|
if (err) g_error_free(err);
|
|
|
|
|
if (err)
|
|
|
|
|
g_error_free(err);
|
|
|
|
|
g_free(work);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
@@ -616,7 +672,8 @@ _ff_parse_line(const char* line)
|
|
|
|
|
|
|
|
|
|
// Parse sender: message after '] '
|
|
|
|
|
const char* after_meta = bracket_end + 1;
|
|
|
|
|
if (*after_meta == ' ') after_meta++;
|
|
|
|
|
if (*after_meta == ' ')
|
|
|
|
|
after_meta++;
|
|
|
|
|
|
|
|
|
|
// Find first *unescaped* ': ' which separates sender from message.
|
|
|
|
|
// Resources may contain escaped \: sequences.
|
|
|
|
|
@@ -685,15 +742,17 @@ _ff_parse_line(const char* line)
|
|
|
|
|
// Parse timestamp
|
|
|
|
|
result->timestamp = g_date_time_new_from_iso8601(result->timestamp_str, NULL);
|
|
|
|
|
if (!result->timestamp) {
|
|
|
|
|
log_warning("flatfile: unparseable timestamp: %s", result->timestamp_str);
|
|
|
|
|
log_warning("flatfile: unparsable timestamp: %s", result->timestamp_str);
|
|
|
|
|
_ff_parsed_line_free(result);
|
|
|
|
|
g_free(work);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Default type/enc if missing
|
|
|
|
|
if (!result->type) result->type = g_strdup("chat");
|
|
|
|
|
if (!result->enc) result->enc = g_strdup("none");
|
|
|
|
|
if (!result->type)
|
|
|
|
|
result->type = g_strdup("chat");
|
|
|
|
|
if (!result->enc)
|
|
|
|
|
result->enc = g_strdup("none");
|
|
|
|
|
|
|
|
|
|
g_free(work);
|
|
|
|
|
return result;
|
|
|
|
|
@@ -841,7 +900,8 @@ _flatfile_close(void)
|
|
|
|
|
static void
|
|
|
|
|
_flatfile_add_incoming(ProfMessage* message)
|
|
|
|
|
{
|
|
|
|
|
if (!message || !message->from_jid) return;
|
|
|
|
|
if (!message || !message->from_jid)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const Jid* to_jid = message->to_jid ? message->to_jid : connection_get_jid();
|
|
|
|
|
const char* type = _ff_get_message_type_str(message->type);
|
|
|
|
|
@@ -894,7 +954,8 @@ _ff_list_log_files(const char* contact_barejid)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GDir* dir = g_dir_open(contact_dir, 0, NULL);
|
|
|
|
|
if (!dir) return NULL;
|
|
|
|
|
if (!dir)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
GSList* files = NULL;
|
|
|
|
|
const gchar* fname;
|
|
|
|
|
@@ -918,7 +979,8 @@ _ff_read_file_messages(const char* filepath, const char* contact_barejid,
|
|
|
|
|
GSList* messages = NULL;
|
|
|
|
|
|
|
|
|
|
FILE* fp = fopen(filepath, "r");
|
|
|
|
|
if (!fp) return NULL;
|
|
|
|
|
if (!fp)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
// BOM detection
|
|
|
|
|
int c1 = fgetc(fp);
|
|
|
|
|
@@ -940,7 +1002,8 @@ _ff_read_file_messages(const char* filepath, const char* contact_barejid,
|
|
|
|
|
|
|
|
|
|
flatfile_parsed_line_t* pl = _ff_parse_line(buf);
|
|
|
|
|
free(buf);
|
|
|
|
|
if (!pl) continue;
|
|
|
|
|
if (!pl)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Check timestamp bounds
|
|
|
|
|
if (start_time) {
|
|
|
|
|
@@ -969,8 +1032,7 @@ _ff_read_file_messages(const char* filepath, const char* contact_barejid,
|
|
|
|
|
// Check JID filter: message must involve contact and our JID
|
|
|
|
|
gboolean matches = FALSE;
|
|
|
|
|
if (myjid && myjid->barejid && contact_barejid) {
|
|
|
|
|
if ((g_strcmp0(pl->from_jid, contact_barejid) == 0) ||
|
|
|
|
|
(g_strcmp0(pl->from_jid, myjid->barejid) == 0)) {
|
|
|
|
|
if ((g_strcmp0(pl->from_jid, contact_barejid) == 0) || (g_strcmp0(pl->from_jid, myjid->barejid) == 0)) {
|
|
|
|
|
matches = TRUE;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
@@ -1098,7 +1160,7 @@ _flatfile_get_previous_chat(const gchar* const contact_barejid, const gchar* sta
|
|
|
|
|
GSList* all_parsed = NULL;
|
|
|
|
|
for (GSList* f = files; f; f = f->next) {
|
|
|
|
|
GSList* file_msgs = _ff_read_file_messages(f->data, contact_barejid,
|
|
|
|
|
start_time, effective_end, myjid);
|
|
|
|
|
start_time, effective_end, myjid);
|
|
|
|
|
all_parsed = g_slist_concat(all_parsed, file_msgs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1127,7 +1189,8 @@ _flatfile_get_previous_chat(const gchar* const contact_barejid, const gchar* sta
|
|
|
|
|
GSList* nth = g_slist_nth(pre_result, MESSAGES_TO_RETRIEVE);
|
|
|
|
|
if (nth) {
|
|
|
|
|
GSList* prev = g_slist_nth(pre_result, MESSAGES_TO_RETRIEVE - 1);
|
|
|
|
|
if (prev) prev->next = NULL;
|
|
|
|
|
if (prev)
|
|
|
|
|
prev->next = NULL;
|
|
|
|
|
g_slist_free_full(nth, (GDestroyNotify)message_free);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
@@ -1135,7 +1198,8 @@ _flatfile_get_previous_chat(const gchar* const contact_barejid, const gchar* sta
|
|
|
|
|
guint skip = total - MESSAGES_TO_RETRIEVE;
|
|
|
|
|
GSList* keep_start = g_slist_nth(pre_result, skip);
|
|
|
|
|
GSList* prev = g_slist_nth(pre_result, skip - 1);
|
|
|
|
|
if (prev) prev->next = NULL;
|
|
|
|
|
if (prev)
|
|
|
|
|
prev->next = NULL;
|
|
|
|
|
g_slist_free_full(pre_result, (GDestroyNotify)message_free);
|
|
|
|
|
pre_result = keep_start;
|
|
|
|
|
}
|
|
|
|
|
@@ -1156,13 +1220,15 @@ _flatfile_get_limits_info(const gchar* const contact_barejid, gboolean is_last)
|
|
|
|
|
ProfMessage* msg = message_init();
|
|
|
|
|
|
|
|
|
|
if (!g_flatfile_account_jid || !contact_barejid) {
|
|
|
|
|
if (is_last) msg->timestamp = g_date_time_new_now_utc();
|
|
|
|
|
if (is_last)
|
|
|
|
|
msg->timestamp = g_date_time_new_now_utc();
|
|
|
|
|
return msg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GSList* files = _ff_list_log_files(contact_barejid);
|
|
|
|
|
if (!files) {
|
|
|
|
|
if (is_last) msg->timestamp = g_date_time_new_now_utc();
|
|
|
|
|
if (is_last)
|
|
|
|
|
msg->timestamp = g_date_time_new_now_utc();
|
|
|
|
|
return msg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1174,7 +1240,8 @@ _flatfile_get_limits_info(const gchar* const contact_barejid, gboolean is_last)
|
|
|
|
|
FILE* fp = fopen(target_file, "r");
|
|
|
|
|
if (!fp) {
|
|
|
|
|
g_slist_free_full(files, g_free);
|
|
|
|
|
if (is_last) msg->timestamp = g_date_time_new_now_utc();
|
|
|
|
|
if (is_last)
|
|
|
|
|
msg->timestamp = g_date_time_new_now_utc();
|
|
|
|
|
return msg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1194,7 +1261,8 @@ _flatfile_get_limits_info(const gchar* const contact_barejid, gboolean is_last)
|
|
|
|
|
while ((buf = _ff_readline(fp, NULL)) != NULL) {
|
|
|
|
|
found = _ff_parse_line(buf);
|
|
|
|
|
free(buf);
|
|
|
|
|
if (found) break;
|
|
|
|
|
if (found)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Find last valid line — read all and keep last
|
|
|
|
|
@@ -1202,7 +1270,8 @@ _flatfile_get_limits_info(const gchar* const contact_barejid, gboolean is_last)
|
|
|
|
|
flatfile_parsed_line_t* pl = _ff_parse_line(buf);
|
|
|
|
|
free(buf);
|
|
|
|
|
if (pl) {
|
|
|
|
|
if (found) _ff_parsed_line_free(found);
|
|
|
|
|
if (found)
|
|
|
|
|
_ff_parsed_line_free(found);
|
|
|
|
|
found = pl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1280,7 +1349,8 @@ _flatfile_verify_integrity(const gchar* const contact_barejid)
|
|
|
|
|
const char* cdir_path = cd->data;
|
|
|
|
|
|
|
|
|
|
GDir* dir = g_dir_open(cdir_path, 0, NULL);
|
|
|
|
|
if (!dir) continue;
|
|
|
|
|
if (!dir)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
GSList* log_files = NULL;
|
|
|
|
|
const gchar* fname;
|
|
|
|
|
@@ -1316,7 +1386,8 @@ _flatfile_verify_integrity(const gchar* const contact_barejid)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FILE* fp = fopen(filepath, "r");
|
|
|
|
|
if (!fp) continue;
|
|
|
|
|
if (!fp)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// BOM check (4l)
|
|
|
|
|
int c1 = fgetc(fp);
|
|
|
|
|
@@ -1352,7 +1423,10 @@ _flatfile_verify_integrity(const gchar* const contact_barejid)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip empty lines and comments
|
|
|
|
|
if (len == 0 || buf[0] == '#') { free(buf); continue; }
|
|
|
|
|
if (len == 0 || buf[0] == '#') {
|
|
|
|
|
free(buf);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
is_empty = FALSE;
|
|
|
|
|
|
|
|
|
|
// UTF-8 validation (4k/4n)
|
|
|
|
|
@@ -1389,7 +1463,7 @@ _flatfile_verify_integrity(const gchar* const contact_barejid)
|
|
|
|
|
issue->level = INTEGRITY_ERROR;
|
|
|
|
|
issue->file = g_strdup(basename);
|
|
|
|
|
issue->line = lineno;
|
|
|
|
|
issue->message = g_strdup("Unparseable line");
|
|
|
|
|
issue->message = g_strdup("Unparsable line");
|
|
|
|
|
issues = g_slist_append(issues, issue);
|
|
|
|
|
free(buf);
|
|
|
|
|
continue;
|
|
|
|
|
@@ -1397,8 +1471,10 @@ _flatfile_verify_integrity(const gchar* const contact_barejid)
|
|
|
|
|
|
|
|
|
|
free(buf); // done with raw line
|
|
|
|
|
|
|
|
|
|
if (!first_ts) first_ts = g_date_time_ref(pl->timestamp);
|
|
|
|
|
if (last_ts) g_date_time_unref(last_ts);
|
|
|
|
|
if (!first_ts)
|
|
|
|
|
first_ts = g_date_time_ref(pl->timestamp);
|
|
|
|
|
if (last_ts)
|
|
|
|
|
g_date_time_unref(last_ts);
|
|
|
|
|
last_ts = g_date_time_ref(pl->timestamp);
|
|
|
|
|
|
|
|
|
|
// Timestamp order within file (4c)
|
|
|
|
|
@@ -1412,7 +1488,8 @@ _flatfile_verify_integrity(const gchar* const contact_barejid)
|
|
|
|
|
issue->message = g_strdup_printf("Timestamp out of order (%s after %s)", ts_cur, ts_prev);
|
|
|
|
|
issues = g_slist_append(issues, issue);
|
|
|
|
|
}
|
|
|
|
|
if (prev_ts) g_date_time_unref(prev_ts);
|
|
|
|
|
if (prev_ts)
|
|
|
|
|
g_date_time_unref(prev_ts);
|
|
|
|
|
prev_ts = g_date_time_ref(pl->timestamp);
|
|
|
|
|
|
|
|
|
|
// Duplicate stanza-id / archive-id (4f)
|
|
|
|
|
@@ -1486,12 +1563,16 @@ _flatfile_verify_integrity(const gchar* const contact_barejid)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (prev_file_last_ts) g_date_time_unref(prev_file_last_ts);
|
|
|
|
|
if (prev_file_last_ts)
|
|
|
|
|
g_date_time_unref(prev_file_last_ts);
|
|
|
|
|
prev_file_last_ts = last_ts ? g_date_time_ref(last_ts) : NULL;
|
|
|
|
|
|
|
|
|
|
if (prev_ts) g_date_time_unref(prev_ts);
|
|
|
|
|
if (first_ts) g_date_time_unref(first_ts);
|
|
|
|
|
if (last_ts) g_date_time_unref(last_ts);
|
|
|
|
|
if (prev_ts)
|
|
|
|
|
g_date_time_unref(prev_ts);
|
|
|
|
|
if (first_ts)
|
|
|
|
|
g_date_time_unref(first_ts);
|
|
|
|
|
if (last_ts)
|
|
|
|
|
g_date_time_unref(last_ts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Second pass: check LMC references across all files (4g)
|
|
|
|
|
@@ -1501,7 +1582,8 @@ _flatfile_verify_integrity(const gchar* const contact_barejid)
|
|
|
|
|
basename_lmc = basename_lmc ? basename_lmc + 1 : filepath;
|
|
|
|
|
|
|
|
|
|
FILE* fp = fopen(filepath, "r");
|
|
|
|
|
if (!fp) continue;
|
|
|
|
|
if (!fp)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Skip BOM
|
|
|
|
|
int b1 = fgetc(fp);
|
|
|
|
|
@@ -1516,12 +1598,17 @@ _flatfile_verify_integrity(const gchar* const contact_barejid)
|
|
|
|
|
while ((buf = _ff_readline(fp, NULL)) != NULL) {
|
|
|
|
|
lineno++;
|
|
|
|
|
gsize len = strlen(buf);
|
|
|
|
|
if (len > 0 && buf[len - 1] == '\r') buf[--len] = '\0';
|
|
|
|
|
if (len == 0 || buf[0] == '#') { free(buf); continue; }
|
|
|
|
|
if (len > 0 && buf[len - 1] == '\r')
|
|
|
|
|
buf[--len] = '\0';
|
|
|
|
|
if (len == 0 || buf[0] == '#') {
|
|
|
|
|
free(buf);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flatfile_parsed_line_t* pl = _ff_parse_line(buf);
|
|
|
|
|
free(buf);
|
|
|
|
|
if (!pl) continue;
|
|
|
|
|
if (!pl)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (pl->replace_id && strlen(pl->replace_id) > 0) {
|
|
|
|
|
if (!g_hash_table_contains(all_stanza_ids, pl->replace_id)) {
|
|
|
|
|
@@ -1538,7 +1625,8 @@ _flatfile_verify_integrity(const gchar* const contact_barejid)
|
|
|
|
|
fclose(fp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (prev_file_last_ts) g_date_time_unref(prev_file_last_ts);
|
|
|
|
|
if (prev_file_last_ts)
|
|
|
|
|
g_date_time_unref(prev_file_last_ts);
|
|
|
|
|
g_hash_table_destroy(seen_ids);
|
|
|
|
|
g_hash_table_destroy(all_stanza_ids);
|
|
|
|
|
g_slist_free_full(log_files, g_free);
|
|
|
|
|
|