Preserve UTF-8 boundaries in DM peer names
This commit is contained in:
1 parent
fa76447f53
commit
d09ed0316f
4 files changed
+235
-8
No files matched your search
+43
-6
@@ -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}",
|
||||
|
||||
Reference in new issue
Block a user