diff --git a/tests/test_android_replies.c b/tests/test_android_replies.c index bad79665..f92ee080 100644 --- a/tests/test_android_replies.c +++ b/tests/test_android_replies.c @@ -177,7 +177,7 @@ static int check_addresses(const char *channel) { /* Собственные сокеты передают профиль независимо от адреса интерфейса; тестовые порты скрыты. */ static int check_local_sockets(void) { struct ETCP_SOCKET* saved = instance->etcp_sockets; - struct ETCP_SOCKET tcp = {.sock_id = 7, .is_tcp = 1, .name = "as_wlan0_v4_tcp", + struct ETCP_SOCKET tcp = {.sock_id = 7, .is_tcp = 1, .name = "wlan\"😀\\tcp\n", .address_profile = {ADDRESS_MAP_UNKNOWN, ADDRESS_IN_UNKNOWN, ADDRESS_SOURCE_ETCP}}; struct ETCP_SOCKET udp = {.sock_id = 8, .name = "as_wlan0_v6_udp", .address_profile = {ADDRESS_MAP_EIM, ADDRESS_IN_OPEN, ADDRESS_SOURCE_ETCP}}; @@ -195,6 +195,7 @@ static int check_local_sockets(void) { instance->etcp_sockets = saved; char value[128]; REQUIRE(json && !extract(json, "$[0].nmapping", value, sizeof(value)) && !strcmp(value, "0")); + REQUIRE(!extract(json, "$[0].nm", value, sizeof(value)) && !strcmp(value, tcp.name)); REQUIRE(!extract(json, "$[0].nsource", value, sizeof(value)) && !strcmp(value, "1")); REQUIRE(!extract(json, "$[0].ad", value, sizeof(value)) && !strcmp(value, "192.168.40.250")); REQUIRE(!extract(json, "$[0].external", value, sizeof(value)) && !strcmp(value, "194.63.142.140")); @@ -301,10 +302,15 @@ static int check_snapshot_errors(const char* channel) { REQUIRE(sqlite3_set_authorizer(db, deny_snapshot, table) == SQLITE_OK); char* messages = utun_bridge_get_messages_json(channel, 10, NULL, 0); REQUIRE(sqlite3_set_authorizer(db, NULL, NULL) == SQLITE_OK && !messages); + peers_table_name(channel, table, sizeof(table)); + REQUIRE(sqlite3_set_authorizer(db, deny_snapshot, table) == SQLITE_OK); + char* members = utun_bridge_get_channel_members_json(channel); + REQUIRE(sqlite3_set_authorizer(db, NULL, NULL) == SQLITE_OK && !members); messages = utun_bridge_get_messages_json(channel, 10, NULL, 0); channels = utun_bridge_get_channels_json(); - REQUIRE(messages && !strcmp(messages, "[]") && channels && strcmp(channels, "[]")); - u_free(messages); u_free(channels); + members = utun_bridge_get_channel_members_json(channel); + REQUIRE(messages && !strcmp(messages, "[]") && channels && strcmp(channels, "[]") && members && strcmp(members, "[]")); + u_free(messages); u_free(channels); u_free(members); printf("[PASS] Android snapshots: SQL denial returns failure, empty history stays valid, retry restores full snapshot\n"); return 0; } diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index 86c5d91d..c3af4b92 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -748,44 +748,22 @@ int utun_bridge_chat_setting_get_int(const char* name, int def) { char* utun_bridge_get_channel_members_json(const char* channel_id) { struct UTUN_INSTANCE* inst = instance_lite_get_instance(); sqlite3* db = chat_core_get_db(inst); - if (!chat_core_is_initialized(inst) || !db || !channel_id || !channel_id[0]) return u_strdup("[]"); - - char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); - - sqlite3_stmt* cnt_st = NULL; - char sql[512]; - snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM \"%s\"", peers_tbl); - /* if table doesn't exist, return empty */ - if (sqlite3_prepare_v2(db, sql, -1, &cnt_st, NULL) != SQLITE_OK) return u_strdup("[]"); - int member_count = 0; - if (sqlite3_step(cnt_st) == SQLITE_ROW) member_count = sqlite3_column_int(cnt_st, 0); - sqlite3_finalize(cnt_st); - if (member_count == 0) return u_strdup("[]"); - - snprintf(sql, sizeof(sql), - "SELECT p.node_id, COALESCE(n.name,'')," - " COALESCE(p.local_nick,''), COALESCE(p.adm_tags,'')," - " COALESCE(p.node_type,0), COALESCE(p.node_RTT,0), COALESCE(p.storage,0)," - " COALESCE(p.userinfo,''),p.source,length(p.adm_tags_sig),COALESCE(p.deleted,0),p.radio_muted" - " FROM \"%s\" p LEFT JOIN nodes n ON p.node_id=n.node_id" - " ORDER BY p.node_id ASC", peers_tbl); - - sqlite3_stmt* st = NULL; - if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) { - bridge_log(BLEV_ERROR, "get_channel_members: main prepare failed ch=%s err=%s", - channel_id, sqlite3_errmsg(db)); - return u_strdup("[]"); + if (!chat_core_is_initialized(inst) || !db || !channel_id || !channel_id[0]) { + bridge_log(BLEV_WARN, "Members snapshot: core/database/channel unavailable"); return NULL; } - - size_t cap = 4096, pos = 1; - char* json = u_malloc(cap); - if (!json) { sqlite3_finalize(st); return u_strdup("[]"); } - json[0] = '['; - + char peers_tbl[80], sql[512]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl)); + snprintf(sql, sizeof(sql), + "SELECT p.node_id,COALESCE(n.name,''),COALESCE(p.local_nick,''),COALESCE(p.adm_tags,'')," + "COALESCE(p.node_type,0),COALESCE(p.node_RTT,0),COALESCE(p.storage,0),COALESCE(p.userinfo,'')," + "p.source,length(p.adm_tags_sig),COALESCE(p.deleted,0),p.radio_muted" + " FROM \"%s\" p LEFT JOIN nodes n ON p.node_id=n.node_id ORDER BY p.node_id ASC", peers_tbl); + struct strbuf out = strbuf_new(); + sqlite3_stmt* st = NULL; + int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL); + if (rc != SQLITE_OK || strbuf_addf(&out, "[") < 0) goto failed; uint64_t my_id = inst->node_id; - int first = 1; - - while (sqlite3_step(st) == SQLITE_ROW) { + unsigned count = 0; + while ((rc = sqlite3_step(st)) == SQLITE_ROW) { uint64_t node_id = (uint64_t)sqlite3_column_int64(st, 0); const char* name = (const char*)sqlite3_column_text(st, 1); const char* local_nick = (const char*)sqlite3_column_text(st, 2); @@ -813,39 +791,21 @@ char* utun_bridge_get_channel_members_json(const char* channel_id) { json_flat_get(adm_tags, "storage", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0) storage_role = 1; } - char* esc_name = json_escape_alloc(local_nick && local_nick[0] ? local_nick : - (name && name[0] ? name : "")); - char* esc_userinfo = json_escape_alloc((const char*)sqlite3_column_text(st, 7)); - if (!esc_name || !esc_userinfo) { - DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "Android members: JSON allocation failed channel=%s node=%016llx", - channel_id, (unsigned long long)node_id); - u_free(esc_name); u_free(esc_userinfo); continue; - } - const char* sep = first ? "" : ","; - first = 0; - - size_t needed = snprintf(NULL, 0, - "%s{\"n\":\"%s\",\"ni\":%llu,\"on\":0,\"ad\":%d,\"sp\":%d,\"md\":%d,\"st\":%d," - "\"vd\":%d,\"dl\":%d,\"tp\":%d,\"rt\":%d,\"is\":%d,\"ui\":\"%s\",\"sr\":%d,\"rm\":%d}", - sep, esc_name, (unsigned long long)node_id, - is_admin, is_super, is_moder, storage_flag, - is_verified, is_deleted, node_type, node_rtt, (node_id == my_id) ? 1 : 0, esc_userinfo, storage_role, sqlite3_column_int(st, 11)); - - while (pos + needed + 2 > cap) { char* tmp = u_realloc(json, cap * 2); if (!tmp) break; json = tmp; cap *= 2; } - if (pos + needed + 2 <= cap) - pos += (size_t)snprintf(json + pos, cap - pos, - "%s{\"n\":\"%s\",\"ni\":%llu,\"on\":0,\"ad\":%d,\"sp\":%d,\"md\":%d,\"st\":%d," - "\"vd\":%d,\"dl\":%d,\"tp\":%d,\"rt\":%d,\"is\":%d,\"ui\":\"%s\",\"sr\":%d,\"rm\":%d}", - sep, esc_name, (unsigned long long)node_id, - is_admin, is_super, is_moder, storage_flag, - is_verified, is_deleted, node_type, node_rtt, (node_id == my_id) ? 1 : 0, esc_userinfo, storage_role, sqlite3_column_int(st, 11)); - u_free(esc_userinfo); - u_free(esc_name); + if (strbuf_addf(&out, "%s{\"n\":", count++ ? "," : "") < 0 || + strbuf_add_json_string(&out, local_nick && local_nick[0] ? local_nick : name) < 0 || + strbuf_addf(&out, ",\"ni\":%llu,\"on\":0,\"ad\":%d,\"sp\":%d,\"md\":%d,\"st\":%d," + "\"vd\":%d,\"dl\":%d,\"tp\":%d,\"rt\":%d,\"is\":%d,\"ui\":", + (unsigned long long)node_id, is_admin, is_super, is_moder, storage_flag, + is_verified, is_deleted, node_type, node_rtt, node_id == my_id) < 0 || + strbuf_add_json_string(&out, (const char*)sqlite3_column_text(st, 7)) < 0 || + strbuf_addf(&out, ",\"sr\":%d,\"rm\":%d}", storage_role, sqlite3_column_int(st, 11)) < 0) goto failed; } + if (rc != SQLITE_DONE || strbuf_addf(&out, "]") < 0) goto failed; sqlite3_finalize(st); - - json[pos++] = ']'; json[pos] = '\0'; - return json; + return strbuf_detach(&out); +failed: + bridge_log(BLEV_ERROR, "Members complete snapshot failed channel=%s: %s", channel_id, sqlite3_errmsg(db)); + sqlite3_finalize(st); strbuf_free(&out); return NULL; } /* Не продолжаем сборку JSON после усечения строки: pos должен оставаться внутри буфера. */ @@ -883,6 +843,7 @@ char* utun_bridge_get_member_detail_json(const char* channel_id, uint64_t node_i int64_t last_seen = sqlite3_column_int64(st, 1); int64_t created = sqlite3_column_int64(st, 2); char* esc_name = json_escape_alloc(name && name[0] ? name : ""); + if (!esc_name) { sqlite3_finalize(st); return NULL; } int rc = member_detail_append(buf, sizeof(buf), &pos, ",\"n\":\"%s\",\"on\":0,\"ls\":%lld,\"cr\":%lld", esc_name, (long long)last_seen, (long long)created); @@ -1208,11 +1169,11 @@ char* utun_bridge_get_local_sockets_json(void) { bridge_address_parts(&socket->nat_addr, external, sizeof(external), &external_family, &external_port)) goto failed; int up = 0, down = 0; if (inst->connections) for (struct ll_entry* entry = inst->connections->head; entry; entry = entry->next) { - struct conn_queue_entry* value = entry->data; + struct conn_queue_entry* value = (struct conn_queue_entry*)entry->data; if (value) bridge_count_socket_links(value->conn, socket, &up, &down); } if (inst->tcp_connections) for (struct ll_entry* entry = inst->tcp_connections->head; entry; entry = entry->next) { - struct tcp_conn_entry* value = entry->data; + struct tcp_conn_entry* value = (struct tcp_conn_entry*)entry->data; if (value) bridge_count_socket_links(value->etcp_conn, socket, &up, &down); } const char* protocol = socket->is_tcp ? socket->reality_enabled ? "TCP+REALITY" : "TCP" : "UDP";