From ec85974968b035d54252c18b7b412389a69f85c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janno=20P=C3=B5ldma?= Date: Tue, 5 Jan 2021 15:29:53 +0200 Subject: [PATCH] Fix serialization of qualified attribute values --- RELEASE_NOTES.md | 4 + .../Extensions/XmlWriterExtensions.cs | 69 ++++++------ .../XRoadLib.Tests/XmlWriterExtensionsTest.cs | 106 ++++++++++++++++++ 3 files changed, 145 insertions(+), 34 deletions(-) create mode 100644 test/XRoadLib.Tests/XmlWriterExtensionsTest.cs diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 82441887..b2c1a53d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +#### 1.4.2 - 05.01.2021 + +* Remove usage of Synchronous API in serialization of qualified attribute values. + #### 1.4.1 - 13.12.2020 * Review UTF8 encoding usages to prevent BOM in outgoing messages. diff --git a/src/XRoadLib/Extensions/XmlWriterExtensions.cs b/src/XRoadLib/Extensions/XmlWriterExtensions.cs index f01580a0..2a413511 100644 --- a/src/XRoadLib/Extensions/XmlWriterExtensions.cs +++ b/src/XRoadLib/Extensions/XmlWriterExtensions.cs @@ -19,62 +19,65 @@ public static class XmlWriterExtensions /// /// Serializes attribute with qualified name content. /// - public static async Task WriteQualifiedAttributeAsync(this XmlWriter writer, string name, XmlQualifiedName qualifiedName) + public static Task WriteQualifiedAttributeAsync(this XmlWriter writer, string name, XmlQualifiedName qualifiedName) { if (qualifiedName == null || qualifiedName.IsEmpty) - return; + return Task.CompletedTask; + + var qualifiedValue = writer.GetQualifiedValue(qualifiedName.Name, qualifiedName.Namespace); - writer.WriteStartAttribute(name); - await writer.WriteQualifiedNameAsync(qualifiedName.Name, qualifiedName.Namespace).ConfigureAwait(false); - writer.WriteEndAttribute(); + return writer.WriteAttributeStringAsync(null, name, null, qualifiedValue); } - private static async Task WriteQualifiedAttributeAsync(this XmlWriter writer, string attributeName, string attributeNamespace, string valueName, string valueNamespace) + private static string GetQualifiedValue(this XmlWriter writer, string localName, string ns) { - writer.WriteStartAttribute(attributeName, attributeNamespace); - await writer.WriteQualifiedNameAsync(valueName, valueNamespace).ConfigureAwait(false); - writer.WriteEndAttribute(); + if (string.IsNullOrEmpty(ns)) + return localName; + + var prefix = writer.LookupPrefix(ns); + if (prefix == null) + throw new ArgumentException(nameof(ns), $"Undefined namespace: {ns}"); + + return $"{prefix}:{localName}"; + } + + private static Task WriteQualifiedAttributeAsync(this XmlWriter writer, string attributeName, string attributeNamespace, string valueName, string valueNamespace) + { + var attributePrefix = writer.LookupPrefix(attributeNamespace); + var qualifiedValue = writer.GetQualifiedValue(valueName, valueNamespace); + return writer.WriteAttributeStringAsync(attributePrefix, attributeName, attributeNamespace, qualifiedValue); } /// /// Serializes xsi:type attribute. /// - public static Task WriteTypeAttributeAsync(this XmlWriter writer, string typeName, string typeNamespace) - { - return writer.WriteQualifiedAttributeAsync(QnXsiType.Name, QnXsiType.Namespace, typeName, typeNamespace); - } + public static Task WriteTypeAttributeAsync(this XmlWriter writer, string typeName, string typeNamespace) => + writer.WriteQualifiedAttributeAsync(QnXsiType.Name, QnXsiType.Namespace, typeName, typeNamespace); /// /// Serializes xsi:type attribute. /// - public static Task WriteTypeAttributeAsync(this XmlWriter writer, XName qualifiedName, string ns = null) - { - return writer.WriteTypeAttributeAsync(qualifiedName.LocalName, ns ?? qualifiedName.NamespaceName); - } + public static Task WriteTypeAttributeAsync(this XmlWriter writer, XName qualifiedName, string ns = null) => + writer.WriteTypeAttributeAsync(qualifiedName.LocalName, ns ?? qualifiedName.NamespaceName); - private static async Task WriteArrayTypeAttributeAsync(this XmlWriter writer, string typeName, string typeNamespace, int arraySize) + private static Task WriteArrayTypeAttributeAsync(this XmlWriter writer, string typeName, string typeNamespace, int arraySize) { - writer.WriteStartAttribute("arrayType", NamespaceConstants.SoapEnc); - await writer.WriteQualifiedNameAsync(typeName, typeNamespace).ConfigureAwait(false); - await writer.WriteStringAsync($"[{arraySize}]").ConfigureAwait(false); - writer.WriteEndAttribute(); + var soapEncPrefix = writer.LookupPrefix(NamespaceConstants.SoapEnc); + var qualifiedValue = writer.GetQualifiedValue(typeName, typeNamespace); + return writer.WriteAttributeStringAsync(soapEncPrefix, "arrayType", NamespaceConstants.SoapEnc, $"{qualifiedValue}[{arraySize}]"); } /// /// Serializes SOAP encoded array type attribute. /// - public static Task WriteArrayTypeAttributeAsync(this XmlWriter writer, XName qualifiedName, int arraySize) - { - return writer.WriteArrayTypeAttributeAsync(qualifiedName.LocalName, qualifiedName.NamespaceName, arraySize); - } + public static Task WriteArrayTypeAttributeAsync(this XmlWriter writer, XName qualifiedName, int arraySize) => + writer.WriteArrayTypeAttributeAsync(qualifiedName.LocalName, qualifiedName.NamespaceName, arraySize); /// /// Serializes xsi:nil element attribute. /// - public static Task WriteNilAttributeAsync(this XmlWriter writer) - { - return writer.WriteAttributeStringAsync(null, "nil", NamespaceConstants.Xsi, "1"); - } + public static Task WriteNilAttributeAsync(this XmlWriter writer) => + writer.WriteAttributeStringAsync(null, "nil", NamespaceConstants.Xsi, "1"); private static async Task WriteCDataEscapeAsync(this XmlWriter writer, string value) { @@ -111,10 +114,8 @@ public static Task WriteStringWithModeAsync(this XmlWriter writer, string value, : writer.WriteCDataEscapeAsync(value); } - public static Task WriteStartElementAsync(this XmlWriter writer, XName name) - { - return writer.WriteStartElementAsync(null, name.LocalName, name.NamespaceName); - } + public static Task WriteStartElementAsync(this XmlWriter writer, XName name) => + writer.WriteStartElementAsync(null, name.LocalName, name.NamespaceName); /// /// Serializes beginning of SOAP envelope into X-Road message. diff --git a/test/XRoadLib.Tests/XmlWriterExtensionsTest.cs b/test/XRoadLib.Tests/XmlWriterExtensionsTest.cs new file mode 100644 index 00000000..6a37305e --- /dev/null +++ b/test/XRoadLib.Tests/XmlWriterExtensionsTest.cs @@ -0,0 +1,106 @@ +using System.IO; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using XRoadLib.Extensions; +using XRoadLib.Serialization; +using Xunit; + +namespace XRoadLib.Tests +{ + public class XmlWriterExtensionsTest + { + [Fact] + public async Task WriteQualifiedAttributeTest() + { +#if NET5_0 + await +#endif + using var stream = new MemoryStream(); + +#if NET5_0 + await +#endif + using var writer = XmlWriter.Create(stream, new XmlWriterSettings { Async = true, Encoding = XRoadEncoding.Utf8 }); + + await writer.WriteStartDocumentAsync(); + await writer.WriteStartElementAsync(null, "test", null); + await writer.WriteAttributeStringAsync(PrefixConstants.Xmlns, "ppp", NamespaceConstants.Xmlns, "urn:qualifiedNamespace"); + await writer.WriteQualifiedAttributeAsync("attributeName", new XmlQualifiedName("qualifiedName", "urn:qualifiedNamespace")); + await writer.WriteEndElementAsync(); + await writer.WriteEndDocumentAsync(); + await writer.FlushAsync(); + + stream.Seek(0, SeekOrigin.Begin); + + var document = XDocument.Load(stream); + + var attribute = document.Element(XName.Get("test"))?.Attribute("attributeName"); + + Assert.NotNull(attribute); + Assert.Equal("ppp:qualifiedName", attribute.Value); + } + + [Fact] + public async Task WriteTypeAttributeTest() + { +#if NET5_0 + await +#endif + using var stream = new MemoryStream(); + +#if NET5_0 + await +#endif + using var writer = XmlWriter.Create(stream, new XmlWriterSettings { Async = true, Encoding = XRoadEncoding.Utf8 }); + + await writer.WriteStartDocumentAsync(); + await writer.WriteStartElementAsync(null, "test", null); + await writer.WriteAttributeStringAsync(PrefixConstants.Xmlns, "ppp", NamespaceConstants.Xmlns, "urn:qualifiedNamespace"); + await writer.WriteTypeAttributeAsync("typeName", "urn:qualifiedNamespace"); + await writer.WriteEndElementAsync(); + await writer.WriteEndDocumentAsync(); + await writer.FlushAsync(); + + stream.Seek(0, SeekOrigin.Begin); + + var document = XDocument.Load(stream); + + var attribute = document.Element(XName.Get("test"))?.Attribute(XName.Get("type", NamespaceConstants.Xsi)); + + Assert.NotNull(attribute); + Assert.Equal("ppp:typeName", attribute.Value); + } + + [Fact] + public async Task WriteArrayTypeAttributeTest() + { +#if NET5_0 + await +#endif + using var stream = new MemoryStream(); + +#if NET5_0 + await +#endif + using var writer = XmlWriter.Create(stream, new XmlWriterSettings { Async = true, Encoding = XRoadEncoding.Utf8 }); + + await writer.WriteStartDocumentAsync(); + await writer.WriteStartElementAsync(null, "test", null); + await writer.WriteAttributeStringAsync(PrefixConstants.Xmlns, "ppp", NamespaceConstants.Xmlns, "urn:qualifiedNamespace"); + await writer.WriteArrayTypeAttributeAsync(XName.Get("typeName", "urn:qualifiedNamespace"), 73); + await writer.WriteEndElementAsync(); + await writer.WriteEndDocumentAsync(); + await writer.FlushAsync(); + + stream.Seek(0, SeekOrigin.Begin); + + var document = XDocument.Load(stream); + + var attribute = document.Element(XName.Get("test"))?.Attribute(XName.Get("arrayType", NamespaceConstants.SoapEnc)); + + Assert.NotNull(attribute); + Assert.Equal("ppp:typeName[73]", attribute.Value); + } + } +} \ No newline at end of file