From ea6eeae21081fa8711938f129a9ff69aa84a9780 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 3 Aug 2026 23:02:49 +0300 Subject: [PATCH] msg b64: base64-encode display prefix in d-field to avoid pipe/quote breaking parse b64_encode/decode (URL-safe A-Za-z0-9-_) in chat_core_priv.h on_media_registered encodes msg_data before full_data on_msg_inserted/chat_core_attachment_download decode base_filename android_jni_bridge decodes prefix for UI display --- src/chat/chat_core_priv.h | 54 +++++++++++++++++++ src/chat/chat_msg.c | 10 +++- .../jni_bridge/android_jni_bridge.c | 13 +++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/chat/chat_core_priv.h b/src/chat/chat_core_priv.h index fc0d9d38..af6d9f88 100644 --- a/src/chat/chat_core_priv.h +++ b/src/chat/chat_core_priv.h @@ -16,9 +16,63 @@ #include #include #include +#include #define CC_ID "chat_core" +/* ── Base64 encode/decode (URL-safe, used for msg "d" field prefix) ── */ + +static const char B64_ALPHABET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +static inline int b64_decode_char(char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '-') return 62; + if (c == '_') return 63; + return -1; +} + +static inline size_t b64_encode(const uint8_t* src, size_t src_len, char* dst, size_t dst_cap) { + size_t out = 0; + for (size_t i = 0; i < src_len; i += 3) { + if (out + 4 > dst_cap) break; + uint32_t v = (uint32_t)src[i] << 16; + if (i + 1 < src_len) v |= (uint32_t)src[i + 1] << 8; + if (i + 2 < src_len) v |= (uint32_t)src[i + 2]; + dst[out++] = B64_ALPHABET[(v >> 18) & 0x3F]; + dst[out++] = B64_ALPHABET[(v >> 12) & 0x3F]; + dst[out++] = (i + 1 < src_len) ? B64_ALPHABET[(v >> 6) & 0x3F] : '='; + dst[out++] = (i + 2 < src_len) ? B64_ALPHABET[v & 0x3F] : '='; + } + return out; +} + +static inline size_t b64_decode(const char* src, size_t src_len, uint8_t* dst, size_t dst_cap) { + size_t out = 0; + int vals[4]; size_t vi = 0; + for (size_t i = 0; i < src_len && out < dst_cap; i++) { + if (src[i] == '=') break; + int v = b64_decode_char(src[i]); + if (v < 0) continue; + vals[vi++] = v; + if (vi == 4) { + dst[out++] = (uint8_t)((vals[0] << 2) | (vals[1] >> 4)); + if (out < dst_cap) dst[out++] = (uint8_t)((vals[1] << 4) | (vals[2] >> 2)); + if (out < dst_cap && i + 1 < src_len && src[i + 1] != '=') + dst[out++] = (uint8_t)((vals[2] << 6) | vals[3]); + vi = 0; + } + } + if (vi == 3 && out < dst_cap) { + dst[out++] = (uint8_t)((vals[0] << 2) | (vals[1] >> 4)); + if (out < dst_cap) dst[out++] = (uint8_t)((vals[1] << 4) | (vals[2] >> 2)); + } else if (vi == 2 && out < dst_cap) { + dst[out++] = (uint8_t)((vals[0] << 2) | (vals[1] >> 4)); + } + return out; +} + /* ── Глобальное состояние (определено в chat_core.c) ── */ struct chat_core_ctx { diff --git a/src/chat/chat_msg.c b/src/chat/chat_msg.c index ead6dae5..28bc2f81 100644 --- a/src/chat/chat_msg.c +++ b/src/chat/chat_msg.c @@ -116,7 +116,10 @@ static void on_media_registered(void* arg, int err, const struct media_index_res if (!full_data) { DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: data alloc fail", CC_ID); u_free(mctx); return; } size_t off = 0; - if (base_len > 0) { memcpy(full_data + off, mctx->msg_data, base_len); off += base_len; } + if (base_len > 0) { + char b64[2048]; size_t b64len = b64_encode(mctx->msg_data, base_len, b64, sizeof(b64)); + memcpy(full_data + off, b64, b64len); off += b64len; + } off += snprintf(full_data + off, fdcap - off, "|%lld|%lld|%d|", (long long)result->file_size, (long long)result->block_size, nb); for (int i = 0; i < 16; i++) off += snprintf(full_data + off, fdcap - off, "%02x", result->media_id[i]); @@ -549,6 +552,9 @@ void on_msg_inserted(struct DB_SYNC_INSTANCE* si, uint64_t record_ts, const char while (body[bfi] && body[bfi] != '|' && bfi < sizeof(base_filename) - 1) base_filename[bfi++] = body[bfi]; base_filename[bfi] = '\0'; + /* base64-decode display prefix (encoded in on_media_registered) */ + uint8_t dec[256]; int dlen = (int)b64_decode(base_filename, bfi, dec, sizeof(dec)); + if (dlen > 0 && dlen < (int)sizeof(base_filename)) { memcpy(base_filename, dec, (size_t)dlen); base_filename[dlen] = '\0'; } if (bfi == 0) snprintf(base_filename, sizeof(base_filename), "file"); const uint8_t* sig_field = strstr(data, "\"sig\":\""); @@ -630,6 +636,8 @@ void chat_core_attachment_download(const char* channel_id, int64_t msg_id) { while (body[bfi] && body[bfi] != '|' && bfi < sizeof(base_filename) - 1) base_filename[bfi++] = body[bfi]; base_filename[bfi] = '\0'; + uint8_t dec[256]; int dlen = (int)b64_decode(base_filename, bfi, dec, sizeof(dec)); + if (dlen > 0 && dlen < (int)sizeof(base_filename)) { memcpy(base_filename, dec, (size_t)dlen); base_filename[dlen] = '\0'; } if (bfi == 0) snprintf(base_filename, sizeof(base_filename), "file"); uint8_t author_sig[64]; memcpy(author_sig, sig_blob, 64); diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index 781ce8f7..75c285af 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -413,6 +413,19 @@ char* utun_bridge_get_messages_json(const char* channel_id, int limit) { const char* cts = strstr((const char*)dptr, "\"ct\":\""); if (cts) { cts += 6; char* cte = strchr((char*)cts, '"'); if (cte) { size_t tlc = (size_t)(cte - cts); if (tlc < sizeof(ct)) { memcpy(ct, cts, tlc); ct[tlc] = '\0'; } } } } + /* base64-decode display prefix for media messages (encoded in on_media_registered) */ + if (strcmp(ct, "text/plain") != 0 && txt[0]) { + char* pipe = strchr(txt, '|'); + if (pipe) { + size_t prefix_len = (size_t)(pipe - txt); + uint8_t dec[1024]; int declen = (int)b64_decode(txt, prefix_len, dec, sizeof(dec)); + if (declen > 0 && declen < 2000) { + size_t suffix_len = strlen(pipe); + memmove(txt + declen, pipe, suffix_len + 1); + memcpy(txt, dec, (size_t)declen); + } + } + } char* esc_txt = json_escape_alloc(txt); char* esc_ct = json_escape_alloc(ct); int is_out = (node_id == (int64_t)my_id) ? 1 : 0;