diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b10419d1e..f748435e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -115,7 +115,9 @@ jobs: cd tests/NATS.Client.CheckNativeAot rm -rf bin obj - dotnet publish -r linux-x64 -c Release -o dist | tee output.txt + + # temporarily ignore MsQuicApi warnings + dotnet publish -r linux-x64 -c Release -o dist | grep -v MsQuicApi | tee output.txt # check for warnings grep -i warning output.txt && exit 1 diff --git a/src/NATS.Client.Core/Internal/HeaderWriter.cs b/src/NATS.Client.Core/Internal/HeaderWriter.cs index 8bb62694d..286fd9998 100644 --- a/src/NATS.Client.Core/Internal/HeaderWriter.cs +++ b/src/NATS.Client.Core/Internal/HeaderWriter.cs @@ -1,5 +1,4 @@ using System.Buffers; -using System.IO.Pipelines; using System.Text; using NATS.Client.Core.Commands; @@ -21,6 +20,37 @@ internal class HeaderWriter private static ReadOnlySpan ColonSpace => new[] { ByteColon, ByteSpace }; + internal static long GetBytesLength(NatsHeaders headers, Encoding encoding) + { + var len = CommandConstants.NatsHeaders10NewLine.Length; + foreach (var kv in headers) + { + foreach (var value in kv.Value) + { + if (value != null) + { + // key length + var keyLength = encoding.GetByteCount(kv.Key); + len += keyLength; + + // colon space length + len += ColonSpace.Length; + + // value length + var valueLength = encoding.GetByteCount(value); + len += valueLength; + + // CrLf length + len += CrLf.Length; + } + } + } + + // CrLf length for empty headers + len += CrLf.Length; + return len; + } + internal long Write(IBufferWriter bufferWriter, NatsHeaders headers) { bufferWriter.WriteSpan(CommandConstants.NatsHeaders10NewLine); @@ -37,10 +67,7 @@ internal long Write(IBufferWriter bufferWriter, NatsHeaders headers) var keySpan = bufferWriter.GetSpan(keyLength); _encoding.GetBytes(kv.Key, keySpan); if (!ValidateKey(keySpan.Slice(0, keyLength))) - { - throw new NatsException( - $"Invalid header key '{kv.Key}': contains colon, space, or other non-printable ASCII characters"); - } + throw new NatsException($"Invalid header key '{kv.Key}': contains colon, space, or other non-printable ASCII characters"); bufferWriter.Advance(keyLength); len += keyLength; @@ -53,9 +80,7 @@ internal long Write(IBufferWriter bufferWriter, NatsHeaders headers) var valueSpan = bufferWriter.GetSpan(valueLength); _encoding.GetBytes(value, valueSpan); if (!ValidateValue(valueSpan.Slice(0, valueLength))) - { throw new NatsException($"Invalid header value for key '{kv.Key}': contains CRLF"); - } bufferWriter.Advance(valueLength); len += valueLength; @@ -81,9 +106,7 @@ private static bool ValidateKey(ReadOnlySpan span) foreach (var b in span) { if (b <= ByteSpace || b == ByteColon || b >= ByteDel) - { return false; - } } return true; @@ -96,15 +119,11 @@ private static bool ValidateValue(ReadOnlySpan span) { var pos = span.IndexOf(ByteCr); if (pos == -1 || pos == span.Length - 1) - { return true; - } pos += 1; if (span[pos] == ByteLf) - { return false; - } span = span[pos..]; } diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index f7bec3f67..d8d447edb 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -1,7 +1,9 @@ using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Text; using Microsoft.Extensions.Primitives; +using NATS.Client.Core.Internal; namespace NATS.Client.Core; @@ -294,6 +296,20 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) } } + /// + /// Returns the bytes length of the header + /// + /// Encoding used. Default to utf8 if not provided + /// Bytes length of the header + public long GetBytesLength(Encoding? encoding = null) + { + // if null set to utf-8 + encoding = encoding ?? Encoding.UTF8; + + var len = HeaderWriter.GetBytesLength(this, encoding); + return len; + } + /// /// Removes the given item from the the collection. /// diff --git a/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs b/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs index 240a0700e..f938451c5 100644 --- a/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs +++ b/tests/NATS.Client.CoreUnit.Tests/NatsHeaderTest.cs @@ -195,4 +195,23 @@ public void GetLastValueTests(string text, bool expectedResult, string? expected Assert.Equal(result, expectedResult); Assert.Equal(lastValue, expectedLastValue); } + + [Fact] + public void GetBytesLengthTest() + { + var headers = new NatsHeaders + { + ["k1"] = "v1", + ["k2"] = new[] { "v2-0", "v2-1" }, + ["a-long-header-key"] = "value", + ["key"] = "a-long-header-value", + }; + var encoding = Encoding.UTF8; + var bytesLength = headers.GetBytesLength(encoding); + + var text = "NATS/1.0\r\nk1: v1\r\nk2: v2-0\r\nk2: v2-1\r\na-long-header-key: value\r\nkey: a-long-header-value\r\n\r\n"; + var expected = new ReadOnlySequence(Encoding.UTF8.GetBytes(text)); + + Assert.Equal(expected.Length, bytesLength); + } }