From 48facb797ed912e4ea6b04b17d1ff190ac2daac4 Mon Sep 17 00:00:00 2001 From: stefan Date: Wed, 12 Sep 2018 19:26:21 +0200 Subject: Update to 3.5.2 and .net core 2.1 --- RSSDP/HttpParserBase.cs | 438 ++++++++++++++++++++++-------------------------- 1 file changed, 203 insertions(+), 235 deletions(-) (limited to 'RSSDP/HttpParserBase.cs') diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs index 7934419b0..e841feab3 100644 --- a/RSSDP/HttpParserBase.cs +++ b/RSSDP/HttpParserBase.cs @@ -3,242 +3,210 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; +using System.IO; namespace Rssdp.Infrastructure { - /// - /// A base class for the and classes. Not intended for direct use. - /// - /// - public abstract class HttpParserBase where T : new() - { - - #region Fields - - private static readonly string[] LineTerminators = new string[] { "\r\n", "\n" }; - private static readonly char[] SeparatorCharacters = new char[] { ',', ';' }; - - #endregion - - #region Public Methods - - /// - /// Parses the provided into either a or object. - /// - /// A string containing the HTTP message to parse. - /// Either a or object containing the parsed data. - public abstract T Parse(string data); - - /// - /// Parses a string containing either an HTTP request or response into a or object. - /// - /// A or object representing the parsed message. - /// A reference to the collection for the object. - /// A string containing the data to be parsed. - /// An object containing the content of the parsed message. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="Honestly, it's fine. MemoryStream doesn't mind.")] - protected virtual HttpContent Parse(T message, System.Net.Http.Headers.HttpHeaders headers, string data) - { - if (data == null) throw new ArgumentNullException("data"); - if (data.Length == 0) throw new ArgumentException("data cannot be an empty string.", "data"); - if (!LineTerminators.Any(data.Contains)) throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", "data"); - - HttpContent retVal = null; - try - { - var contentStream = new System.IO.MemoryStream(); - try - { - retVal = new StreamContent(contentStream); - - var lines = data.Split(LineTerminators, StringSplitOptions.None); - - //First line is the 'request' line containing http protocol details like method, uri, http version etc. - ParseStatusLine(lines[0], message); - - int lineIndex = ParseHeaders(headers, retVal.Headers, lines); - - if (lineIndex < lines.Length - 1) - { - //Read rest of any remaining data as content. - if (lineIndex < lines.Length - 1) - { - //This is inefficient in multiple ways, but not sure of a good way of correcting. Revisit. - var body = System.Text.UTF8Encoding.UTF8.GetBytes(String.Join(null, lines, lineIndex, lines.Length - lineIndex)); - contentStream.Write(body, 0, body.Length); - contentStream.Seek(0, System.IO.SeekOrigin.Begin); - } - } - } - catch - { - if (contentStream != null) - contentStream.Dispose(); - - throw; - } - } - catch - { - if (retVal != null) - retVal.Dispose(); - - throw; - } - - return retVal; - } - - /// - /// Used to parse the first line of an HTTP request or response and assign the values to the appropriate properties on the . - /// - /// The first line of the HTTP message to be parsed. - /// Either a or to assign the parsed values to. - protected abstract void ParseStatusLine(string data, T message); - - /// - /// Returns a boolean indicating whether the specified HTTP header name represents a content header (true), or a message header (false). - /// - /// A string containing the name of the header to return the type of. - protected abstract bool IsContentHeader(string headerName); - - /// - /// Parses the HTTP version text from an HTTP request or response status line and returns a object representing the parsed values. - /// - /// A string containing the HTTP version, from the message status line. - /// A object containing the parsed version data. - protected static Version ParseHttpVersion(string versionData) - { - if (versionData == null) throw new ArgumentNullException("versionData"); - - var versionSeparatorIndex = versionData.IndexOf('/'); - if (versionSeparatorIndex <= 0 || versionSeparatorIndex == versionData.Length) throw new ArgumentException("request header line is invalid. Http Version not supplied or incorrect format.", "versionData"); - - return Version.Parse(versionData.Substring(versionSeparatorIndex + 1)); - } - - #endregion - - #region Private Methods - - /// - /// Parses a line from an HTTP request or response message containing a header name and value pair. - /// - /// A string containing the data to be parsed. - /// A reference to a collection to which the parsed header will be added. - /// A reference to a collection for the message content, to which the parsed header will be added. - private void ParseHeader(string line, System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders) - { - //Header format is - //name: value - var headerKeySeparatorIndex = line.IndexOf(":", StringComparison.OrdinalIgnoreCase); - var headerName = line.Substring(0, headerKeySeparatorIndex).Trim(); - var headerValue = line.Substring(headerKeySeparatorIndex + 1).Trim(); - - //Not sure how to determine where request headers and and content headers begin, - //at least not without a known set of headers (general headers first the content headers) - //which seems like a bad way of doing it. So we'll assume if it's a known content header put it there - //else use request headers. - - var values = ParseValues(headerValue); - var headersToAddTo = IsContentHeader(headerName) ? contentHeaders : headers; - - if (values.Count > 1) - headersToAddTo.TryAddWithoutValidation(headerName, values); - else - headersToAddTo.TryAddWithoutValidation(headerName, values.First()); - } - - private int ParseHeaders(System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders, string[] lines) - { - //Blank line separates headers from content, so read headers until we find blank line. - int lineIndex = 1; - string line = null, nextLine = null; - while (lineIndex + 1 < lines.Length && !String.IsNullOrEmpty((line = lines[lineIndex++]))) - { - //If the following line starts with space or tab (or any whitespace), it is really part of this header but split for human readability. - //Combine these lines into a single comma separated style header for easier parsing. - while (lineIndex < lines.Length && !String.IsNullOrEmpty((nextLine = lines[lineIndex]))) - { - if (nextLine.Length > 0 && Char.IsWhiteSpace(nextLine[0])) - { - line += "," + nextLine.TrimStart(); - lineIndex++; - } - else - break; - } - - ParseHeader(line, headers, contentHeaders); - } - return lineIndex; - } - - private static IList ParseValues(string headerValue) - { - // This really should be better and match the HTTP 1.1 spec, - // but this should actually be good enough for SSDP implementations - // I think. - var values = new List(); - - if (headerValue == "\"\"") - { - values.Add(String.Empty); - return values; - } - - var indexOfSeparator = headerValue.IndexOfAny(SeparatorCharacters); - if (indexOfSeparator <= 0) - values.Add(headerValue); - else - { - var segments = headerValue.Split(SeparatorCharacters); - if (headerValue.Contains("\"")) - { - for (int segmentIndex = 0; segmentIndex < segments.Length; segmentIndex++) - { - var segment = segments[segmentIndex]; - if (segment.Trim().StartsWith("\"", StringComparison.OrdinalIgnoreCase)) - segment = CombineQuotedSegments(segments, ref segmentIndex, segment); - - values.Add(segment); - } - } - else - values.AddRange(segments); - } - - return values; - } - - private static string CombineQuotedSegments(string[] segments, ref int segmentIndex, string segment) - { - var trimmedSegment = segment.Trim(); - for (int index = segmentIndex; index < segments.Length; index++) - { - if (trimmedSegment == "\"\"" || - ( - trimmedSegment.EndsWith("\"", StringComparison.OrdinalIgnoreCase) - && !trimmedSegment.EndsWith("\"\"", StringComparison.OrdinalIgnoreCase) - && !trimmedSegment.EndsWith("\\\"", StringComparison.OrdinalIgnoreCase)) - ) - { - segmentIndex = index; - return trimmedSegment.Substring(1, trimmedSegment.Length - 2); - } - - if (index + 1 < segments.Length) - trimmedSegment += "," + segments[index + 1].TrimEnd(); - } - - segmentIndex = segments.Length; - if (trimmedSegment.StartsWith("\"", StringComparison.OrdinalIgnoreCase) && trimmedSegment.EndsWith("\"", StringComparison.OrdinalIgnoreCase)) - return trimmedSegment.Substring(1, trimmedSegment.Length - 2); - else - return trimmedSegment; - } - - #endregion - - } + /// + /// A base class for the and classes. Not intended for direct use. + /// + /// + public abstract class HttpParserBase where T : new() + { + + #region Fields + + private readonly string[] LineTerminators = new string[] { "\r\n", "\n" }; + private readonly char[] SeparatorCharacters = new char[] { ',', ';' }; + + #endregion + + #region Public Methods + + private static byte[] EmptyByteArray = new byte[]{}; + + /// + /// Parses the provided into either a or object. + /// + /// A string containing the HTTP message to parse. + /// Either a or object containing the parsed data. + public abstract T Parse(string data); + + /// + /// Parses a string containing either an HTTP request or response into a or object. + /// + /// A or object representing the parsed message. + /// A reference to the collection for the object. + /// A string containing the data to be parsed. + /// An object containing the content of the parsed message. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Honestly, it's fine. MemoryStream doesn't mind.")] + protected virtual void Parse(T message, System.Net.Http.Headers.HttpHeaders headers, string data) + { + if (data == null) throw new ArgumentNullException("data"); + if (data.Length == 0) throw new ArgumentException("data cannot be an empty string.", "data"); + if (!LineTerminators.Any(data.Contains)) throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", "data"); + + using (var retVal = new ByteArrayContent(EmptyByteArray)) + { + var lines = data.Split(LineTerminators, StringSplitOptions.None); + + //First line is the 'request' line containing http protocol details like method, uri, http version etc. + ParseStatusLine(lines[0], message); + + ParseHeaders(headers, retVal.Headers, lines); + } + } + + /// + /// Used to parse the first line of an HTTP request or response and assign the values to the appropriate properties on the . + /// + /// The first line of the HTTP message to be parsed. + /// Either a or to assign the parsed values to. + protected abstract void ParseStatusLine(string data, T message); + + /// + /// Returns a boolean indicating whether the specified HTTP header name represents a content header (true), or a message header (false). + /// + /// A string containing the name of the header to return the type of. + protected abstract bool IsContentHeader(string headerName); + + /// + /// Parses the HTTP version text from an HTTP request or response status line and returns a object representing the parsed values. + /// + /// A string containing the HTTP version, from the message status line. + /// A object containing the parsed version data. + protected Version ParseHttpVersion(string versionData) + { + if (versionData == null) throw new ArgumentNullException("versionData"); + + var versionSeparatorIndex = versionData.IndexOf('/'); + if (versionSeparatorIndex <= 0 || versionSeparatorIndex == versionData.Length) throw new ArgumentException("request header line is invalid. Http Version not supplied or incorrect format.", "versionData"); + + return Version.Parse(versionData.Substring(versionSeparatorIndex + 1)); + } + + #endregion + + #region Private Methods + + /// + /// Parses a line from an HTTP request or response message containing a header name and value pair. + /// + /// A string containing the data to be parsed. + /// A reference to a collection to which the parsed header will be added. + /// A reference to a collection for the message content, to which the parsed header will be added. + private void ParseHeader(string line, System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders) + { + //Header format is + //name: value + var headerKeySeparatorIndex = line.IndexOf(":", StringComparison.OrdinalIgnoreCase); + var headerName = line.Substring(0, headerKeySeparatorIndex).Trim(); + var headerValue = line.Substring(headerKeySeparatorIndex + 1).Trim(); + + //Not sure how to determine where request headers and and content headers begin, + //at least not without a known set of headers (general headers first the content headers) + //which seems like a bad way of doing it. So we'll assume if it's a known content header put it there + //else use request headers. + + var values = ParseValues(headerValue); + var headersToAddTo = IsContentHeader(headerName) ? contentHeaders : headers; + + if (values.Count > 1) + headersToAddTo.TryAddWithoutValidation(headerName, values); + else + headersToAddTo.TryAddWithoutValidation(headerName, values.First()); + } + + private int ParseHeaders(System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders, string[] lines) + { + //Blank line separates headers from content, so read headers until we find blank line. + int lineIndex = 1; + string line = null, nextLine = null; + while (lineIndex + 1 < lines.Length && !String.IsNullOrEmpty((line = lines[lineIndex++]))) + { + //If the following line starts with space or tab (or any whitespace), it is really part of this header but split for human readability. + //Combine these lines into a single comma separated style header for easier parsing. + while (lineIndex < lines.Length && !String.IsNullOrEmpty((nextLine = lines[lineIndex]))) + { + if (nextLine.Length > 0 && Char.IsWhiteSpace(nextLine[0])) + { + line += "," + nextLine.TrimStart(); + lineIndex++; + } + else + break; + } + + ParseHeader(line, headers, contentHeaders); + } + return lineIndex; + } + + private IList ParseValues(string headerValue) + { + // This really should be better and match the HTTP 1.1 spec, + // but this should actually be good enough for SSDP implementations + // I think. + var values = new List(); + + if (headerValue == "\"\"") + { + values.Add(String.Empty); + return values; + } + + var indexOfSeparator = headerValue.IndexOfAny(SeparatorCharacters); + if (indexOfSeparator <= 0) + values.Add(headerValue); + else + { + var segments = headerValue.Split(SeparatorCharacters); + if (headerValue.Contains("\"")) + { + for (int segmentIndex = 0; segmentIndex < segments.Length; segmentIndex++) + { + var segment = segments[segmentIndex]; + if (segment.Trim().StartsWith("\"", StringComparison.OrdinalIgnoreCase)) + segment = CombineQuotedSegments(segments, ref segmentIndex, segment); + + values.Add(segment); + } + } + else + values.AddRange(segments); + } + + return values; + } + + private string CombineQuotedSegments(string[] segments, ref int segmentIndex, string segment) + { + var trimmedSegment = segment.Trim(); + for (int index = segmentIndex; index < segments.Length; index++) + { + if (trimmedSegment == "\"\"" || + ( + trimmedSegment.EndsWith("\"", StringComparison.OrdinalIgnoreCase) + && !trimmedSegment.EndsWith("\"\"", StringComparison.OrdinalIgnoreCase) + && !trimmedSegment.EndsWith("\\\"", StringComparison.OrdinalIgnoreCase)) + ) + { + segmentIndex = index; + return trimmedSegment.Substring(1, trimmedSegment.Length - 2); + } + + if (index + 1 < segments.Length) + trimmedSegment += "," + segments[index + 1].TrimEnd(); + } + + segmentIndex = segments.Length; + if (trimmedSegment.StartsWith("\"", StringComparison.OrdinalIgnoreCase) && trimmedSegment.EndsWith("\"", StringComparison.OrdinalIgnoreCase)) + return trimmedSegment.Substring(1, trimmedSegment.Length - 2); + else + return trimmedSegment; + } + + #endregion + + } } \ No newline at end of file -- cgit v1.2.3