- March 24, 2007
- 12,073
- 7,459
- Home Country
- Germany
- Moderator
- #31
To be clear: in all commits after the Unit Test was added, the header parsing should work 100% correct already. Please try to confirm this first, before making changes.
internal void ParseHeaderAndBody( Stream stream, out string firstLine )
{
const byte CR = 13;
const byte LF = 10;
byte[] data = (stream as MemoryStream).ToArray();
int splitSize = 4;
var splitIndex = FindIndexSinglePattern( data, new[] { CR, LF, CR, LF } );
if( splitIndex == -1 )
{
splitSize = 2;
splitIndex = FindIndexSinglePattern( data, new[] { LF, LF } );
}
string header = Encoding.UTF8.GetString( data, 0, splitIndex );
string[] lines = header.Split( splitSize == 2 ? new [] { "\n" } : new [] { "\r\n" }, StringSplitOptions.None );
if( lines.Length < 1 )
throw new InvalidDataException( "Invalid empty HTTP header" );
firstLine = lines[ 0 ].Trim();
ParseRemainingHeaderLines( lines );
// Check for correct body length
int contentLength;
int bodySize = data.Length - splitIndex - splitSize;
if( int.TryParse( this[ "CONTENT-LENGTH" ], out contentLength ) && contentLength != -1 && contentLength != bodySize )
throw new InvalidDataException( "Invalid HTTP content length: header {0}, actual body: {1}", contentLength, bodySize );
}
// finds the starting index of the given byte pattern
public int FindIndexSinglePattern( byte[] data, byte[] pattern )
{
var matchCount = 0;
for( var i = 0; i < data.Length; i++ )
{
matchCount = data[ i ] == pattern[ matchCount ] ? matchCount + 1 : 0;
if( matchCount == pattern.Length )
return i - matchCount + 1;
}
return -1;
}
private void ParseRemainingHeaderLines( string[] lines )
{
for( int i = 1; i < lines.Length; i++ )
{
string line = lines[ i ].Trim();
int index = line.IndexOf( ':' );
if( index == -1 )
throw new InvalidDataException( "Invalid HTTP header line '{0}'", line );
try
{
string key = line.Substring( 0, index ).Trim();
string value = line.Substring( index + 1 ).Trim();
SetHeader( key, value );
}
catch( ArgumentException e )
{
throw new InvalidDataException( "Invalid HTTP header line '{0}'", e, line );
}
}
}
[TestMethod]
public void ParseHttpRequestHeadersPerformanceTest()
{
var watch = Stopwatch.StartNew();
for( var i = 0; i < 10000; i++ )
{
ParseHttpRequestHeaders();
}
watch.Stop();
Console.WriteLine("Elapsed time: {0}ms", watch.ElapsedMilliseconds);
}
It's not ok to expect a MemoryStream, while the argument is a generic stream. The callers are SSDP/GENA controller classes, they pass a stream which might be of any type.Another thing to note is that the Parse method takes a Stream, but nowhere in the code is anything but a MemoryStream passed to it (as you can see, I'm brutally using that to grab all the bytes passed in).
internal static class StreamExtensions
{
public static byte[] ToArray(this Stream stream)
{
const int BUFFER_SIZE = 4096;
MemoryStream memoryStream = stream as MemoryStream;
if (memoryStream != null)
return memoryStream.ToArray();
using (memoryStream = new MemoryStream())
{
byte[] bodyBuffer = new byte[BUFFER_SIZE];
int bytesRead;
do
{
bytesRead = stream.Read(bodyBuffer, 0, BUFFER_SIZE);
memoryStream.Write(bodyBuffer, 0, bytesRead);
} while (bytesRead > 0);
return memoryStream.ToArray();
}
}
}
It's not ok to expect a MemoryStream, while the argument is a generic stream. The callers are SSDP/GENA controller classes, they pass a stream which might be of any type.Another thing to note is that the Parse method takes a Stream, but nowhere in the code is anything but a MemoryStream passed to it (as you can see, I'm brutally using that to grab all the bytes passed in).
EndPoint remoteEP = new IPEndPoint( state.Endpoint.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, 0);
Stream stream = new MemoryStream(state.Buffer, 0, socket.EndReceiveFrom(ar, ref remoteEP));
...
SimpleHTTPRequest request;
SimpleHTTPRequest.Parse(stream, out request);
I also extended the unit tests to include body-less calls (like GET, CONNECT, NOTIFY...) (see https://github.com/MediaPortal/MediaPortal-2/commit/89ba22c7a1d27ea07c85b49fa5b0a05ec183e556). In this case your current code does fail (split index is -1). But generally I like your approach . I think the required changes are small to get this fully working. Can you check this?
In this case your current code does fail (split index is -1). But generally I like your approach . I think the required changes are small to get this fully working. Can you check this?
string fullRequest = string.Join(delimiter, requestHeaders.ToArray());
string fullRequest = string.Concat( requestHeaders.Select( h => h + delimiter ) );
var splitIndex = FindIndexSinglePattern( data, new[] { CR, LF, CR, LF } );
if( splitIndex == -1 )
{
splitSize = 2;
splitIndex = FindIndexSinglePattern( data, new[] { LF, LF } );
if( splitIndex == -1 )
throw new InvalidDataException( "No end of HTTP header marker found" );
}