Skip to content

Commit 1de0fec

Browse files
committed
(#322) SharpXmppHelper: sanitize text on the outgoing XML
1 parent 15e679c commit 1de0fec

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

Emulsion.Tests/Xmpp/SharpXmppHelperTests.fs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2024 Emulsion contributors <https://github.com/codingteam/emulsion>
1+
// SPDX-FileCopyrightText: 2025 Emulsion contributors <https://github.com/codingteam/emulsion>
22
//
33
// SPDX-License-Identifier: MIT
44

@@ -16,6 +16,22 @@ open Emulsion.Xmpp
1616
open Emulsion.Xmpp.SharpXmppHelper.Attributes
1717
open Emulsion.Xmpp.SharpXmppHelper.Elements
1818

19+
[<Fact>]
20+
let ``SanitizeXmlText processes emoji as-is``(): unit =
21+
Assert.Equal("🐙", SharpXmppHelper.SanitizeXmlText "🐙")
22+
Assert.Equal("test🐙", SharpXmppHelper.SanitizeXmlText "test🐙")
23+
24+
[<Fact>]
25+
let ``SanitizeXmlText replaces parts of UTF-16 surrogate pair with the replacement char``(): unit =
26+
let octopus = "🐙"
27+
Assert.Equal(2, octopus.Length)
28+
let firstHalf = string(octopus[0])
29+
let secondHalf = string(octopus[1])
30+
Assert.Equal("🐙", firstHalf + secondHalf)
31+
Assert.Equal("", SharpXmppHelper.SanitizeXmlText firstHalf)
32+
Assert.Equal("", SharpXmppHelper.SanitizeXmlText secondHalf)
33+
Assert.Equal("test�", SharpXmppHelper.SanitizeXmlText $"test{secondHalf}")
34+
1935
[<Fact>]
2036
let ``Message body has a proper namespace``() =
2137
let message = SharpXmppHelper.message "" "cthulhu@test" "text"

Emulsion/Xmpp/SharpXmppHelper.fs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
// SPDX-FileCopyrightText: 2024 Emulsion contributors <https://github.com/codingteam/emulsion>
1+
// SPDX-FileCopyrightText: 2025 Emulsion contributors <https://github.com/codingteam/emulsion>
22
//
33
// SPDX-License-Identifier: MIT
44

55
/// Helper functions to deal with SharpXMPP low-level details (such as XML stuff).
66
module Emulsion.Xmpp.SharpXmppHelper
77

88
open System
9+
open System.Buffers
10+
open System.Text
911
open System.Xml.Linq
1012

13+
open Microsoft.FSharp.NativeInterop
1114
open SharpXMPP
1215
open SharpXMPP.XMPP
1316
open SharpXMPP.XMPP.Client.Elements
@@ -49,6 +52,28 @@ let private bookmark (roomJid: string) (nickname: string) (password: string opti
4952
room.Add(nickElement)
5053
room
5154

55+
#nowarn "9" // for NativePtr
56+
let SanitizeXmlText(text: string): string =
57+
let mutable hasError = false
58+
let mutable span = text.AsSpan()
59+
while not hasError && not span.IsEmpty do
60+
let mutable rune = Rune()
61+
let mutable consumed = 0
62+
if Rune.DecodeFromUtf16(span, &rune, &consumed) = OperationStatus.Done
63+
then span <- span.Slice consumed
64+
else hasError <- true
65+
66+
if hasError then
67+
let builder = StringBuilder()
68+
for r in text.EnumerateRunes() do
69+
let length = r.Utf16SequenceLength
70+
let buf = Span(NativePtr.stackalloc<char> length |> NativePtr.toVoidPtr, length)
71+
r.EncodeToUtf16 buf |> ignore
72+
builder.Append(buf) |> ignore
73+
builder.ToString()
74+
else
75+
text
76+
5277
let joinRoom (client: XmppClient) (roomJid: string) (nickname: string) (password: string option): unit =
5378
let room = bookmark roomJid nickname password
5479
client.BookmarkManager.Join room
@@ -59,7 +84,7 @@ let message (id: string) (toAddr: string) (text: string): XMPPMessage =
5984
m.SetAttributeValue(Type, "groupchat")
6085
m.SetAttributeValue(To, toAddr)
6186
let body = XElement(Body)
62-
body.Value <- text
87+
body.Value <- SanitizeXmlText text
6388
m.Add(body)
6489
m
6590

0 commit comments

Comments
 (0)