feat: add NatsHeaderParser for MIME header parsing

This commit is contained in:
Joseph Doherty
2026-02-23 00:33:24 -05:00
parent 7cf6bb866e
commit e87d4c00d9
2 changed files with 152 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
using System.Text;
namespace NATS.Server.Protocol;
public readonly struct NatsHeaders
{
public int Status { get; init; }
public string Description { get; init; }
public Dictionary<string, string[]> Headers { get; init; }
public static readonly NatsHeaders Invalid = new() { Status = -1, Description = string.Empty, Headers = new() };
}
public static class NatsHeaderParser
{
private static readonly byte[] CrLf = "\r\n"u8.ToArray();
private static readonly byte[] Prefix = "NATS/1.0"u8.ToArray();
public static NatsHeaders Parse(ReadOnlySpan<byte> data)
{
if (data.Length < Prefix.Length)
return NatsHeaders.Invalid;
if (!data[..Prefix.Length].SequenceEqual(Prefix))
return NatsHeaders.Invalid;
int pos = Prefix.Length;
int status = 0;
string description = string.Empty;
// Parse status line: NATS/1.0[ status[ description]]\r\n
int lineEnd = data[pos..].IndexOf(CrLf);
if (lineEnd < 0)
return NatsHeaders.Invalid;
var statusLine = data[pos..(pos + lineEnd)];
pos += lineEnd + 2; // skip \r\n
if (statusLine.Length > 0)
{
int si = 0;
while (si < statusLine.Length && statusLine[si] == (byte)' ')
si++;
int numStart = si;
while (si < statusLine.Length && statusLine[si] >= (byte)'0' && statusLine[si] <= (byte)'9')
si++;
if (si > numStart)
{
status = int.Parse(Encoding.ASCII.GetString(statusLine[numStart..si]));
while (si < statusLine.Length && statusLine[si] == (byte)' ')
si++;
if (si < statusLine.Length)
description = Encoding.ASCII.GetString(statusLine[si..]);
}
}
// Parse key-value headers until empty line
var headers = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
while (pos < data.Length)
{
var remaining = data[pos..];
if (remaining.Length >= 2 && remaining[0] == (byte)'\r' && remaining[1] == (byte)'\n')
break;
lineEnd = remaining.IndexOf(CrLf);
if (lineEnd < 0)
break;
var headerLine = remaining[..lineEnd];
pos += lineEnd + 2;
int colon = headerLine.IndexOf((byte)':');
if (colon < 0)
continue;
var key = Encoding.ASCII.GetString(headerLine[..colon]).Trim();
var value = Encoding.ASCII.GetString(headerLine[(colon + 1)..]).Trim();
if (!headers.TryGetValue(key, out var values))
{
values = [];
headers[key] = values;
}
values.Add(value);
}
var result = new Dictionary<string, string[]>(headers.Count, StringComparer.OrdinalIgnoreCase);
foreach (var (k, v) in headers)
result[k] = v.ToArray();
return new NatsHeaders
{
Status = status,
Description = description,
Headers = result,
};
}
}