Preserve UTF-8 boundaries in DM peer names

This commit is contained in:
evgeny committed 2026-10-26 17:27:18 +01:00
1 parent fa76447f53
commit d09ed0316f
4 files changed
+235 -8

No files matched your search

+43 -6
View File
@@ -217,7 +217,7 @@ struct dm_conv {
uint64_t peer_node_id;
uint8_t peer_x25519[32];
uint8_t peer_ed25519[32];
char peer_name[64];
char peer_name[DM_PEER_NAME_MAX + 1];
uint64_t group_id;
uint8_t content_key[DM_CONTENT_KEY_SIZE];
uint64_t last_out_seq;
@@ -227,6 +227,37 @@ struct dm_conv {
enum dm_conv_result { DM_CONV_ERROR = -1, DM_CONV_FOUND = 0, DM_CONV_NOT_FOUND = 1 };
/* Копировать целый UTF-8 префикс; NUL внутри SQL-строки и битый суффикс не попадают в имя/JSON. */
static void dm_copy_peer_name(char out[DM_PEER_NAME_MAX + 1], const char* name, size_t len, const char* conv) {
size_t size = 0;
while (size < len && size < DM_PEER_NAME_MAX) {
unsigned c = (unsigned char)name[size];
if (c && c < 0x80) { size++; continue; }
unsigned count, scalar, minimum;
if (c >= 0xc2 && c <= 0xdf) { count = 1; scalar = c & 31; minimum = 0x80; }
else if (c >= 0xe0 && c <= 0xef) { count = 2; scalar = c & 15; minimum = 0x800; }
else if (c >= 0xf0 && c <= 0xf4) { count = 3; scalar = c & 7; minimum = 0x10000; }
else goto invalid;
if (count > len - size - 1) goto invalid;
for (unsigned i = 1; i <= count; i++) {
c = (unsigned char)name[size + i];
if ((c & 0xc0) != 0x80) goto invalid;
scalar = (scalar << 6) | (c & 63);
}
if (scalar < minimum || scalar > 0x10ffff || (scalar >= 0xd800 && scalar <= 0xdfff)) goto invalid;
if (count + 1 > DM_PEER_NAME_MAX - size) break;
size += count + 1;
}
if (size < len) DEBUG_DEBUG(DEBUG_CATEGORY_DM, "%s: peer name shortened conv=%s bytes=%zu->%zu", DM_ID, conv, len, size);
goto done;
invalid:
DEBUG_WARN(DEBUG_CATEGORY_DM, "%s: invalid peer name encoding conv=%s offset=%zu bytes=%zu; suffix omitted",
DM_ID, conv, size, len);
done:
if (size) memcpy(out, name, size);
out[size] = '\0';
}
/* FOUND выдаёт полную беседу; NOT_FOUND — только SQLITE_DONE; ERROR не меняет *c и БД. */
static enum dm_conv_result dm_conv_load(struct dm_state* dm, const char* conv_id, struct dm_conv* c) {
sqlite3_stmt* st = NULL;
@@ -259,7 +290,7 @@ static enum dm_conv_result dm_conv_load(struct dm_state* dm, const char* conv_id
memcpy(loaded.peer_x25519, x, 32);
memcpy(loaded.peer_ed25519, e, 32);
const char* name = (const char*)sqlite3_column_text(st, 3);
if (name) snprintf(loaded.peer_name, sizeof(loaded.peer_name), "%s", name);
dm_copy_peer_name(loaded.peer_name, name, (size_t)sqlite3_column_bytes(st, 3), conv_id);
loaded.group_id = (uint64_t)sqlite3_column_int64(st, 4);
loaded.last_out_seq = (uint64_t)sqlite3_column_int64(st, 5);
loaded.last_in_seq = (uint64_t)sqlite3_column_int64(st, 6);
@@ -493,7 +524,9 @@ int dm_accept_message(struct UTUN_INSTANCE* inst, const uint8_t* msg, size_t len
}
if (dm_node_pubkeys(dm, author, c.peer_x25519, c.peer_ed25519) != 0 ||
dm_derive_content_key(inst->my_keys.private_key, c.peer_x25519, c.content_key) != 0) return -1;
chat_core_get_node_name(inst, author, c.peer_name, sizeof(c.peer_name));
char name[128] = {0};
chat_core_get_node_name(inst, author, name, sizeof(name));
dm_copy_peer_name(c.peer_name, name, strlen(name), conv_id);
}
if (c.peer_node_id != author) {
DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: conversation author mismatch conv=%s", DM_ID, conv_id);
@@ -760,7 +793,7 @@ int dm_start(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
c.peer_node_id = peer_node_id;
memcpy(c.peer_x25519, peer_x25519, 32);
memcpy(c.peer_ed25519, peer_ed25519, 32);
if (peer_name) snprintf(c.peer_name, sizeof(c.peer_name), "%s", peer_name);
dm_copy_peer_name(c.peer_name, peer_name, peer_name ? strlen(peer_name) : 0, conv_id);
c.group_id = source_ch_id ? strtoull(source_ch_id, NULL, 10) : 0;
if (dm_derive_content_key(inst->my_keys.private_key, c.peer_x25519, c.content_key) != 0) return -1;
@@ -927,17 +960,21 @@ int dm_list_conversations_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf
const char* conv_id = (const char*)sqlite3_column_text(st, 0);
uint64_t peer = (uint64_t)sqlite3_column_int64(st, 1);
const char* name = (const char*)sqlite3_column_text(st, 2);
size_t name_len = (size_t)sqlite3_column_bytes(st, 2);
uint64_t last_ts = (uint64_t)sqlite3_column_int64(st, 3);
uint64_t out_seq = (uint64_t)sqlite3_column_int64(st, 4);
uint64_t in_seq = (uint64_t)sqlite3_column_int64(st, 5);
char resolved[128];
if (!name || !name[0]) {
if (!name || !name_len) {
resolved[0] = '\0';
chat_core_get_node_name(inst, peer, resolved, sizeof(resolved));
name = resolved;
name_len = strlen(resolved);
}
char safe_name[DM_PEER_NAME_MAX + 1];
dm_copy_peer_name(safe_name, name, name_len, conv_id);
char esc_name[512];
json_escape(name ? name : "", esc_name, sizeof(esc_name));
json_escape(safe_name, esc_name, sizeof(esc_name));
if (strbuf_addf(&json,
"%s{\"conv_id\":\"%s\",\"peer_node_id\":%llu,\"peer_name\":\"%s\","
"\"last_ts\":%llu,\"out_seq\":%llu,\"in_seq\":%llu,\"generation\":%llu}",