using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; namespace LaunchDarkly.EventSource { /// /// An internal class containing helper methods to parse Server Sent Event data. /// internal static class EventParser { /// /// The result returned by or . /// internal struct Result { internal string FieldName { get; set; } // null means it's a comment internal string ValueString { get; set; } internal Utf8ByteSpan ValueBytes { get; set; } internal string GetValueAsString() => ValueString is null ? ValueBytes.GetString() : ValueString; internal bool IsComment => FieldName is null; internal bool IsDataField => FieldName == "data"; internal bool IsEventField => FieldName == "event"; internal bool IsIdField => FieldName == "id"; internal bool IsRetryField => FieldName == "retry"; } /// /// Attempts to parse a single non-empty line of SSE content that was read as a string. Empty lines /// should not be not passed to this method. /// /// a line that was read from the stream, not including any trailing CR/LF /// a containing the parsed field or comment; ValueString will /// be set rather than ValueBytes internal static Result ParseLineString(string line) { var colonPos = line.IndexOf(':'); if (colonPos == 0) // comment { return new Result { ValueString = line }; } if (colonPos < 0) // field name without a value - assume empty value { return new Result { FieldName = line, ValueString = "" }; } int valuePos = colonPos + 1; if (valuePos < line.Length && line[valuePos] == ' ') { valuePos++; // trim a single leading space from the value, if present } return new Result { FieldName = line.Substring(0, colonPos), ValueString = line.Substring(valuePos) }; } /// /// Attempts to parse a single non-empty line of SSE content that was read as UTF-8 bytes. Empty lines /// should not be not passed to this method. /// /// a line that was read from the stream, not including any trailing CR/LF /// a containing the parsed field or comment; ValueBytes /// will be set rather than ValueString public static Result ParseLineUtf8Bytes(Utf8ByteSpan line) { if (line.Length > 0 && line.Data[line.Offset] == ':') // comment { return new Result { ValueBytes = line }; } int colonPos = 0; for (; colonPos < line.Length && line.Data[line.Offset + colonPos] != ':'; colonPos++) { } string fieldName = Encoding.UTF8.GetString(line.Data, line.Offset, colonPos); if (colonPos == line.Length) // field name without a value - assume empty value { return new Result { FieldName = fieldName, ValueBytes = new Utf8ByteSpan() }; } int valuePos = colonPos + 1; if (valuePos < line.Length && line.Data[line.Offset + valuePos] == ' ') { valuePos++; // trim a single leading space from the value, if present } return new Result { FieldName = fieldName, ValueBytes = new Utf8ByteSpan(line.Data, line.Offset + valuePos, line.Length - valuePos) }; } } }