Cherry pick: Fix ExpandedNodeId.Format output for not well formed uri and JSON Verbose WriteStatusCode (#2795)

-JSON Encoder Verbose and NonReversible encoding with WriteStatusCode may produce invalid JSON
-ExpandedNodeId.Format does not handle not well formed uri correctly, as in the previous implementation
This commit is contained in:
Martin Regen
2024-10-13 08:17:37 +02:00
committed by GitHub
parent 4c2744eeab
commit 6ca0b1a78e
4 changed files with 88 additions and 8 deletions
@@ -1345,17 +1345,18 @@ namespace Opc.Ua
return;
}
// Verbose and NonReversible
PushStructure(fieldName);
if (value != StatusCodes.Good)
{
PushStructure(fieldName);
WriteSimpleField("Code", value.Code.ToString(CultureInfo.InvariantCulture), EscapeOptions.NoFieldNameEscape);
string symbolicId = StatusCode.LookupSymbolicId(value.CodeBits);
if (!string.IsNullOrEmpty(symbolicId))
{
WriteSimpleField("Symbol", symbolicId, EscapeOptions.Quotes | EscapeOptions.NoFieldNameEscape);
}
PopStructure();
}
PopStructure();
}
/// <summary>
+30 -3
View File
@@ -1265,10 +1265,37 @@ namespace Opc.Ua
/// </summary>
public static string EscapeUri(string uri)
{
if (!String.IsNullOrWhiteSpace(uri))
if (!string.IsNullOrWhiteSpace(uri))
{
var builder = new UriBuilder(uri.Replace(";", "%3b"));
return builder.Uri.AbsoluteUri;
// back compat: for not well formed Uri, fall back to legacy formatting behavior - see #2793
if (!Uri.IsWellFormedUriString(uri, UriKind.Absolute) ||
!Uri.TryCreate(uri.Replace(";", "%3b"), UriKind.Absolute, out Uri validUri))
{
var buffer = new StringBuilder();
foreach (char ch in uri)
{
switch (ch)
{
case ';':
case '%':
{
buffer.AppendFormat(CultureInfo.InvariantCulture, "%{0:X2}", Convert.ToInt16(ch));
break;
}
default:
{
buffer.Append(ch);
break;
}
}
}
return buffer.ToString();
}
else
{
return validUri.AbsoluteUri;
}
}
return String.Empty;
@@ -374,13 +374,19 @@ namespace Opc.Ua.Client.ComplexTypes.Tests.Types
if (jsonEncoding == JsonEncodingType.Compact || jsonEncoding == JsonEncodingType.Reversible)
{
oText = "0";
// default statuscode is not encoded
continue;
}
else if (jsonEncoding == JsonEncodingType.Verbose)
{
oText = "{}";
}
else
{
oText = "{\"Code\": 0,\"Symbol\":\"Good\"}";
// default statuscode is not encoded
continue;
}
continue;
}
else if (property.Name == "Guid")
{
@@ -286,8 +286,8 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
$"{{\"IdType\":3,\"Id\":\"{s_byteString64}\",\"Namespace\":88}}", null,
$"\"ns=88;b={s_byteString64}\"", null},
{ BuiltInType.StatusCode, new StatusCode(StatusCodes.Good), null, null, null, null},
{ BuiltInType.StatusCode, new StatusCode(StatusCodes.Good), $"{StatusCodes.Good}", "", null, null, true},
{ BuiltInType.StatusCode, new StatusCode(StatusCodes.Good), null, null, null, "{}"},
{ BuiltInType.StatusCode, new StatusCode(StatusCodes.Good), $"{StatusCodes.Good}", "{}", null, "{}", true},
{ BuiltInType.StatusCode, new StatusCode(StatusCodes.BadBoundNotFound), $"{StatusCodes.BadBoundNotFound}",
$"{{\"Code\":{StatusCodes.BadBoundNotFound}, \"Symbol\":\"{nameof(StatusCodes.BadBoundNotFound)}\"}}"},
{ BuiltInType.StatusCode, new StatusCode(StatusCodes.BadCertificateInvalid),
@@ -352,6 +352,9 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
{ BuiltInType.DataValue, new DataValue(), "{}", null},
{ BuiltInType.DataValue, new DataValue(StatusCodes.Good), "{}", null},
{ BuiltInType.DataValue, new DataValue(StatusCodes.BadNotWritable),
$"{{\"StatusCode\":{StatusCodes.BadNotWritable}}}",
$"{{\"StatusCode\":{{\"Code\":{StatusCodes.BadNotWritable}, \"Symbol\":\"{nameof(StatusCodes.BadNotWritable)}\"}}}}"},
{ BuiltInType.Enumeration, (TestEnumType) 0, "0", "\"0\""},
{ BuiltInType.Enumeration, TestEnumType.Three, TestEnumType.Three.ToString("d"), $"\"{TestEnumType.Three}_{TestEnumType.Three.ToString("d")}\""},
@@ -372,6 +375,9 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
"{\"Body\":{\"Foo\":\"bar_999\"}}", "{\"Foo\":\"bar_999\"}",
"{\"Body\":{\"Foo\":\"bar_999\"}}", null}
}.ToArray();
[DatapointSource]
public static StatusCode[] GoodAndBadStatusCodes = { StatusCodes.Good, StatusCodes.BadAlreadyExists };
#endregion
#region Test Setup
@@ -438,6 +444,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
Assert.Throws<NotSupportedException>(() => encoder.ForceNamespaceUriForIndex1 = true);
Assert.Throws<NotSupportedException>(() => encoder.IncludeDefaultNumberValues = true);
Assert.Throws<NotSupportedException>(() => encoder.IncludeDefaultValues = true);
Assert.Throws<NotSupportedException>(() => encoder.EncodeNodeIdAsString = false);
}
}
@@ -1162,6 +1169,45 @@ namespace Opc.Ua.Core.Tests.Types.Encoders
DateTimeEncodeStringTest(dateTime);
}
/// <summary>
/// Validate that a ExpandedNodeId returns the expected
/// result for a not well formed Uri.
/// </summary>
[Test]
public void NotWellFormedUriInExpandedNodeId2String()
{
string namespaceUri = "KEPServerEX";
string nodeName = "Data Type Examples.16 Bit Device.K Registers.Double3";
String expectedNodeIdString = $"nsu={namespaceUri};s={nodeName}";
ExpandedNodeId expandedNodeId = new ExpandedNodeId(expectedNodeIdString);
string stringifiedExpandedNodId = expandedNodeId.ToString();
TestContext.Out.WriteLine(stringifiedExpandedNodId);
Assert.AreEqual(expectedNodeIdString, stringifiedExpandedNodId);
}
/// <summary>
/// Validate that a statuscode in a DataValue produces valid JSON.
/// </summary>
[Theory]
public void DataValueWithStatusCodes(
JsonEncodingType jsonEncodingType,
[ValueSource(nameof(GoodAndBadStatusCodes))] StatusCode statusCodeVariant,
[ValueSource(nameof(GoodAndBadStatusCodes))] StatusCode statusCode)
{
var dataValue = new DataValue() {
Value = new Variant(statusCodeVariant),
ServerTimestamp = DateTime.UtcNow,
StatusCode = statusCode
};
using (var jsonEncoder = new JsonEncoder(m_context, jsonEncodingType))
{
jsonEncoder.WriteDataValue("Data", dataValue);
var result = jsonEncoder.CloseAndReturnText();
PrettifyAndValidateJson(result, true);
}
}
/// <summary>
/// Validate that the DateTime format strings return an equal result.
/// </summary>