diff --git a/src/common/util.cpp b/src/common/util.cpp index 2a8dd71d..9d12b839 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -840,5 +840,113 @@ std::string get_nix_version_display_string() return pr.first + " " + pr.second; } + // Replaces invalid UTF-8 sequences with the UTF-8 replacement character (U+FFFD). +// If the original string is already valid UTF-8, it remains untouched. +// Returns true if the function modified the string, false otherwise. + bool sanitize_utf8(std::string& str_to_sanitize) { + const unsigned char* data = reinterpret_cast(str_to_sanitize.data()); + const unsigned char* end = data + str_to_sanitize.size(); + + std::string output; // remains empty unless invalid data is found + bool foundInvalid = false; + + // We'll track where the last "valid segment" started. + const unsigned char* segmentStart = data; + + // Helper lambda to append the replacement character U+FFFD (0xEF 0xBF 0xBD). + auto appendReplacementChar = [&](const unsigned char* pos) { + // If this is our first detected invalid sequence, start building 'output'. + // - Copy everything from segmentStart to 'pos' (not inclusive). + if (!foundInvalid) { + foundInvalid = true; + // Reserve approximate space to avoid repeated allocations + output.reserve(str_to_sanitize.size()); + output.append(reinterpret_cast(segmentStart), + reinterpret_cast(pos)); + } + // Append the UTF-8 replacement character + output.append("\xEF\xBF\xBD"); + }; + + while (data < end) { + unsigned char c = *data; + int extraBytes = 0; + + // 1) Determine how many bytes the current sequence should have, + // based on the leading byte. + if ((c & 0x80) == 0) { + // 1-byte sequence (ASCII) + extraBytes = 0; + } + else if ((c & 0xE0) == 0xC0) { + // 2-byte sequence + extraBytes = 1; + } + else if ((c & 0xF0) == 0xE0) { + // 3-byte sequence + extraBytes = 2; + } + else if ((c & 0xF8) == 0xF0) { + // 4-byte sequence + extraBytes = 3; + } + else { + // Invalid leading byte + appendReplacementChar(data); + data++; + // Next segment of "known good" data starts after this invalid byte + segmentStart = data; + continue; + } + + // 2) Check that we have enough bytes left in the string + if (data + extraBytes >= end) { + // We don't have enough continuation bytes for a complete sequence + appendReplacementChar(data); + // Nothing more we can parse, so we're done + data = end; + break; + } + + // 3) Validate the continuation bytes, which must match 10xxxxxx + bool invalidContinuation = false; + for (int i = 1; i <= extraBytes; ++i) { + if ((data[i] & 0xC0) != 0x80) { + invalidContinuation = true; + break; + } + } + + if (invalidContinuation) { + // One or more continuation bytes are invalid + appendReplacementChar(data); + data++; + segmentStart = data; + continue; + } + + // If we reach here, (c + continuation) forms a valid UTF-8 sequence + data += (extraBytes + 1); + } + + // If we never encountered invalid data, 'foundInvalid' is false. + // In that case, we do nothing and simply return false. + if (!foundInvalid) { + return false; // No change was made + } + + // Otherwise, we need to copy the last valid segment (if any) into 'output'. + // This covers the data from 'segmentStart' to the end. + if (segmentStart < end) { + output.append(reinterpret_cast(segmentStart), + reinterpret_cast(end)); + } + + // Now replace the original string with our sanitized version + str_to_sanitize.swap(output); + + // Indicate we performed a modification + return true; + } } // namespace tools diff --git a/src/common/util.h b/src/common/util.h index cfa5b19c..ebf261aa 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -46,6 +46,7 @@ namespace tools std::error_code replace_file(const std::string& replacement_name, const std::string& replaced_name); uint64_t get_total_system_memory(); std::string pretty_print_big_nums(std::uint64_t num); + bool sanitize_utf8(std::string& input); std::pair pretty_print_big_nums_to_pair(std::uint64_t num); inline crypto::hash get_proof_of_trust_hash(const nodetool::proof_of_trust& pot) diff --git a/src/wallet/core_fast_rpc_proxy.h b/src/wallet/core_fast_rpc_proxy.h index bf8ac62e..0b1c4b0e 100644 --- a/src/wallet/core_fast_rpc_proxy.h +++ b/src/wallet/core_fast_rpc_proxy.h @@ -15,6 +15,11 @@ namespace tools core_fast_rpc_proxy(currency::core_rpc_server& rpc_srv) :m_rpc(rpc_srv) {} //------------------------------------------------------------------------------------------------------------------------------ + virtual bool is_daemon_inbox() + { + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ virtual bool set_connection_addr(const std::string& url) override { return true; diff --git a/src/wallet/core_rpc_proxy.h b/src/wallet/core_rpc_proxy.h index 5a4a25b9..655548b7 100644 --- a/src/wallet/core_rpc_proxy.h +++ b/src/wallet/core_rpc_proxy.h @@ -67,6 +67,8 @@ namespace tools std::shared_ptr get_editable_proxy_diagnostic_info() { return m_pdiganostic_info; } virtual bool get_transfer_address(const std::string& adr_str, currency::account_public_address& addr, std::string& payment_id){ return false; } virtual void set_connectivity(unsigned int connection_timeout, size_t repeats_count) {} + // This method determines if the daemons share the same address space as the caller (which may help decide if some RPC calls can be skipped). + virtual bool is_daemon_inbox() { return false; } protected: std::shared_ptr m_pdiganostic_info; }; diff --git a/src/wallet/plain_wallet_api.cpp b/src/wallet/plain_wallet_api.cpp index 4fcfe6b7..d8c8a07e 100644 --- a/src/wallet/plain_wallet_api.cpp +++ b/src/wallet/plain_wallet_api.cpp @@ -42,7 +42,7 @@ LOG_ERROR("Core already deinitialised or not initialized yet."); \ epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); \ ok_response.result.return_code = API_RETURN_CODE_UNINITIALIZED; \ - return epee::serialization::store_t_to_json(ok_response); \ + return sanitized_store_to_json(ok_response); \ } namespace plain_wallet { @@ -80,6 +80,13 @@ namespace plain_wallet typedef epee::json_rpc::response error_response; + template + std::string sanitized_store_to_json(const t_struct& t_obj) + { + std::string r = epee::serialization::store_t_to_json(t_obj); + tools::sanitize_utf8(r); + return r; + } std::string get_set_working_dir(bool need_to_set = false, const std::string val = "") { @@ -182,7 +189,7 @@ namespace plain_wallet inst_ptr->gwm.quick_clear_wallets_no_save(); epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); ok_response.result.return_code = API_RETURN_CODE_OK; - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } @@ -197,7 +204,7 @@ namespace plain_wallet LOG_ERROR("Double-initialization in plain_wallet detected."); epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); ok_response.result.return_code = API_RETURN_CODE_ALREADY_EXISTS; - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } epee::static_helpers::set_or_call_on_destruct(true, static_destroy_handler); @@ -254,7 +261,7 @@ namespace plain_wallet error_response err_result = AUTO_VAL_INIT(err_result); err_result.error.code = API_RETURN_CODE_INTERNAL_ERROR; err_result.error.message = LOCATION_STR + " \nmessage:" + ec.message(); - return epee::serialization::store_t_to_json(err_result); + return sanitized_store_to_json(err_result); } std::string app_config_folder = get_app_config_folder(); @@ -264,14 +271,14 @@ namespace plain_wallet error_response err_result = AUTO_VAL_INIT(err_result); err_result.error.code = API_RETURN_CODE_INTERNAL_ERROR; err_result.error.message = LOCATION_STR + " \nmessage:" + ec.message(); - return epee::serialization::store_t_to_json(err_result); + return sanitized_store_to_json(err_result); } #endif std::atomic_store(&ginstance_ptr, ptr); #ifndef CAKEWALLET epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); ok_response.result.return_code = API_RETURN_CODE_OK; - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); #else return API_RETURN_CODE_OK; #endif @@ -299,7 +306,7 @@ namespace plain_wallet { error_response err_result = AUTO_VAL_INIT(err_result); err_result.error.code = ret_code; - return epee::serialization::store_t_to_json(err_result); + return sanitized_store_to_json(err_result); } return res_str; } @@ -311,13 +318,13 @@ namespace plain_wallet { error_response err_result = AUTO_VAL_INIT(err_result); err_result.error.code = ret_code; - return epee::serialization::store_t_to_json(err_result); + return sanitized_store_to_json(err_result); } else { epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); ok_response.result.return_code = API_RETURN_CODE_OK; - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } } bool is_wallet_exist(const std::string& path) @@ -344,7 +351,7 @@ namespace plain_wallet epee::log_space::log_singletone::truncate_log_files(); epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); ok_response.result.return_code = API_RETURN_CODE_OK; - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } std::string get_connectivity_status() @@ -374,7 +381,7 @@ namespace plain_wallet std::string wallet_files_path = get_wallets_folder(); strings_list sl = AUTO_VAL_INIT(sl); epee::file_io_utils::get_folder_content(wallet_files_path, sl.items, true); - return epee::serialization::store_t_to_json(sl); + return sanitized_store_to_json(sl); } std::string get_export_private_info(const std::string& target_dir) @@ -388,18 +395,18 @@ namespace plain_wallet LOG_ERROR("Failed to create target directory(" << full_target_path << "):" << ec.message()); epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); ok_response.result.return_code = API_RETURN_CODE_FAIL; - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } if(!tools::copy_dir(src_folder_path, full_target_path)) { LOG_ERROR("Failed to copy target directory"); epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); ok_response.result.return_code = API_RETURN_CODE_FAIL; - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); ok_response.result.return_code = API_RETURN_CODE_OK; - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } std::string delete_wallet(const std::string& file_name) @@ -410,7 +417,7 @@ namespace plain_wallet boost::filesystem::remove(wallet_files_path + file_name, er); epee::json_rpc::response ok_response = AUTO_VAL_INIT(ok_response); ok_response.result.return_code = API_RETURN_CODE_OK; - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } std::string get_address_info(const std::string& addr) @@ -463,11 +470,11 @@ namespace plain_wallet inst_ptr->gwm.run_wallet(ok_response.result.wallet_id); } - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } error_response err_result = AUTO_VAL_INIT(err_result); err_result.error.code = rsp; - return epee::serialization::store_t_to_json(err_result); + return sanitized_store_to_json(err_result); } std::string restore(const std::string& seed, const std::string& path, const std::string& password, const std::string& seed_password) @@ -487,11 +494,11 @@ namespace plain_wallet { inst_ptr->gwm.run_wallet(ok_response.result.wallet_id); } - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } error_response err_result = AUTO_VAL_INIT(err_result); err_result.error.code = rsp; - return epee::serialization::store_t_to_json(err_result); + return sanitized_store_to_json(err_result); } std::string generate(const std::string& path, const std::string& password) @@ -511,11 +518,11 @@ namespace plain_wallet { inst_ptr->gwm.run_wallet(ok_response.result.wallet_id); } - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } error_response err_result = AUTO_VAL_INIT(err_result); err_result.error.code = rsp; - return epee::serialization::store_t_to_json(err_result); + return sanitized_store_to_json(err_result); } std::string get_opened_wallets() @@ -523,7 +530,7 @@ namespace plain_wallet GET_INSTANCE_PTR(inst_ptr); epee::json_rpc::response, epee::json_rpc::dummy_error> ok_response = AUTO_VAL_INIT(ok_response); inst_ptr->gwm.get_opened_wallets(ok_response.result); - return epee::serialization::store_t_to_json(ok_response); + return sanitized_store_to_json(ok_response); } std::string close_wallet(hwallet h) @@ -588,7 +595,7 @@ namespace plain_wallet view::api_response ar = AUTO_VAL_INIT(ar); ar.error_code = API_RETURN_CODE_OK; - return epee::serialization::store_t_to_json(ar); + return sanitized_store_to_json(ar); } std::string handle_run_wallet(uint64_t instance_id) @@ -609,7 +616,7 @@ namespace plain_wallet view::api_response ar = AUTO_VAL_INIT(ar); ar.error_code = inst_ptr->gwm.run_wallet(instance_id); - return epee::serialization::store_t_to_json(ar); + return sanitized_store_to_json(ar); } std::string handle_configure(const std::string& settings_json) @@ -621,11 +628,11 @@ namespace plain_wallet if (!res) { conf_resp.status = API_RETURN_CODE_BAD_ARG; - return epee::serialization::store_t_to_json(conf_resp); + return sanitized_store_to_json(conf_resp); } inst_ptr->postponed_run_wallet = conf.postponed_run_wallet; conf_resp.status = API_RETURN_CODE_OK; - return epee::serialization::store_t_to_json(conf_resp); + return sanitized_store_to_json(conf_resp); } std::string sync_call(const std::string& method_name, uint64_t instance_id, const std::string& params) @@ -636,7 +643,7 @@ namespace plain_wallet close_wallet(instance_id); view::api_responce_return_code rc = AUTO_VAL_INIT(rc); rc.return_code = API_RETURN_CODE_OK; - res = epee::serialization::store_t_to_json(rc); + res = sanitized_store_to_json(rc); } else if (method_name == "open") { @@ -645,7 +652,7 @@ namespace plain_wallet { view::api_response ar = AUTO_VAL_INIT(ar); ar.error_code = "Wrong parameter"; - res = epee::serialization::store_t_to_json(ar); + res = sanitized_store_to_json(ar); }else { res = open(owr.path, owr.pass); @@ -658,7 +665,7 @@ namespace plain_wallet { view::api_response ar = AUTO_VAL_INIT(ar); ar.error_code = "Wrong parameter"; - res = epee::serialization::store_t_to_json(ar); + res = sanitized_store_to_json(ar); } else { @@ -672,13 +679,13 @@ namespace plain_wallet { view::api_response ar = AUTO_VAL_INIT(ar); ar.error_code = "Wrong parameter"; - res = epee::serialization::store_t_to_json(ar); + res = sanitized_store_to_json(ar); } else { view::api_response_t rsp = AUTO_VAL_INIT(rsp); rsp.error_code = tools::get_seed_phrase_info(sip.seed_phrase, sip.seed_password, rsp.response_data); - res = epee::serialization::store_t_to_json(rsp); + res = sanitized_store_to_json(rsp); } } else if (method_name == "invoke") @@ -705,7 +712,7 @@ namespace plain_wallet { view::api_response ar = AUTO_VAL_INIT(ar); ar.error_code = "UNKNOWN METHOD"; - res = epee::serialization::store_t_to_json(ar); + res = sanitized_store_to_json(ar); } return res; } @@ -748,7 +755,7 @@ namespace plain_wallet wallet_extended_info wei = AUTO_VAL_INIT(wei); inst_ptr->gwm.get_wallet_info(h, wei.wi); inst_ptr->gwm.get_wallet_info_extra(h, wei.wi_extended); - return epee::serialization::store_t_to_json(wei); + return sanitized_store_to_json(wei); } std::string reset_wallet_password(hwallet h, const std::string& password) { diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 62c2e947..433c5c6d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4588,7 +4588,11 @@ void wallet2::get_recent_transfers_history(std::vectoris_daemon_inbox()) + { + wti.remote_aliases = get_aliases_for_address(wti.remote_addresses[0]); + } } if (trs.size() >= count)