diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4326975 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,36 @@ +root = true + +[*] +indent_style = tab +encoding = utf-8 + +[*.{xml,csproj,json}] +indent_size = 4 +tab_size = 4 + +[*.cs] +indent_size = 4 +tab_size = 4 + +dotnet_style_qualification_for_field = true +dotnet_style_qualification_for_property = true +dotnet_style_qualification_for_method = true +dotnet_style_qualification_for_event = true +csharp_prefer_simple_using_statement = false +dotnet_style_require_accessibility_modifiers = true +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true +dotnet_style_prefer_collection_expression = false + +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = false:silent +csharp_style_conditional_delegate_call = true:silent +csharp_style_prefer_primary_constructors = false:silent +csharp_style_prefer_top_level_statements = false:silent + +dotnet_diagnostic.CS1998.severity = error +dotnet_diagnostic.CS1591.severity = none +dotnet_diagnostic.CA2255.severity = none +dotnet_diagnostic.IDE0060.severity = none +dotnet_diagnostic.IDE0280.severity = error \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f7608ab..8e0e505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,55 +2,24 @@ [![github](https://img.shields.io/badge/XmppSharp-1?style=plastic&logo=github&label=Github)](https://github.com/nathan130200/XmppSharp) [![nuget](https://img.shields.io/badge/XmppSharp-1?style=plastic&logo=nuget&label=NuGet&color=blue)](https://www.nuget.org/packages/XmppSharp/) -A manipulation library and utility for XMPP inspired by the classic agsXMPP library (developed by [AG-Software](https://www.ag-software.net)). The main objective of this library is to reduce the level of unnecessary verbosity for constructing XML tags. - -# Changelog - -### 1.4.0 - -- **Breaking changes**: Some types renamed or will be moved to another files/namespaces! -- Added target support for .NET 6. -- Add service discovery elements. -- Enhance JID construction. -- Make JID records types again, but keep setters instead init-only. -- `Jid.Empty` now provide a empty jid instance. -- `Jid(string jid)` contructor will try parse as full jid before set domain component. -- Added `ReadOnlyJid` that wraps Jid instance into a read-only struct for immutable/exposing. -- Added `ReadOnlyJid.Empty` static helper to fast create its instance. -- `ReadOnlyJid` has proper ways to cast between `Jid` and other `ReadOnlyJid` instances. -- Added helper method to convert `XElement` objects into Xmpp Sharp `Element` objects. -- Begin add XML docstrings in types/methods/fields/properties etc. - -### 1.4.1 - -- Fix NRE while setting attribute with `SetAttributeValue` - -### 1.4.2 - -- Minor changes & fixes. -- Added helper methods to fast get functions to parse primitive types. - -### 2.0.0 - -- 2.0 is here! I was finally able to migrate to an improved system using `XElement`. - - First of all, removed ALL AI-generated docstrings. I think it was one of the most bizarre ideas I've ever had and it didn't work out very well, it generated a lot of redundant things that weren't well explained. Then soon I will calmly document everything. -- Some things **_may_** break! For example, how to create/declare your elements. But I did everything I could to maintain the compatibility layer between old `Element` class and `XElement` class! -- By the way, most of the xmpp elements remained the same, the way to access/define data also remained almost the same. -- `CDATA` and `Comment` nodes are supported now when parsing (Even though it is not recommended to use these types of nodes in the XMPP protocol standards, this depends on each developer and the needs of each project.). -- Added extra helper methods around XElement class. -- All XMPP elements are now around `XElement` class and all `System.Xml.Linq` related. - -### 2.0.1 - -- Minor bug fixes around `XmppSharp.Jid` equality comparer. - -### 3.0 - -- Rollback using builtin .NET xml types in `System.Xml.Linq` - - > I'm so sorry for reverting this change). I've found a critical issue around namespace declarations in XElement, so i've decided to do not keep using. Instead i've reimplemente again `Element` classes now with support for `Cdata`, `Text` and `Comment` nodes. - -- All other features still the same (extend to `Element` class and etc...) - -### Notes -I ask users to report any bugs that may exist. Even though I carry out internal tests on my XMPP server, there may be some bug or other that goes unnoticed during the tests. \ No newline at end of file +A manipulation library and utility with the main objective to reduce the level of unnecessary verbosity for constructing XML tags for XMPP protocol. + +
+ +Updates + +- 1.4 + - Last stable version before rework. + +- 2.0 + - Mistakenly changed to `XElement` and rolled back after discovering critical problem when declaring/replacing namespaces. + +- 3.0 + - Bring back own XML specific classes, now supporting `Element` with extra nodes `Comment`, `Cdata`, `Text`. + +- 3.1 + - Add enhanced utilities & helper methods to interact with element and nodes. + +
+ +Don't forget to report __XMPP#__ bugs & crashes on github even with unit tests we can encounter some unexpected bug! \ No newline at end of file diff --git a/README.md b/README.md index 46c4251..08a833e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # XMPP# -A manipulation library and utility for XMPP inspired by the classic agsXMPP library (developed by [AG-Software](https://www.ag-software.net)) -The main objective of this library is to reduce the level of unnecessary verbosity for constructing XML tags. +A manipulation library and utility with the main objective to reduce the level of unnecessary verbosity for constructing XML tags for XMPP protocol. + +
### XMPP Parser diff --git a/XmppSharp.Test/AttributeHelperTests.cs b/XmppSharp.Test/AttributeHelperTests.cs new file mode 100644 index 0000000..124f02f --- /dev/null +++ b/XmppSharp.Test/AttributeHelperTests.cs @@ -0,0 +1,110 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Security.Authentication; +using XmppSharp.Dom; +using XmppSharp.Protocol.Sasl; +using XmppSharp.Protocol.ServiceDiscovery.IdentityValues; +using XmppSharp.Protocol.Tls; + +namespace XmppSharp.Test; + +[TestClass] +public class AttributeHelperTests +{ + Element element = default!; + + [TestInitialize] + public void Initialize() + => element = new("root"); + + [TestCleanup] + public void Cleanup() + => element = null!; + + [TestMethod] + public void WithXmppEnum() + { + WithXmppEnum(TlsPolicy.Required, "required"); + WithXmppEnum(TlsPolicy.Optional, "optional"); + WithXmppEnum(MechanismType.Plain, "PLAIN"); + WithXmppEnum(MechanismType.DigestMD5, "DIGEST-MD5"); + WithXmppEnum(ConferenceValues.Text, "text"); + WithXmppEnum(PubSubValues.Owned, "pep"); + WithXmppEnum(PubSubValues.Service, "service"); + WithXmppEnum(GatewayValues.Discord, "discord"); + WithXmppEnum(GatewayValues.Facebook, "facebook"); + WithXmppEnum(GatewayValues.AIM, "aim"); + } + + void WithXmppEnum(T value, string rawValue) where T : struct, Enum + { + Debug.WriteLine($"WithXmppEnum<{typeof(T).Name}>: raw value={rawValue}"); + + element.SetAttribute("data", value.ToXmppName()); + Assert.AreEqual(rawValue, element.GetAttribute("data")); + + var actual = XmppEnum.ParseOrThrow(element.GetAttribute("data")!); + Assert.AreEqual(value, actual); + + Debug.WriteLine($" {typeof(T).Name}.{actual}: parsed? {value.Equals(actual)}"); + ParserTests.PrintResult(element); + Debug.WriteLine(" ---------------------------------\n"); + } + + enum EnumTestType + { + CSharp, + VisualBasic + } + + [TestMethod] + public void WithBitwise() + { + WithBitwise(FileAccess.ReadWrite, true); + WithBitwise(SslProtocols.Tls12 | SslProtocols.Tls13, true); + } + + [TestMethod] + public void WithBitwiseAsString() + { + WithBitwise(FileAccess.ReadWrite, false); + WithBitwise(SslProtocols.Tls12 | SslProtocols.Tls13, false); + } + + [TestMethod] + public void WithString() + { + WithBitwise(EnumTestType.CSharp, false); + WithBitwise(EnumTestType.VisualBasic, false); + WithBitwise(ConferenceValues.Text, false); + WithBitwise(TestTimeout.Infinite, false); + WithBitwise(TestIdGenerationStrategy.FullyQualified, false); + WithBitwise(ThreadWaitReason.ExecutionDelay, false); + WithBitwise(ThreadWaitReason.LpcReply, false); + WithBitwise(ThreadPriority.AboveNormal, false); + WithBitwise(ThreadPriority.Highest, false); + } + + void WithBitwise(T value, bool isNumber, [CallerMemberName] string func = default!) where T : struct, Enum + { + var typeName = typeof(T).Name; + object rawValue = Convert.ChangeType(value, Enum.GetUnderlyingType(typeof(T))); + + element.SetAttributeEnum("data", (T?)value, isNumber); + Assert.AreEqual(isNumber ? rawValue.ToString() : value.ToString(), element.GetAttribute("data")); + + var actual = element.GetAttributeEnum("data", isNumber: isNumber); + Debug.WriteLine($"{func}<{typeName}>: {value} ({rawValue} @ {rawValue.GetType().Name})"); + + var isParsed = value.Equals(actual); + + Debug.WriteLine($" {typeName}.{actual}: parsed? {isParsed}\n"); + + ParserTests.PrintResult(element); + + Debug.WriteLine("\n---------------------------------\n"); + + if (!isParsed) + Assert.Fail(); + } +} diff --git a/XmppSharp.Test/ParserTests.cs b/XmppSharp.Test/ParserTests.cs index ca57cbb..ba03177 100644 --- a/XmppSharp.Test/ParserTests.cs +++ b/XmppSharp.Test/ParserTests.cs @@ -8,166 +8,166 @@ namespace XmppSharp.Test; [TestClass] public class ParserTests { - internal static async Task ParseFromBuffer([StringSyntax("Xml")] string xml, [CallerMemberName] string callerName = default!) - { - using var stream = new MemoryStream(); - stream.Write(xml.GetBytes()); - stream.Position = 0; - - using var parser = new XmppParser(bufferSize: 16); - parser.Reset(stream); - - var tcs = new TaskCompletionSource(); - - parser.OnStreamStart += e => - { - tcs.TrySetResult(e); - return Task.CompletedTask; - }; - - parser.OnStreamElement += e => - { - tcs.TrySetResult(e); - return Task.CompletedTask; - }; - - _ = Task.Run(async () => - { - try - { - while (true) - { - await Task.Delay(1); - - var result = await parser.Advance(); - - if (!result) - break; - } - } - catch (Exception error) - { - Debug.WriteLine($"\n{error}\n"); - tcs.TrySetException(error); - } - }); - - Exception timeout; - - try - { - throw new OperationCanceledException("Parser took too long?"); - } - catch (Exception e) - { - timeout = e; - } - - _ = Task.Delay(5000) - .ContinueWith(_ => tcs.TrySetException(timeout)); - - return await tcs.Task; - } - - internal static void PrintResult(Element e) - { - Debug.WriteLine("\n\nRESULT:\n\n" + e.ToString(true) + "\n"); - } - - [TestMethod] - public async Task ParseFromBuffer() - { - var elem = await ParseFromBuffer(""); - Debug.WriteLine("tagname= " + elem.TagName + ", uri= " + elem.GetNamespace()); - - Assert.AreEqual("foo", elem.TagName); - Assert.AreEqual("bar", elem.GetNamespace()); - PrintResult(elem); - } - - [TestMethod] - public async Task ParseElementWithComment() - { - var elem = await ParseFromBuffer(""); - Debug.WriteLine("ty= " + elem.FirstNode?.GetType()?.FullName ?? ""); - - if (elem.FirstNode is not Comment comment) - Assert.Fail(); - else - Assert.AreEqual("my comment", comment.Value); - - PrintResult(elem); - } - - [TestMethod] - public async Task ParseElementWithText() - { - var elem = await ParseFromBuffer("text"); - - if (elem.FirstNode is not Text t) - Assert.Fail("expected first node to be a Text node"); - else - Assert.AreEqual(t.Value, "text"); - } - - [TestMethod] - public async Task ParseElementWithCdata() - { - var elem = await ParseFromBuffer(""); - Debug.WriteLine("ty= " + elem.FirstNode?.GetType()?.FullName ?? ""); - - if (elem.FirstNode is not Cdata cdata) - Assert.Fail(); - else - Assert.AreEqual("my cdata", cdata.Value); - - PrintResult(elem); - } - - [TestMethod] - public async Task ParseWithCommentAndCdata() - { - var elem = await ParseFromBuffer(""); - Debug.WriteLine("first node= " + elem.FirstNode?.GetType()); - Debug.WriteLine("last node= " + elem.LastNode?.GetType()); - - var comment = elem.FirstNode as Comment; - var cdata = elem.LastNode as Cdata; - - if (comment == null || cdata == null) - Assert.Fail(); - - Assert.AreEqual("my comment", comment.Value); - Assert.AreEqual("my cdata", cdata.Value); - - PrintResult(elem); - } - - [TestMethod] - public async Task ParseWithCommentAndCdataAndText() - { - var elem = await ParseFromBuffer("SOME_TEXT_HERE"); - Debug.WriteLine("first node= " + elem.FirstNode?.GetType()); - Debug.WriteLine("last node= " + elem.LastNode?.GetType()); - - var comment = elem.FirstNode as Comment; - var cdata = elem.LastNode as Cdata; - - if (comment == null || cdata == null) - Assert.Fail(); - - if (elem.Nodes().ElementAtOrDefault(1) is not Text t) - Assert.Fail(); - else - { - Assert.IsNotNull(t.Value); - Assert.AreEqual("SOME_TEXT_HERE", t.Value); - Debug.WriteLine("middle node= " + t.GetType()); - } - - Assert.AreEqual("my comment", comment.Value); - Assert.AreEqual("my cdata", cdata.Value); - - PrintResult(elem); - } + internal static async Task ParseFromBuffer([StringSyntax("Xml")] string xml, [CallerMemberName] string callerName = default!) + { + using var stream = new MemoryStream(); + stream.Write(xml.GetBytes()); + stream.Position = 0; + + using var parser = new XmppParser(bufferSize: 16); + parser.Reset(stream); + + var tcs = new TaskCompletionSource(); + + parser.OnStreamStart += e => + { + tcs.TrySetResult(e); + return Task.CompletedTask; + }; + + parser.OnStreamElement += e => + { + tcs.TrySetResult(e); + return Task.CompletedTask; + }; + + _ = Task.Run(async () => + { + try + { + while (true) + { + await Task.Delay(1); + + var result = await parser.Advance(); + + if (!result) + break; + } + } + catch (Exception error) + { + Debug.WriteLine($"\n{error}\n"); + tcs.TrySetException(error); + } + }); + + Exception timeout; + + try + { + throw new OperationCanceledException("Parser took too long?"); + } + catch (Exception e) + { + timeout = e; + } + + _ = Task.Delay(5000) + .ContinueWith(_ => tcs.TrySetException(timeout)); + + return await tcs.Task; + } + + internal static void PrintResult(Element e) + { + Debug.WriteLine("\nRESULT:\n" + e.ToString(true) + "\n"); + } + + [TestMethod] + public async Task ParseFromBuffer() + { + var elem = await ParseFromBuffer(""); + Debug.WriteLine("tagname= " + elem.TagName + ", uri= " + elem.GetNamespace()); + + Assert.AreEqual("foo", elem.TagName); + Assert.AreEqual("bar", elem.GetNamespace()); + PrintResult(elem); + } + + [TestMethod] + public async Task ParseElementWithComment() + { + var elem = await ParseFromBuffer(""); + Debug.WriteLine("ty= " + elem.FirstNode?.GetType()?.FullName ?? ""); + + if (elem.FirstNode is not Comment comment) + Assert.Fail(); + else + Assert.AreEqual("my comment", comment.Value); + + PrintResult(elem); + } + + [TestMethod] + public async Task ParseElementWithText() + { + var elem = await ParseFromBuffer("text"); + + if (elem.FirstNode is not Text t) + Assert.Fail("expected first node to be a Text node"); + else + Assert.AreEqual(t.Value, "text"); + } + + [TestMethod] + public async Task ParseElementWithCdata() + { + var elem = await ParseFromBuffer(""); + Debug.WriteLine("ty= " + elem.FirstNode?.GetType()?.FullName ?? ""); + + if (elem.FirstNode is not Cdata cdata) + Assert.Fail(); + else + Assert.AreEqual("my cdata", cdata.Value); + + PrintResult(elem); + } + + [TestMethod] + public async Task ParseWithCommentAndCdata() + { + var elem = await ParseFromBuffer(""); + Debug.WriteLine("first node= " + elem.FirstNode?.GetType()); + Debug.WriteLine("last node= " + elem.LastNode?.GetType()); + + var comment = elem.FirstNode as Comment; + var cdata = elem.LastNode as Cdata; + + if (comment == null || cdata == null) + Assert.Fail(); + + Assert.AreEqual("my comment", comment.Value); + Assert.AreEqual("my cdata", cdata.Value); + + PrintResult(elem); + } + + [TestMethod] + public async Task ParseWithCommentAndCdataAndText() + { + var elem = await ParseFromBuffer("SOME_TEXT_HERE"); + Debug.WriteLine("first node= " + elem.FirstNode?.GetType()); + Debug.WriteLine("last node= " + elem.LastNode?.GetType()); + + var comment = elem.FirstNode as Comment; + var cdata = elem.LastNode as Cdata; + + if (comment == null || cdata == null) + Assert.Fail(); + + if (elem.Nodes().ElementAtOrDefault(1) is not Text t) + Assert.Fail(); + else + { + Assert.IsNotNull(t.Value); + Assert.AreEqual("SOME_TEXT_HERE", t.Value); + Debug.WriteLine("middle node= " + t.GetType()); + } + + Assert.AreEqual("my comment", comment.Value); + Assert.AreEqual("my cdata", cdata.Value); + + PrintResult(elem); + } } \ No newline at end of file diff --git a/XmppSharp.Test/XmlTests.cs b/XmppSharp.Test/XmlTests.cs new file mode 100644 index 0000000..d365075 --- /dev/null +++ b/XmppSharp.Test/XmlTests.cs @@ -0,0 +1,142 @@ +using System.Diagnostics; +using XmppSharp.Dom; +using XmppSharp.Factory; +using XmppSharp.Protocol; +using XmppSharp.Protocol.Base; +using XmppSharp.Protocol.Sasl; + +namespace XmppSharp.Test; + +[TestClass] +public class XmlTests +{ + [TestMethod] + public void EnumerateElementsFromRegistry() + { + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + EnumerateElementsFromRegistry(); + } + + static void EnumerateElementsFromRegistry() where T : Element + { + var values = ElementFactory.GetTags(); + Debug.WriteLine("count: " + values.Count()); + + foreach (var (tagName, uri) in values) + Debug.WriteLine(" " + tagName + " (namespace: " + uri + ")"); + + Debug.WriteLine("\n"); + } + + [TestMethod] + [DataRow("stream:stream", Namespace.Stream, typeof(StreamStream))] + [DataRow("stream:features", Namespace.Stream, typeof(StreamFeatures))] + [DataRow("stream:error", Namespace.Stream, typeof(StreamError))] + + [DataRow("iq", Namespace.Client, typeof(Iq))] + [DataRow("iq", Namespace.Server, typeof(Iq))] + [DataRow("iq", Namespace.Accept, typeof(Iq))] + [DataRow("iq", Namespace.Connect, typeof(Iq))] + + [DataRow("message", Namespace.Client, typeof(Message))] + [DataRow("message", Namespace.Server, typeof(Message))] + [DataRow("message", Namespace.Accept, typeof(Message))] + [DataRow("message", Namespace.Connect, typeof(Message))] + + [DataRow("presence", Namespace.Client, typeof(Presence))] + [DataRow("presence", Namespace.Server, typeof(Presence))] + [DataRow("presence", Namespace.Accept, typeof(Presence))] + [DataRow("presence", Namespace.Connect, typeof(Presence))] + + [DataRow("iq", "foobar", typeof(Element))] + [DataRow("message", "foobar", typeof(Element))] + [DataRow("presence", "foobar", typeof(Element))] + public void CreateElementFromFactory(string tagName, string uri, Type expectedType) + { + var elem = ElementFactory.Create(tagName, uri); + Assert.AreEqual(elem.TagName, tagName); + + var ns = elem.Prefix == null + ? elem.GetNamespace() + : elem.GetNamespace(elem.Prefix); + + Assert.AreEqual(ns, uri); + Assert.IsInstanceOfType(elem, expectedType); + } + + [TestMethod] + [DataRow("", typeof(StreamStream))] + [DataRow("", typeof(Iq))] + [DataRow("", typeof(Message))] + [DataRow("", typeof(Presence))] + [DataRow("", typeof(Iq))] + [DataRow("", typeof(Message))] + [DataRow("", typeof(Presence))] + [DataRow("", typeof(Auth))] + public async Task CreateElementFromParser(string xml, Type expectedType) + { + var elem = await ParserTests.ParseFromBuffer(xml); + Assert.IsInstanceOfType(elem, expectedType); + ParserTests.PrintResult(elem); + } + + [TestMethod] + public async Task CloneNodes() + { + var inXml = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +" + .Replace("\r\n", "") + .Replace("\n", "") + .Replace("\r", "") + .Replace("\t", "") + ; + + var elem = await ParserTests.ParseFromBuffer(inXml); + + var cloned = elem.Clone(); + Assert.AreNotSame(elem, cloned); + + var outXml = cloned.ToString(false); + + Assert.AreEqual(inXml, outXml); + } +} \ No newline at end of file diff --git a/XmppSharp.Test/XmlTreeTests.cs b/XmppSharp.Test/XmlTreeTests.cs deleted file mode 100644 index c626245..0000000 --- a/XmppSharp.Test/XmlTreeTests.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System.Diagnostics; -using XmppSharp.Dom; -using XmppSharp.Factory; -using XmppSharp.Protocol; -using XmppSharp.Protocol.Base; -using XmppSharp.Protocol.Sasl; - -namespace XmppSharp.Test; - -[TestClass] -public class XmlTreeTests -{ - [TestMethod] - public void EnumerateElementsFromRegistry() - { - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - EnumerateElementsFromRegistry(); - } - - static void EnumerateElementsFromRegistry() where T : Element - { - var values = ElementFactory.GetTags(); - Debug.WriteLine("count: " + values.Count()); - - foreach (var (tagName, uri) in values) - Debug.WriteLine(" " + tagName + " (namespace: " + uri + ")"); - - Debug.WriteLine("\n"); - } - - [TestMethod] - [DataRow("stream:stream", Namespace.Stream, typeof(StreamStream))] - [DataRow("stream:features", Namespace.Stream, typeof(StreamFeatures))] - [DataRow("stream:error", Namespace.Stream, typeof(StreamError))] - - [DataRow("iq", Namespace.Client, typeof(Iq))] - [DataRow("iq", Namespace.Server, typeof(Iq))] - [DataRow("iq", Namespace.Accept, typeof(Iq))] - [DataRow("iq", Namespace.Connect, typeof(Iq))] - - [DataRow("message", Namespace.Client, typeof(Message))] - [DataRow("message", Namespace.Server, typeof(Message))] - [DataRow("message", Namespace.Accept, typeof(Message))] - [DataRow("message", Namespace.Connect, typeof(Message))] - - [DataRow("presence", Namespace.Client, typeof(Presence))] - [DataRow("presence", Namespace.Server, typeof(Presence))] - [DataRow("presence", Namespace.Accept, typeof(Presence))] - [DataRow("presence", Namespace.Connect, typeof(Presence))] - - [DataRow("iq", "foobar", typeof(Element))] - [DataRow("message", "foobar", typeof(Element))] - [DataRow("presence", "foobar", typeof(Element))] - public void CreateElementFromFactory(string tagName, string uri, Type expectedType) - { - var elem = ElementFactory.Create(tagName, uri); - Assert.AreEqual(elem.TagName, tagName); - - var ns = elem.Prefix == null - ? elem.GetNamespace() - : elem.GetNamespace(elem.Prefix); - - Assert.AreEqual(ns, uri); - Assert.IsInstanceOfType(elem, expectedType); - } - - [TestMethod] - [DataRow("", typeof(StreamStream))] - [DataRow("", typeof(Iq))] - [DataRow("", typeof(Message))] - [DataRow("", typeof(Presence))] - [DataRow("", typeof(Iq))] - [DataRow("", typeof(Message))] - [DataRow("", typeof(Presence))] - [DataRow("", typeof(Auth))] - public async Task CreateElementFromParser(string xml, Type expectedType) - { - var elem = await ParserTests.ParseFromBuffer(xml); - Assert.IsInstanceOfType(elem, expectedType); - ParserTests.PrintResult(elem); - } - - [TestMethod] - public async Task CloneNodes() - { - var inXml = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -" - .Replace("\r\n", "") - .Replace("\n", "") - .Replace("\r", "") - .Replace("\t", "") - ; - - var elem = await ParserTests.ParseFromBuffer(inXml); - - var cloned = elem.Clone(); - Assert.AreNotSame(elem, cloned); - - var outXml = cloned.ToString(false); - - Assert.AreEqual(inXml, outXml); - } -} \ No newline at end of file diff --git a/XmppSharp.Test/XmppEnumTests.cs b/XmppSharp.Test/XmppEnumTests.cs index 84a99a9..7843189 100644 --- a/XmppSharp.Test/XmppEnumTests.cs +++ b/XmppSharp.Test/XmppEnumTests.cs @@ -9,58 +9,58 @@ namespace XmppSharp.Test; [TestClass] public class XmppEnumTests { - [TestMethod] - public void ParseOrThrow() - { - ParseFromString(IqType.Get, "get"); - ParseFromString(IqType.Set, "set"); - ParseFromString(IqType.Result, "result"); - ParseFromString(IqType.Error, "error"); - ParseFromString(MechanismType.Plain, "PLAIN"); - ParseFromString(MechanismType.DigestMD5, "DIGEST-MD5"); - ParseFromString(MechanismType.ScramSha1, "SCRAM-SHA-1"); - ParseFromString(MechanismType.ScramSha1Plus, "SCRAM-SHA-1-PLUS"); - ParseFromString(MechanismType.External, "EXTERNAL"); - ParseFromString(TlsPolicy.Required, "required"); - ParseFromString(TlsPolicy.Optional, "optional"); - ParseFromString(ComponentValues.C2S, "c2s"); - ParseFromString(ComponentValues.Router, "router"); - ParseFromString(ComponentValues.S2S, "s2s"); - Assert.ThrowsException(() => XmppEnum.ParseOrThrow("UNSPECIFIED")); - Assert.IsTrue(XmppEnum.Parse("UNSPECIFIED") == null); - } + [TestMethod] + public void ParseOrThrow() + { + ParseFromString(IqType.Get, "get"); + ParseFromString(IqType.Set, "set"); + ParseFromString(IqType.Result, "result"); + ParseFromString(IqType.Error, "error"); + ParseFromString(MechanismType.Plain, "PLAIN"); + ParseFromString(MechanismType.DigestMD5, "DIGEST-MD5"); + ParseFromString(MechanismType.ScramSha1, "SCRAM-SHA-1"); + ParseFromString(MechanismType.ScramSha1Plus, "SCRAM-SHA-1-PLUS"); + ParseFromString(MechanismType.External, "EXTERNAL"); + ParseFromString(TlsPolicy.Required, "required"); + ParseFromString(TlsPolicy.Optional, "optional"); + ParseFromString(ComponentValues.C2S, "c2s"); + ParseFromString(ComponentValues.Router, "router"); + ParseFromString(ComponentValues.S2S, "s2s"); + Assert.ThrowsException(() => XmppEnum.ParseOrThrow("UNSPECIFIED")); + Assert.IsTrue(XmppEnum.Parse("UNSPECIFIED") == null); + } - static void ParseFromString(T expected, string source) where T : struct, Enum - { - var actual = XmppEnum.ParseOrThrow(source); - Assert.AreEqual(actual, expected); - Debug.WriteLine("ParseFromString(" + typeof(T).Name + "): " + source + " = " + expected); - } + static void ParseFromString(T expected, string source) where T : struct, Enum + { + var actual = XmppEnum.ParseOrThrow(source); + Assert.AreEqual(actual, expected); + Debug.WriteLine("ParseFromString(" + typeof(T).Name + "): " + source + " = " + expected); + } - [TestMethod] - public void ToXmppName() - { - ToXmppName(IqType.Get, "get"); - ToXmppName(IqType.Set, "set"); - ToXmppName(IqType.Result, "result"); - ToXmppName(IqType.Error, "error"); - ToXmppName(MechanismType.Plain, "PLAIN"); - ToXmppName(MechanismType.DigestMD5, "DIGEST-MD5"); - ToXmppName(MechanismType.ScramSha1, "SCRAM-SHA-1"); - ToXmppName(MechanismType.ScramSha1Plus, "SCRAM-SHA-1-PLUS"); - ToXmppName(MechanismType.External, "EXTERNAL"); - ToXmppName(MechanismType.Unspecified, null!); - ToXmppName(TlsPolicy.Required, "required"); - ToXmppName(TlsPolicy.Optional, "optional"); - ToXmppName(ComponentValues.C2S, "c2s"); - ToXmppName(ComponentValues.Router, "router"); - ToXmppName(ComponentValues.S2S, "s2s"); - } + [TestMethod] + public void ToXmppName() + { + ToXmppName(IqType.Get, "get"); + ToXmppName(IqType.Set, "set"); + ToXmppName(IqType.Result, "result"); + ToXmppName(IqType.Error, "error"); + ToXmppName(MechanismType.Plain, "PLAIN"); + ToXmppName(MechanismType.DigestMD5, "DIGEST-MD5"); + ToXmppName(MechanismType.ScramSha1, "SCRAM-SHA-1"); + ToXmppName(MechanismType.ScramSha1Plus, "SCRAM-SHA-1-PLUS"); + ToXmppName(MechanismType.External, "EXTERNAL"); + ToXmppName(MechanismType.Unspecified, null!); + ToXmppName(TlsPolicy.Required, "required"); + ToXmppName(TlsPolicy.Optional, "optional"); + ToXmppName(ComponentValues.C2S, "c2s"); + ToXmppName(ComponentValues.Router, "router"); + ToXmppName(ComponentValues.S2S, "s2s"); + } - static void ToXmppName(T value, string expected) where T : struct, Enum - { - var actual = XmppEnum.ToXmppName(value); - Assert.AreEqual(expected, actual, true); - Debug.WriteLine("ToXmppName(" + typeof(T).Name + "." + value + "): " + (expected ?? "")); - } + static void ToXmppName(T value, string expected) where T : struct, Enum + { + var actual = XmppEnum.ToXmppName(value); + Assert.AreEqual(expected, actual, true); + Debug.WriteLine("ToXmppName(" + typeof(T).Name + "." + value + "): " + (expected ?? "")); + } } \ No newline at end of file diff --git a/XmppSharp.sln b/XmppSharp.sln index 572d120..e24165b 100644 --- a/XmppSharp.sln +++ b/XmppSharp.sln @@ -5,16 +5,14 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XmppSharp", "XmppSharp\XmppSharp.csproj", "{9524D04E-04EA-4BE2-866D-36513B852BB5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution Files", "{BCAADF99-16AC-4E6C-94FC-A7DB74FDF621}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XmppSharp.Test", "XmppSharp.Test\XmppSharp.Test.csproj", "{37601B18-4B72-4BBE-8600-0E73D96BA6CD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution Files", "{5D1FF1C4-B9ED-461B-8EAE-A23C799AED0F}" ProjectSection(SolutionItems) = preProject - .gitignore = .gitignore + .editorconfig = .editorconfig CHANGELOG.md = CHANGELOG.md - LICENSE = LICENSE - README.md = README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XmppSharp.Test", "XmppSharp.Test\XmppSharp.Test.csproj", "{37601B18-4B72-4BBE-8600-0E73D96BA6CD}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/XmppSharp/AsyncDelegates.cs b/XmppSharp/AsyncDelegates.cs index d2a3086..e098c7e 100644 --- a/XmppSharp/AsyncDelegates.cs +++ b/XmppSharp/AsyncDelegates.cs @@ -34,86 +34,86 @@ namespace XmppSharp; public static class AsyncUtilities { - /// - /// Helper function that safely invokes the event. - /// - /// Delegate of the event that will be invoked. - /// Returns a task that invokes the event asynchronously. - public static async Task InvokeAsync(this AsyncAction func) - { - try - { - if (func != null) - await func(); - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } - } + /// + /// Helper function that safely invokes the event. + /// + /// Delegate of the event that will be invoked. + /// Returns a task that invokes the event asynchronously. + public static async Task InvokeAsync(this AsyncAction func) + { + try + { + if (func != null) + await func(); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } - /// - /// Helper function that safely invokes the event. - /// - /// Parameter type for the event. - /// Delegate of the event that will be invoked. - /// Parameter value. - /// Returns a task that invokes the event asynchronously. - public static async Task InvokeAsync(this AsyncAction func, TParam param) - { - try - { - if (func != null) - await func(param); - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } - } + /// + /// Helper function that safely invokes the event. + /// + /// Parameter type for the event. + /// Delegate of the event that will be invoked. + /// Parameter value. + /// Returns a task that invokes the event asynchronously. + public static async Task InvokeAsync(this AsyncAction func, TParam param) + { + try + { + if (func != null) + await func(param); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } - /// - /// Helper function that safely invokes the event and returns a value. - /// - /// Type of result that will be returned. - /// When awaited, returns the event value or the default value of if an error occurs. - public static async Task InvokeAsync(this AsyncFunc func) - { - try - { - if (func != null) - return await func(); - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } + /// + /// Helper function that safely invokes the event and returns a value. + /// + /// Type of result that will be returned. + /// When awaited, returns the event value or the default value of if an error occurs. + public static async Task InvokeAsync(this AsyncFunc func) + { + try + { + if (func != null) + return await func(); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } - return default; - } + return default; + } - /// - /// Helper function that safely invokes the event. - /// - /// Parameter type for the event. - /// Type of result that will be returned. - /// Delegate of the event that will be invoked. - /// Parameter value. - /// When awaited, returns the event value or the default value of if an error occurs. - /// The error will be displayed in the debug window using . - /// - public static async Task InvokeAsync(this AsyncFunc func, TParam param) - { - try - { - if (func != null) - return await func(param); - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } + /// + /// Helper function that safely invokes the event. + /// + /// Parameter type for the event. + /// Type of result that will be returned. + /// Delegate of the event that will be invoked. + /// Parameter value. + /// When awaited, returns the event value or the default value of if an error occurs. + /// The error will be displayed in the debug window using . + /// + public static async Task InvokeAsync(this AsyncFunc func, TParam param) + { + try + { + if (func != null) + return await func(param); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } - return default; - } + return default; + } } \ No newline at end of file diff --git a/XmppSharp/Attributes/XmppMemberAttribute.cs b/XmppSharp/Attributes/XmppMemberAttribute.cs index 3dc541f..dbb6343 100644 --- a/XmppSharp/Attributes/XmppMemberAttribute.cs +++ b/XmppSharp/Attributes/XmppMemberAttribute.cs @@ -6,8 +6,8 @@ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class XmppMemberAttribute : Attribute { - public string Name { get; } + public string Name { get; } - public XmppMemberAttribute(string name) - => Name = name; + public XmppMemberAttribute(string name) + => this.Name = name; } \ No newline at end of file diff --git a/XmppSharp/Attributes/XmppTagAttribute.cs b/XmppSharp/Attributes/XmppTagAttribute.cs index 02724ee..4a50882 100644 --- a/XmppSharp/Attributes/XmppTagAttribute.cs +++ b/XmppSharp/Attributes/XmppTagAttribute.cs @@ -3,12 +3,12 @@ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public sealed class XmppTagAttribute : Attribute { - public string LocalName { get; } - public string NamespaceURI { get; } + public string LocalName { get; } + public string NamespaceURI { get; } - public XmppTagAttribute(string localName, string namespaceURI) - { - LocalName = localName; - NamespaceURI = namespaceURI; - } + public XmppTagAttribute(string localName, string namespaceURI) + { + this.LocalName = localName; + this.NamespaceURI = namespaceURI; + } } diff --git a/XmppSharp/Dom/Cdata.cs b/XmppSharp/Dom/Cdata.cs index 76ae51a..752a991 100644 --- a/XmppSharp/Dom/Cdata.cs +++ b/XmppSharp/Dom/Cdata.cs @@ -6,12 +6,12 @@ namespace XmppSharp.Dom; [DebuggerDisplay("{Value,nq}")] public class Cdata : Node { - public Cdata(string value) => Value = value; - public Cdata(Cdata other) => Value = other.Value; + public Cdata(string value) => this.Value = value; + public Cdata(Cdata other) => this.Value = other.Value; - public override Node Clone() - => new Cdata(this); + public override Node Clone() + => new Cdata(this); - public override void WriteTo(XmlWriter writer) - => writer.WriteCData(Value); + public override void WriteTo(XmlWriter writer) + => writer.WriteCData(this.Value); } \ No newline at end of file diff --git a/XmppSharp/Dom/Comment.cs b/XmppSharp/Dom/Comment.cs index 6a77b73..f1e04de 100644 --- a/XmppSharp/Dom/Comment.cs +++ b/XmppSharp/Dom/Comment.cs @@ -6,12 +6,12 @@ namespace XmppSharp.Dom; [DebuggerDisplay("{Value,nq}")] public class Comment : Node { - public Comment(string value) => Value = value; - public Comment(Comment other) => Value = other.Value; + public Comment(string value) => this.Value = value; + public Comment(Comment other) => this.Value = other.Value; - public override Node Clone() - => new Comment(this); + public override Node Clone() + => new Comment(this); - public override void WriteTo(XmlWriter writer) - => writer.WriteComment(Value); + public override void WriteTo(XmlWriter writer) + => writer.WriteComment(this.Value); } diff --git a/XmppSharp/Dom/Element.cs b/XmppSharp/Dom/Element.cs index 427d970..0bcdfaa 100644 --- a/XmppSharp/Dom/Element.cs +++ b/XmppSharp/Dom/Element.cs @@ -7,469 +7,473 @@ namespace XmppSharp.Dom; public class Element : Node { - private string _localName, _prefix; - private readonly Dictionary _attributes = new(); - private readonly List _childNodes = new(); - - Element() - { - - } - - public Element(string qualifiedName) - { - var info = Xml.ExtractQualifiedName(qualifiedName); - _localName = info.LocalName; - _prefix = info.HasPrefix ? info.Prefix : null; - } - - public Element(string qualifiedName, string namespaceURI) : this(qualifiedName) - { - if (namespaceURI != null) - { - if (_prefix != null) - SetNamespace(_prefix, namespaceURI); - else - SetNamespace(namespaceURI); - } - } - - public IEnumerable Nodes() - { - lock (_childNodes) - return _childNodes.ToArray(); - } - - static void GetDescendantNodes(Element parent, List result) - { - foreach (var node in parent.Nodes()) - { - result.Add(node); - - if (node is Element e) - GetDescendantNodes(e, result); - } - } - - static void GetDescendantElements(Element parent, List result) - { - foreach (var element in parent.Children()) - { - result.Add(element); - - GetDescendantElements(element, result); - } - } - - public IEnumerable DescendantNodes() - { - var result = new List(); - GetDescendantNodes(this, result); - return result; - } - - public IEnumerable DescendantNodesAndSelf() - { - var result = new List { this }; - GetDescendantNodes(this, result); - return result; - } - - public IEnumerable Descendants() - { - var result = new List(); - GetDescendantElements(this, result); - return result; - } - - public IEnumerable DescendantsAndSelf() - { - var result = new List { this }; - GetDescendantElements(this, result); - return result; - } - - public override string Value - { - get => string.Concat(DescendantNodes().OfType().Select(x => x.Value)); - set - { - Nodes().Remove(); - - if (value != null) - AddChild(new Text(value)); - } - } - - public override string ToString() - => ToString(false); - - public string ToString(bool indented, char indentChar = ' ', int indentSize = 2) - { - using (StringBuilderPool.Rent(out var sb)) - { - using (var writer = Xml.CreateWriter(indented, sb, indentChar, indentSize)) - WriteTo(writer); - - return sb.ToString(); - } - } - - public string? DefaultNamespace - { - get => GetNamespace(); - set => SetNamespace(value); - } - - public override Element Clone() - { - var elem = ElementFactory.Create(TagName, GetNamespace(Prefix)); - elem._localName = _localName; - elem._prefix = _prefix; - - lock (_attributes) - { - foreach (var (key, value) in _attributes) - elem._attributes[key] = value; - } - - lock (_childNodes) - { - foreach (var node in _childNodes) - { - var childNode = node.Clone(); - elem._childNodes.Add(childNode); - childNode._parent = elem; - } - } - - return elem; - } - - public override void WriteTo(XmlWriter writer) - { - var ns = GetNamespace(_prefix); - - string skipAttribute = "xmlns"; - - if (_prefix == null) - writer.WriteStartElement(_localName, ns); - else - { - writer.WriteStartElement(_prefix, _localName, ns); - skipAttribute = $"xmlns:{_prefix}"; - } - - lock (_attributes) - { - foreach (var (name, value) in _attributes) - { - if (skipAttribute == name) - continue; - - var info = Xml.ExtractQualifiedName(name); - - if (!info.HasPrefix) - writer.WriteAttributeString(name, value); - else - writer.WriteAttributeString(info.Prefix, info.LocalName, info.Prefix switch - { - "xml" => Namespace.Xml, - "xmlns" => Namespace.Xmlns, - _ => GetNamespace(info.Prefix) ?? string.Empty - }); - } - } - - lock (_childNodes) - { - foreach (var node in _childNodes) - node.WriteTo(writer); - } - - writer.WriteEndElement(); - } - - public string LocalName - { - get => _localName; - set - { - Require.NotNullOrWhiteSpace(value, nameof(LocalName)); - _localName = value; - } - } - - public string Prefix - { - get => _prefix; - set => _prefix = string.IsNullOrWhiteSpace(value) ? null : value; - } - - public string TagName - => _prefix == null ? _localName : string.Concat(_prefix, ':', _localName); - - public Node FirstNode - { - get - { - lock (_childNodes) - return _childNodes.Count > 0 ? _childNodes[0] : null; - } - } - - public Node LastNode - { - get - { - lock (_childNodes) - return _childNodes.Count > 0 ? _childNodes[^1] : null; - } - } - - public Element FirstChild - { - get - { - lock (_childNodes) - { - for (int i = 0; i < _childNodes.Count; i++) - { - if (_childNodes[i] is Element e) - return e; - } - } - - return null; - } - } - - public Element LastChild - { - get - { - lock (_childNodes) - { - for (int i = _childNodes.Count - 1; i >= 0; i--) - { - if (_childNodes[i] is Element e) - return e; - } - } - - return null; - } - } - - public virtual void AddChild(Node n) - { - if (n == null) - return; - - if (n.Parent != null) - n = n.Clone(); - - lock (_childNodes) - _childNodes.Add(n); - - n._parent = this; - } - - public virtual void RemoveChild(Node n) - { - if (n._parent != this) - return; - - lock (_childNodes) - _childNodes.Remove(n); - - n._parent = null; - - if (n is Element elem) - { - var prefix = elem.Prefix; - - if (prefix != null) - elem.SetNamespace(prefix, GetNamespace(prefix)); - else - elem.SetNamespace(GetNamespace()); - } - } - - public string GetNamespace(string? prefix = default) - { - string result; - - if (string.IsNullOrWhiteSpace(prefix)) - result = GetAttribute("xmlns"); - else - result = GetAttribute($"xmlns:{prefix}"); - - if (result != null) - return result; - - return _parent?.GetNamespace(prefix); - } - - public void SetNamespace(string uri) - => SetAttribute("xmlns", uri); - - public void SetNamespace(string prefix, string uri) - { - Require.NotNullOrWhiteSpace(prefix); - SetAttribute($"xmlns:{prefix}", uri); - } - - public string GetAttribute(string name) - { - Require.NotNullOrWhiteSpace(name); - - string value; - - lock (_attributes) - _attributes.TryGetValue(name, out value); - - return value; - } - - public void SetAttribute(string name, object? value) - { - Require.NotNullOrWhiteSpace(name); - - lock (_attributes) - { - if (value == null) - _attributes.Remove(name); - else - _attributes[name] = Convert.ToString(value, CultureInfo.InvariantCulture); - } - } - - public void RemoveAttribute(string name) - { - Require.NotNullOrWhiteSpace(name); - - lock (_attributes) - _attributes.Remove(name); - } - - public bool HasAttribute(string name) - { - Require.NotNullOrWhiteSpace(name); - - lock (_attributes) - return _attributes.ContainsKey(name); - } - - public IEnumerable Children() - { - lock (_childNodes) - { - foreach (var node in _childNodes) - { - if (node is Element e) - yield return e; - } - } - } - - public IEnumerable Children() - => Children().OfType(); - - public IEnumerable Children(string? tagName, string namespaceURI) - { - Require.NotNull(tagName); - - return Children(x => x.TagName == tagName && x.Prefix == null - ? x.GetNamespace() == namespaceURI - : x.GetNamespace(x.Prefix) == namespaceURI); - } - - public IEnumerable Children(Func predicate) - { - Require.NotNull(predicate); - - lock (_childNodes) - { - foreach (var node in _childNodes) - { - if (node is Element child && predicate(child)) - yield return child; - } - } - } - - public Element Child(string tagName, string? namespaceURI) - { - Require.NotNull(tagName); - - return Children(x => x.TagName == tagName && x.Prefix == null - ? x.GetNamespace() == namespaceURI - : x.GetNamespace(x.Prefix) == namespaceURI) - .FirstOrDefault(); - } - - public T Child() where T : Element - => Children().OfType().FirstOrDefault(); - - public Element Child(Func predicate) - { - Require.NotNull(predicate); - - foreach (var element in Children()) - { - if (predicate(element)) - return element; - } - - return null; - } - - public string? GetTag(string tagName, string? namespaceURI = default) - { - Require.NotNullOrWhiteSpace(tagName); - return Child(tagName, namespaceURI)?.Value; - } - - public void SetTag(string tagName) - { - Require.NotNullOrWhiteSpace(tagName); - AddChild(new Element(tagName)); - } - - public void SetTag(string tagName, object? value) - { - Require.NotNullOrWhiteSpace(tagName); - - var elem = new Element(tagName); - - if (value != null) - elem.Value = Convert.ToString(value, CultureInfo.InvariantCulture); - - AddChild(elem); - } - - public void SetTag(string tagName, string? namespaceURI, object? value) - { - Require.NotNullOrWhiteSpace(tagName); - - var elem = new Element(tagName, namespaceURI); - - if (value != null) - elem.Value = Convert.ToString(value, CultureInfo.InvariantCulture); - - AddChild(elem); - } - - public void RemoveTag(string tagName, string? namespaceURI = default) - { - Require.NotNullOrWhiteSpace(tagName); - Children(tagName, namespaceURI).ForEach(n => n.Remove()); - } - - public bool HasTag(string tagName, string? namespaceURI = default) - { - Require.NotNullOrWhiteSpace(tagName); - return Child(tagName, namespaceURI) is not null; - } + private string _localName, _prefix; + private readonly Dictionary _attributes = new(); + private readonly List _childNodes = new(); + + Element() + { + + } + + public Element(string qualifiedName) + { + var info = Xml.ExtractQualifiedName(qualifiedName); + this._localName = info.LocalName; + this._prefix = info.HasPrefix ? info.Prefix : null; + } + + public Element(string qualifiedName, string namespaceURI) : this(qualifiedName) + { + if (namespaceURI != null) + { + if (this._prefix != null) + this.SetNamespace(this._prefix, namespaceURI); + else + this.SetNamespace(namespaceURI); + } + } + + public IEnumerable Nodes() + { + lock (this._childNodes) + return this._childNodes.ToArray(); + } + + static void GetDescendantNodes(Element parent, List result) + { + foreach (var node in parent.Nodes()) + { + result.Add(node); + + if (node is Element e) + GetDescendantNodes(e, result); + } + } + + static void GetDescendantElements(Element parent, List result) + { + foreach (var element in parent.Children()) + { + result.Add(element); + + GetDescendantElements(element, result); + } + } + + public IEnumerable DescendantNodes() + { + var result = new List(); + GetDescendantNodes(this, result); + return result; + } + + public IEnumerable DescendantNodesAndSelf() + { + var result = new List { this }; + GetDescendantNodes(this, result); + return result; + } + + public IEnumerable Descendants() + { + var result = new List(); + GetDescendantElements(this, result); + return result; + } + + public IEnumerable DescendantsAndSelf() + { + var result = new List { this }; + GetDescendantElements(this, result); + return result; + } + + public override string Value + { + get => string.Concat(this.DescendantNodes().OfType().Select(x => x.Value)); + set + { + this.Nodes().Remove(); + + if (value != null) + this.AddChild(new Text(value)); + } + } + + public override string ToString() + => this.ToString(false); + + public string ToString(bool indented, char indentChar = ' ', int indentSize = 2) + { + using (StringBuilderPool.Rent(out var sb)) + { + using (var writer = Xml.CreateWriter(indented, sb, indentChar, indentSize)) + this.WriteTo(writer); + + return sb.ToString(); + } + } + + public string? DefaultNamespace + { + get => this.GetNamespace(); + set => this.SetNamespace(value); + } + + public override Element Clone() + { + var elem = ElementFactory.Create(this.TagName, this.GetNamespace(this.Prefix)); + elem._localName = this._localName; + elem._prefix = this._prefix; + + lock (this._attributes) + { + foreach (var (key, value) in this._attributes) + elem._attributes[key] = value; + } + + lock (this._childNodes) + { + foreach (var node in this._childNodes) + { + var childNode = node.Clone(); + elem._childNodes.Add(childNode); + childNode._parent = elem; + } + } + + return elem; + } + + public override void WriteTo(XmlWriter writer) + { + var ns = this.GetNamespace(this._prefix); + + string skipAttribute = "xmlns"; + + if (this._prefix == null) + writer.WriteStartElement(this._localName, ns); + else + { + writer.WriteStartElement(this._prefix, this._localName, ns); + skipAttribute = $"xmlns:{this._prefix}"; + } + + lock (this._attributes) + { + foreach (var (name, value) in this._attributes) + { + if (skipAttribute == name) + continue; + + var info = Xml.ExtractQualifiedName(name); + + if (!info.HasPrefix) + writer.WriteAttributeString(name, value); + else + writer.WriteAttributeString(info.Prefix, info.LocalName, info.Prefix switch + { + "xml" => Namespace.Xml, + "xmlns" => Namespace.Xmlns, + _ => this.GetNamespace(info.Prefix) ?? string.Empty + }); + } + } + + lock (this._childNodes) + { + foreach (var node in this._childNodes) + node.WriteTo(writer); + } + + writer.WriteEndElement(); + } + + public string LocalName + { + get => this._localName; + set + { + Require.NotNullOrWhiteSpace(value, nameof(this.LocalName)); + this._localName = value; + } + } + + public string Prefix + { + get => this._prefix; + set => this._prefix = string.IsNullOrWhiteSpace(value) ? null : value; + } + + public string TagName + => this._prefix == null ? this._localName : string.Concat(this._prefix, ':', this._localName); + + public Node FirstNode + { + get + { + lock (this._childNodes) + return this._childNodes.Count > 0 ? this._childNodes[0] : null; + } + } + + public Node LastNode + { + get + { + lock (this._childNodes) + return this._childNodes.Count > 0 ? this._childNodes[^1] : null; + } + } + + public Element FirstChild + { + get + { + lock (this._childNodes) + { + for (int i = 0; i < this._childNodes.Count; i++) + { + if (this._childNodes[i] is Element e) + return e; + } + } + + return null; + } + } + + public Element LastChild + { + get + { + lock (this._childNodes) + { + for (int i = this._childNodes.Count - 1; i >= 0; i--) + { + if (this._childNodes[i] is Element e) + return e; + } + } + + return null; + } + } + + public virtual void AddChild(Node n) + { + if (n == null) + return; + + if (n.Parent != null) + n = n.Clone(); + + lock (this._childNodes) + this._childNodes.Add(n); + + n._parent = this; + } + + public virtual void RemoveChild(Node n) + { + if (n._parent != this) + return; + + lock (this._childNodes) + this._childNodes.Remove(n); + + n._parent = null; + + if (n is Element elem) + { + var prefix = elem.Prefix; + + if (prefix != null) + elem.SetNamespace(prefix, this.GetNamespace(prefix)); + else + elem.SetNamespace(this.GetNamespace()); + } + } + + public string GetNamespace(string? prefix = default) + { + string result; + + if (string.IsNullOrWhiteSpace(prefix)) + result = this.GetAttribute("xmlns"); + else + result = this.GetAttribute($"xmlns:{prefix}"); + + if (result != null) + return result; + + return this._parent?.GetNamespace(prefix); + } + + public void SetNamespace(string uri) + => this.SetAttribute("xmlns", uri); + + public void SetNamespace(string prefix, string uri) + { + Require.NotNullOrWhiteSpace(prefix); + this.SetAttribute($"xmlns:{prefix}", uri); + } + + public string? GetAttribute(string name) + { + Require.NotNullOrWhiteSpace(name); + + string value; + + lock (this._attributes) + this._attributes.TryGetValue(name, out value); + + return value; + } + + public Element SetAttribute(string name, object? value) + { + Require.NotNullOrWhiteSpace(name); + + lock (this._attributes) + { + if (value == null) + this._attributes.Remove(name); + else + this._attributes[name] = Convert.ToString(value, CultureInfo.InvariantCulture); + } + + return this; + } + + public Element RemoveAttribute(string name) + { + Require.NotNullOrWhiteSpace(name); + + lock (this._attributes) + this._attributes.Remove(name); + + return this; + } + + public bool HasAttribute(string name) + { + Require.NotNullOrWhiteSpace(name); + + lock (this._attributes) + return this._attributes.ContainsKey(name); + } + + public IEnumerable Children() + { + lock (this._childNodes) + { + foreach (var node in this._childNodes) + { + if (node is Element e) + yield return e; + } + } + } + + public IEnumerable Children() + => this.Children().OfType(); + + public IEnumerable Children(string? tagName, string namespaceURI) + { + Require.NotNull(tagName); + + return this.Children(x => x.TagName == tagName && x.Prefix == null + ? x.GetNamespace() == namespaceURI + : x.GetNamespace(x.Prefix) == namespaceURI); + } + + public IEnumerable Children(Func predicate) + { + Require.NotNull(predicate); + + lock (this._childNodes) + { + foreach (var node in this._childNodes) + { + if (node is Element child && predicate(child)) + yield return child; + } + } + } + + public Element Child(string tagName, string? namespaceURI) + { + Require.NotNull(tagName); + + return this.Children(x => x.TagName == tagName && x.Prefix == null + ? x.GetNamespace() == namespaceURI + : x.GetNamespace(x.Prefix) == namespaceURI) + .FirstOrDefault(); + } + + public T Child() where T : Element + => this.Children().OfType().FirstOrDefault(); + + public Element Child(Func predicate) + { + Require.NotNull(predicate); + + foreach (var element in this.Children()) + { + if (predicate(element)) + return element; + } + + return null; + } + + public string? GetTag(string tagName, string? namespaceURI = default) + { + Require.NotNullOrWhiteSpace(tagName); + return this.Child(tagName, namespaceURI)?.Value; + } + + public void SetTag(string tagName) + { + Require.NotNullOrWhiteSpace(tagName); + this.AddChild(new Element(tagName)); + } + + public void SetTag(string tagName, object? value) + { + Require.NotNullOrWhiteSpace(tagName); + + var elem = new Element(tagName); + + if (value != null) + elem.Value = Convert.ToString(value, CultureInfo.InvariantCulture); + + this.AddChild(elem); + } + + public void SetTag(string tagName, string? namespaceURI, object? value) + { + Require.NotNullOrWhiteSpace(tagName); + + var elem = new Element(tagName, namespaceURI); + + if (value != null) + elem.Value = Convert.ToString(value, CultureInfo.InvariantCulture); + + this.AddChild(elem); + } + + public void RemoveTag(string tagName, string? namespaceURI = default) + { + Require.NotNullOrWhiteSpace(tagName); + this.Children(tagName, namespaceURI).ForEach(n => n.Remove()); + } + + public bool HasTag(string tagName, string? namespaceURI = default) + { + Require.NotNullOrWhiteSpace(tagName); + return this.Child(tagName, namespaceURI) is not null; + } } diff --git a/XmppSharp/Dom/Node.cs b/XmppSharp/Dom/Node.cs index 8784c5c..c7f52ca 100644 --- a/XmppSharp/Dom/Node.cs +++ b/XmppSharp/Dom/Node.cs @@ -4,32 +4,32 @@ namespace XmppSharp.Dom; public abstract class Node : ICloneable { - internal Element _parent; + internal Element _parent; - public Element Parent - { - get => _parent; - set - { - _parent?.RemoveChild(this); - (_parent = value)?.AddChild(this); - } - } + public Element Parent + { + get => this._parent; + set + { + this._parent?.RemoveChild(this); + (this._parent = value)?.AddChild(this); + } + } - public virtual void Remove() - { - _parent?.RemoveChild(this); - _parent = null; - } + public virtual void Remove() + { + this._parent?.RemoveChild(this); + this._parent = null; + } - public virtual string Value - { - get; - set; - } + public virtual string Value + { + get; + set; + } - public abstract void WriteTo(XmlWriter writer); - public abstract Node Clone(); + public abstract void WriteTo(XmlWriter writer); + public abstract Node Clone(); - object ICloneable.Clone() => Clone(); + object ICloneable.Clone() => this.Clone(); } diff --git a/XmppSharp/Dom/Text.cs b/XmppSharp/Dom/Text.cs index a4ac7a1..caf37d6 100644 --- a/XmppSharp/Dom/Text.cs +++ b/XmppSharp/Dom/Text.cs @@ -6,12 +6,12 @@ namespace XmppSharp.Dom; [DebuggerDisplay("{Value,nq}")] public class Text : Node { - public Text(string value) => Value = value; - public Text(Text other) => Value = other.Value; + public Text(string value) => this.Value = value; + public Text(Text other) => this.Value = other.Value; - public override Node Clone() - => new Text(this); + public override Node Clone() + => new Text(this); - public override void WriteTo(XmlWriter writer) - => writer.WriteString(Value); + public override void WriteTo(XmlWriter writer) + => writer.WriteString(this.Value); } diff --git a/XmppSharp/Exceptions/JabberException.cs b/XmppSharp/Exceptions/JabberException.cs index 429115d..d3baacd 100644 --- a/XmppSharp/Exceptions/JabberException.cs +++ b/XmppSharp/Exceptions/JabberException.cs @@ -5,18 +5,18 @@ /// public class JabberException : Exception { - /// - public JabberException() - { - } + /// + public JabberException() + { + } - /// - public JabberException(string? message) : base(message) - { - } + /// + public JabberException(string? message) : base(message) + { + } - /// - public JabberException(string? message, Exception? innerException) : base(message, innerException) - { - } + /// + public JabberException(string? message, Exception? innerException) : base(message, innerException) + { + } } diff --git a/XmppSharp/Exceptions/JabberSaslException.cs b/XmppSharp/Exceptions/JabberSaslException.cs index 304c8b5..21a3fca 100644 --- a/XmppSharp/Exceptions/JabberSaslException.cs +++ b/XmppSharp/Exceptions/JabberSaslException.cs @@ -7,25 +7,25 @@ namespace XmppSharp.Exceptions; /// public sealed class JabberSaslException : JabberException { - /// - /// Determines the condition of the problem. - /// - public FailureCondition Condition { get; } + /// + /// Determines the condition of the problem. + /// + public FailureCondition Condition { get; } - /// - /// Initializes a new instance of with the specified condition. - /// - /// Error condition that caused the exception. - public JabberSaslException(FailureCondition condition) : base($"SASL authentication failed with error {condition}.") - => Condition = condition; + /// + /// Initializes a new instance of with the specified condition. + /// + /// Error condition that caused the exception. + public JabberSaslException(FailureCondition condition) : base($"SASL authentication failed with error {condition}.") + => this.Condition = condition; - /// - /// Initializes a new instance of with the specified condition and inner exception. - /// - /// Error condition that caused the exception. - /// Inner exception that may have caused the problem. - public JabberSaslException(FailureCondition condition, Exception innerException) : base($"SASL authentication failed with error {condition}.", innerException) - { + /// + /// Initializes a new instance of with the specified condition and inner exception. + /// + /// Error condition that caused the exception. + /// Inner exception that may have caused the problem. + public JabberSaslException(FailureCondition condition, Exception innerException) : base($"SASL authentication failed with error {condition}.", innerException) + { - } + } } \ No newline at end of file diff --git a/XmppSharp/Exceptions/JabberStreamException.cs b/XmppSharp/Exceptions/JabberStreamException.cs index 6ddbadd..88813ab 100644 --- a/XmppSharp/Exceptions/JabberStreamException.cs +++ b/XmppSharp/Exceptions/JabberStreamException.cs @@ -7,26 +7,26 @@ namespace XmppSharp.Exceptions; /// public sealed class JabberStreamException : JabberException { - /// - /// Determines the condition of the problem. - /// - public StreamErrorCondition Condition { get; } + /// + /// Determines the condition of the problem. + /// + public StreamErrorCondition Condition { get; } - /// - /// Initializes a new instance of with the specified condition. - /// - /// Error condition that caused the exception. - public JabberStreamException(StreamErrorCondition condition) : base($"XMPP stream error of type '{condition}' was thrown.") - => Condition = condition; + /// + /// Initializes a new instance of with the specified condition. + /// + /// Error condition that caused the exception. + public JabberStreamException(StreamErrorCondition condition) : base($"XMPP stream error of type '{condition}' was thrown.") + => this.Condition = condition; - /// - /// Initializes a new instance of with the specified condition and inner exception. - /// - /// Error condition that caused the exception. - /// Inner exception that may have caused the problem. - public JabberStreamException(StreamErrorCondition condition, Exception innerException) : base($"XMPP stream error of type '{condition}' was thrown.", innerException) - => Condition = condition; + /// + /// Initializes a new instance of with the specified condition and inner exception. + /// + /// Error condition that caused the exception. + /// Inner exception that may have caused the problem. + public JabberStreamException(StreamErrorCondition condition, Exception innerException) : base($"XMPP stream error of type '{condition}' was thrown.", innerException) + => this.Condition = condition; - public JabberStreamException(StreamErrorCondition condition, string message) : base(message) - => Condition = condition; + public JabberStreamException(StreamErrorCondition condition, string message) : base(message) + => this.Condition = condition; } diff --git a/XmppSharp/Factory/ElementFactory.cs b/XmppSharp/Factory/ElementFactory.cs index cd691a9..01199bf 100644 --- a/XmppSharp/Factory/ElementFactory.cs +++ b/XmppSharp/Factory/ElementFactory.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Reflection; using System.Runtime.CompilerServices; using XmppSharp.Attributes; @@ -6,73 +6,82 @@ namespace XmppSharp.Factory; /// -/// Provides a central mechanism for registering and creating XML elements in a type-safe manner, -/// primarily used within the XMPP context. +/// Provides a central mechanism for registering and creating XML elements in a type-safe mapping. /// public static class ElementFactory { - [ModuleInitializer] - internal static void Initialize() - => RegisterElements(typeof(ElementFactory).Assembly); - - static readonly ConcurrentDictionary ElementTypes = new(XmlTagInfo.Comparer); - - public static IEnumerable GetTags() where T : Element - => ElementTypes.Where(x => x.Value == typeof(T)).Select(x => x.Key); - - public static void RegisterElement() where T : Element - => RegisterElement(typeof(T)); - - public static void RegisterElements(Assembly assembly) - { - var types = from type in assembly.GetTypes() - where !type.IsAbstract && type.IsSubclassOf(typeof(Element)) - && type.GetCustomAttributes().Any() - select type; - - foreach (var type in types) - RegisterElement(type); - } - - public static void RegisterElement(Type type) - { - foreach (var tag in type.GetCustomAttributes()) - ElementTypes[new(tag.LocalName, tag.NamespaceURI)] = type; - } - - public static void RegisterElement(string localName, string ns, Type type) - => ElementTypes[new(localName, ns)] = type; - - static bool GetElementType(string localName, string ns, out Type type) - { - type = default; - - foreach (var (tag, value) in ElementTypes) - { - if (tag.LocalName == localName && tag.NamespaceURI == ns) - { - type = value; - break; - } - } - - return type != null; - } - - public static Element Create(string qualifiedName, string ns) - { - Element elem = default; - - if (GetElementType(qualifiedName, ns, out var type)) - elem = Activator.CreateInstance(type) as Element; - else - elem = new Element(qualifiedName); - - if (!string.IsNullOrEmpty(elem.Prefix)) - elem.SetNamespace(elem.Prefix, ns); - else - elem.SetNamespace(ns); - - return elem; - } -} \ No newline at end of file + static volatile bool bIsInitialized; + + static ElementFactory() => Initialize(); + + [ModuleInitializer] + internal static void Initialize() + { + if (!bIsInitialized) + { + RegisterElements(typeof(ElementFactory).Assembly); + bIsInitialized = true; + } + } + + static readonly ConcurrentDictionary ElementTypes = new(XmlTagInfo.Comparer); + + public static IEnumerable GetTags() where T : Element + => ElementTypes.Where(x => x.Value == typeof(T)).Select(x => x.Key); + + public static void RegisterElement() where T : Element + => RegisterElement(typeof(T)); + + public static void RegisterElements(Assembly assembly) + { + var types = from type in assembly.GetTypes() + where !type.IsAbstract && type.IsSubclassOf(typeof(Element)) + && type.GetCustomAttributes().Any() + select type; + + foreach (var type in types) + RegisterElement(type); + } + + public static void RegisterElement(Type type) + { + foreach (var tag in type.GetCustomAttributes()) + ElementTypes[new(tag.LocalName, tag.NamespaceURI)] = type; + } + + public static void RegisterElement(string localName, string ns, Type type) + => ElementTypes[new(localName, ns)] = type; + + static bool GetElementType(string localName, string ns, out Type type) + { + type = default; + + foreach (var (tag, value) in ElementTypes) + { + if (tag.LocalName == localName && tag.NamespaceURI == ns) + { + type = value; + break; + } + } + + return type != null; + } + + public static Element Create(string qualifiedName, string ns) + { + Element elem; + + if (GetElementType(qualifiedName, ns, out var type)) + elem = Activator.CreateInstance(type) as Element; + else + elem = new Element(qualifiedName); + + if (!string.IsNullOrEmpty(elem.Prefix)) + elem.SetNamespace(elem.Prefix, ns); + else + elem.SetNamespace(ns); + + return elem; + } +} diff --git a/XmppSharp/Jid.cs b/XmppSharp/Jid.cs index 53e5475..ab60b61 100644 --- a/XmppSharp/Jid.cs +++ b/XmppSharp/Jid.cs @@ -11,293 +11,293 @@ namespace XmppSharp; public sealed record Jid : IEquatable #if NET7_0_OR_GREATER - , IParsable + , IParsable #endif { #if NET7_0_OR_GREATER - /// - public static bool TryParse(string? s, IFormatProvider? provider, out Jid result) - => TryParse(s, out result); + /// + public static bool TryParse(string? s, IFormatProvider? provider, out Jid result) + => TryParse(s, out result); - /// - public static Jid Parse(string s, IFormatProvider? provider) - => Parse(s); + /// + public static Jid Parse(string s, IFormatProvider? provider) + => Parse(s); #endif - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal string _local, _domain, _resource; - - /// - /// Gets or sets the local part of the jid. Generally contains the username. - /// - public string? Local - { - get => _local; - set => _local = EnsureByteSize(value?.ToLowerInvariant()); - } - - /// - /// Gets or sets the part of the kid's domain. - /// - /// It usually contains the hostname, address, or qualified name of a server or domain. - /// - /// - public string Domain - { - get => _domain; - set - { - if (string.IsNullOrWhiteSpace(value)) - throw new InvalidOperationException("Domain part cannot be null or empty."); - - _domain = EnsureByteSize(value); - } - } - - /// - /// Gets or sets the resource part of the jid. - /// - /// Contains a string that serves as a unique identifier to identify connections or - /// allow more than one access to the same user account and password. - /// - /// - public string? Resource - { - get => _resource; - set => _resource = EnsureByteSize(value); - } - - static string EnsureByteSize(string? s, [CallerMemberName] string memberName = null) - { - if (string.IsNullOrEmpty(s)) - return s; - - int len; - - if ((len = Encoding.UTF8.GetByteCount(s)) > 1023) - throw new ArgumentOutOfRangeException(memberName, len, $"{memberName} part exceeds the maximum bytes allowed."); - - return s; - } - - /// - /// Initialize a new instance of . - /// - public Jid() - { - - } - - /// - /// Creates an empty jid. - /// - public static Jid Empty => new(); - - /// - /// Initializes a new instance of . - /// - /// String that will attempt to be validated to be valid JID, or by default will assign to . - public Jid(string jid) - { - if (!TryParseComponents(jid, out _local, out _domain, out _resource)) - Domain = jid; - } - - /// - /// Initializes a new instance of . - /// - /// Initial value for the local part. - /// Initial value for the domain part. - /// Initial value for the resource part. - public Jid(string? local, string domain, string? resource) - => (Local, Domain, Resource) = (local, domain, resource); - - /// - /// Try parsing or throw an exception if you encounter a problem. - /// - /// String that will attempt to be validated to be valid JID - /// JID instance. - /// When the supplied string is invalid. - public static Jid Parse(string input) - { - Require.NotNullOrEmpty(input); - - if (!TryParse(input, out var result)) - throw new FormatException("Invalid jid."); - - return result; - } - - internal const char LocalPart = '@'; - internal const char ResourcePart = '/'; - - static bool TryParseComponents(string input, out string local, out string domain, out string resource) - { - local = default; domain = default; resource = default; - - if (string.IsNullOrWhiteSpace(input)) - return false; - - if (!input.Contains(LocalPart) && !input.Contains(ResourcePart)) - { - domain = input; - return true; - } - else - { - var at = input.IndexOf(LocalPart); - - if (at != -1) - local = input[0..at]; - - var slash = input.IndexOf(ResourcePart); - - if (slash == -1) - domain = input[(at + 1)..]; - else - { - domain = input[(at + 1)..slash]; - resource = input[(slash + 1)..]; - } - - if (string.IsNullOrEmpty(domain)) - return false; - - return Uri.CheckHostName(domain) != UriHostNameType.Unknown; - } - } - - /// - /// It tries to parse the JID through the string, but it doesn't throw exceptions. - /// - /// String that will attempt to be validated to be valid JID - /// JID that was successfully parsed or if it is invalid. - /// if the JID was successfully parsed, otherwise. - public static bool TryParse(string input, out Jid result) - { - result = default; - - if (TryParseComponents(input, out var local, out var domain, out var resource)) - { - result = new Jid - { - _local = local, - _domain = domain, - _resource = resource - }; - } - - return result != null; - } - - /// - /// Gets string representation of the JID in the XMPP form. - /// - public override string ToString() - { - using (StringBuilderPool.Rent(out var sb)) - { - if (_local != null) - sb.Append(_local).Append('@'); - - if (_domain != null) - sb.Append(_domain); - - if (_resource != null) - sb.Append('/').Append(_resource); - - return sb.ToString(); - } - } - - /// - /// Determines whether the current JID is bare, that is, it has no resource. - /// - public bool IsBare - => _resource is null; - - /// - /// Gets a copy of the current JID without the resource part. - /// - public Jid Bare => new() - { - _local = _local, - _domain = _domain, - _resource = null - }; - - public override int GetHashCode() => HashCode.Combine( - _local?.GetHashCode() ?? 0, - _domain?.GetHashCode() ?? 0, - _resource?.GetHashCode() ?? 0); - - public bool Equals(Jid other) - { - if (ReferenceEquals(other, null)) - return false; - - if (ReferenceEquals(other, this)) - return true; - - return IsBare ? IsBareEquals(this, other) - : IsFullEqual(this, other); - } - - /// - /// Determines whether both JIDs are the same "Bare" (i.e. they have no resource part) - /// - /// First JID to compare. - /// Second JID to compare. - /// if both JIDs are equals, otherwise . - public static bool IsBareEquals(Jid lhs, Jid rhs) - { - if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null)) - return ReferenceEquals(lhs, rhs); - - if (!lhs.IsBare || !rhs.IsBare) - return false; - - return string.Equals(lhs._local, rhs._local, StringComparison.OrdinalIgnoreCase) - && string.Equals(lhs._domain, rhs._domain, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Determines whether both JIDs are the same "Full" (i.e. both have resource part) - /// - /// First JID to compare. - /// Second JID to compare. - /// if both JIDs are equals, otherwise . - public static bool IsFullEqual(Jid lhs, Jid rhs) - { - if (lhs is null || rhs is null) - return lhs is null && rhs is null; - - return string.Equals(lhs._local, rhs._local, StringComparison.OrdinalIgnoreCase) - && string.Equals(lhs._domain, rhs._domain, StringComparison.OrdinalIgnoreCase) - && string.Equals(lhs._resource, rhs.Resource, StringComparison.Ordinal); - } - - /// - /// Implicit convert string to JID. - /// - public static implicit operator Jid(string s) - { - if (string.IsNullOrWhiteSpace(s)) - return null; - - if (TryParse(s, out var jid)) - return jid; - - return null; - } - - /// - /// Implicit convert JID to string. - /// - public static implicit operator string(Jid j) - => j.ToString(); + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal string _local, _domain, _resource; + + /// + /// Gets or sets the local part of the jid. Generally contains the username. + /// + public string? Local + { + get => this._local; + set => this._local = EnsureByteSize(value?.ToLowerInvariant()); + } + + /// + /// Gets or sets the part of the kid's domain. + /// + /// It usually contains the hostname, address, or qualified name of a server or domain. + /// + /// + public string Domain + { + get => this._domain; + set + { + if (string.IsNullOrWhiteSpace(value)) + throw new InvalidOperationException("Domain part cannot be null or empty."); + + this._domain = EnsureByteSize(value); + } + } + + /// + /// Gets or sets the resource part of the jid. + /// + /// Contains a string that serves as a unique identifier to identify connections or + /// allow more than one access to the same user account and password. + /// + /// + public string? Resource + { + get => this._resource; + set => this._resource = EnsureByteSize(value); + } + + static string EnsureByteSize(string? s, [CallerMemberName] string memberName = null) + { + if (string.IsNullOrEmpty(s)) + return s; + + int len; + + if ((len = Encoding.UTF8.GetByteCount(s)) > 1023) + throw new ArgumentOutOfRangeException(memberName, len, $"{memberName} part exceeds the maximum bytes allowed."); + + return s; + } + + /// + /// Initialize a new instance of . + /// + public Jid() + { + + } + + /// + /// Creates an empty jid. + /// + public static Jid Empty => new(); + + /// + /// Initializes a new instance of . + /// + /// String that will attempt to be validated to be valid JID, or by default will assign to . + public Jid(string jid) + { + if (!TryParseComponents(jid, out this._local, out this._domain, out this._resource)) + this.Domain = jid; + } + + /// + /// Initializes a new instance of . + /// + /// Initial value for the local part. + /// Initial value for the domain part. + /// Initial value for the resource part. + public Jid(string? local, string domain, string? resource) + => (this.Local, this.Domain, this.Resource) = (local, domain, resource); + + /// + /// Try parsing or throw an exception if you encounter a problem. + /// + /// String that will attempt to be validated to be valid JID + /// JID instance. + /// When the supplied string is invalid. + public static Jid Parse(string input) + { + Require.NotNullOrEmpty(input); + + if (!TryParse(input, out var result)) + throw new FormatException("Invalid jid."); + + return result; + } + + internal const char LocalPart = '@'; + internal const char ResourcePart = '/'; + + static bool TryParseComponents(string input, out string local, out string domain, out string resource) + { + local = default; domain = default; resource = default; + + if (string.IsNullOrWhiteSpace(input)) + return false; + + if (!input.Contains(LocalPart) && !input.Contains(ResourcePart)) + { + domain = input; + return true; + } + else + { + var at = input.IndexOf(LocalPart); + + if (at != -1) + local = input[0..at]; + + var slash = input.IndexOf(ResourcePart); + + if (slash == -1) + domain = input[(at + 1)..]; + else + { + domain = input[(at + 1)..slash]; + resource = input[(slash + 1)..]; + } + + if (string.IsNullOrEmpty(domain)) + return false; + + return Uri.CheckHostName(domain) != UriHostNameType.Unknown; + } + } + + /// + /// It tries to parse the JID through the string, but it doesn't throw exceptions. + /// + /// String that will attempt to be validated to be valid JID + /// JID that was successfully parsed or if it is invalid. + /// if the JID was successfully parsed, otherwise. + public static bool TryParse(string input, out Jid result) + { + result = default; + + if (TryParseComponents(input, out var local, out var domain, out var resource)) + { + result = new Jid + { + _local = local, + _domain = domain, + _resource = resource + }; + } + + return result != null; + } + + /// + /// Gets string representation of the JID in the XMPP form. + /// + public override string ToString() + { + using (StringBuilderPool.Rent(out var sb)) + { + if (this._local != null) + sb.Append(this._local).Append('@'); + + if (this._domain != null) + sb.Append(this._domain); + + if (this._resource != null) + sb.Append('/').Append(this._resource); + + return sb.ToString(); + } + } + + /// + /// Determines whether the current JID is bare, that is, it has no resource. + /// + public bool IsBare + => this._resource is null; + + /// + /// Gets a copy of the current JID without the resource part. + /// + public Jid Bare => new() + { + _local = this._local, + _domain = this._domain, + _resource = null + }; + + public override int GetHashCode() => HashCode.Combine( + this._local?.GetHashCode() ?? 0, + this._domain?.GetHashCode() ?? 0, + this._resource?.GetHashCode() ?? 0); + + public bool Equals(Jid other) + { + if (ReferenceEquals(other, null)) + return false; + + if (ReferenceEquals(other, this)) + return true; + + return this.IsBare ? IsBareEquals(this, other) + : IsFullEqual(this, other); + } + + /// + /// Determines whether both JIDs are the same "Bare" (i.e. they have no resource part) + /// + /// First JID to compare. + /// Second JID to compare. + /// if both JIDs are equals, otherwise . + public static bool IsBareEquals(Jid lhs, Jid rhs) + { + if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null)) + return ReferenceEquals(lhs, rhs); + + if (!lhs.IsBare || !rhs.IsBare) + return false; + + return string.Equals(lhs._local, rhs._local, StringComparison.OrdinalIgnoreCase) + && string.Equals(lhs._domain, rhs._domain, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Determines whether both JIDs are the same "Full" (i.e. both have resource part) + /// + /// First JID to compare. + /// Second JID to compare. + /// if both JIDs are equals, otherwise . + public static bool IsFullEqual(Jid lhs, Jid rhs) + { + if (lhs is null || rhs is null) + return lhs is null && rhs is null; + + return string.Equals(lhs._local, rhs._local, StringComparison.OrdinalIgnoreCase) + && string.Equals(lhs._domain, rhs._domain, StringComparison.OrdinalIgnoreCase) + && string.Equals(lhs._resource, rhs.Resource, StringComparison.Ordinal); + } + + /// + /// Implicit convert string to JID. + /// + public static implicit operator Jid(string s) + { + if (string.IsNullOrWhiteSpace(s)) + return null; + + if (TryParse(s, out var jid)) + return jid; + + return null; + } + + /// + /// Implicit convert JID to string. + /// + public static implicit operator string(Jid j) + => j.ToString(); } diff --git a/XmppSharp/Namespace.cs b/XmppSharp/Namespace.cs index 2c54b95..59047e2 100644 --- a/XmppSharp/Namespace.cs +++ b/XmppSharp/Namespace.cs @@ -2,373 +2,373 @@ public static class Namespace { - /// - /// W3C: Xml Namespacehttp://www.w3.org/XML/1998/namespace - /// - public const string Xml = "http://www.w3.org/XML/1998/namespace"; - - /// - /// W3C: Xmlns Namespacehttp://www.w3.org/2000/xmlns/ - /// - public const string Xmlns = "http://www.w3.org/2000/xmlns/"; - - /// - /// W3C: XHTML Namespacehttp://www.w3.org/1999/xhtml - /// - public const string XHtml = "http://www.w3.org/1999/xhtml"; - - /// - /// XEP-0108: User Activityhttp://jabber.org/protocol/activity - /// - public const string UserActivity = "http://jabber.org/protocol/activity"; - - /// - /// XEP-0033: Extended Stanza Addressinghttp://jabber.org/protocol/address - /// - public const string ExtendedStanzaAddressing = "http://jabber.org/protocol/address"; - - /// - /// XEP-0079: Advanced Message Processinghttp://jabber.org/protocol/amp - /// - public const string Amp = "http://jabber.org/protocol/amp"; - - /// - /// XEP-0079: Advanced Message Processinghttp://jabber.org/protocol/amp#errors - /// - public const string AmpErrors = "http://jabber.org/protocol/amp#errors"; - - /// - /// XEP-0065: SOCKS5 Bytestreamshttp://jabber.org/protocol/bytestreams - /// - public const string ByteStreams = "http://jabber.org/protocol/bytestreams"; - - /// - /// XEP-0115: Entity Capabilitieshttp://jabber.org/protocol/caps - /// - public const string EntityCapabilities = "http://jabber.org/protocol/caps"; - - /// - /// XEP-0085: Chat State Notificationshttp://jabber.org/protocol/chatstates - /// - public const string ChatStates = "http://jabber.org/protocol/chatstates"; - - /// - /// XEP-0050: Ad-Hoc Commandshttp://jabber.org/protocol/commands - /// - public const string AdHoc = "http://jabber.org/protocol/commands"; - - /// - /// XEP-0138: Stream Compressionhttp://jabber.org/protocol/compress - /// - public const string StreamCompression = "http://jabber.org/protocol/compress"; - - /// - /// XEP-0030: Service Discoveryhttp://jabber.org/protocol/disco#info - /// - public const string DiscoInfo = "http://jabber.org/protocol/disco#info"; - - /// - /// XEP-0030: Service Discoveryhttp://jabber.org/protocol/disco#items - /// - public const string DiscoItems = "http://jabber.org/protocol/disco#items"; - - /// - /// XEP-0020: Feature Negotiationhttp://jabber.org/protocol/feature-neg - /// - public const string FeatureNeg = "http://jabber.org/protocol/feature-neg"; - - /// - /// XEP-0080: User Geolocationhttp://jabber.org/protocol/geoloc - /// - public const string GeoLoc = "http://jabber.org/protocol/geoloc"; - - /// - /// XEP-0070: Verifying HTTP Requests via XMPPhttp://jabber.org/protocol/http-auth - /// - public const string HttpAuth = "http://jabber.org/protocol/http-auth"; - - /// - /// XEP-0124: Bidirectional-streams Over Synchronous HTTPhttp://jabber.org/protocol/httpbind - /// - public const string HttpBind = "http://jabber.org/protocol/httpbind"; - - /// - /// XEP-0047: In-Band Bytestreamshttp://jabber.org/protocol/ibb - /// - public const string Ibb = "http://jabber.org/protocol/ibb"; - - /// - /// XEP-0107: User Moodhttp://jabber.org/protocol/mood - /// - public const string UserMood = "http://jabber.org/protocol/mood"; - - /// - /// XEP-0045: Multi-User Chathttp://jabber.org/protocol/muc - /// - public const string Muc = "http://jabber.org/protocol/muc"; - - /// - /// XEP-0045: Multi-User Chathttp://jabber.org/protocol/muc#admin - /// - public const string MucAdmin = "http://jabber.org/protocol/muc#admin"; - - /// - /// XEP-0045: Multi-User Chathttp://jabber.org/protocol/muc#owner - /// - public const string MucOwner = "http://jabber.org/protocol/muc#owner"; - - /// - /// XEP-0045: Multi-User Chathttp://jabber.org/protocol/muc#user - /// - public const string MucUser = "http://jabber.org/protocol/muc#user"; - - /// - /// XEP-0172: User Nicknamehttp://jabber.org/protocol/nick - /// - public const string Nick = "http://jabber.org/protocol/nick"; - - /// - /// XEP-0013: Flexible Offline Message Retrievalhttp://jabber.org/protocol/offline - /// - public const string Offline = "http://jabber.org/protocol/offline"; - - /// - /// XEP-0112: User Physical Locationhttp://jabber.org/protocol/physloc - /// - public const string PhysicalLocation = "http://jabber.org/protocol/physloc"; - - /// - /// XEP-0060: Publish-Subscribehttp://jabber.org/protocol/pubsub - /// - public const string PubSub = "http://jabber.org/protocol/pubsub"; - - /// - /// XEP-0060: Publish-Subscribehttp://jabber.org/protocol/pubsub#errors - /// - public const string PubSubErrors = "http://jabber.org/protocol/pubsub#errors"; - - /// - /// XEP-0060: Publish-Subscribehttp://jabber.org/protocol/pubsub#event - /// - public const string PubSubEvent = "http://jabber.org/protocol/pubsub#event"; - - /// - /// XEP-0060: Publish-Subscribehttp://jabber.org/protocol/pubsub#owner - /// - public const string PubSubOwner = "http://jabber.org/protocol/pubsub#owner"; - - /// - /// XEP-0144: Roster Item Exchangehttp://jabber.org/protocol/rosterx - /// - public const string RosterItemExchange = "http://jabber.org/protocol/rosterx"; - - /// - /// XEP-0141: Data Forms Layouthttp://jabber.org/protocol/xdata-layout - /// - public const string DataFormsLayout = "http://jabber.org/protocol/xdata-layout"; - - /// - /// XEP-0122: Data Forms Validationhttp://jabber.org/protocol/xdata-validate - /// - public const string DataFormsValidation = "http://jabber.org/protocol/xdata-validate"; - - /// - /// XEP-0114: Existing Component Protocoljabber:component:accept - /// - public const string Accept = "jabber:component:accept"; - - /// - /// XEP-0114: Existing Component Protocoljabber:component:connect - /// - public const string Connect = "jabber:component:connect"; - - /// - /// XEP-0078: Non-SASL Authenticationjabber:iq:auth - /// - public const string IqAuth = "jabber:iq:auth"; - - /// - /// XEP-0100: Gateway Interactionjabber:iq:gateway - /// - public const string IqGateway = "jabber:iq:gateway"; - - /// - /// XEP-0012: Last Activityjabber:iq:last - /// - public const string IqLast = "jabber:iq:last"; - - /// - /// XEP-0066: Out of Band Datajabber:iq:oob - /// - public const string IqOob = "jabber:iq:oob"; - - /// - /// XEP-0016: Privacy Listsjabber:iq:privacy - /// - public const string IqPrivacy = "jabber:iq:privacy"; - - /// - /// XEP-0049: Private XML Storagejabber:iq:private - /// - public const string IqPrivate = "jabber:iq:private"; - - /// - /// XEP-0077: In-Band Registrationjabber:iq:register - /// - public const string IqRegister = "jabber:iq:register"; - - /// - /// XEP-0321: Remote Roster Managementjabber:iq:roster - /// - public const string IqRoster = "jabber:iq:roster"; - - /// - /// XEP-0009: Jabber-RPCjabber:iq:rpc - /// - public const string IqRpc = "jabber:iq:rpc"; - - /// - /// XEP-0055: Jabber Searchjabber:iq:search - /// - public const string IqSearch = "jabber:iq:search"; - - /// - /// XEP-0092: Software Versionjabber:iq:version - /// - public const string IqVersion = "jabber:iq:version"; - - /// - /// XEP-0220: Server Dialbackjabber:server:dialback - /// - public const string Dialback = "jabber:server:dialback"; - - /// - /// XEP-0249: Direct MUC Invitationsjabber:x:conference - /// - public const string DirectMuc = "jabber:x:conference"; - - /// - /// XEP-0004: Data Formsjabber:x:data - /// - public const string DataForms = "jabber:x:data"; - - /// - /// XEP-0066: Out of Band Datajabber:x:oob - /// - public const string Oob = "jabber:x:oob"; - - /// - /// XEP-0083: Nested Roster Groupsroster:delimiter - /// - public const string RosterDdelimiter = "roster:delimiter"; - - /// - /// RFC-6120: XMPP Corehttp://etherx.jabber.org/streams - /// - public const string Stream = "http://etherx.jabber.org/streams"; - - /// - /// RFC-6120: XMPP Corejabber:client - /// - public const string Client = "jabber:client"; - - /// - /// RFC-6120: XMPP Corejabber:server - /// - public const string Server = "jabber:server"; - - /// - /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-bind - /// - public const string Bind = "urn:ietf:params:xml:ns:xmpp-bind"; - - /// - /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-sasl - /// - public const string Sasl = "urn:ietf:params:xml:ns:xmpp-sasl"; - - /// - /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-session - /// - public const string Session = "urn:ietf:params:xml:ns:xmpp-session"; - - /// - /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-stanzas - /// - public const string Stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas"; - - /// - /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-streams - /// - public const string Streams = "urn:ietf:params:xml:ns:xmpp-streams"; - - /// - /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-tls - /// - public const string Tls = "urn:ietf:params:xml:ns:xmpp-tls"; - - /// - /// XEP-0224: Attentionurn:xmpp:attention:0 - /// - public const string Attention = "urn:xmpp:attention:0"; - - /// - /// XEP-0084: User Avatarsurn:xmpp:avatar:data - /// - public const string UserAvatarsData = "urn:xmpp:avatar:data"; - - /// - /// XEP-0084: User Avatarsurn:xmpp:avatar:metadata - /// - public const string UserAvatarsMetadata = "urn:xmpp:avatar:metadata"; - - /// - /// XEP-0288: Bidirectional Server-to-Server Connectionsurn:xmpp:bidi - /// - public const string Bidi = "urn:xmpp:bidi"; - - /// - /// XEP-0231: Bits of Binaryurn:xmpp:bob - /// - public const string Bob = "urn:xmpp:bob"; - - /// - /// XEP-0158: CAPTCHA Formsurn:xmpp:captcha - /// - public const string Captcha = "urn:xmpp:captcha"; - - /// - /// XEP-0203: Delayed Deliveryurn:xmpp:delay - /// - public const string Delay = "urn:xmpp:delay"; - - /// - /// XEP-0199: XMPP Pingurn:xmpp:ping - /// - public const string Ping = "urn:xmpp:ping"; - - /// - /// XEP-0198: Stream Managementurn:xmpp:sm:3 - /// - public const string StreamManagement = "urn:xmpp:sm:3"; - - /// - /// XEP-0202: Entity Timeurn:xmpp:time - /// - public const string EntityTime = "urn:xmpp:time"; - - /// - /// XEP-0054: vcard-tempvcard-temp - /// - public const string vCard = "vcard-temp"; - - /// - /// XEP-0153: vCard-Based Avatarsvcard-temp:x:update - /// - public const string vCardUpdate = "vcard-temp:x:update"; - - /// - /// urn:cryonline:k01 - /// - public const string CryOnline = "urn:cryonline:k01"; + /// + /// W3C: Xml Namespacehttp://www.w3.org/XML/1998/namespace + /// + public const string Xml = "http://www.w3.org/XML/1998/namespace"; + + /// + /// W3C: Xmlns Namespacehttp://www.w3.org/2000/xmlns/ + /// + public const string Xmlns = "http://www.w3.org/2000/xmlns/"; + + /// + /// W3C: XHTML Namespacehttp://www.w3.org/1999/xhtml + /// + public const string XHtml = "http://www.w3.org/1999/xhtml"; + + /// + /// XEP-0108: User Activityhttp://jabber.org/protocol/activity + /// + public const string UserActivity = "http://jabber.org/protocol/activity"; + + /// + /// XEP-0033: Extended Stanza Addressinghttp://jabber.org/protocol/address + /// + public const string ExtendedStanzaAddressing = "http://jabber.org/protocol/address"; + + /// + /// XEP-0079: Advanced Message Processinghttp://jabber.org/protocol/amp + /// + public const string Amp = "http://jabber.org/protocol/amp"; + + /// + /// XEP-0079: Advanced Message Processinghttp://jabber.org/protocol/amp#errors + /// + public const string AmpErrors = "http://jabber.org/protocol/amp#errors"; + + /// + /// XEP-0065: SOCKS5 Bytestreamshttp://jabber.org/protocol/bytestreams + /// + public const string ByteStreams = "http://jabber.org/protocol/bytestreams"; + + /// + /// XEP-0115: Entity Capabilitieshttp://jabber.org/protocol/caps + /// + public const string EntityCapabilities = "http://jabber.org/protocol/caps"; + + /// + /// XEP-0085: Chat State Notificationshttp://jabber.org/protocol/chatstates + /// + public const string ChatStates = "http://jabber.org/protocol/chatstates"; + + /// + /// XEP-0050: Ad-Hoc Commandshttp://jabber.org/protocol/commands + /// + public const string AdHoc = "http://jabber.org/protocol/commands"; + + /// + /// XEP-0138: Stream Compressionhttp://jabber.org/protocol/compress + /// + public const string StreamCompression = "http://jabber.org/protocol/compress"; + + /// + /// XEP-0030: Service Discoveryhttp://jabber.org/protocol/disco#info + /// + public const string DiscoInfo = "http://jabber.org/protocol/disco#info"; + + /// + /// XEP-0030: Service Discoveryhttp://jabber.org/protocol/disco#items + /// + public const string DiscoItems = "http://jabber.org/protocol/disco#items"; + + /// + /// XEP-0020: Feature Negotiationhttp://jabber.org/protocol/feature-neg + /// + public const string FeatureNeg = "http://jabber.org/protocol/feature-neg"; + + /// + /// XEP-0080: User Geolocationhttp://jabber.org/protocol/geoloc + /// + public const string GeoLoc = "http://jabber.org/protocol/geoloc"; + + /// + /// XEP-0070: Verifying HTTP Requests via XMPPhttp://jabber.org/protocol/http-auth + /// + public const string HttpAuth = "http://jabber.org/protocol/http-auth"; + + /// + /// XEP-0124: Bidirectional-streams Over Synchronous HTTPhttp://jabber.org/protocol/httpbind + /// + public const string HttpBind = "http://jabber.org/protocol/httpbind"; + + /// + /// XEP-0047: In-Band Bytestreamshttp://jabber.org/protocol/ibb + /// + public const string Ibb = "http://jabber.org/protocol/ibb"; + + /// + /// XEP-0107: User Moodhttp://jabber.org/protocol/mood + /// + public const string UserMood = "http://jabber.org/protocol/mood"; + + /// + /// XEP-0045: Multi-User Chathttp://jabber.org/protocol/muc + /// + public const string Muc = "http://jabber.org/protocol/muc"; + + /// + /// XEP-0045: Multi-User Chathttp://jabber.org/protocol/muc#admin + /// + public const string MucAdmin = "http://jabber.org/protocol/muc#admin"; + + /// + /// XEP-0045: Multi-User Chathttp://jabber.org/protocol/muc#owner + /// + public const string MucOwner = "http://jabber.org/protocol/muc#owner"; + + /// + /// XEP-0045: Multi-User Chathttp://jabber.org/protocol/muc#user + /// + public const string MucUser = "http://jabber.org/protocol/muc#user"; + + /// + /// XEP-0172: User Nicknamehttp://jabber.org/protocol/nick + /// + public const string Nick = "http://jabber.org/protocol/nick"; + + /// + /// XEP-0013: Flexible Offline Message Retrievalhttp://jabber.org/protocol/offline + /// + public const string Offline = "http://jabber.org/protocol/offline"; + + /// + /// XEP-0112: User Physical Locationhttp://jabber.org/protocol/physloc + /// + public const string PhysicalLocation = "http://jabber.org/protocol/physloc"; + + /// + /// XEP-0060: Publish-Subscribehttp://jabber.org/protocol/pubsub + /// + public const string PubSub = "http://jabber.org/protocol/pubsub"; + + /// + /// XEP-0060: Publish-Subscribehttp://jabber.org/protocol/pubsub#errors + /// + public const string PubSubErrors = "http://jabber.org/protocol/pubsub#errors"; + + /// + /// XEP-0060: Publish-Subscribehttp://jabber.org/protocol/pubsub#event + /// + public const string PubSubEvent = "http://jabber.org/protocol/pubsub#event"; + + /// + /// XEP-0060: Publish-Subscribehttp://jabber.org/protocol/pubsub#owner + /// + public const string PubSubOwner = "http://jabber.org/protocol/pubsub#owner"; + + /// + /// XEP-0144: Roster Item Exchangehttp://jabber.org/protocol/rosterx + /// + public const string RosterItemExchange = "http://jabber.org/protocol/rosterx"; + + /// + /// XEP-0141: Data Forms Layouthttp://jabber.org/protocol/xdata-layout + /// + public const string DataFormsLayout = "http://jabber.org/protocol/xdata-layout"; + + /// + /// XEP-0122: Data Forms Validationhttp://jabber.org/protocol/xdata-validate + /// + public const string DataFormsValidation = "http://jabber.org/protocol/xdata-validate"; + + /// + /// XEP-0114: Existing Component Protocoljabber:component:accept + /// + public const string Accept = "jabber:component:accept"; + + /// + /// XEP-0114: Existing Component Protocoljabber:component:connect + /// + public const string Connect = "jabber:component:connect"; + + /// + /// XEP-0078: Non-SASL Authenticationjabber:iq:auth + /// + public const string IqAuth = "jabber:iq:auth"; + + /// + /// XEP-0100: Gateway Interactionjabber:iq:gateway + /// + public const string IqGateway = "jabber:iq:gateway"; + + /// + /// XEP-0012: Last Activityjabber:iq:last + /// + public const string IqLast = "jabber:iq:last"; + + /// + /// XEP-0066: Out of Band Datajabber:iq:oob + /// + public const string IqOob = "jabber:iq:oob"; + + /// + /// XEP-0016: Privacy Listsjabber:iq:privacy + /// + public const string IqPrivacy = "jabber:iq:privacy"; + + /// + /// XEP-0049: Private XML Storagejabber:iq:private + /// + public const string IqPrivate = "jabber:iq:private"; + + /// + /// XEP-0077: In-Band Registrationjabber:iq:register + /// + public const string IqRegister = "jabber:iq:register"; + + /// + /// XEP-0321: Remote Roster Managementjabber:iq:roster + /// + public const string IqRoster = "jabber:iq:roster"; + + /// + /// XEP-0009: Jabber-RPCjabber:iq:rpc + /// + public const string IqRpc = "jabber:iq:rpc"; + + /// + /// XEP-0055: Jabber Searchjabber:iq:search + /// + public const string IqSearch = "jabber:iq:search"; + + /// + /// XEP-0092: Software Versionjabber:iq:version + /// + public const string IqVersion = "jabber:iq:version"; + + /// + /// XEP-0220: Server Dialbackjabber:server:dialback + /// + public const string Dialback = "jabber:server:dialback"; + + /// + /// XEP-0249: Direct MUC Invitationsjabber:x:conference + /// + public const string DirectMuc = "jabber:x:conference"; + + /// + /// XEP-0004: Data Formsjabber:x:data + /// + public const string DataForms = "jabber:x:data"; + + /// + /// XEP-0066: Out of Band Datajabber:x:oob + /// + public const string Oob = "jabber:x:oob"; + + /// + /// XEP-0083: Nested Roster Groupsroster:delimiter + /// + public const string RosterDdelimiter = "roster:delimiter"; + + /// + /// RFC-6120: XMPP Corehttp://etherx.jabber.org/streams + /// + public const string Stream = "http://etherx.jabber.org/streams"; + + /// + /// RFC-6120: XMPP Corejabber:client + /// + public const string Client = "jabber:client"; + + /// + /// RFC-6120: XMPP Corejabber:server + /// + public const string Server = "jabber:server"; + + /// + /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-bind + /// + public const string Bind = "urn:ietf:params:xml:ns:xmpp-bind"; + + /// + /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-sasl + /// + public const string Sasl = "urn:ietf:params:xml:ns:xmpp-sasl"; + + /// + /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-session + /// + public const string Session = "urn:ietf:params:xml:ns:xmpp-session"; + + /// + /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-stanzas + /// + public const string Stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas"; + + /// + /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-streams + /// + public const string Streams = "urn:ietf:params:xml:ns:xmpp-streams"; + + /// + /// RFC-6120: XMPP Coreurn:ietf:params:xml:ns:xmpp-tls + /// + public const string Tls = "urn:ietf:params:xml:ns:xmpp-tls"; + + /// + /// XEP-0224: Attentionurn:xmpp:attention:0 + /// + public const string Attention = "urn:xmpp:attention:0"; + + /// + /// XEP-0084: User Avatarsurn:xmpp:avatar:data + /// + public const string UserAvatarsData = "urn:xmpp:avatar:data"; + + /// + /// XEP-0084: User Avatarsurn:xmpp:avatar:metadata + /// + public const string UserAvatarsMetadata = "urn:xmpp:avatar:metadata"; + + /// + /// XEP-0288: Bidirectional Server-to-Server Connectionsurn:xmpp:bidi + /// + public const string Bidi = "urn:xmpp:bidi"; + + /// + /// XEP-0231: Bits of Binaryurn:xmpp:bob + /// + public const string Bob = "urn:xmpp:bob"; + + /// + /// XEP-0158: CAPTCHA Formsurn:xmpp:captcha + /// + public const string Captcha = "urn:xmpp:captcha"; + + /// + /// XEP-0203: Delayed Deliveryurn:xmpp:delay + /// + public const string Delay = "urn:xmpp:delay"; + + /// + /// XEP-0199: XMPP Pingurn:xmpp:ping + /// + public const string Ping = "urn:xmpp:ping"; + + /// + /// XEP-0198: Stream Managementurn:xmpp:sm:3 + /// + public const string StreamManagement = "urn:xmpp:sm:3"; + + /// + /// XEP-0202: Entity Timeurn:xmpp:time + /// + public const string EntityTime = "urn:xmpp:time"; + + /// + /// XEP-0054: vcard-tempvcard-temp + /// + public const string vCard = "vcard-temp"; + + /// + /// XEP-0153: vCard-Based Avatarsvcard-temp:x:update + /// + public const string vCardUpdate = "vcard-temp:x:update"; + + /// + /// urn:cryonline:k01 + /// + public const string CryOnline = "urn:cryonline:k01"; } \ No newline at end of file diff --git a/XmppSharp/Protocol/Base/DirectionalElement.cs b/XmppSharp/Protocol/Base/DirectionalElement.cs index 5b4259c..016bcad 100644 --- a/XmppSharp/Protocol/Base/DirectionalElement.cs +++ b/XmppSharp/Protocol/Base/DirectionalElement.cs @@ -2,42 +2,42 @@ public abstract class DirectionalElement : Element { - protected DirectionalElement(string qualifiedName) : base(qualifiedName) - { - } - - protected DirectionalElement(string qualifiedName, string namespaceURI) : base(qualifiedName, namespaceURI) - { - } - - public Jid From - { - get - { - var jid = GetAttribute("from"); - - if (Jid.TryParse(jid, out var result)) - return result; - - return null; - } - set => SetAttribute("from", value?.ToString()); - } - - public Jid To - { - get - { - var jid = GetAttribute("to"); - - if (Jid.TryParse(jid, out var result)) - return result; - - return null; - } - set => SetAttribute("to", value?.ToString()); - } - - public void SwitchDirection() - => (From, To) = (To, From); + protected DirectionalElement(string qualifiedName) : base(qualifiedName) + { + } + + protected DirectionalElement(string qualifiedName, string namespaceURI) : base(qualifiedName, namespaceURI) + { + } + + public Jid From + { + get + { + var jid = this.GetAttribute("from"); + + if (Jid.TryParse(jid, out var result)) + return result; + + return null; + } + set => this.SetAttribute("from", value?.ToString()); + } + + public Jid To + { + get + { + var jid = this.GetAttribute("to"); + + if (Jid.TryParse(jid, out var result)) + return result; + + return null; + } + set => this.SetAttribute("to", value?.ToString()); + } + + public void SwitchDirection() + => (this.From, this.To) = (this.To, this.From); } diff --git a/XmppSharp/Protocol/Base/Ping.cs b/XmppSharp/Protocol/Base/Ping.cs index cff2e05..d3ac73d 100644 --- a/XmppSharp/Protocol/Base/Ping.cs +++ b/XmppSharp/Protocol/Base/Ping.cs @@ -5,8 +5,8 @@ namespace XmppSharp.Protocol.Base; [XmppTag("ping", "urn:xmpp:ping")] public class Ping : Element { - public Ping() : base("ping", Namespace.Ping) - { + public Ping() : base("ping", Namespace.Ping) + { - } + } } diff --git a/XmppSharp/Protocol/Base/Stanza.cs b/XmppSharp/Protocol/Base/Stanza.cs index 9f9b67e..6c90b12 100644 --- a/XmppSharp/Protocol/Base/Stanza.cs +++ b/XmppSharp/Protocol/Base/Stanza.cs @@ -2,44 +2,44 @@ public abstract class Stanza : Element { - protected Stanza(string qualifiedName) : base(qualifiedName) - { - } + protected Stanza(string qualifiedName) : base(qualifiedName) + { + } - protected Stanza(string qualifiedName, string namespaceURI) : base(qualifiedName, namespaceURI) - { - } + protected Stanza(string qualifiedName, string namespaceURI) : base(qualifiedName, namespaceURI) + { + } - public string Id - { - get => GetAttribute("id"); - set => SetAttribute("id", value); - } + public string Id + { + get => this.GetAttribute("id"); + set => this.SetAttribute("id", value); + } - public string Type - { - get => GetAttribute("type"); - set => SetAttribute("type", value); - } + public string Type + { + get => this.GetAttribute("type"); + set => this.SetAttribute("type", value); + } - public string Language - { - get => GetAttribute("xml:lang"); - set => SetAttribute("xml:lang", value); - } + public string Language + { + get => this.GetAttribute("xml:lang"); + set => this.SetAttribute("xml:lang", value); + } - public void GenerateId() - => Id = Guid.NewGuid().ToString("d"); + public void GenerateId() + => this.Id = Guid.NewGuid().ToString("d"); - public StanzaError Error - { - get => Child(); - set - { - if (value == null) - RemoveTag("error", Namespace.Stanzas); - else - AddChild(value); - } - } + public StanzaError Error + { + get => this.Child(); + set + { + if (value == null) + this.RemoveTag("error", Namespace.Stanzas); + else + this.AddChild(value); + } + } } \ No newline at end of file diff --git a/XmppSharp/Protocol/Base/StanzaError.cs b/XmppSharp/Protocol/Base/StanzaError.cs index 0afb676..45c13b9 100644 --- a/XmppSharp/Protocol/Base/StanzaError.cs +++ b/XmppSharp/Protocol/Base/StanzaError.cs @@ -8,54 +8,54 @@ namespace XmppSharp.Protocol.Base; [XmppTag("error", "jabber:component:connect")] public class StanzaError : Element { - public StanzaError() : base("error", Namespace.Client) - { - - } - - public StanzaErrorType? Type - { - get => XmppEnum.Parse(GetAttribute("type")); - set - { - if (!value.TryGetValue(out var result)) - RemoveAttribute("type"); - else - SetAttribute("type", result.ToXmppName()); - } - } - - public StanzaErrorCondition? Condition - { - get - { - foreach (var (tag, value) in XmppEnum.GetValues()) - { - if (HasTag(Namespace.Stanzas + tag)) - return value; - } - - return default; - } - set - { - XmppEnum.GetNames() - .ForEach(tag => RemoveTag(Namespace.Stanzas + tag)); - - if (value.TryGetValue(out var result)) - SetTag(Namespace.Stanzas + result.ToXmppName()); - } - } - - public string Text - { - get => GetTag("text", Namespace.Stanzas); - set - { - if (value == null) - RemoveTag("text", Namespace.Stanzas); - else - SetTag("text", Namespace.Stanzas, value); - } - } + public StanzaError() : base("error", Namespace.Client) + { + + } + + public StanzaErrorType? Type + { + get => XmppEnum.Parse(this.GetAttribute("type")); + set + { + if (!value.TryGetValue(out var result)) + this.RemoveAttribute("type"); + else + this.SetAttribute("type", result.ToXmppName()); + } + } + + public StanzaErrorCondition? Condition + { + get + { + foreach (var (tag, value) in XmppEnum.GetValues()) + { + if (this.HasTag(Namespace.Stanzas + tag)) + return value; + } + + return default; + } + set + { + XmppEnum.GetNames() + .ForEach(tag => this.RemoveTag(Namespace.Stanzas + tag)); + + if (value.TryGetValue(out var result)) + this.SetTag(Namespace.Stanzas + result.ToXmppName()); + } + } + + public string Text + { + get => this.GetTag("text", Namespace.Stanzas); + set + { + if (value == null) + this.RemoveTag("text", Namespace.Stanzas); + else + this.SetTag("text", Namespace.Stanzas, value); + } + } } diff --git a/XmppSharp/Protocol/Base/StanzaErrorCondition.cs b/XmppSharp/Protocol/Base/StanzaErrorCondition.cs index dfed467..274c671 100644 --- a/XmppSharp/Protocol/Base/StanzaErrorCondition.cs +++ b/XmppSharp/Protocol/Base/StanzaErrorCondition.cs @@ -8,135 +8,135 @@ namespace XmppSharp.Protocol.Base; [XmppEnum] public enum StanzaErrorCondition { - /// - /// The request was invalid. - /// - [XmppMember("bad-request")] - BadRequest, - - /// - /// The operation failed due to a conflict (e.g., resource already exists). - /// - [XmppMember("conflict")] - Conflict, - - /// - /// The requested feature is not implemented on the server. - /// - [XmppMember("feature-not-implemented")] - FeatureNotImplemented, - - /// - /// The sender is not authorized to perform the requested operation. - /// - [XmppMember("forbidden")] - Forbidden, - - /// - /// The requested entity (e.g., user, room) no longer exists. - /// - [XmppMember("gone")] - Gone, - - /// - /// The server encountered an internal error and could not process the request. - /// - [XmppMember("internal-server-error")] - InternalServerError, - - /// - /// The requested item (e.g., message, roster entry) was not found. - /// - [XmppMember("item-not-found")] - ItemNotFound, - - /// - /// The JID (Jabber ID) specified in the request was invalid or malformed. - /// - [XmppMember("jid-malformed")] - JidMalformed, - - /// - /// The server cannot accept the request with the provided parameters. - /// - [XmppMember("not-acceptable")] - NotAcceptable, - - /// - /// The requested operation is not allowed for the current resource or context. - /// - [XmppMember("not-allowed")] - NotAllowed, - - /// - /// The sender is not authorized to access the requested resource. - /// - [XmppMember("not-authorized")] - NotAuthorized, - - /// - /// The request violates server policy. - /// - [XmppMember("policy-violation")] - PolicyViolation, - - /// - /// The intended recipient is currently unavailable. - /// - [XmppMember("recipient-unavailable")] - RecipientUnavailable, - - /// - /// The server is redirecting the client to a different location. - /// - [XmppMember("redirect")] - Redirect, - - /// - /// The client needs to register with the server before performing the request. - /// - [XmppMember("registration-required")] - RegistrationRequired, - - /// - /// The server could not locate the remote server specified in the request. - /// - [XmppMember("remote-server-not-found")] - RemoteServerNotFound, - - /// - /// The server timed out waiting for a response from the remote server. - /// - [XmppMember("remote-server-timeout")] - RemoteServerTimeout, - - /// - /// The server lacks the resources to process the request. - /// - [XmppMember("resource-constraint")] - ResourceConstraint, - - /// - /// The requested service is unavailable on the server. - /// - [XmppMember("service-unavailable")] - ServiceUnavailable, - - /// - /// The client needs to subscribe or be authorized before performing the request. - /// - [XmppMember("subscription-required")] - SubscriptionRequired, - - /// - /// The error condition is not explicitly defined in the XMPP standard. - /// - [XmppMember("undefined-condition")] - UndefinedCondition, - - /// - /// The server received an unexpected request type. - /// - [XmppMember("unexpected-request")] - UnexpectedRequest + /// + /// The request was invalid. + /// + [XmppMember("bad-request")] + BadRequest, + + /// + /// The operation failed due to a conflict (e.g., resource already exists). + /// + [XmppMember("conflict")] + Conflict, + + /// + /// The requested feature is not implemented on the server. + /// + [XmppMember("feature-not-implemented")] + FeatureNotImplemented, + + /// + /// The sender is not authorized to perform the requested operation. + /// + [XmppMember("forbidden")] + Forbidden, + + /// + /// The requested entity (e.g., user, room) no longer exists. + /// + [XmppMember("gone")] + Gone, + + /// + /// The server encountered an internal error and could not process the request. + /// + [XmppMember("internal-server-error")] + InternalServerError, + + /// + /// The requested item (e.g., message, roster entry) was not found. + /// + [XmppMember("item-not-found")] + ItemNotFound, + + /// + /// The JID (Jabber ID) specified in the request was invalid or malformed. + /// + [XmppMember("jid-malformed")] + JidMalformed, + + /// + /// The server cannot accept the request with the provided parameters. + /// + [XmppMember("not-acceptable")] + NotAcceptable, + + /// + /// The requested operation is not allowed for the current resource or context. + /// + [XmppMember("not-allowed")] + NotAllowed, + + /// + /// The sender is not authorized to access the requested resource. + /// + [XmppMember("not-authorized")] + NotAuthorized, + + /// + /// The request violates server policy. + /// + [XmppMember("policy-violation")] + PolicyViolation, + + /// + /// The intended recipient is currently unavailable. + /// + [XmppMember("recipient-unavailable")] + RecipientUnavailable, + + /// + /// The server is redirecting the client to a different location. + /// + [XmppMember("redirect")] + Redirect, + + /// + /// The client needs to register with the server before performing the request. + /// + [XmppMember("registration-required")] + RegistrationRequired, + + /// + /// The server could not locate the remote server specified in the request. + /// + [XmppMember("remote-server-not-found")] + RemoteServerNotFound, + + /// + /// The server timed out waiting for a response from the remote server. + /// + [XmppMember("remote-server-timeout")] + RemoteServerTimeout, + + /// + /// The server lacks the resources to process the request. + /// + [XmppMember("resource-constraint")] + ResourceConstraint, + + /// + /// The requested service is unavailable on the server. + /// + [XmppMember("service-unavailable")] + ServiceUnavailable, + + /// + /// The client needs to subscribe or be authorized before performing the request. + /// + [XmppMember("subscription-required")] + SubscriptionRequired, + + /// + /// The error condition is not explicitly defined in the XMPP standard. + /// + [XmppMember("undefined-condition")] + UndefinedCondition, + + /// + /// The server received an unexpected request type. + /// + [XmppMember("unexpected-request")] + UnexpectedRequest } \ No newline at end of file diff --git a/XmppSharp/Protocol/Base/StanzaErrorType.cs b/XmppSharp/Protocol/Base/StanzaErrorType.cs index d670703..1677198 100644 --- a/XmppSharp/Protocol/Base/StanzaErrorType.cs +++ b/XmppSharp/Protocol/Base/StanzaErrorType.cs @@ -9,33 +9,33 @@ namespace XmppSharp.Protocol.Base; [XmppEnum] public enum StanzaErrorType { - /// - /// Indicates an authentication-related error. - /// - [XmppMember("auth")] - Auth, + /// + /// Indicates an authentication-related error. + /// + [XmppMember("auth")] + Auth, - /// - /// Signals that the recipient should cancel the current operation. - /// - [XmppMember("cancel")] - Cancel, + /// + /// Signals that the recipient should cancel the current operation. + /// + [XmppMember("cancel")] + Cancel, - /// - /// Instructs the recipient to continue with the operation, potentially with adjustments. - /// - [XmppMember("continue")] - Continue, + /// + /// Instructs the recipient to continue with the operation, potentially with adjustments. + /// + [XmppMember("continue")] + Continue, - /// - /// Requests the recipient to modify their request or behavior to proceed. - /// - [XmppMember("modify")] - Modify, + /// + /// Requests the recipient to modify their request or behavior to proceed. + /// + [XmppMember("modify")] + Modify, - /// - /// Suggests that the recipient try the operation again after a waiting period. - /// - [XmppMember("wait")] - Wait + /// + /// Suggests that the recipient try the operation again after a waiting period. + /// + [XmppMember("wait")] + Wait } \ No newline at end of file diff --git a/XmppSharp/Protocol/Base/StreamError.cs b/XmppSharp/Protocol/Base/StreamError.cs index aa31069..368783a 100644 --- a/XmppSharp/Protocol/Base/StreamError.cs +++ b/XmppSharp/Protocol/Base/StreamError.cs @@ -5,49 +5,49 @@ namespace XmppSharp.Protocol.Base; [XmppTag("stream:error", "http://etherx.jabber.org/streams")] public class StreamError : Element { - public StreamError() : base("stream:error", Namespace.Stream) - { - - } - - public StreamError(StreamErrorCondition condition, string text = default) : this() - { - Condition = condition; - - if (text != null) - Text = text; - } - - public StreamErrorCondition Condition - { - get - { - foreach (var (name, value) in XmppEnum.GetValues()) - { - if (HasTag(Namespace.Streams + name)) - return value; - } - - return StreamErrorCondition.UndefinedCondition; - } - set - { - RemoveTag(Namespace.Streams + Condition.ToXmppName()); - - if (XmppEnum.IsDefined(value)) - SetTag(Namespace.Streams + value.ToXmppName()); - } - } - - public string? Text - { - get => GetTag("text", Namespace.Streams); - set - { - if (value == null) - RemoveTag("text", Namespace.Streams); - else - SetTag("text", Namespace.Streams, value); - } - } + public StreamError() : base("stream:error", Namespace.Stream) + { + + } + + public StreamError(StreamErrorCondition condition, string text = default) : this() + { + this.Condition = condition; + + if (text != null) + this.Text = text; + } + + public StreamErrorCondition Condition + { + get + { + foreach (var (name, value) in XmppEnum.GetValues()) + { + if (this.HasTag(Namespace.Streams + name)) + return value; + } + + return StreamErrorCondition.UndefinedCondition; + } + set + { + this.RemoveTag(Namespace.Streams + this.Condition.ToXmppName()); + + if (XmppEnum.IsDefined(value)) + this.SetTag(Namespace.Streams + value.ToXmppName()); + } + } + + public string? Text + { + get => this.GetTag("text", Namespace.Streams); + set + { + if (value == null) + this.RemoveTag("text", Namespace.Streams); + else + this.SetTag("text", Namespace.Streams, value); + } + } } \ No newline at end of file diff --git a/XmppSharp/Protocol/Base/StreamErrorCondition.cs b/XmppSharp/Protocol/Base/StreamErrorCondition.cs index aa7fd89..fe6ecf5 100644 --- a/XmppSharp/Protocol/Base/StreamErrorCondition.cs +++ b/XmppSharp/Protocol/Base/StreamErrorCondition.cs @@ -8,153 +8,153 @@ namespace XmppSharp.Protocol.Base; [XmppEnum] public enum StreamErrorCondition { - /// - /// Indicates a malformed XML format in the received data. - /// - [XmppMember("bad-format")] - BadFormat, - - /// - /// Signals an invalid namespace prefix used in the communication. - /// - [XmppMember("bad-namespace-prefix")] - BadNamespacePrefix, - - /// - /// Represents a resource naming conflict. - /// - [XmppMember("conflict")] - Conflict, - - /// - /// Indicates a connection timeout during communication establishment. - /// - [XmppMember("connection-timeout")] - ConnectionTimeout, - - /// - /// Signals that the target host is no longer available. - /// - [XmppMember("host-gone")] - HostGone, - - /// - /// Indicates that the specified host cannot be found. - /// - [XmppMember("host-unknown")] - HostUnknown, - - /// - /// Represents an issue with message addressing within an XMPP stanza. - /// - [XmppMember("improper-addressing")] - ImproperAddressing, - - /// - /// Signals a generic server-side error. - /// - [XmppMember("internal-server-error")] - InternalServerError, - - /// - /// Indicates an invalid "from" attribute value in an XMPP stanza. - /// - [XmppMember("invalid-from")] - InvalidFrom, - - /// - /// Signals an invalid namespace used in the communication. - /// - [XmppMember("invalid-namespace")] - InvalidNamespace, - - /// - /// Indicates invalid XML content within the received data. - /// - [XmppMember("invalid-xml")] - InvalidXml, - - /// - /// Signals that the provided credentials are not authorized for the requested action. - /// - [XmppMember("not-authorized")] - NotAuthorized, - - /// - /// Indicates a malformed XML structure in the received data. - /// - [XmppMember("not-well-formed")] - NotWellFormed, - - /// - /// Signals a violation of a server policy by the client. - /// - [XmppMember("policy-violation")] - PolicyViolation, - - /// - /// Indicates a failure to establish a connection to a remote server. - /// - [XmppMember("remote-connection-failed")] - RemoteConnectionFailed, - - /// - /// Signals a server reset requiring reconnection. - /// - [XmppMember("reset")] - Reset, - - /// - /// Indicates insufficient server resources to handle the request. - /// - [XmppMember("resource-constraint")] - ResourceConstraint, - - /// - /// Signals that the XML content violates server restrictions. - /// - [XmppMember("restricted-xml")] - RestrictedXml, - - /// - /// Instructs the client to connect to a different host. - /// - [XmppMember("see-other-host")] - SeeOtherHost, - - /// - /// Signals a server shutdown in progress. - /// - [XmppMember("system-shutdown")] - SystemShutdown, - - /// - /// Represents an unspecified error condition. - /// - [XmppMember("undefined-condition")] - UndefinedCondition, - - /// - /// Signals that the server doesn't support the client's character encoding. - /// - [XmppMember("unsupported-encoding")] - UnsupportedEncoding, - - /// - /// Signals that the server doesn't support a requested feature. - /// - [XmppMember("unsupported-feature")] - UnsupportedFeature, - - /// - /// Signals that the server doesn't support a requested stanza. - /// - [XmppMember("unsupported-stanza-type")] - UnsupportedStanzaType, - - /// - /// Signals that the server doesn't support a requested version. - /// - [XmppMember("unsupported-version")] - UnsupportedVersion + /// + /// Indicates a malformed XML format in the received data. + /// + [XmppMember("bad-format")] + BadFormat, + + /// + /// Signals an invalid namespace prefix used in the communication. + /// + [XmppMember("bad-namespace-prefix")] + BadNamespacePrefix, + + /// + /// Represents a resource naming conflict. + /// + [XmppMember("conflict")] + Conflict, + + /// + /// Indicates a connection timeout during communication establishment. + /// + [XmppMember("connection-timeout")] + ConnectionTimeout, + + /// + /// Signals that the target host is no longer available. + /// + [XmppMember("host-gone")] + HostGone, + + /// + /// Indicates that the specified host cannot be found. + /// + [XmppMember("host-unknown")] + HostUnknown, + + /// + /// Represents an issue with message addressing within an XMPP stanza. + /// + [XmppMember("improper-addressing")] + ImproperAddressing, + + /// + /// Signals a generic server-side error. + /// + [XmppMember("internal-server-error")] + InternalServerError, + + /// + /// Indicates an invalid "from" attribute value in an XMPP stanza. + /// + [XmppMember("invalid-from")] + InvalidFrom, + + /// + /// Signals an invalid namespace used in the communication. + /// + [XmppMember("invalid-namespace")] + InvalidNamespace, + + /// + /// Indicates invalid XML content within the received data. + /// + [XmppMember("invalid-xml")] + InvalidXml, + + /// + /// Signals that the provided credentials are not authorized for the requested action. + /// + [XmppMember("not-authorized")] + NotAuthorized, + + /// + /// Indicates a malformed XML structure in the received data. + /// + [XmppMember("not-well-formed")] + NotWellFormed, + + /// + /// Signals a violation of a server policy by the client. + /// + [XmppMember("policy-violation")] + PolicyViolation, + + /// + /// Indicates a failure to establish a connection to a remote server. + /// + [XmppMember("remote-connection-failed")] + RemoteConnectionFailed, + + /// + /// Signals a server reset requiring reconnection. + /// + [XmppMember("reset")] + Reset, + + /// + /// Indicates insufficient server resources to handle the request. + /// + [XmppMember("resource-constraint")] + ResourceConstraint, + + /// + /// Signals that the XML content violates server restrictions. + /// + [XmppMember("restricted-xml")] + RestrictedXml, + + /// + /// Instructs the client to connect to a different host. + /// + [XmppMember("see-other-host")] + SeeOtherHost, + + /// + /// Signals a server shutdown in progress. + /// + [XmppMember("system-shutdown")] + SystemShutdown, + + /// + /// Represents an unspecified error condition. + /// + [XmppMember("undefined-condition")] + UndefinedCondition, + + /// + /// Signals that the server doesn't support the client's character encoding. + /// + [XmppMember("unsupported-encoding")] + UnsupportedEncoding, + + /// + /// Signals that the server doesn't support a requested feature. + /// + [XmppMember("unsupported-feature")] + UnsupportedFeature, + + /// + /// Signals that the server doesn't support a requested stanza. + /// + [XmppMember("unsupported-stanza-type")] + UnsupportedStanzaType, + + /// + /// Signals that the server doesn't support a requested version. + /// + [XmppMember("unsupported-version")] + UnsupportedVersion } \ No newline at end of file diff --git a/XmppSharp/Protocol/Base/StreamFeatures.cs b/XmppSharp/Protocol/Base/StreamFeatures.cs index 78dc575..2634a26 100644 --- a/XmppSharp/Protocol/Base/StreamFeatures.cs +++ b/XmppSharp/Protocol/Base/StreamFeatures.cs @@ -7,58 +7,58 @@ namespace XmppSharp.Protocol.Base; [XmppTag("stream:features", Namespace.Stream)] public class StreamFeatures : Element { - public StreamFeatures() : base("stream:features", Namespace.Stream) - { + public StreamFeatures() : base("stream:features", Namespace.Stream) + { - } + } - public Mechanisms Mechanisms - { - get => Child(); - set - { - RemoveTag("mechanisms", Namespace.Sasl); + public Mechanisms Mechanisms + { + get => this.Child(); + set + { + this.RemoveTag("mechanisms", Namespace.Sasl); - if (value != null) - AddChild(value); - } - } + if (value != null) + this.AddChild(value); + } + } - public StartTls StartTls - { - get => Child(); - set - { - RemoveTag("starttls", Namespace.Tls); + public StartTls StartTls + { + get => this.Child(); + set + { + this.RemoveTag("starttls", Namespace.Tls); - if (value != null) - AddChild(value); - } - } + if (value != null) + this.AddChild(value); + } + } - public bool SupportsStartTls - => HasTag("starttls", Namespace.Tls); + public bool SupportsStartTls + => this.HasTag("starttls", Namespace.Tls); - public bool SupportsAuthentication - => HasTag("mechanisms", Namespace.Sasl); + public bool SupportsAuthentication + => this.HasTag("mechanisms", Namespace.Sasl); - public bool SupportsBind - { - get => HasTag("bind", Namespace.Bind); - set - { - Action fn = !value ? RemoveTag : SetTag; - fn("bind", Namespace.Bind); - } - } + public bool SupportsBind + { + get => this.HasTag("bind", Namespace.Bind); + set + { + Action fn = !value ? this.RemoveTag : this.SetTag; + fn("bind", Namespace.Bind); + } + } - public bool SupportsSession - { - get => HasTag("session", Namespace.Session); - set - { - Action fn = !value ? RemoveTag : SetTag; - fn("session", Namespace.Session); - } - } + public bool SupportsSession + { + get => this.HasTag("session", Namespace.Session); + set + { + Action fn = !value ? this.RemoveTag : this.SetTag; + fn("session", Namespace.Session); + } + } } \ No newline at end of file diff --git a/XmppSharp/Protocol/Base/StreamStream.cs b/XmppSharp/Protocol/Base/StreamStream.cs index a57a9ca..d60ed11 100644 --- a/XmppSharp/Protocol/Base/StreamStream.cs +++ b/XmppSharp/Protocol/Base/StreamStream.cs @@ -5,26 +5,26 @@ namespace XmppSharp.Protocol.Base; [XmppTag("stream:stream", "http://etherx.jabber.org/streams")] public class StreamStream : Element { - public StreamStream() : base("stream:stream", Namespace.Stream) - { + public StreamStream() : base("stream:stream", Namespace.Stream) + { - } + } - public string Id - { - get => GetAttribute("id"); - set => SetAttribute("id", value); - } + public string Id + { + get => this.GetAttribute("id"); + set => this.SetAttribute("id", value); + } - public string Language - { - get => GetAttribute("xml:lang"); - set => SetAttribute("xml:lang", value); - } + public string Language + { + get => this.GetAttribute("xml:lang"); + set => this.SetAttribute("xml:lang", value); + } - public string Version - { - get => GetAttribute("version"); - set => SetAttribute("version", value); - } + public string Version + { + get => this.GetAttribute("version"); + set => this.SetAttribute("version", value); + } } diff --git a/XmppSharp/Protocol/Client/Bind.cs b/XmppSharp/Protocol/Client/Bind.cs index e22571f..bd6d739 100644 --- a/XmppSharp/Protocol/Client/Bind.cs +++ b/XmppSharp/Protocol/Client/Bind.cs @@ -5,46 +5,46 @@ namespace XmppSharp.Protocol.Client; [XmppTag("bind", Namespace.Bind)] public class Bind : Element { - public Bind() : base("bind", Namespace.Bind) - { - - } - - public Bind(string resource) : this() - => Resource = resource; - - public Bind(Jid jid) : this() - => Jid = jid; - - public string Resource - { - get => GetTag("resource"); - set - { - if (value == null) - RemoveTag("resource"); - else - SetTag("resource", value); - } - } - - public Jid Jid - { - get - { - var jid = GetTag("jid"); - - if (Jid.TryParse(jid, out var result)) - return result; - - return null; - } - set - { - if (value == null) - RemoveTag("jid"); - else - SetTag("jid", value.ToString()); - } - } + public Bind() : base("bind", Namespace.Bind) + { + + } + + public Bind(string resource) : this() + => this.Resource = resource; + + public Bind(Jid jid) : this() + => this.Jid = jid; + + public string Resource + { + get => this.GetTag("resource"); + set + { + if (value == null) + this.RemoveTag("resource"); + else + this.SetTag("resource", value); + } + } + + public Jid Jid + { + get + { + var jid = this.GetTag("jid"); + + if (Jid.TryParse(jid, out var result)) + return result; + + return null; + } + set + { + if (value == null) + this.RemoveTag("jid"); + else + this.SetTag("jid", value.ToString()); + } + } } diff --git a/XmppSharp/Protocol/Client/Session.cs b/XmppSharp/Protocol/Client/Session.cs index 99157a5..c18f8b5 100644 --- a/XmppSharp/Protocol/Client/Session.cs +++ b/XmppSharp/Protocol/Client/Session.cs @@ -5,8 +5,8 @@ namespace XmppSharp.Protocol.Client; [XmppTag("session", Namespace.Session)] public class Session : Element { - public Session() : base("session", Namespace.Session) - { + public Session() : base("session", Namespace.Session) + { - } + } } \ No newline at end of file diff --git a/XmppSharp/Protocol/Component/Handshake.cs b/XmppSharp/Protocol/Component/Handshake.cs index b201cb6..ea8d880 100644 --- a/XmppSharp/Protocol/Component/Handshake.cs +++ b/XmppSharp/Protocol/Component/Handshake.cs @@ -7,33 +7,33 @@ namespace XmppSharp.Protocol.Component; [XmppTag("handshake", Namespace.Connect)] public class Handshake : Element { - public Handshake() : base("handshake", Namespace.Accept) - { + public Handshake() : base("handshake", Namespace.Accept) + { - } + } - public Handshake(string streamId, string password) : this() - { - Value = GetAuthenticationHash(streamId, password); - } + public Handshake(string streamId, string password) : this() + { + this.Value = GetAuthenticationHash(streamId, password); + } - /// - /// Calculates the SHA-1 hash of the combined stream ID and password for authentication. - /// - /// The stream ID. - /// The password. - /// The hexadecimal string representation of the component handshake value. - public static string GetAuthenticationHash(string streamId, string pwd) - { - return SHA1.HashData(string.Concat(streamId, pwd).GetBytes()).ToHex(); - } + /// + /// Calculates the SHA-1 hash of the combined stream ID and password for authentication. + /// + /// The stream ID. + /// The password. + /// The hexadecimal string representation of the component handshake value. + public static string GetAuthenticationHash(string streamId, string pwd) + { + return SHA1.HashData(string.Concat(streamId, pwd).GetBytes()).ToHex(); + } - /// - /// Verifies if the handshake value matches the hash of the provided stream ID and password. - /// - /// The stream ID used for comparison. - /// The password used for comparison. - /// True if the handshake value matches the hash, false otherwise. - public bool HasAuthentication(string streamId, string password) - => Value == GetAuthenticationHash(streamId, password); + /// + /// Verifies if the handshake value matches the hash of the provided stream ID and password. + /// + /// The stream ID used for comparison. + /// The password used for comparison. + /// True if the handshake value matches the hash, false otherwise. + public bool HasAuthentication(string streamId, string password) + => this.Value == GetAuthenticationHash(streamId, password); } \ No newline at end of file diff --git a/XmppSharp/Protocol/DataForms/Field.cs b/XmppSharp/Protocol/DataForms/Field.cs index 7edef68..b7d0dc0 100644 --- a/XmppSharp/Protocol/DataForms/Field.cs +++ b/XmppSharp/Protocol/DataForms/Field.cs @@ -5,62 +5,62 @@ namespace XmppSharp.Protocol.DataForms; [XmppTag("field", Namespace.DataForms)] public class Field : Element { - public Field() : base("field", Namespace.DataForms) - { + public Field() : base("field", Namespace.DataForms) + { - } + } - public string? Name - { - get => GetAttribute("var"); - set => SetAttribute("var", value); - } + public string? Name + { + get => this.GetAttribute("var"); + set => this.SetAttribute("var", value); + } - public FieldType Type - { - get => XmppEnum.ParseOrDefault(GetAttribute("type"), FieldType.TextSingle); - set => SetAttribute("type", value.ToXmppName()); - } + public FieldType Type + { + get => XmppEnum.ParseOrDefault(this.GetAttribute("type"), FieldType.TextSingle); + set => this.SetAttribute("type", value.ToXmppName()); + } - public string? Label - { - get => GetAttribute("label"); - set => SetAttribute("label", value); - } + public string? Label + { + get => this.GetAttribute("label"); + set => this.SetAttribute("label", value); + } - public string? Description - { - get => GetTag("desc"); - set => SetTag("desc", value); - } + public string? Description + { + get => this.GetTag("desc"); + set => this.SetTag("desc", value); + } - public bool IsRequired - { - get => HasTag("required"); - set - { - if (!value) - RemoveTag("required"); - else - SetTag("required"); - } - } + public bool IsRequired + { + get => this.HasTag("required"); + set + { + if (!value) + this.RemoveTag("required"); + else + this.SetTag("required"); + } + } - public new string Value - { - get => GetTag("value"); - set => SetTag("value", value); - } + public new string Value + { + get => this.GetTag("value"); + set => this.SetTag("value", value); + } - public IEnumerable"; - - public static XmlQualifiedName ExtractQualifiedName(string source) - { - Require.NotNullOrWhiteSpace(source); - - var ofs = source.IndexOf(':'); - - string prefix = default; - - if (ofs != -1) - prefix = source[0..ofs]; - - var localName = source[(ofs + 1)..]; - - return new() - { - LocalName = localName, - Prefix = prefix - }; - } - - internal static XmlWriter CreateWriter(bool indented, StringBuilder output, char indentChar, int indentSize) - { - Require.NotNull(output); - - var settings = new XmlWriterSettings - { - Indent = indented, - IndentChars = new(indentChar, indentSize), - CheckCharacters = true, - CloseOutput = true, - ConformanceLevel = ConformanceLevel.Fragment, - Encoding = Encoding.UTF8, - NamespaceHandling = NamespaceHandling.OmitDuplicates, - OmitXmlDeclaration = true, - NewLineChars = "\n" - }; - - return XmlWriter.Create(new StringWriter(output), settings); - } - - public static void Remove(this IEnumerable source) where T : Node - { - if (source is IList list) - { - foreach (var item in list) - item.Remove(); - } - else - { - foreach (var item in source) - item.Remove(); - } - } + public const string XmppStreamEnd = ""; + + public static XmlQualifiedName ExtractQualifiedName(string source) + { + Require.NotNullOrWhiteSpace(source); + + var ofs = source.IndexOf(':'); + + string prefix = default; + + if (ofs != -1) + prefix = source[0..ofs]; + + var localName = source[(ofs + 1)..]; + + return new() + { + LocalName = localName, + Prefix = prefix + }; + } + + internal static XmlWriter CreateWriter(bool indented, StringBuilder output, char indentChar, int indentSize) + { + Require.NotNull(output); + + var settings = new XmlWriterSettings + { + Indent = indented, + IndentChars = new(indentChar, indentSize), + CheckCharacters = true, + CloseOutput = true, + ConformanceLevel = ConformanceLevel.Fragment, + Encoding = Encoding.UTF8, + NamespaceHandling = NamespaceHandling.OmitDuplicates, + OmitXmlDeclaration = true, + NewLineChars = "\n" + }; + + return XmlWriter.Create(new StringWriter(output), settings); + } + + public static void Remove(this IEnumerable source) where T : Node + { + Require.NotNull(source); + + if (source is IList list) + { + foreach (var item in list) + item.Remove(); + } + else + { + foreach (var item in source) + item.Remove(); + } + } + + public static Element C(this Element parent, string qualifiedName, string namespaceURI = default, object value = default) + { + Require.NotNull(parent); + Require.NotNullOrWhiteSpace(qualifiedName); + + var result = new Element(qualifiedName, namespaceURI); + + if (value != null) + result.Value = Convert.ToString(value, CultureInfo.InvariantCulture); + + parent.AddChild(result); + + return result; + } + + public static Element C(this Element parent, string qualifiedName, in Dictionary attributes, object value = default) + { + Require.NotNull(parent); + Require.NotNullOrWhiteSpace(qualifiedName); + Require.NotNull(attributes); + + var child = new Element(qualifiedName); + + if (value != null) + child.Value = Convert.ToString(value, CultureInfo.InvariantCulture); + + foreach (var (attName, attVal) in attributes) + child.SetAttribute(attName, attVal); + + return child; + } + + public static T C(this T parent, Element child) where T : Element + { + Require.NotNull(parent); + Require.NotNull(child); + + parent.AddChild(child); + + return parent; + } + + public static T C(this T parent, Func factory) where T : Element + { + Require.NotNull(parent); + Require.NotNull(factory); + + var child = factory(parent); + + if (child != null) + parent.AddChild(child); + + return parent; + } + + public static async Task C(this T parent, AsyncFunc factory) where T : Element + { + Require.NotNull(parent); + Require.NotNull(factory); + + var child = await factory(parent); + + if (child != null) + parent.AddChild(child); + + return parent; + } + + public static T SetAttributeValue(this T parent, string name, object? rawValue, string? format = default, IFormatProvider? ifp = default) + where T : Element + { + Require.NotNull(parent); + Require.NotNullOrWhiteSpace(name); + + ifp ??= CultureInfo.InvariantCulture; + + string result; + + if (rawValue is null) + result = string.Empty; + else if (rawValue is IFormattable fmt) + result = fmt.ToString(format, ifp); + else if (rawValue is IConvertible conv) + result = conv.ToString(ifp); + else + result = rawValue.ToString(); + + parent.SetAttribute(name, result); + + return parent; + } + + public static TEnum GetAttributeEnum(this Element element, string name, TEnum defaultValue = default, bool isNumber = false, bool ignoreCase = true, IFormatProvider ifp = default) + where TEnum : struct, Enum + { + Require.NotNull(element); + Require.NotNullOrWhiteSpace(name); + + ifp ??= CultureInfo.InvariantCulture; + + var temp = element.GetAttribute(name); + + if (isNumber) + { + var baseType = Enum.GetUnderlyingType(typeof(TEnum)); + return (TEnum)Convert.ChangeType(temp, baseType, ifp); + } + else + { + if (Enum.TryParse(temp, ignoreCase, out var result)) + return result; + } + + return defaultValue; + } + + public static T SetAttributeEnum(this T element, string name, TEnum? value, bool isNumber = false, string? format = default, IFormatProvider? ifp = default) + where T : Element + where TEnum : struct, Enum + { + Require.NotNull(element); + Require.NotNullOrWhiteSpace(name); + + var rawValue = value ?? default; + + if (!isNumber) + return element.SetAttributeValue(name, Convert.ToString(rawValue, ifp)); + else + { + var attrVal = Convert.ChangeType(rawValue, Enum.GetUnderlyingType(typeof(TEnum))); + return element.SetAttributeValue(name, attrVal, format, ifp); + } + } + + public static T SetAttributeValue(this T parent, string name, TStruct? value, string? format = default, IFormatProvider? ifp = default) + where T : Element + where TStruct : struct + { + Require.NotNull(parent); + Require.NotNullOrWhiteSpace(name); + + return parent.SetAttributeValue(name, value ?? default, format, ifp); + } + +#if NET7_0_OR_GREATER + + public static T GetAttributeValue(this Element parent, string name, T defaultValue, IFormatProvider? ifp = default) where T : IParsable + { + Require.NotNull(parent); + Require.NotNullOrWhiteSpace(name); + + var rawValue = parent.GetAttribute(name); + + if (rawValue == null) + return defaultValue; + + if (!T.TryParse(rawValue, ifp, out var result)) + result = defaultValue; + + return result; + } + +#endif + + public static T GetAttributeValue(this Element parent, string name, TryParseDelegate? converter, T defaultValue = default) + { + Require.NotNull(parent); + Require.NotNullOrWhiteSpace(name); + + converter ??= TryParseHelpers.GetConverter(typeof(T)) as TryParseDelegate; + Require.NotNull(converter); + + var value = parent.GetAttribute(name); + + if (value == null) + return defaultValue; + + if (!converter(value, out var result)) + result = defaultValue; + + return result; + } } \ No newline at end of file diff --git a/XmppSharp/XmlTagInfo.cs b/XmppSharp/XmlTagInfo.cs index 42566cc..142d84b 100644 --- a/XmppSharp/XmlTagInfo.cs +++ b/XmppSharp/XmlTagInfo.cs @@ -2,42 +2,42 @@ public readonly record struct XmlTagInfo : IEquatable { - private readonly string _localName; - private readonly string _namespaceURI; - - public XmlTagInfo(string localName, string namespaceURI = default) - { - _localName = localName; - _namespaceURI = namespaceURI ?? string.Empty; - } - - public string LocalName - { - get => _localName; - init => _localName = value; - } - - public string NamespaceURI - { - get => _namespaceURI; - init => _namespaceURI = value ?? string.Empty; - } - - public readonly void Deconstruct(out string localName, out string? namespaceURI) - { - localName = _localName; - namespaceURI = _namespaceURI; - } - - public readonly override int GetHashCode() - => HashCode.Combine(LocalName, NamespaceURI); - - public readonly bool Equals(XmlTagInfo other) - { - return LocalName.Equals(other.LocalName, StringComparison.Ordinal) - && NamespaceURI.Equals(other.NamespaceURI, StringComparison.Ordinal); - } - - public static IEqualityComparer Comparer { get; } - = EqualityComparer.Default; + private readonly string _localName; + private readonly string _namespaceURI; + + public XmlTagInfo(string localName, string namespaceURI = default) + { + this._localName = localName; + this._namespaceURI = namespaceURI ?? string.Empty; + } + + public string LocalName + { + get => this._localName; + init => this._localName = value; + } + + public string NamespaceURI + { + get => this._namespaceURI; + init => this._namespaceURI = value ?? string.Empty; + } + + public readonly void Deconstruct(out string localName, out string? namespaceURI) + { + localName = this._localName; + namespaceURI = this._namespaceURI; + } + + public readonly override int GetHashCode() + => HashCode.Combine(this.LocalName, this.NamespaceURI); + + public readonly bool Equals(XmlTagInfo other) + { + return this.LocalName.Equals(other.LocalName, StringComparison.Ordinal) + && this.NamespaceURI.Equals(other.NamespaceURI, StringComparison.Ordinal); + } + + public static IEqualityComparer Comparer { get; } + = EqualityComparer.Default; } diff --git a/XmppSharp/XmppEnum.cs b/XmppSharp/XmppEnum.cs index 5404f58..9129dfe 100644 --- a/XmppSharp/XmppEnum.cs +++ b/XmppSharp/XmppEnum.cs @@ -6,170 +6,185 @@ namespace XmppSharp; public static class XmppEnum { - [StackTraceHidden] - static void ThrowIfNotXmppEnum() - { - if (!IsValidType()) - throw new XmppEnumException($"Type '{typeof(T).FullName}' is not valid xmpp enum!"); - } - - /// - /// Determines whether the given type is a valid XMPP enum type. - /// - public static bool IsValidType() - => typeof(T).GetCustomAttributes().Any(); - - public static bool IsDefined(T value) where T : struct, Enum - { - IsValidType(); - - foreach (var (_, other) in XmppEnum.Values) - { - if (XmppEnum.Comparer.Equals(other, value)) - return true; - } - - return false; - } - - /// - /// Gets all XMPP names mapped to the given type. - /// - /// Enum type annotated with - public static IEnumerable GetNames() where T : struct, Enum - { - ThrowIfNotXmppEnum(); - return XmppEnum.Values.Select(x => x.Key); - } - - /// - /// Gets the mapping of all names and values mapped to the given type. - /// - /// Enum type annotated with - public static IReadOnlyDictionary GetValues() where T : struct, Enum - { - ThrowIfNotXmppEnum(); - return XmppEnum.Values; - } - - /// - /// Converts the value of the given enum to the XMPP name. - /// - /// Enum type annotated with - /// Enum value that will be mapped. - public static string? ToXmppName(this T value) where T : struct, Enum - { - ThrowIfNotXmppEnum(); - - if (!IsDefined(value)) - return null; - - return XmppEnum.ToXml(value); - } - - /// - /// Parses the given string into the target XMPP enum, or if no match is found. - /// - /// Enum type annotated with - /// String that will be mapped. - public static T? Parse(string value) where T : struct, Enum - { - ThrowIfNotXmppEnum(); - return XmppEnum.Parse(value); - } - - /// - /// Parses the given string into the target XMPP enum, or returns the provided fallback value if none is match. - /// - /// Enum type annotated with - /// String that will be mapped. - /// Fallback value in case no match is found. - public static T ParseOrDefault(string value, T defaultValue) where T : struct, Enum - { - ThrowIfNotXmppEnum(); - return XmppEnum.ParseOrDefault(value, defaultValue); - } - - /// - /// Parses the string provided in the destination XMPP enum, or throws an exception if none is match - /// - /// Enum type annotated with - /// String that will be mapped. - public static T ParseOrThrow(string value) where T : struct, Enum - { - ThrowIfNotXmppEnum(); - return XmppEnum.ParseOrThrow(value); - } + [StackTraceHidden] + static void ThrowIfNotXmppEnum() + { + if (!IsValidType()) + throw new XmppEnumException($"Type '{typeof(T).FullName}' is not valid xmpp enum!"); + } + + /// + /// Determines whether the given type is a valid XMPP enum type. + /// + public static bool IsValidType() + => typeof(T).GetCustomAttributes().Any(); + + public static bool IsDefined(T value) where T : struct, Enum + { + ThrowIfNotXmppEnum(); + + foreach (var (_, other) in XmppEnum.Values) + { + if (XmppEnum.Comparer.Equals(other, value)) + return true; + } + + return false; + } + + /// + /// Gets all XMPP names mapped to the given type. + /// + /// Enum type annotated with + public static IEnumerable GetNames() where T : struct, Enum + { + ThrowIfNotXmppEnum(); + return XmppEnum.Values.Select(x => x.Key); + } + + /// + /// Gets the mapping of all names and values mapped to the given type. + /// + /// Enum type annotated with + public static IReadOnlyDictionary GetValues() where T : struct, Enum + { + ThrowIfNotXmppEnum(); + return XmppEnum.Values; + } + + /// + /// Converts the value of the given enum to the XMPP name. + /// + /// Enum type annotated with + /// Enum value that will be mapped. + public static string? ToXmppName(this T value) where T : struct, Enum + { + ThrowIfNotXmppEnum(); + + if (!IsDefined(value)) + return null; + + return XmppEnum.ToXml(value); + } + + /// + /// Parses the given string into the target XMPP enum, or if no match is found. + /// + /// Enum type annotated with + /// String that will be mapped. + public static T? Parse(string? value) where T : struct, Enum + { + ThrowIfNotXmppEnum(); + + if (string.IsNullOrWhiteSpace(value)) + return default; + + return XmppEnum.Parse(value); + } + + /// + /// Parses the given string into the target XMPP enum, or returns the provided fallback value if none is match. + /// + /// Enum type annotated with + /// String that will be mapped. + /// Fallback value in case no match is found. + public static T ParseOrDefault(string? value, T defaultValue) where T : struct, Enum + { + ThrowIfNotXmppEnum(); + + if (string.IsNullOrWhiteSpace(value)) + return defaultValue; + + return XmppEnum.ParseOrDefault(value, defaultValue); + } + + public static bool TryParse(string? value, out T result) where T : struct, Enum + { + var temp = Parse(value); + result = temp.Value; + return temp.HasValue; + } + + /// + /// Parses the string provided in the destination XMPP enum, or throws an exception if none is match + /// + /// Enum type annotated with + /// String that will be mapped. + public static T ParseOrThrow(string value) where T : struct, Enum + { + ThrowIfNotXmppEnum(); + return XmppEnum.ParseOrThrow(value); + } } internal class XmppEnum - where T : struct, Enum + where T : struct, Enum { - public static IReadOnlyDictionary Values { get; set; } - public static EqualityComparer Comparer { get; } = EqualityComparer.Default; - - public static string? ToXml(T value) - { - foreach (var (name, self) in Values) - { - if (Comparer.Equals(self, value)) - return name; - } - - return default; - } - - public static T? Parse(string value) - { - foreach (var (name, self) in Values) - { - if (name == value) - return self; - } - - return default; - } - - public static T ParseOrDefault(string value, T defaultValue) - { - foreach (var (name, self) in Values) - { - if (name == value) - return self; - } - - return defaultValue; - } - - public static T ParseOrThrow(string value) - { - foreach (var (name, self) in Values) - { - if (name == value) - return self; - } - - throw new XmppEnumException($"This xmpp enum of type {typeof(T).FullName} does not contain a member that matches the value '{value}'"); - } - - static XmppEnum() - { - var baseType = typeof(T); - - var values = new Dictionary(); - - foreach (var member in Enum.GetNames()) - { - var field = baseType.GetField(member); - - var name = field.GetCustomAttribute()?.Name; - - if (name == null) - continue; - - values[name] = (T)field.GetValue(null); - } - - Values = values; - } + public static IReadOnlyDictionary Values { get; set; } + public static EqualityComparer Comparer { get; } = EqualityComparer.Default; + + public static string? ToXml(T value) + { + foreach (var (name, self) in Values) + { + if (Comparer.Equals(self, value)) + return name; + } + + return default; + } + + public static T? Parse(string value) + { + foreach (var (name, self) in Values) + { + if (name == value) + return self; + } + + return default; + } + + public static T ParseOrDefault(string value, T defaultValue) + { + foreach (var (name, self) in Values) + { + if (name == value) + return self; + } + + return defaultValue; + } + + public static T ParseOrThrow(string value) + { + foreach (var (name, self) in Values) + { + if (name == value) + return self; + } + + throw new XmppEnumException($"This xmpp enum of type {typeof(T).FullName} does not contain a member that matches the value '{value}'"); + } + + static XmppEnum() + { + var baseType = typeof(T); + + var values = new Dictionary(); + + foreach (var member in Enum.GetNames()) + { + var field = baseType.GetField(member); + + var name = field.GetCustomAttribute()?.Name; + + if (name == null) + continue; + + values[name] = (T)field.GetValue(null); + } + + Values = values; + } } \ No newline at end of file diff --git a/XmppSharp/XmppEnumException.cs b/XmppSharp/XmppEnumException.cs index aa1f8c5..69f1416 100644 --- a/XmppSharp/XmppEnumException.cs +++ b/XmppSharp/XmppEnumException.cs @@ -3,15 +3,15 @@ /// public class XmppEnumException : Exception { - /// - internal XmppEnumException() : base() - { + /// + internal XmppEnumException() : base() + { - } + } - /// - internal XmppEnumException(string? message) : base(message) - { + /// + internal XmppEnumException(string? message) : base(message) + { - } + } } diff --git a/XmppSharp/XmppParser.cs b/XmppSharp/XmppParser.cs index c022283..7a9b596 100644 --- a/XmppSharp/XmppParser.cs +++ b/XmppSharp/XmppParser.cs @@ -19,238 +19,238 @@ namespace XmppSharp; public sealed class XmppParser : IDisposable { - private XmlReader _reader; - private NameTable _nameTable = new(); - private volatile bool _disposed; - - private readonly Encoding _encoding; - private readonly int _bufferSize; - - public const int DefaultBufferSize = 256; - - public XmppParser(Encoding? encoding = default, int bufferSize = DefaultBufferSize) - { - _encoding = encoding ?? Encoding.UTF8; - _bufferSize = bufferSize <= 0 ? DefaultBufferSize : bufferSize; - } - - /// - /// The event is triggered when the XMPP open tag is found <stream:stream> - /// - public event AsyncAction OnStreamStart; - - /// - /// The event is triggered when any well-formed element is found. - /// However, if the XML tag is registered using the parser will automatically construct the element in the registered type. - /// Elements that cannot be constructed using only return element as base type of . - /// - public event AsyncAction OnStreamElement; - - /// - /// The event is triggered when the XMPP close tag is found </stream:stream> - /// - public event AsyncAction OnStreamEnd; - - /// - public void Dispose() - { - if (_disposed) - return; - - _disposed = true; - - _reader?.Dispose(); - _nameTable = null; - } + private XmlReader _reader; + private NameTable _nameTable = new(); + private volatile bool _disposed; + + private readonly Encoding _encoding; + private readonly int _bufferSize; + + public const int DefaultBufferSize = 256; + + public XmppParser(Encoding? encoding = default, int bufferSize = DefaultBufferSize) + { + this._encoding = encoding ?? Encoding.UTF8; + this._bufferSize = bufferSize <= 0 ? DefaultBufferSize : bufferSize; + } + + /// + /// The event is triggered when the XMPP open tag is found <stream:stream> + /// + public event AsyncAction OnStreamStart; + + /// + /// The event is triggered when any well-formed element is found. + /// However, if the XML tag is registered using the parser will automatically construct the element in the registered type. + /// Elements that cannot be constructed using only return element as base type of . + /// + public event AsyncAction OnStreamElement; + + /// + /// The event is triggered when the XMPP close tag is found </stream:stream> + /// + public event AsyncAction OnStreamEnd; + + /// + public void Dispose() + { + if (this._disposed) + return; + + this._disposed = true; + + this._reader?.Dispose(); + this._nameTable = null; + } #if !NET7_0_OR_GREATER - internal class ThrowingResolver : XmlResolver - { - public static ThrowingResolver Shared { get; } = new(); + internal class ThrowingResolver : XmlResolver + { + public static ThrowingResolver Shared { get; } = new(); - public override object? GetEntity(Uri absoluteUri, string? role, Type? ofObjectToReturn) - { - throw new NotSupportedException($"Unable to resolve XML entity: {absoluteUri} ({role})"); - } - } + public override object? GetEntity(Uri absoluteUri, string? role, Type? ofObjectToReturn) + { + throw new NotSupportedException($"Unable to resolve XML entity: {absoluteUri} ({role})"); + } + } #endif - /// - /// Restarts the internal state of the XML parser. - /// - /// Stream that will be used to read the characters. - /// If this instance of has already been disposed. - public void Reset(Stream stream) - { - _reader?.Dispose(); + /// + /// Restarts the internal state of the XML parser. + /// + /// Stream that will be used to read the characters. + /// If this instance of has already been disposed. + public void Reset(Stream stream) + { + this._reader?.Dispose(); #if NET7_0_OR_GREATER - ObjectDisposedException.ThrowIf(_disposed, this); + ObjectDisposedException.ThrowIf(this._disposed, this); #else - if (_disposed) - throw new ObjectDisposedException(GetType().FullName); + if (this._disposed) + throw new ObjectDisposedException(this.GetType().FullName); #endif - _reader = XmlReader.Create(new StreamReader(stream, _encoding, false, _bufferSize, true), new() - { - CloseInput = true, - Async = true, - IgnoreProcessingInstructions = true, - IgnoreWhitespace = true, - ConformanceLevel = ConformanceLevel.Fragment, - DtdProcessing = DtdProcessing.Prohibit, + this._reader = XmlReader.Create(new StreamReader(stream, this._encoding, false, this._bufferSize, true), new() + { + CloseInput = true, + Async = true, + IgnoreProcessingInstructions = true, + IgnoreWhitespace = true, + ConformanceLevel = ConformanceLevel.Fragment, + DtdProcessing = DtdProcessing.Prohibit, #if NET7_0_OR_GREATER - XmlResolver = XmlResolver.ThrowingResolver, + XmlResolver = XmlResolver.ThrowingResolver, #else - XmlResolver = ThrowingResolver.Shared, + XmlResolver = ThrowingResolver.Shared, #endif - ValidationFlags = XmlSchemaValidationFlags.AllowXmlAttributes, - NameTable = _nameTable - }); - } - - private Element _rootElem; - - /// - /// Gets the XML element in current scope. - /// - public Element? CurrentElement - => _rootElem; - - /// - /// Gets the XML depth in the parser tree. - /// - public int Depth - { - get - { - if (_disposed) - return 0; - - return _reader?.Depth ?? 0; - } - } - - public async Task Advance() - { - if (_disposed) - return false; - - if (_reader == null || _reader != null && _reader.EOF) - return false; - - bool result; - - try - { - result = await _reader.ReadAsync(); - } - catch (XmlException e) - { - throw new JabberStreamException(StreamErrorCondition.InvalidXml, e); - } - - if (result) - { - switch (_reader.NodeType) - { - case XmlNodeType.Element: - { - Element currentElem; - - if (_reader.Name != "stream:stream") - { - var ns = _reader.NamespaceURI; - - if (string.IsNullOrEmpty(ns) && _reader.LocalName is "iq" or "message" or "presence") - ns = "jabber:client"; - - currentElem = ElementFactory.Create(_reader.Name, ns); - } - else - currentElem = new StreamStream(); - - if (_reader.HasAttributes) - { - while (_reader.MoveToNextAttribute()) - currentElem.SetAttribute(_reader.Name, _reader.Value); - - _reader.MoveToElement(); - } - - if (_reader.Name == "stream:stream") - { - if (_reader.NamespaceURI != Namespace.Stream) - throw new JabberStreamException(StreamErrorCondition.InvalidNamespace); - - await OnStreamStart.InvokeAsync((StreamStream)currentElem); - } - else - { - if (_reader.IsEmptyElement) - { - if (_rootElem != null) - _rootElem.AddChild(currentElem); - else - await OnStreamElement.InvokeAsync(currentElem); - } - else - { - _rootElem?.AddChild(currentElem); - _rootElem = currentElem; - } - } - } - break; - - case XmlNodeType.EndElement: - { - if (_reader.Name == "stream:stream") - await OnStreamEnd.InvokeAsync(); - else - { - if (_rootElem == null) - throw new JabberStreamException(StreamErrorCondition.InvalidXml, "The element in the current scope was not expected to be null."); - - var parent = _rootElem.Parent; - - if (parent == null) - await OnStreamElement.InvokeAsync(_rootElem); - - _rootElem = parent; - } - } - break; - - case XmlNodeType.SignificantWhitespace: - case XmlNodeType.Text: - { - if (_rootElem != null) - { - if (_rootElem.LastNode is Text text) - text.Value += _reader.Value; - else - _rootElem.AddChild(new Text(_reader.Value)); - } - } - break; - - case XmlNodeType.Comment: - _rootElem?.AddChild(new Comment(_reader.Value)); - break; - - case XmlNodeType.CDATA: - _rootElem?.AddChild(new Cdata(_reader.Value)); - break; - - default: - break; - } - - return true; - } - - return false; - } + ValidationFlags = XmlSchemaValidationFlags.AllowXmlAttributes, + NameTable = this._nameTable + }); + } + + private Element _rootElem; + + /// + /// Gets the XML element in current scope. + /// + public Element? CurrentElement + => this._rootElem; + + /// + /// Gets the XML depth in the parser tree. + /// + public int Depth + { + get + { + if (this._disposed) + return 0; + + return this._reader?.Depth ?? 0; + } + } + + public async Task Advance() + { + if (this._disposed) + return false; + + if (this._reader == null || this._reader != null && this._reader.EOF) + return false; + + bool result; + + try + { + result = await this._reader.ReadAsync(); + } + catch (XmlException e) + { + throw new JabberStreamException(StreamErrorCondition.InvalidXml, e); + } + + if (result) + { + switch (this._reader.NodeType) + { + case XmlNodeType.Element: + { + Element currentElem; + + if (this._reader.Name != "stream:stream") + { + var ns = this._reader.NamespaceURI; + + if (string.IsNullOrEmpty(ns) && this._reader.LocalName is "iq" or "message" or "presence") + ns = "jabber:client"; + + currentElem = ElementFactory.Create(this._reader.Name, ns); + } + else + currentElem = new StreamStream(); + + if (this._reader.HasAttributes) + { + while (this._reader.MoveToNextAttribute()) + currentElem.SetAttribute(this._reader.Name, this._reader.Value); + + this._reader.MoveToElement(); + } + + if (this._reader.Name == "stream:stream") + { + if (this._reader.NamespaceURI != Namespace.Stream) + throw new JabberStreamException(StreamErrorCondition.InvalidNamespace); + + await OnStreamStart.InvokeAsync((StreamStream)currentElem); + } + else + { + if (this._reader.IsEmptyElement) + { + if (this._rootElem != null) + this._rootElem.AddChild(currentElem); + else + await OnStreamElement.InvokeAsync(currentElem); + } + else + { + this._rootElem?.AddChild(currentElem); + this._rootElem = currentElem; + } + } + } + break; + + case XmlNodeType.EndElement: + { + if (this._reader.Name == "stream:stream") + await OnStreamEnd.InvokeAsync(); + else + { + if (this._rootElem == null) + throw new JabberStreamException(StreamErrorCondition.InvalidXml, "The element in the current scope was not expected to be null."); + + var parent = this._rootElem.Parent; + + if (parent == null) + await OnStreamElement.InvokeAsync(this._rootElem); + + this._rootElem = parent; + } + } + break; + + case XmlNodeType.SignificantWhitespace: + case XmlNodeType.Text: + { + if (this._rootElem != null) + { + if (this._rootElem.LastNode is Text text) + text.Value += this._reader.Value; + else + this._rootElem.AddChild(new Text(this._reader.Value)); + } + } + break; + + case XmlNodeType.Comment: + this._rootElem?.AddChild(new Comment(this._reader.Value)); + break; + + case XmlNodeType.CDATA: + this._rootElem?.AddChild(new Cdata(this._reader.Value)); + break; + + default: + break; + } + + return true; + } + + return false; + } } diff --git a/XmppSharp/XmppSharp.csproj b/XmppSharp/XmppSharp.csproj index 9410dc0..bd170b1 100644 --- a/XmppSharp/XmppSharp.csproj +++ b/XmppSharp/XmppSharp.csproj @@ -3,7 +3,6 @@ enable annotations - CA2255;CS1998;CS1591;IDE0028 net6.0;net7.0;net8.0 @@ -27,7 +26,7 @@ - 3.0.0 + 3.1.0 true nathan130200 CHANGELOG.md