From ccd1b56cae45d6e8be252dac3f98e749265494ff Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 26 Dec 2023 07:37:00 -0800 Subject: [PATCH] escape player-provided text in various places --- src/ChatCommands.cc | 24 +++++++++++++----------- src/ProxyCommands.cc | 6 +++--- src/ReceiveCommands.cc | 22 +++++++++++++--------- src/ReceiveSubcommands.cc | 2 +- src/Text.cc | 38 +++++++++++++++++++++++++++++++++++++- src/Text.hh | 7 +++++++ 6 files changed, 74 insertions(+), 25 deletions(-) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 055d2f33..b4e19729 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -639,7 +639,7 @@ static void proxy_command_get_player_card(shared_ptr send_guild_card(ses->client_channel, p.guild_card_number, p.guild_card_number, p.name, "", "", p.language, p.section_id, p.char_class); } } catch (const exception& e) { - send_text_message_printf(ses->client_channel, "Error: %s", e.what()); + send_text_message(ses->client_channel, "Error: " + remove_color(e.what())); } } } @@ -685,7 +685,7 @@ static void server_command_lobby_event(shared_ptr c, const std::string& uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { - send_text_message(c, "$C6No such lobby event."); + send_text_message(c, "$C6No such lobby event"); return; } @@ -699,7 +699,7 @@ static void proxy_command_lobby_event(shared_ptr ses } else { uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { - send_text_message(ses->client_channel, "$C6No such lobby event."); + send_text_message(ses->client_channel, "$C6No such lobby event"); } else { ses->config.override_lobby_event = new_event; if (!is_v1_or_v2(ses->version())) { @@ -714,7 +714,7 @@ static void server_command_lobby_event_all(shared_ptr c, const std::stri uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { - send_text_message(c, "$C6No such lobby event."); + send_text_message(c, "$C6No such lobby event"); return; } @@ -919,7 +919,8 @@ static void server_command_password(shared_ptr c, const std::string& arg } else { l->password = args; - send_text_message_printf(l, "$C6Game password:\n%s", l->password.c_str()); + string escaped = remove_color(l->password); + send_text_message_printf(l, "$C6Game password:\n%s", escaped.c_str()); } } @@ -1113,7 +1114,8 @@ static void server_command_change_bank(shared_ptr c, const std::string& } else if (new_char_index <= 4) { c->use_character_bank(new_char_index - 1); auto bp = c->current_bank_character(); - auto name = bp->disp.name.decode(c->language()); + + auto name = escape_player_name(bp->disp.name.decode(c->language())); send_text_message_printf(c, "$C6Using %s\'s bank (%zu)", name.c_str(), new_char_index); } else { throw runtime_error("invalid bank number"); @@ -1201,7 +1203,7 @@ static void server_command_save(shared_ptr c, const std::string&) { c->save_all(); send_text_message(c, "All data saved"); } catch (const exception& e) { - send_text_message_printf(c, "Can\'t save data:\n%s", e.what()); + send_text_message(c, "Can\'t save data:\n" + remove_color(e.what())); } c->reschedule_save_game_data_event(); } @@ -1212,7 +1214,7 @@ static void server_command_save(shared_ptr c, const std::string&) { static string name_for_client(shared_ptr c) { auto player = c->character(false); if (player.get()) { - return player->disp.name.decode(player->inventory.language); + return escape_player_name(player->disp.name.decode(player->inventory.language)); } if (c->license.get()) { @@ -1778,7 +1780,7 @@ static void server_command_surrender(shared_ptr c, const std::string&) { send_text_message(c, "$C6Battle has not\nyet started"); return; } - const string& name = c->character()->disp.name.decode(c->language()); + string name = remove_color(c->character()->disp.name.decode(c->language())); send_text_message_printf(l, "$C6%s has\nsurrendered", name.c_str()); for (const auto& watcher_l : l->watcher_lobbies) { send_text_message_printf(watcher_l, "$C6%s has\nsurrendered", name.c_str()); @@ -1983,7 +1985,7 @@ void on_chat_command(std::shared_ptr c, const std::string& text) { } catch (const precondition_failed& e) { send_text_message(c, e.what()); } catch (const exception& e) { - send_text_message_printf(c, "$C6Failed:\n%s", e.what()); + send_text_message(c, "$C6Failed:\n" + remove_color(e.what())); } } } @@ -2007,7 +2009,7 @@ void on_chat_command(shared_ptr ses, const std::stri } catch (const precondition_failed& e) { send_text_message(ses->client_channel, e.what()); } catch (const exception& e) { - send_text_message_printf(ses->client_channel, "$C6Failed:\n%s", e.what()); + send_text_message(ses->client_channel, "$C6Failed:\n" + remove_color(e.what())); } } } diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 458aba8c..af4178de 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1400,8 +1400,7 @@ static HandlerResult S_65_67_68_EB(shared_ptr ses, u if (index >= ses->lobby_players.size()) { ses->log.warning("Ignoring invalid player index %zu at position %zu", index, x); } else { - string name = entry.disp.visual.name.decode(entry.inventory.language); - + string name = escape_player_name(entry.disp.visual.name.decode(entry.inventory.language)); if (ses->license && (entry.lobby_data.guild_card_number == ses->remote_guild_card_number)) { entry.lobby_data.guild_card_number = ses->license->serial_number; num_replacements++; @@ -1578,9 +1577,10 @@ static HandlerResult S_66_69_E9(shared_ptr ses, uint ses->log.warning("Lobby leave command references missing position"); } else { auto& p = ses->lobby_players[index]; + string name = escape_player_name(p.name); if (ses->config.check_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED)) { send_text_message_printf(ses->client_channel, "$C4Leave: %zu/%" PRIu32 "\n%s", - index, p.guild_card_number, p.name.c_str()); + index, p.guild_card_number, name.c_str()); } p.guild_card_number = 0; p.name.clear(); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 8c907ec7..4384258e 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1786,7 +1786,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& game_c = game->clients[x]; if (game_c.get()) { auto player = game_c->character(); - string name = player->disp.name.decode(game_c->language()); + string name = escape_player_name(player->disp.name.decode(game_c->language())); if (game->is_ep3()) { info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n", x + 1, name.c_str(), player->disp.stats.level + 1); @@ -1808,16 +1808,16 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { bool cheats_enabled = game->check_flag(Lobby::Flag::CHEATS_ENABLED); bool locked = !game->password.empty(); if (cheats_enabled && locked) { - info += "$C4Locked$C7, $C6cheats enabled$C7\n"; + info += "$C4Locked$C7, $C6cheats on$C7\n"; } else if (cheats_enabled) { - info += "$C6Cheats enabled$C7\n"; + info += "$C6Cheats on$C7\n"; } else if (locked) { info += "$C4Locked$C7\n"; } if (game->quest) { info += (game->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) ? "$C6Quest: " : "$C4Quest: "; - info += game->quest->name; + info += remove_color(game->quest->name); info += "\n"; } else if (game->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) { info += "$C6Quest in progress\n"; @@ -1862,17 +1862,18 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { auto team = tourn->get_team(team_index); if (team) { string message; - if (team->name.empty()) { + string team_name = escape_player_name(team->name); + if (team_name.empty()) { message = "(No registrant)"; } else if (team->max_players == 1) { message = string_printf("$C6%s$C7\n%zu %s (%s)\nPlayers:", - team->name.c_str(), + team_name.c_str(), team->num_rounds_cleared, team->num_rounds_cleared == 1 ? "win" : "wins", team->is_active ? "active" : "defeated"); } else { message = string_printf("$C6%s$C7\n%zu %s (%s)%s\nPlayers:", - team->name.c_str(), + team_name.c_str(), team->num_rounds_cleared, team->num_rounds_cleared == 1 ? "win" : "wins", team->is_active ? "active" : "defeated", @@ -1883,10 +1884,13 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { if (player.player_name.empty()) { message += string_printf("\n $C6%08" PRIX32 "$C7", player.serial_number); } else { - message += string_printf("\n $C6%s$C7 (%08" PRIX32 ")", player.player_name.c_str(), player.serial_number); + string player_name = escape_player_name(player.player_name); + message += string_printf("\n $C6%s$C7 (%08" PRIX32 ")", player_name.c_str(), player.serial_number); } } else { - message += string_printf("\n $C3%s \"%s\"$C7", player.com_deck->player_name.c_str(), player.com_deck->deck_name.c_str()); + string player_name = escape_player_name(player.com_deck->player_name); + string deck_name = escape_player_name(player.com_deck->deck_name); + message += string_printf("\n $C3%s \"%s\"$C7", player_name.c_str(), deck_name.c_str()); } } send_ship_info(c, message); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 17bae742..cc9fded9 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -842,7 +842,7 @@ static void on_word_select_t(shared_ptr c, uint8_t command, uint8_t, voi } } catch (const exception& e) { - string name = c->character()->disp.name.decode(c->language()); + string name = escape_player_name(c->character()->disp.name.decode(c->language())); lc->log.warning("Untranslatable Word Select message: %s", e.what()); send_text_message_printf(lc, "$C4Untranslatable Word\nSelect message from\n%s", name.c_str()); } diff --git a/src/Text.cc b/src/Text.cc index 1dcf2f01..803ec261 100644 --- a/src/Text.cc +++ b/src/Text.cc @@ -281,7 +281,6 @@ void add_color(StringWriter& w, const char* src, size_t max_input_chars) { } src++; } - w.put(0); } string add_color(const string& s) { @@ -290,6 +289,35 @@ string add_color(const string& s) { return std::move(w.str()); } +void remove_color(StringWriter& w, const char* src, size_t max_input_chars) { + for (size_t x = 0; (x < max_input_chars) && *src; x++) { + if (*src == '$') { + w.put('%'); + w.put('s'); + } else if (*src == '%') { + w.put('%'); + w.put('%'); + } else if (*src == '#') { + w.put('%'); + w.put('n'); + } else if (*src == '\t') { + w.put('$'); + } else if (*src == '\n') { + w.put('#'); + } else { + w.put(*src); + } + src++; + } + w.put(0); +} + +string remove_color(const string& s) { + StringWriter w; + remove_color(w, s.data(), s.size()); + return std::move(w.str()); +} + string strip_color(const string& s) { string ret; for (size_t r = 0; r < s.size(); r++) { @@ -302,3 +330,11 @@ string strip_color(const string& s) { } return ret; } + +string escape_player_name(const string& name) { + if (name.size() > 2 && name[0] == '\t' && name[1] != 'C') { + return remove_color(name.substr(2)); + } else { + return remove_color(name); + } +} diff --git a/src/Text.hh b/src/Text.hh index 0b1d2834..12f16156 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -549,4 +549,11 @@ std::string add_color(const std::string& s); size_t add_color_inplace(char* a, size_t max_chars); void add_color_inplace(std::string& s); +// remove_color does the opposite of add_color (it changes \t into $, for +// example). strip_color is irreversible; it deletes color escape sequences. +void remove_color(StringWriter& w, const char* src, size_t max_input_chars); +std::string remove_color(const std::string& s); + std::string strip_color(const std::string& s); + +std::string escape_player_name(const std::string& name);