diff --git a/Content.Server/Chat/Managers/ChatSanitizationManager.cs b/Content.Server/Chat/Managers/ChatSanitizationManager.cs index c9566244357..c5ac2d2935b 100644 --- a/Content.Server/Chat/Managers/ChatSanitizationManager.cs +++ b/Content.Server/Chat/Managers/ChatSanitizationManager.cs @@ -33,7 +33,7 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager { ":D", "chatsan-smiles-widely" }, { "D:", "chatsan-frowns-deeply" }, { ":O", "chatsan-surprised" }, - { ":3", "chatsan-smiles" }, //nope + { ":3", "chatsan-smiles" }, { ":S", "chatsan-uncertain" }, { ":>", "chatsan-grins" }, { ":<", "chatsan-pouts" }, diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 94af12ea201..6b000532bec 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -263,6 +263,8 @@ public void TrySendInGameICMessage( } } + message = FormattedMessage.EscapeText(message); + // Otherwise, send whatever type. switch (desiredType) { @@ -408,7 +410,7 @@ private void SendEntitySpeak( return; // The original message - var message = TransformSpeech(source, FormattedMessage.RemoveMarkup(originalMessage), language); + var message = TransformSpeech(source, FormattedMessage.RemoveMarkupPermissive(originalMessage), language); if (message.Length == 0) return; @@ -481,7 +483,7 @@ private void SendEntityWhisper( if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker) return; - var message = TransformSpeech(source, FormattedMessage.RemoveMarkup(originalMessage), language); + var message = TransformSpeech(source, FormattedMessage.RemoveMarkupPermissive(originalMessage), language); if (message.Length == 0) return; @@ -517,7 +519,7 @@ private void SendEntityWhisper( var canUnderstandLanguage = _language.CanUnderstand(listener, language.ID); // How the entity perceives the message depends on whether it can understand its language - var perceivedMessage = FormattedMessage.EscapeText(canUnderstandLanguage ? message : languageObfuscatedMessage); + var perceivedMessage = canUnderstandLanguage ? message : languageObfuscatedMessage; // Result is the intermediate message derived from the perceived one via obfuscation // Wrapped message is the result wrapped in an "x says y" string @@ -544,7 +546,7 @@ private void SendEntityWhisper( _chatManager.ChatMessageToOne(ChatChannel.Whisper, result, wrappedMessage, source, false, session.Channel); } - var replayWrap = WrapWhisperMessage(source, "chat-manager-entity-whisper-wrap-message", name, FormattedMessage.EscapeText(message), language); + var replayWrap = WrapWhisperMessage(source, "chat-manager-entity-whisper-wrap-message", name, message, language); _replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, replayWrap, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); var ev = new EntitySpokeEvent(source, message, channel, true, language); @@ -591,7 +593,7 @@ private void SendEntityEmote( var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message", ("entityName", name), ("entity", ent), - ("message", FormattedMessage.RemoveMarkup(action))); + ("message", FormattedMessage.RemoveMarkupPermissive(action))); if (checkEmote) TryEmoteChatInput(source, action); @@ -771,8 +773,10 @@ private bool CanSendInGame(string message, IConsoleShell? shell = null, ICommonS // ReSharper disable once InconsistentNaming private string SanitizeInGameICMessage(EntityUid source, string message, out string? emoteStr, bool capitalize = true, bool punctuate = false, bool capitalizeTheWordI = true) { - var newMessage = message.Trim(); - newMessage = SanitizeMessageReplaceWords(newMessage); + var newMessage = SanitizeMessageReplaceWords(message.Trim()); + + GetRadioKeycodePrefix(source, newMessage, out newMessage, out var prefix); + _sanitizer.TrySanitizeOutSmilies(newMessage, source, out newMessage, out emoteStr); if (capitalize) newMessage = SanitizeMessageCapital(newMessage); @@ -781,9 +785,7 @@ private string SanitizeInGameICMessage(EntityUid source, string message, out str if (punctuate) newMessage = SanitizeMessagePeriod(newMessage); - _sanitizer.TrySanitizeOutSmilies(newMessage, source, out newMessage, out emoteStr); - - return newMessage; + return prefix + newMessage; } private string SanitizeInGameOOCMessage(string message) diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index ff2a30ccbdb..fe5da426d9d 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -86,6 +86,35 @@ public SpeechVerbPrototype GetSpeechVerb(EntityUid source, string message, Speec return current ?? _prototypeManager.Index(speech.SpeechVerb); } + /// + /// Splits the input message into a radio prefix part and the rest to preserve it during sanitization. + /// + /// + /// This is primarily for the chat emote sanitizer, which can match against ":b" as an emote, which is a valid radio keycode. + /// + public void GetRadioKeycodePrefix(EntityUid source, + string input, + out string output, + out string prefix) + { + prefix = string.Empty; + output = input; + + // If the string is less than 2, then it's probably supposed to be an emote. + // No one is sending empty radio messages! + if (input.Length <= 2) + return; + + if (!(input.StartsWith(RadioChannelPrefix) || input.StartsWith(RadioChannelAltPrefix))) + return; + + if (!_keyCodes.TryGetValue(input[1], out _)) + return; + + prefix = input[..2]; + output = input[2..]; + } + /// /// Attempts to resolve radio prefixes in chat messages (e.g., remove a leading ":e" and resolve the requested /// channel. Returns true if a radio message was attempted, even if the channel is invalid. diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 56684deb19e..ac3c5c8dd11 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -23,7 +23,7 @@ namespace Content.Shared.Preferences; [Serializable, NetSerializable] public sealed partial class HumanoidCharacterProfile : ICharacterProfile { - private static readonly Regex RestrictedNameRegex = new("[^A-Z,a-z,0-9, -]"); + private static readonly Regex RestrictedNameRegex = new(@"[^A-Za-z0-9 '\-]"); private static readonly Regex ICNameCaseRegex = new(@"^(?\w)|\b(?\w)(?=\w*$)"); public const int MaxNameLength = 64;