Commit 5b45d41c authored by Brickner_cp's avatar Brickner_cp

HTTP

parent 335e1ca8
namespace PcapDotNet.Base
{
public static class CharExtensions
{
public static bool IsUpperCaseAlpha(this char c)
{
return c >= 'A' && c <= 'Z';
}
}
}
\ No newline at end of file
...@@ -85,6 +85,7 @@ ...@@ -85,6 +85,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CharExtensions.cs" />
<Compile Include="FuncExtensions.cs" /> <Compile Include="FuncExtensions.cs" />
<Compile Include="IDictionaryExtensions.cs" /> <Compile Include="IDictionaryExtensions.cs" />
<Compile Include="IEnumerableExtensions.cs" /> <Compile Include="IEnumerableExtensions.cs" />
......
...@@ -113,6 +113,13 @@ namespace PcapDotNet.Packets.Test ...@@ -113,6 +113,13 @@ namespace PcapDotNet.Packets.Test
new HttpField("abc", ""), new HttpField("abc", ""),
new HttpField("B", "C"))); new HttpField("B", "C")));
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"\r\n",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(),
Datagram.Empty);
TestHttpResponse("HTTP/"); TestHttpResponse("HTTP/");
TestHttpResponse("HTTP/1"); TestHttpResponse("HTTP/1");
TestHttpResponse("HTTP/1."); TestHttpResponse("HTTP/1.");
...@@ -133,13 +140,14 @@ namespace PcapDotNet.Packets.Test ...@@ -133,13 +140,14 @@ namespace PcapDotNet.Packets.Test
new HttpField("Cache-Control", "no-cache"))); new HttpField("Cache-Control", "no-cache")));
TestHttpResponse("HTTP/1.1 200 OK\r\n" + TestHttpResponse("HTTP/1.1 200 OK\r\n" +
"Transfer-Encoding: chunked,a, b , c\r\n\t,d , e;f=g;h=\"ijk lmn\"\r\n", "Transfer-Encoding: chunked,a, b , c\r\n\t,d , e;f=g;h=\"ijk lmn\"\r\n",
HttpVersion.Version11, 200, "OK", HttpVersion.Version11, 200, "OK",
new HttpHeader( new HttpHeader(
new HttpTransferEncodingField("chunked", "a", "b", "c", "d", "e;f=g;h=\"ijk lmn\""))); new HttpTransferEncodingField("chunked", "a", "b", "c", "d", "e;f=g;h=\"ijk lmn\"")));
} }
private static void TestHttpRequest(string httpString, string expectedMethod = null, string expectedUri = null, HttpVersion expectedVersion = null, HttpHeader expectedHeader = null) private static void TestHttpRequest(string httpString, string expectedMethod = null, string expectedUri = null, HttpVersion expectedVersion = null, HttpHeader expectedHeader = null, Datagram expectedBody = null)
{ {
Packet packet = BuildPacket(httpString); Packet packet = BuildPacket(httpString);
...@@ -153,9 +161,10 @@ namespace PcapDotNet.Packets.Test ...@@ -153,9 +161,10 @@ namespace PcapDotNet.Packets.Test
HttpRequestDatagram request = (HttpRequestDatagram)http; HttpRequestDatagram request = (HttpRequestDatagram)http;
Assert.AreEqual(expectedMethod, request.Method, "Method " + httpString); Assert.AreEqual(expectedMethod, request.Method, "Method " + httpString);
Assert.AreEqual(expectedUri, request.Uri, "Uri " + httpString); Assert.AreEqual(expectedUri, request.Uri, "Uri " + httpString);
Assert.AreEqual(expectedBody, request.Body);
} }
private static void TestHttpResponse(string httpString, HttpVersion expectedVersion = null, uint? expectedStatusCode = null, string expectedReasonPhrase = null, HttpHeader expectedHeader = null) private static void TestHttpResponse(string httpString, HttpVersion expectedVersion = null, uint? expectedStatusCode = null, string expectedReasonPhrase = null, HttpHeader expectedHeader = null, Datagram expectedBody = null)
{ {
Packet packet = BuildPacket(httpString); Packet packet = BuildPacket(httpString);
...@@ -169,6 +178,7 @@ namespace PcapDotNet.Packets.Test ...@@ -169,6 +178,7 @@ namespace PcapDotNet.Packets.Test
HttpResponseDatagram response = (HttpResponseDatagram)http; HttpResponseDatagram response = (HttpResponseDatagram)http;
Assert.AreEqual(expectedStatusCode, response.StatusCode, "StatusCode " + httpString); Assert.AreEqual(expectedStatusCode, response.StatusCode, "StatusCode " + httpString);
Assert.AreEqual(expectedReasonPhrase == null ? null : new Datagram(Encoding.ASCII.GetBytes(expectedReasonPhrase)), response.ReasonPhrase, "ReasonPhrase " + httpString); Assert.AreEqual(expectedReasonPhrase == null ? null : new Datagram(Encoding.ASCII.GetBytes(expectedReasonPhrase)), response.ReasonPhrase, "ReasonPhrase " + httpString);
Assert.AreEqual(expectedBody, response.Body);
} }
private static Packet BuildPacket(string httpString) private static Packet BuildPacket(string httpString)
......
namespace PcapDotNet.Packets.Http using System;
namespace PcapDotNet.Packets.Http
{ {
internal static class ByteExtensions internal static class ByteExtensions
{ {
...@@ -40,6 +42,21 @@ ...@@ -40,6 +42,21 @@
value.IsDigit(); value.IsDigit();
} }
public static int ToHexadecimalValue(this byte value)
{
if (value >= AsciiBytes.Zero && value <= AsciiBytes.Nine)
return value - AsciiBytes.Zero;
if (value >= AsciiBytes.LowerA && value <= AsciiBytes.LowerF)
return value - AsciiBytes.LowerA + 10;
if (value >= AsciiBytes.UpperA && value <= AsciiBytes.UpperF)
return value - AsciiBytes.UpperA + 10;
throw new ArgumentOutOfRangeException("value", value,
string.Format("Must be a valid ASCII hexadecimal character ({0}-{1}, {2}-{3}, {4}-{5})",
AsciiBytes.Zero, AsciiBytes.Nine,
AsciiBytes.LowerA, AsciiBytes.LowerF,
AsciiBytes.UpperA, AsciiBytes.UpperF));
}
public static bool IsSpaceOrHorizontalTab(this byte value) public static bool IsSpaceOrHorizontalTab(this byte value)
{ {
return value == AsciiBytes.Space || value == AsciiBytes.HorizontalTab; return value == AsciiBytes.Space || value == AsciiBytes.HorizontalTab;
......
namespace PcapDotNet.Packets.Http
{
public class HttpContentLengthField : HttpField
{
public const string Name = "Content-Length";
public HttpContentLengthField(uint contentLength)
:base(Name, contentLength.ToString())
{
ContentLength = contentLength;
}
public uint? ContentLength { get; private set;}
internal HttpContentLengthField(byte[] fieldValue)
:base(Name, fieldValue)
{
HttpParser parser = new HttpParser(fieldValue);
uint? contentLength;
parser.DecimalNumber(out contentLength);
if (!parser.Success)
return;
ContentLength = contentLength;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
...@@ -228,13 +229,85 @@ namespace PcapDotNet.Packets.Http ...@@ -228,13 +229,85 @@ namespace PcapDotNet.Packets.Http
if (_bodyOffset != null) if (_bodyOffset != null)
{ {
int bodyOffsetValue = _bodyOffset.Value; int bodyOffsetValue = _bodyOffset.Value;
if (!IsBodyPossible)
{
_body = new Datagram(new byte[0]);
return _body;
}
HttpTransferEncodingField transferEncodingField = Header.TransferEncoding;
if (transferEncodingField != null)
{
if (transferEncodingField.TransferCodings.Any(coding => coding != "identity"))
{
_body = ReadChunked();
return _body;
}
}
// HttpContentLengthField contentLengthField = Header.ContentLength;
// if (contentLengthField != null)
// {
// int contentLength = contentLengthField.ContentLength;
// _body = new Datagram(Buffer, StartOffset + bodyOffsetValue, Math.Min(contentLength, Length - bodyOffsetValue));
// return _body;
// }
//
// HttpContentTypeField contentTypeField = Header.ContentType;
// if (contentTypeField != null)
// {
// if (contentTypeField.MediaTypes.Contains("multipart/byteranges"))
// {
// int transferLength = contentTypeField.TransferLength;
// _body = new Datagram(Buffer, StartOffset + bodyOffsetValue, Math.Min(transferLength, Length - bodyOffsetValue));
// }
//
// }
_body = new Datagram(Buffer, StartOffset + bodyOffsetValue, Length - bodyOffsetValue); _body = new Datagram(Buffer, StartOffset + bodyOffsetValue, Length - bodyOffsetValue);
} }
} }
return _body; return _body;
} }
} }
private Datagram ReadChunked()
{
List<Datagram> contentData = new List<Datagram>();
HttpParser parser = new HttpParser(Buffer, StartOffset + _bodyOffset.Value, Length - _bodyOffset.Value);
uint? chunkSize;
while (parser.HexadecimalNumber(out chunkSize).SkipChunkExtensions().CarriageReturnLineFeed().Success)
{
uint chunkSizeValue = chunkSize.Value;
if (chunkSizeValue == 0)
{
int? endOffset;
HttpHeader trailerHeader = new HttpHeader(GetHeaderFields(out endOffset, Buffer, parser.Offset, Buffer.Length - parser.Offset));
parser.CarriageReturnLineFeed();
break;
}
int actualChunkSize = (int)Math.Min(chunkSizeValue, Buffer.Length - parser.Offset);
contentData.Add(new Datagram(Buffer, parser.Offset, actualChunkSize));
parser.Skip(actualChunkSize);
parser.CarriageReturnLineFeed();
}
int contentLength = contentData.Sum(datagram => datagram.Length);
byte[] contentBuffer = new byte[contentLength];
int contentBufferOffset = 0;
foreach (Datagram datagram in contentData)
{
datagram.Write(contentBuffer, contentBufferOffset);
contentBufferOffset += datagram.Length;
}
Datagram content = new Datagram(contentBuffer);
return new Datagram(Buffer, StartOffset + _bodyOffset.Value, parser.Offset - StartOffset);
}
protected abstract bool IsBodyPossible { get; }
internal static HttpDatagram CreateDatagram(byte[] buffer, int offset, int length) internal static HttpDatagram CreateDatagram(byte[] buffer, int offset, int length)
{ {
if (length >= _httpSlash.Length && buffer.SequenceEqual(offset, _httpSlash, 0, _httpSlash.Length)) if (length >= _httpSlash.Length && buffer.SequenceEqual(offset, _httpSlash, 0, _httpSlash.Length))
...@@ -271,23 +344,35 @@ namespace PcapDotNet.Packets.Http ...@@ -271,23 +344,35 @@ namespace PcapDotNet.Packets.Http
_header = new HttpHeader(GetHeaderFields()); _header = new HttpHeader(GetHeaderFields());
} }
private IEnumerable<KeyValuePair<string, IEnumerable<byte>>> GetHeaderFields() private List<KeyValuePair<string, IEnumerable<byte>>> GetHeaderFields()
{ {
int headerOffsetValue = _headerOffset.Value; int headerOffsetValue = _headerOffset.Value;
HttpParser parser = new HttpParser(Buffer, StartOffset + headerOffsetValue, Length - headerOffsetValue); int? endOffset;
var result = GetHeaderFields(out endOffset, Buffer, StartOffset + headerOffsetValue, Length - headerOffsetValue);
if (endOffset != null)
_bodyOffset = endOffset.Value - StartOffset;
return result;
}
private static List<KeyValuePair<string, IEnumerable<byte>>> GetHeaderFields(out int? endOffset, byte[] buffer, int offset, int length)
{
endOffset = null;
var headerFields = new List<KeyValuePair<string, IEnumerable<byte>>>();
HttpParser parser = new HttpParser(buffer, offset, length);
while (parser.Success) while (parser.Success)
{ {
if (parser.IsCarriageReturnLineFeed()) if (parser.IsCarriageReturnLineFeed())
{ {
_bodyOffset = parser.Offset + 2 - StartOffset; endOffset = parser.Offset + 2;
break; break;
} }
string fieldName; string fieldName;
IEnumerable<byte> fieldValue; IEnumerable<byte> fieldValue;
parser.Token(out fieldName).Colon().FieldValue(out fieldValue).CarriageReturnLineFeed(); parser.Token(out fieldName).Colon().FieldValue(out fieldValue).CarriageReturnLineFeed();
if (parser.Success) if (parser.Success)
yield return new KeyValuePair<string, IEnumerable<byte>>(fieldName, fieldValue); headerFields.Add(new KeyValuePair<string, IEnumerable<byte>>(fieldName, fieldValue));
} }
return headerFields;
} }
private static readonly byte[] _httpSlash = Encoding.ASCII.GetBytes("HTTP/"); private static readonly byte[] _httpSlash = Encoding.ASCII.GetBytes("HTTP/");
......
...@@ -15,6 +15,8 @@ namespace PcapDotNet.Packets.Http ...@@ -15,6 +15,8 @@ namespace PcapDotNet.Packets.Http
{ {
case HttpTransferEncodingField.Name: case HttpTransferEncodingField.Name:
return new HttpTransferEncodingField(fieldValue); return new HttpTransferEncodingField(fieldValue);
case HttpContentLengthField.Name:
return new HttpContentLengthField(fieldValue);
default: default:
return new HttpField(fieldName, fieldValue.ToArray()); return new HttpField(fieldName, fieldValue.ToArray());
......
...@@ -18,6 +18,17 @@ namespace PcapDotNet.Packets.Http ...@@ -18,6 +18,17 @@ namespace PcapDotNet.Packets.Http
{ {
} }
public HttpTransferEncodingField TransferEncoding
{
get
{
HttpField field;
if (!_fields.TryGetValue(HttpTransferEncodingField.Name, out field))
return null;
return (HttpTransferEncodingField)field;
}
}
public bool Equals(HttpHeader other) public bool Equals(HttpHeader other)
{ {
return other != null && return other != null &&
...@@ -34,6 +45,16 @@ namespace PcapDotNet.Packets.Http ...@@ -34,6 +45,16 @@ namespace PcapDotNet.Packets.Http
return this.SequenceToString("\r\n"); return this.SequenceToString("\r\n");
} }
public IEnumerator<HttpField> GetEnumerator()
{
return _fields.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
internal HttpHeader(IEnumerable<KeyValuePair<string, IEnumerable<byte>>> fields) internal HttpHeader(IEnumerable<KeyValuePair<string, IEnumerable<byte>>> fields)
{ {
var mergedFields = new Dictionary<string, IEnumerable<byte>>(); var mergedFields = new Dictionary<string, IEnumerable<byte>>();
...@@ -53,16 +74,6 @@ namespace PcapDotNet.Packets.Http ...@@ -53,16 +74,6 @@ namespace PcapDotNet.Packets.Http
_fields = mergedFields.ToDictionary(field => field.Key, field => HttpField.CreateField(field.Key, field.Value.ToArray())); _fields = mergedFields.ToDictionary(field => field.Key, field => HttpField.CreateField(field.Key, field.Value.ToArray()));
} }
public IEnumerator<HttpField> GetEnumerator()
{
return _fields.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private readonly Dictionary<string, HttpField> _fields = new Dictionary<string, HttpField>(); private readonly Dictionary<string, HttpField> _fields = new Dictionary<string, HttpField>();
} }
} }
...@@ -234,12 +234,41 @@ namespace PcapDotNet.Packets.Http ...@@ -234,12 +234,41 @@ namespace PcapDotNet.Packets.Http
int numDigits = digits.Count(); int numDigits = digits.Count();
int numInsignificantDigits = digits.TakeWhile(value => value == AsciiBytes.Zero).Count(); int numInsignificantDigits = digits.TakeWhile(value => value == AsciiBytes.Zero).Count();
if (numDigits - numInsignificantDigits > 9)
{
number = null;
return Fail();
}
uint numberValue = digits.Select(value => (uint)(value - AsciiBytes.Zero)).Aggregate<uint, uint>(0, (accumulated, value) => 10 * accumulated + value); uint numberValue = digits.Select(value => (uint)(value - AsciiBytes.Zero)).Aggregate<uint, uint>(0, (accumulated, value) => 10 * accumulated + value);
if (numDigits - numInsignificantDigits >= 8) number = numberValue;
_offset += numDigits;
return this;
}
public HttpParser HexadecimalNumber(out uint? number)
{
if (!Success)
{
number = null;
return this;
}
var digits = Range.TakeWhile(value => value.IsHexadecimalDigit());
if (!digits.Any())
{
number = null;
return Fail();
}
int numDigits = digits.Count();
int numInsignificantDigits = digits.TakeWhile(value => value == AsciiBytes.Zero).Count();
if (numDigits - numInsignificantDigits > 8)
{ {
number = null; number = null;
return Fail(); return Fail();
} }
uint numberValue = digits.Select(value => (uint)(value.ToHexadecimalValue())).Aggregate<uint, uint>(0, (accumulated, value) => 16 * accumulated + value);
number = numberValue; number = numberValue;
_offset += numDigits; _offset += numDigits;
return this; return this;
...@@ -364,6 +393,29 @@ namespace PcapDotNet.Packets.Http ...@@ -364,6 +393,29 @@ namespace PcapDotNet.Packets.Http
return Fail(); return Fail();
} }
public HttpParser SkipChunkExtensions()
{
while (IsNext(AsciiBytes.Semicolon))
{
Bytes(AsciiBytes.Semicolon);
string chunkExtensionName;
Token(out chunkExtensionName);
if (IsNext(AsciiBytes.EqualsSign))
{
Bytes(AsciiBytes.EqualsSign);
Datagram chunkExtensionValue;
if (IsNext(AsciiBytes.DoubleQuotationMark))
QuotedString(out chunkExtensionValue);
else
Token(out chunkExtensionValue);
}
}
return this;
}
private bool IsNext() private bool IsNext()
{ {
return _offset < _totalLength; return _offset < _totalLength;
...@@ -430,6 +482,14 @@ namespace PcapDotNet.Packets.Http ...@@ -430,6 +482,14 @@ namespace PcapDotNet.Packets.Http
return this; return this;
} }
public HttpParser Skip(int count)
{
_offset += count;
if (_offset > _totalLength)
return Fail();
return this;
}
private static readonly byte[] _httpSlash = Encoding.ASCII.GetBytes("HTTP/"); private static readonly byte[] _httpSlash = Encoding.ASCII.GetBytes("HTTP/");
private readonly byte[] _buffer; private readonly byte[] _buffer;
......
...@@ -32,6 +32,11 @@ namespace PcapDotNet.Packets.Http ...@@ -32,6 +32,11 @@ namespace PcapDotNet.Packets.Http
} }
} }
protected override bool IsBodyPossible
{
get { throw new NotImplementedException(); }
}
internal override void ParseSpecificFirstLine(out HttpVersion version, out int? headerOffset) internal override void ParseSpecificFirstLine(out HttpVersion version, out int? headerOffset)
{ {
HttpParser parser = new HttpParser(Buffer, StartOffset, Length); HttpParser parser = new HttpParser(Buffer, StartOffset, Length);
......
...@@ -33,6 +33,19 @@ namespace PcapDotNet.Packets.Http ...@@ -33,6 +33,19 @@ namespace PcapDotNet.Packets.Http
} }
} }
protected override bool IsBodyPossible
{
get
{
uint statusCodeValue = StatusCode.Value;
if (statusCodeValue >= 100 && statusCodeValue <= 199 || statusCodeValue == 204 || statusCodeValue == 205 || statusCodeValue == 304)
return false;
// if (IsResponseToHeadRequest)
// return false;
return true;
}
}
internal override void ParseSpecificFirstLine(out HttpVersion version, out int? headerOffset) internal override void ParseSpecificFirstLine(out HttpVersion version, out int? headerOffset)
{ {
HttpParser parser = new HttpParser(Buffer, StartOffset, Length); HttpParser parser = new HttpParser(Buffer, StartOffset, Length);
......
...@@ -16,7 +16,7 @@ namespace PcapDotNet.Packets.Http ...@@ -16,7 +16,7 @@ namespace PcapDotNet.Packets.Http
public HttpTransferEncodingField(IList<string> transferCodings) public HttpTransferEncodingField(IList<string> transferCodings)
:base(Name, transferCodings.SequenceToString(",")) :base(Name, transferCodings.SequenceToString(","))
{ {
TransferCodings = transferCodings.AsReadOnly(); SetTransferCodings(transferCodings);
} }
public HttpTransferEncodingField(params string[] transferCodings) public HttpTransferEncodingField(params string[] transferCodings)
...@@ -24,36 +24,47 @@ namespace PcapDotNet.Packets.Http ...@@ -24,36 +24,47 @@ namespace PcapDotNet.Packets.Http
{ {
} }
internal HttpTransferEncodingField(byte[] fieldValue)
: base(Name, fieldValue)
{
string fieldValueString = HttpRegex.GetString(fieldValue);
Match match = _regex.Match(fieldValueString);
if (!match.Success)
return;
TransferCodings = match.Groups[RegexTransferCodingGroupName].Captures.Cast<Capture>().Select(capture => capture.Value).ToArray().AsReadOnly(); public bool Equals(HttpTransferEncodingField other)
{
return other != null &&
(ReferenceEquals(TransferCodings, other.TransferCodings) ||
TransferCodings != null && other.TransferCodings != null && TransferCodings.SequenceEqual(other.TransferCodings));
} }
public bool Equals(HttpTransferEncodingField other) public ReadOnlyCollection<string> TransferCodings{get{return _transferCodings;}}
{
return other != null && private void SetTransferCodings(IList<string> transferCodings)
(ReferenceEquals(TransferCodings, other.TransferCodings) || {
TransferCodings != null && other.TransferCodings != null && TransferCodings.SequenceEqual(other.TransferCodings)); if (transferCodings.Any(coding => coding.Any(c => c.IsUpperCaseAlpha())))
} _transferCodings = transferCodings.Select(coding => coding.ToLowerInvariant()).ToArray().AsReadOnly();
else
public ReadOnlyCollection<string> TransferCodings { get; private set; } _transferCodings = _transferCodings.AsReadOnly();
}
public override bool Equals(HttpField other) public override bool Equals(HttpField other)
{ {
return Equals(other as HttpTransferEncodingField); return Equals(other as HttpTransferEncodingField);
} }
internal HttpTransferEncodingField(byte[] fieldValue)
: base(Name, fieldValue)
{
string fieldValueString = HttpRegex.GetString(fieldValue);
Match match = _regex.Match(fieldValueString);
if (!match.Success)
return;
SetTransferCodings(match.Groups[RegexTransferCodingGroupName].Captures.Cast<Capture>().Select(capture => capture.Value).ToArray());
}
protected override string ValueToString() protected override string ValueToString()
{ {
return TransferCodings == null ? string.Empty : TransferCodings.SequenceToString(","); return TransferCodings == null ? string.Empty : TransferCodings.SequenceToString(",");
} }
private ReadOnlyCollection<string> _transferCodings;
private static readonly Regex _valueRegex = HttpRegex.Or(HttpRegex.Token, HttpRegex.QuotedString); private static readonly Regex _valueRegex = HttpRegex.Or(HttpRegex.Token, HttpRegex.QuotedString);
private static readonly Regex _attributeRegex = HttpRegex.Token; private static readonly Regex _attributeRegex = HttpRegex.Token;
private static readonly Regex _parameterRegex = HttpRegex.Concat(_attributeRegex, HttpRegex.Build("="), _valueRegex); private static readonly Regex _parameterRegex = HttpRegex.Concat(_attributeRegex, HttpRegex.Build("="), _valueRegex);
......
...@@ -111,6 +111,7 @@ ...@@ -111,6 +111,7 @@
<Compile Include="Http\ByteExtensions.cs" /> <Compile Include="Http\ByteExtensions.cs" />
<Compile Include="Http\HttpCommaSeparatedField.cs" /> <Compile Include="Http\HttpCommaSeparatedField.cs" />
<Compile Include="Http\HttpCommaSeparatedInnerField.cs" /> <Compile Include="Http\HttpCommaSeparatedInnerField.cs" />
<Compile Include="Http\HttpContentLengthField.cs" />
<Compile Include="Http\HttpDatagram.cs" /> <Compile Include="Http\HttpDatagram.cs" />
<Compile Include="Http\HttpField.cs" /> <Compile Include="Http\HttpField.cs" />
<Compile Include="Http\HttpHeader.cs" /> <Compile Include="Http\HttpHeader.cs" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment