fix: use byte-length for close message truncation, add exception-safe disposal

- CreateCloseMessage now operates on UTF-8 byte length (matching Go's
  len(body) behavior) instead of character length, with proper UTF-8
  boundary detection during truncation
- WsCompression.Compress now uses try/finally for exception-safe disposal
  of DeflateStream and MemoryStream
This commit is contained in:
Joseph Doherty
2026-02-23 04:47:57 -05:00
parent d49bc5b0d7
commit 1a1aa9d642
2 changed files with 33 additions and 17 deletions

View File

@@ -22,19 +22,24 @@ public static class WsCompression
{
var output = new MemoryStream();
var deflate = new DeflateStream(output, CompressionLevel.Fastest, leaveOpen: true);
deflate.Write(data);
deflate.Flush();
try
{
deflate.Write(data);
deflate.Flush();
var compressed = output.ToArray();
var compressed = output.ToArray();
deflate.Dispose();
output.Dispose();
// Remove trailing 4-byte sync marker (0x00 0x00 0xff 0xff) per RFC 7692
if (compressed.Length >= 4)
return compressed[..^4];
// Remove trailing 4-byte sync marker (0x00 0x00 0xff 0xff) per RFC 7692
if (compressed.Length >= 4)
return compressed[..^4];
return compressed;
return compressed;
}
finally
{
deflate.Dispose();
output.Dispose();
}
}
/// <summary>

View File

@@ -102,16 +102,27 @@ public static class WsFrameWriter
/// </summary>
public static byte[] CreateCloseMessage(int status, string body)
{
if (body.Length > WsConstants.MaxControlPayloadSize - WsConstants.CloseStatusSize)
var bodyBytes = Encoding.UTF8.GetBytes(body);
int maxBody = WsConstants.MaxControlPayloadSize - WsConstants.CloseStatusSize;
if (bodyBytes.Length > maxBody)
{
body = body[..(WsConstants.MaxControlPayloadSize - WsConstants.CloseStatusSize - 3)] + "...";
var suffix = "..."u8;
int truncLen = maxBody - suffix.Length;
// Find a valid UTF-8 boundary by walking back from truncation point
while (truncLen > 0 && (bodyBytes[truncLen] & 0xC0) == 0x80)
truncLen--;
var buf = new byte[WsConstants.CloseStatusSize + truncLen + suffix.Length];
BinaryPrimitives.WriteUInt16BigEndian(buf, (ushort)status);
bodyBytes.AsSpan(0, truncLen).CopyTo(buf.AsSpan(WsConstants.CloseStatusSize));
suffix.CopyTo(buf.AsSpan(WsConstants.CloseStatusSize + truncLen));
return buf;
}
var bodyBytes = Encoding.UTF8.GetBytes(body);
var buf = new byte[WsConstants.CloseStatusSize + bodyBytes.Length];
BinaryPrimitives.WriteUInt16BigEndian(buf, (ushort)status);
bodyBytes.CopyTo(buf.AsSpan(WsConstants.CloseStatusSize));
return buf;
var result = new byte[WsConstants.CloseStatusSize + bodyBytes.Length];
BinaryPrimitives.WriteUInt16BigEndian(result, (ushort)status);
bodyBytes.CopyTo(result.AsSpan(WsConstants.CloseStatusSize));
return result;
}
/// <summary>