Commit 60f12a94 authored by Brickner_cp's avatar Brickner_cp

Warnings, Code Analysis and Documentation. 3 warnings left.

Ignore annoying Wireshark's frame.time field and use frame.time_epoch instead (in tests).
Code Coverage 93.24%
parent 389166c0
......@@ -68,11 +68,13 @@ namespace PcapDotNet.Core.Test
[TestMethod]
public void ComparePacketsToWiresharkTest()
{
#pragma warning disable 162 // This code is unreachable on purpose
if (IsRetry)
{
ComparePacketsToWireshark(null);
return;
}
#pragma warning restore 162
Random random = new Random();
for (int i = 0; i != 10; ++i)
......@@ -437,15 +439,21 @@ namespace PcapDotNet.Core.Test
{
switch (field.Name())
{
case "frame.time":
string fieldShow = field.Show();
if (fieldShow == "Not representable")
break;
fieldShow = fieldShow.Substring(0, fieldShow.Length - 2);
DateTime fieldTimestamp = fieldShow[4] == ' '
? DateTime.ParseExact(fieldShow, "MMM d, yyyy HH:mm:ss.fffffff", CultureInfo.InvariantCulture)
: DateTime.ParseExact(fieldShow, "MMM dd, yyyy HH:mm:ss.fffffff", CultureInfo.InvariantCulture);
MoreAssert.IsInRange(fieldTimestamp.AddSeconds(-2), fieldTimestamp.AddSeconds(2), packet.Timestamp, "Timestamp");
// case "frame.time":
// string fieldShow = field.Show();
// if (fieldShow == "Not representable")
// break;
// fieldShow = fieldShow.Substring(0, fieldShow.Length - 2);
// DateTime fieldTimestamp = fieldShow[4] == ' '
// ? DateTime.ParseExact(fieldShow, "MMM d, yyyy HH:mm:ss.fffffff", CultureInfo.InvariantCulture)
// : DateTime.ParseExact(fieldShow, "MMM dd, yyyy HH:mm:ss.fffffff", CultureInfo.InvariantCulture);
// MoreAssert.IsInRange(fieldTimestamp.AddSeconds(-2), fieldTimestamp.AddSeconds(2), packet.Timestamp, "Timestamp");
// break;
case "frame.time_epoch":
double timeEpoch = double.Parse(field.Show());
DateTime fieldTimestamp = new DateTime(1970, 1, 1).AddSeconds(timeEpoch);
MoreAssert.IsInRange(fieldTimestamp.AddSeconds(-2), fieldTimestamp.AddSeconds(2), packet.Timestamp.ToUniversalTime(), "Timestamp");
break;
case "frame.len":
......@@ -1203,7 +1211,7 @@ namespace PcapDotNet.Core.Test
{
// todo seems like a bug in tshark https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=5235
break;
field.AssertShowDecimal(tcpDatagram.Length);
// field.AssertShowDecimal(tcpDatagram.Length);
}
else
field.AssertShowDecimal(tcpDatagram.Payload.Length);
......
......@@ -141,7 +141,7 @@ namespace PcapDotNet.Packets.Test
"Cache-Control: no-cache\r\n",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(
new HttpField("Cache-Control", "no-cache")));
HttpField.CreateField("Cache-Control", "no-cache")));
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"A:B\r\n" +
......@@ -153,11 +153,11 @@ namespace PcapDotNet.Packets.Test
"E: I,J\r\n",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(
new HttpField("A", "B"),
new HttpField("B", "C"),
new HttpField("C", "D"),
new HttpField("D", "E,F"),
new HttpField("E", "G,H,I,J")));
HttpField.CreateField("A", "B"),
HttpField.CreateField("B", "C"),
HttpField.CreateField("C", "D"),
HttpField.CreateField("D", "E,F"),
HttpField.CreateField("E", "G,H,I,J")));
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"\r\n" +
......@@ -171,7 +171,7 @@ namespace PcapDotNet.Packets.Test
"B: C\r\n",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(
new HttpField("A", "B")), string.Empty);
HttpField.CreateField("A", "B")), string.Empty);
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"A: B\r\n" +
......@@ -179,7 +179,7 @@ namespace PcapDotNet.Packets.Test
"B: C\r\n",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(
new HttpField("A", "B")));
HttpField.CreateField("A", "B")));
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"A: B\r\n" +
......@@ -187,50 +187,50 @@ namespace PcapDotNet.Packets.Test
"B: C\r\n",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(
new HttpField("A", "B"),
new HttpField("abc", ""),
new HttpField("B", "C")));
HttpField.CreateField("A", "B"),
HttpField.CreateField("abc", ""),
HttpField.CreateField("B", "C")));
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"A:B\r\n" +
"B",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(new HttpField("A", "B")));
new HttpHeader(HttpField.CreateField("A", "B")));
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"A:B\r\n" +
"B\r\n",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(new HttpField("A", "B")));
new HttpHeader(HttpField.CreateField("A", "B")));
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"A:B\r\n" +
"B\r\n" +
"C:D\r\n",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(new HttpField("A", "B")));
new HttpHeader(HttpField.CreateField("A", "B")));
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"A:B\r\n" +
"B:",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(new HttpField("A", "B")));
new HttpHeader(HttpField.CreateField("A", "B")));
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"A:B\r\n" +
"B:\r\n",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(new HttpField("A", "B"),
new HttpField("B", string.Empty)));
new HttpHeader(HttpField.CreateField("A", "B"),
HttpField.CreateField("B", string.Empty)));
TestHttpRequest("GET /url HTTP/1.1\r\n" +
"A:B\r\n" +
"B:\r\n" +
"C:D\r\n",
"GET", "/url", HttpVersion.Version11,
new HttpHeader(new HttpField("A", "B"),
new HttpField("B", string.Empty),
new HttpField("C", "D")));
new HttpHeader(HttpField.CreateField("A", "B"),
HttpField.CreateField("B", string.Empty),
HttpField.CreateField("C", "D")));
// Request Body
TestHttpRequest("GET /url HTTP/1.1\r\n" +
......@@ -268,7 +268,7 @@ namespace PcapDotNet.Packets.Test
"Cache-Control: no-cache\r\n",
HttpVersion.Version11, 200, "OK",
new HttpHeader(
new HttpField("Cache-Control", "no-cache")));
HttpField.CreateField("Cache-Control", "no-cache")));
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",
......@@ -341,8 +341,8 @@ namespace PcapDotNet.Packets.Test
"--THIS_STRING_SEPARATES--",
HttpVersion.Version11, 206, "Partial content",
new HttpHeader(
new HttpField("Date", "Wed, 15 Nov 1995 06:25:24 GMT"),
new HttpField("Last-modified", "Wed, 15 Nov 1995 04:58:08 GMT"),
HttpField.CreateField("Date", "Wed, 15 Nov 1995 06:25:24 GMT"),
HttpField.CreateField("Last-modified", "Wed, 15 Nov 1995 04:58:08 GMT"),
new HttpContentTypeField("multipart", "byteranges",
new HttpFieldParameters(new KeyValuePair<string, string>("boundary", "THIS_STRING_SEPARATES")))),
"--THIS_STRING_SEPARATES\r\n" +
......@@ -383,12 +383,12 @@ namespace PcapDotNet.Packets.Test
HttpRequestDatagram request = (HttpRequestDatagram)https[0];
Assert.AreEqual(new HttpRequestMethod(HttpRequestKnownMethod.Get), request.Method);
Assert.AreEqual("/url1", request.Uri);
Assert.AreEqual(new HttpHeader(new HttpField("A", "B"), new HttpField("B", "C")), request.Header);
Assert.AreEqual(new HttpHeader(HttpField.CreateField("A", "B"), HttpField.CreateField("B", "C")), request.Header);
request = (HttpRequestDatagram)https[1];
Assert.AreEqual(new HttpRequestMethod(HttpRequestKnownMethod.Get), request.Method);
Assert.AreEqual("/url2", request.Uri);
Assert.AreEqual(new HttpHeader(new HttpField("C", "D"), new HttpField("D", "E")), request.Header);
Assert.AreEqual(new HttpHeader(HttpField.CreateField("C", "D"), HttpField.CreateField("D", "E")), request.Header);
}
[TestMethod]
......
......@@ -1122,7 +1122,7 @@ namespace PcapDotNet.Packets.TestUtils
{
fieldName = random.NextHttpToken();
} while (fieldNames.Contains(fieldName));
return new HttpField(fieldName, random.NextHttpFieldValue());
return HttpField.CreateField(fieldName, random.NextHttpFieldValue());
case HttpTransferEncodingField.FieldNameUpper:
int numTransferCodings = random.Next(1, 10);
......
......@@ -15,16 +15,27 @@ namespace PcapDotNet.Packets.Http
public const string FieldName = "Content-Length";
/// <summary>
/// The field name in lowercase.
/// The field name in uppercase.
/// </summary>
public const string FieldNameUpper = "CONTENT-LENGTH";
/// <summary>
/// Creates a Content Length Field according to a given content length.
/// </summary>
/// <param name="contentLength">
/// The size of the entity-body, in decimal number of OCTETs, sent to the recipient or,
/// in the case of the HEAD method, the size of the entity-body that would have been sent had the request been a GET.
/// </param>
public HttpContentLengthField(uint contentLength)
:base(FieldName, contentLength.ToString(CultureInfo.InvariantCulture))
{
ContentLength = contentLength;
}
/// <summary>
/// The size of the entity-body, in decimal number of OCTETs, sent to the recipient or,
/// in the case of the HEAD method, the size of the entity-body that would have been sent had the request been a GET.
/// </summary>
public uint? ContentLength { get; private set;}
internal HttpContentLengthField(byte[] fieldValue)
......
......@@ -7,11 +7,38 @@ using PcapDotNet.Base;
namespace PcapDotNet.Packets.Http
{
/// <summary>
/// RFC 2616.
/// The Content-Type entity-header field indicates the media type of the entity-body sent to the recipient or, in the case of the HEAD method,
/// the media type that would have been sent had the request been a GET.
///
/// <pre>
/// Content-Type = "Content-Type" ":" media-type
/// </pre>
///
/// An example of the field is
/// <pre>
/// Content-Type: text/html; charset=ISO-8859-4
/// </pre>
/// </summary>
public class HttpContentTypeField : HttpField, IEquatable<HttpContentTypeField>
{
/// <summary>
/// The field name.
/// </summary>
public const string FieldName = "Content-Type";
/// <summary>
/// The field name in uppercase.
/// </summary>
public const string FieldNameUpper = "CONTENT-TYPE";
/// <summary>
/// Creates a Content Type Field according to the given media type, media subtype and parameters.
/// </summary>
/// <param name="mediaType">The main type of the content of this HTTP message.</param>
/// <param name="mediaSubtype">The subtype of the content of this HTTP message.</param>
/// <param name="parameters">Parameters on the specific type.</param>
public HttpContentTypeField(string mediaType, string mediaSubtype, HttpFieldParameters parameters)
: base(FieldName, string.Format(CultureInfo.InvariantCulture, "{0}/{1}{2}", mediaType, mediaSubtype, parameters))
{
......@@ -20,6 +47,25 @@ namespace PcapDotNet.Packets.Http
Parameters = parameters;
}
/// <summary>
/// The main type of the content of this HTTP message.
/// </summary>
public string MediaType { get; private set; }
/// <summary>
/// The subtype of the content of this HTTP message.
/// </summary>
public string MediaSubtype { get; private set; }
/// <summary>
/// Parameters on the specific type.
/// </summary>
public HttpFieldParameters Parameters { get; private set; }
/// <summary>
/// True iff the two fields are equal.
/// Two content type fields are equal if they have the same media type and subtype and same parameters.
/// </summary>
public bool Equals(HttpContentTypeField other)
{
return other != null &&
......@@ -28,10 +74,10 @@ namespace PcapDotNet.Packets.Http
Parameters.Equals(other.Parameters);
}
public string MediaType { get; private set; }
public string MediaSubtype { get; private set; }
public HttpFieldParameters Parameters { get; private set;}
/// <summary>
/// True iff the two fields are equal.
/// Two content type fields are equal if they have the same media type and subtype and same parameters.
/// </summary>
public override bool Equals(HttpField other)
{
return Equals(other as HttpContentTypeField);
......
......@@ -207,13 +207,29 @@ namespace PcapDotNet.Packets.Http
public Datagram Body { get; set; }
}
/// <summary>
/// True iff the message is a request and iff the message is not a response.
/// </summary>
public abstract bool IsRequest { get; }
/// <summary>
/// True iff the message is a response and iff the message is not a request.
/// </summary>
public bool IsResponse { get { return !IsRequest; } }
/// <summary>
/// The version of this HTTP message.
/// </summary>
public HttpVersion Version { get; private set;}
/// <summary>
/// The header of the HTTP message.
/// </summary>
public HttpHeader Header { get; private set;}
/// <summary>
/// Message Body.
/// </summary>
public Datagram Body { get; private set; }
internal static HttpDatagram CreateDatagram(byte[] buffer, int offset, int length)
......
......@@ -11,8 +11,17 @@ using PcapDotNet.Base;
namespace PcapDotNet.Packets.Http
{
/// <summary>
/// An HTTP field with a name and value.
/// </summary>
public class HttpField : IEquatable<HttpField>
{
/// <summary>
/// Creates a field according to the field name and value.
/// </summary>
/// <param name="fieldName">The name of the field to create.</param>
/// <param name="fieldValue">The bytes value of the field to create.</param>
/// <returns>The constructed HTTP field.</returns>
public static HttpField CreateField(string fieldName, byte[] fieldValue)
{
if (fieldName == null)
......@@ -32,65 +41,45 @@ namespace PcapDotNet.Packets.Http
}
}
public HttpField(string name, string value)
: this(name, value, _defaultEncoding)
{
}
public HttpField(string name, string value, Encoding encoding)
: this(name, encoding == null ? null : encoding.GetBytes(NormalizeValue(value)))
{
}
private static string NormalizeValue(string value)
{
StringBuilder stringBuilder = new StringBuilder(value.Length);
int offset = 0;
while (offset != value.Length)
{
if (value[offset] == '"')
{
int start = offset;
++offset;
while (offset != value.Length && value[offset] != '"')
{
if (value[offset] == '\\' && offset != value.Length - 1)
++offset;
++offset;
}
if (value[offset] == '"')
++offset;
stringBuilder.Append(value.Substring(start, offset - start));
}
else if (value[offset] == '\t' || value[offset] == ' ' || value[offset] == '\r' || value[offset] == '\n')
/// <summary>
/// Creates a field according to the field name and encoded string value.
/// </summary>
/// <param name="fieldName">The name of the field to create.</param>
/// <param name="fieldValue">The value of the field to create encoded in the given encoding.</param>
/// <param name="fieldValueEncoding">The encoding that encodes the given field value.</param>
/// <returns>The constructed HTTP field.</returns>
public static HttpField CreateField(string fieldName, string fieldValue, Encoding fieldValueEncoding)
{
stringBuilder.Append(' ');
++offset;
while (offset != value.Length && (value[offset] == '\t' || value[offset] == ' ' || value[offset] == '\r' || value[offset] == '\n'))
++offset;
}
else
{
stringBuilder.Append(value[offset]);
++offset;
}
}
return stringBuilder.ToString();
}
if (fieldValueEncoding == null)
throw new ArgumentNullException("fieldValueEncoding");
public HttpField(string name, IList<byte> value)
:this(name, value.AsReadOnly())
{
return CreateField(fieldName, fieldValueEncoding.GetBytes(NormalizeValue(fieldValue)));
}
public HttpField(string name, ReadOnlyCollection<byte> value)
/// <summary>
/// Creates a field according to the field name and encoded string value.
/// </summary>
/// <param name="fieldName">The name of the field to create.</param>
/// <param name="fieldValue">The value of the field to create encoded in ISO-8859-1 encoding.</param>
/// <returns>The constructed HTTP field.</returns>
public static HttpField CreateField(string fieldName, string fieldValue)
{
Name = name;
Value = value;
return CreateField(fieldName, fieldValue, _defaultEncoding);
}
/// <summary>
/// The name of the field.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// The Value of the field.
/// </summary>
public ReadOnlyCollection<byte> Value { get; private set; }
/// <summary>
/// The Value of the field as a string using ISO-8859-1 encoding.
/// </summary>
public string ValueString
{
get
......@@ -99,6 +88,9 @@ namespace PcapDotNet.Packets.Http
}
}
/// <summary>
/// The number of bytes the field will take in the HTTP protocol.
/// </summary>
public int Length
{
get
......@@ -107,26 +99,62 @@ namespace PcapDotNet.Packets.Http
}
}
/// <summary>
/// True iff the two HTTP fields are of equal value.
/// Two fields are equal iff they have the same name (case insensitive) and the same bytes value.
/// </summary>
public virtual bool Equals(HttpField other)
{
return other != null && Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) && Value.SequenceEqual(other.Value);
}
/// <summary>
/// True iff the two HTTP fields are of equal value.
/// Two fields are equal iff they have the same name (case insensitive) and the same bytes value.
/// </summary>
public override bool Equals(object obj)
{
return Equals(obj as HttpField);
}
/// <summary>
/// Returns a hash code of this field according to the name and value.
/// </summary>
public override int GetHashCode()
{
return Name.ToUpperInvariant().GetHashCode() ^ Value.BytesSequenceGetHashCode();
}
/// <summary>
/// A string representing the field similar to how it would like in the HTTP protocol.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0}: {1}", Name, ValueString);
}
internal HttpField(string name, string value)
: this(name, value, _defaultEncoding)
{
}
internal HttpField(string name, string value, Encoding encoding)
: this(name, encoding == null ? null : encoding.GetBytes(NormalizeValue(value)))
{
}
internal HttpField(string name, IList<byte> value)
: this(name, value.AsReadOnly())
{
}
internal HttpField(string name, ReadOnlyCollection<byte> value)
{
Name = name;
Value = value;
}
internal void Write(byte[] buffer, ref int offset)
{
buffer.Write(ref offset, Name, Encoding.ASCII);
......@@ -136,6 +164,42 @@ namespace PcapDotNet.Packets.Http
buffer.WriteCarriageReturnLinefeed(ref offset);
}
private static string NormalizeValue(string value)
{
StringBuilder stringBuilder = new StringBuilder(value.Length);
int offset = 0;
while (offset != value.Length)
{
if (value[offset] == '"')
{
int start = offset;
++offset;
while (offset != value.Length && value[offset] != '"')
{
if (value[offset] == '\\' && offset != value.Length - 1)
++offset;
++offset;
}
if (value[offset] == '"')
++offset;
stringBuilder.Append(value.Substring(start, offset - start));
}
else if (value[offset] == '\t' || value[offset] == ' ' || value[offset] == '\r' || value[offset] == '\n')
{
stringBuilder.Append(' ');
++offset;
while (offset != value.Length && (value[offset] == '\t' || value[offset] == ' ' || value[offset] == '\r' || value[offset] == '\n'))
++offset;
}
else
{
stringBuilder.Append(value[offset]);
++offset;
}
}
return stringBuilder.ToString();
}
private static readonly Encoding _defaultEncoding = Encoding.GetEncoding(28591);
}
}
\ No newline at end of file
......@@ -8,20 +8,41 @@ using PcapDotNet.Base;
namespace PcapDotNet.Packets.Http
{
/// <summary>
/// Represents a set of HTTP field parameters.
/// Used for some of HTTP fields.
/// All parameters must have different names.
/// </summary>
public class HttpFieldParameters : IEnumerable<KeyValuePair<string, string>>, IEquatable<HttpFieldParameters>
{
/// <summary>
/// Creates the parameters from an array of parameters. Keys are the parameters names and values are the parameters values.
/// </summary>
public HttpFieldParameters(params KeyValuePair<string, string>[] parameters)
:this((IEnumerable<KeyValuePair<string, string>>)parameters)
{
}
/// <summary>
/// Creates the parameters from an enumerable of parameters. Keys are the parameters names and values are the parameters values.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
public HttpFieldParameters(IEnumerable<KeyValuePair<string, string>> parameters)
{
_parameters = parameters.ToDictionary(pair => pair.Key, pair => pair.Value);
}
/// <summary>
/// Number of parameters.
/// </summary>
public int Count { get { return _parameters.Count; } }
/// <summary>
/// Returns the value of the given parameter name.
/// </summary>
/// <param name="name">The name of the parameter.</param>
/// <returns>The value of the parameter.</returns>
public string this[string name]
{
get
......@@ -33,6 +54,9 @@ namespace PcapDotNet.Packets.Http
}
}
/// <summary>
/// Enumerates over the parameters.
/// </summary>
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return _parameters.GetEnumerator();
......@@ -43,31 +67,42 @@ namespace PcapDotNet.Packets.Http
return GetEnumerator();
}
/// <summary>
/// Two HTTP field parameters are equal if all of their parameters are equal.
/// </summary>
public bool Equals(HttpFieldParameters other)
{
return other != null && _parameters.DictionaryEquals(other._parameters);
}
/// <summary>
/// Two HTTP field parameters are equal if all of their parameters are equal.
/// </summary>
public override bool Equals(object obj)
{
return Equals(obj as HttpFieldParameters);
}
/// <summary>
/// Xor of all of the hash codes of the parameters names and values.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return
_parameters.Select(pair => new KeyValuePair<string, string>(pair.Key.ToUpperInvariant(), pair.Value))
.OrderBy(pair => pair.Key)
.Xor(pair => pair.Key.GetHashCode() ^ pair.Value.GetHashCode());
}
/// <summary>
/// Returns a string of parameters beginning and separated by semicolon and equal sign between keys and values.
/// </summary>
public override string ToString()
{
if (!this.Any())
return string.Empty;
StringBuilder stringBuilder = new StringBuilder();
// stringBuilder.Append(" ");
foreach (var parameter in this)
{
stringBuilder.Append(";");
......
......@@ -6,20 +6,37 @@ using PcapDotNet.Base;
namespace PcapDotNet.Packets.Http
{
/// <summary>
/// Represents HTTP header.
/// The header is a container for HTTP fields.
/// Insensitive to the case of HTTP field names.
/// </summary>
public class HttpHeader : IEnumerable<HttpField>, IEquatable<HttpHeader>
{
/// <summary>
/// An empty HTTP header.
/// </summary>
public static HttpHeader Empty { get { return _empty; } }
/// <summary>
/// Creates a header from an enumerable of fields.
/// </summary>
public HttpHeader(IEnumerable<HttpField> fields)
{
_fields = fields.ToDictionary(field => field.Name, field => field, StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Creates a header from an array of fields.
/// </summary>
public HttpHeader(params HttpField[] fields)
:this((IEnumerable<HttpField>)fields)
{
}
/// <summary>
/// The number of bytes the header takes.
/// </summary>
public int BytesLength
{
get
......@@ -28,11 +45,20 @@ namespace PcapDotNet.Packets.Http
}
}
/// <summary>
/// Returns the field with the given field name or null if it doesn't exist.
/// Case insensitive.
/// </summary>
/// <param name="fieldName">The case insensitive name of the field.</param>
/// <returns>The field with the matching case insensitive name or null if it doesn't exist.</returns>
public HttpField this[string fieldName]
{
get { return GetField<HttpField>(fieldName); }
}
/// <summary>
/// The HTTP Transfer Encoding field if it exists (null otherwise).
/// </summary>
public HttpTransferEncodingField TransferEncoding
{
get
......@@ -41,6 +67,9 @@ namespace PcapDotNet.Packets.Http
}
}
/// <summary>
/// The HTTP Content Length field if it exists (null otherwise).
/// </summary>
public HttpContentLengthField ContentLength
{
get
......@@ -49,6 +78,9 @@ namespace PcapDotNet.Packets.Http
}
}
/// <summary>
/// The HTTP Content Type field if it exists (null otherwise).
/// </summary>
public HttpContentTypeField ContentType
{
get
......@@ -57,27 +89,42 @@ namespace PcapDotNet.Packets.Http
}
}
/// <summary>
/// Two HTTP headers are equal if they have the same fields with the same values.
/// </summary>
public bool Equals(HttpHeader other)
{
return other != null &&
_fields.DictionaryEquals(other._fields);
}
/// <summary>
/// Two HTTP headers are equal if they have the same fields with the same values.
/// </summary>
public override bool Equals(object obj)
{
return Equals(obj as HttpHeader);
}
/// <summary>
/// Xor of the hash codes of the fields.
/// </summary>
public override int GetHashCode()
{
return _fields.Select(pair => pair.Value).SequenceGetHashCode();
}
/// <summary>
/// Returns a string of all the fields with endline separators.
/// </summary>
public override string ToString()
{
return this.SequenceToString("\r\n");
}
/// <summary>
/// Enumerates over the HTTP fields.
/// </summary>
public IEnumerator<HttpField> GetEnumerator()
{
return _fields.Values.GetEnumerator();
......@@ -88,6 +135,29 @@ namespace PcapDotNet.Packets.Http
return GetEnumerator();
}
/// <summary>
/// Writes the HTTP header to the given buffer in the given offset.
/// </summary>
/// <param name="buffer">The buffer to write the header to.</param>
/// <param name="offset">The offset in the given buffer to start writing the header.</param>
public void Write(byte[] buffer, int offset)
{
Write(buffer, ref offset);
}
/// <summary>
/// Writes the HTTP header to the given buffer in the given offset.
/// Increments the offset by the number of bytes written.
/// </summary>
/// <param name="buffer">The buffer to write the header to.</param>
/// <param name="offset">The offset in the given buffer to start writing the header. Incremented by the number of bytes written.</param>
public void Write(byte[] buffer, ref int offset)
{
foreach (HttpField field in this)
field.Write(buffer, ref offset);
buffer.WriteCarriageReturnLinefeed(ref offset);
}
internal HttpHeader(IEnumerable<KeyValuePair<string, IEnumerable<byte>>> fields)
{
var mergedFields = new Dictionary<string, IEnumerable<byte>>(StringComparer.OrdinalIgnoreCase);
......@@ -107,18 +177,6 @@ namespace PcapDotNet.Packets.Http
_fields = mergedFields.ToDictionary(field => field.Key, field => HttpField.CreateField(field.Key, field.Value.ToArray()), StringComparer.OrdinalIgnoreCase);
}
public void Write(byte[] buffer, int offset)
{
Write(buffer, ref offset);
}
public void Write(byte[] buffer, ref int offset)
{
foreach (HttpField field in this)
field.Write(buffer, ref offset);
buffer.WriteCarriageReturnLinefeed(ref offset);
}
private T GetField<T>(string fieldName) where T : HttpField
{
HttpField field;
......
......@@ -8,12 +8,34 @@ namespace PcapDotNet.Packets.Http
/// </summary>
public abstract class HttpLayer : SimpleLayer, IEquatable<HttpLayer>
{
/// <summary>
/// True iff the message is a request and iff the message is not a response.
/// </summary>
public abstract bool IsRequest { get; }
/// <summary>
/// True iff the message is a response and iff the message is not a request.
/// </summary>
public bool IsResponse { get { return !IsRequest; } }
/// <summary>
/// The version of this HTTP message.
/// </summary>
public HttpVersion Version { get; set; }
/// <summary>
/// The header of the HTTP message.
/// </summary>
public HttpHeader Header { get; set; }
/// <summary>
/// Message Body.
/// </summary>
public Datagram Body { get; set; }
/// <summary>
/// The number of bytes this layer will take.
/// </summary>
public override int Length
{
get
......@@ -24,11 +46,19 @@ namespace PcapDotNet.Packets.Http
}
}
/// <summary>
/// Two HTTP layers are equal iff they have the same version, header and body.
/// Extended by specific HTTP layers types for more checks.
/// </summary>
public override bool Equals(Layer other)
{
return Equals(other as HttpLayer);
}
/// <summary>
/// Two HTTP layers are equal iff they have the same version, header and body.
/// Extended by specific HTTP layers types for more checks.
/// </summary>
public virtual bool Equals(HttpLayer other)
{
return other != null &&
......@@ -37,6 +67,11 @@ namespace PcapDotNet.Packets.Http
(ReferenceEquals(Body, other.Body) || Body.Equals(other.Body));
}
/// <summary>
/// Writes the HTTP layer to the buffer.
/// </summary>
/// <param name="buffer">The buffer to write the layer to.</param>
/// <param name="offset">The offset in the buffer to start writing the layer at.</param>
protected override void Write(byte[] buffer, int offset)
{
WriteFirstLine(buffer, ref offset);
......@@ -46,7 +81,7 @@ namespace PcapDotNet.Packets.Http
buffer.Write(offset, Body);
}
protected abstract int FirstLineLength { get; }
protected abstract void WriteFirstLine(byte[] buffer, ref int offset);
internal abstract int FirstLineLength { get; }
internal abstract void WriteFirstLine(byte[] buffer, ref int offset);
}
}
\ No newline at end of file
......@@ -14,14 +14,27 @@ namespace PcapDotNet.Packets.Http
public string Uri { get; set; }
}
/// <summary>
/// True since the message is a request.
/// </summary>
public override bool IsRequest
{
get { return true; }
}
/// <summary>
/// The HTTP Request Method.
/// </summary>
public HttpRequestMethod Method { get; private set; }
/// <summary>
/// The HTTP Request URI.
/// </summary>
public string Uri { get; private set; }
/// <summary>
/// Creates a Layer that represents the datagram to be used with PacketBuilder.
/// </summary>
public override ILayer ExtractLayer()
{
return new HttpRequestLayer
......
......@@ -9,17 +9,32 @@ namespace PcapDotNet.Packets.Http
/// </summary>
public class HttpRequestLayer : HttpLayer, IEquatable<HttpRequestLayer>
{
/// <summary>
/// True since the message is a request.
/// </summary>
public override bool IsRequest { get { return true; } }
/// <summary>
/// The HTTP Request Method.
/// </summary>
public HttpRequestMethod Method { get; set; }
/// <summary>
/// The HTTP Request URI.
/// </summary>
public string Uri { get; set; }
/// <summary>
/// Two HTTP Request layers are equal iff they have the same version, header, body, method and uri.
/// </summary>
public override bool Equals(HttpLayer other)
{
return Equals(other as HttpRequestLayer);
}
/// <summary>
/// Two HTTP Request layers are equal iff they have the same version, header, body, method and uri.
/// </summary>
public bool Equals(HttpRequestLayer other)
{
return base.Equals(other) &&
......@@ -27,7 +42,7 @@ namespace PcapDotNet.Packets.Http
(ReferenceEquals(Uri, other.Uri) || Uri.Equals(other.Uri));
}
protected override int FirstLineLength
internal override int FirstLineLength
{
get
{
......@@ -46,7 +61,7 @@ namespace PcapDotNet.Packets.Http
}
}
protected override void WriteFirstLine(byte[] buffer, ref int offset)
internal override void WriteFirstLine(byte[] buffer, ref int offset)
{
if (Method == null)
return;
......
......@@ -5,13 +5,23 @@ using PcapDotNet.Base;
namespace PcapDotNet.Packets.Http
{
/// <summary>
/// HTTP request method.
/// Example: GET
/// </summary>
public class HttpRequestMethod : IEquatable<HttpRequestMethod>
{
/// <summary>
/// Creates a method from a method string.
/// </summary>
public HttpRequestMethod(string method)
{
Method = method;
}
/// <summary>
/// Creates a method from a known method.
/// </summary>
public HttpRequestMethod(HttpRequestKnownMethod method)
{
Method = method.ToString().ToUpperInvariant();
......@@ -19,8 +29,15 @@ namespace PcapDotNet.Packets.Http
throw new ArgumentException("Invalid known request method given: " + method, "method");
}
/// <summary>
/// The method string.
/// </summary>
public string Method { get; private set; }
/// <summary>
/// Returns the known method that matches the method string.
/// Returns HttpRequestKnownMethod.Unknown if no matching known method could be found.
/// </summary>
public HttpRequestKnownMethod KnownMethod
{
get
......@@ -33,6 +50,9 @@ namespace PcapDotNet.Packets.Http
}
}
/// <summary>
/// The number of bytes this method takes.
/// </summary>
public int Length
{
get { return Method.Length; }
......@@ -54,16 +74,25 @@ namespace PcapDotNet.Packets.Http
return result;
}
/// <summary>
/// Two methods are equal iff they have the same method string.
/// </summary>
public bool Equals(HttpRequestMethod other)
{
return other != null && Method.Equals(other.Method);
}
/// <summary>
/// Two methods are equal iff they have the same method string.
/// </summary>
public override bool Equals(object obj)
{
return Equals(obj as HttpRequestMethod);
}
/// <summary>
/// The hash code of the method string.
/// </summary>
public override int GetHashCode()
{
return Method.GetHashCode();
......
......@@ -15,15 +15,29 @@ namespace PcapDotNet.Packets.Http
public uint? StatusCode{ get; set;}
}
/// <summary>
/// False since this is message is a response.
/// </summary>
public override bool IsRequest
{
get { return false; }
}
/// <summary>
/// The status code of the response.
/// null if no status code exists.
/// </summary>
public uint? StatusCode{get; private set;}
/// <summary>
/// The data of the reason phrase.
/// Example: OK
/// </summary>
public Datagram ReasonPhrase { get; private set;}
/// <summary>
/// Creates a Layer that represents the datagram to be used with PacketBuilder.
/// </summary>
public override ILayer ExtractLayer()
{
return new HttpResponseLayer
......
......@@ -9,17 +9,34 @@ namespace PcapDotNet.Packets.Http
/// </summary>
public class HttpResponseLayer : HttpLayer, IEquatable<HttpResponseLayer>
{
/// <summary>
/// false since this is a response.
/// </summary>
public override bool IsRequest { get { return false; } }
/// <summary>
/// The status code of the response.
/// null if no status code exists.
/// </summary>
public uint? StatusCode { get; set; }
/// <summary>
/// The data of the reason phrase.
/// Example: OK
/// </summary>
public Datagram ReasonPhrase { get; set; }
/// <summary>
/// Two HTTP response layers are equal iff they have the same version, header, body, status code and reason phrase.
/// </summary>
public override bool Equals(HttpLayer other)
{
return Equals(other as HttpResponseLayer);
}
/// <summary>
/// Two HTTP response layers are equal iff they have the same version, header, body, status code and reason phrase.
/// </summary>
public bool Equals(HttpResponseLayer other)
{
return base.Equals(other) &&
......@@ -27,7 +44,7 @@ namespace PcapDotNet.Packets.Http
(ReferenceEquals(ReasonPhrase, other.ReasonPhrase) || ReasonPhrase.Equals(other.ReasonPhrase));
}
protected override int FirstLineLength
internal override int FirstLineLength
{
get
{
......@@ -48,7 +65,7 @@ namespace PcapDotNet.Packets.Http
}
}
protected override void WriteFirstLine(byte[] buffer, ref int offset)
internal override void WriteFirstLine(byte[] buffer, ref int offset)
{
if (Version == null)
return;
......
......@@ -8,23 +8,87 @@ using PcapDotNet.Base;
namespace PcapDotNet.Packets.Http
{
/// <summary>
/// RFC 2616.
/// The Transfer-Encoding general-header field indicates what (if any) type of transformation has been applied to the message body
/// in order to safely transfer it between the sender and the recipient.
/// This differs from the content-coding in that the transfer-coding is a property of the message, not of the entity.
///
/// <pre>
/// Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding
/// </pre>
///
/// Example:
///
/// <pre>
/// Transfer-Encoding: chunked
/// </pre>
///
/// If multiple encodings have been applied to an entity, the transfer-codings MUST be listed in the order in which they were applied.
/// Additional information about the encoding parameters MAY be provided by other entity-header fields not defined by this specification.
/// </summary>
public class HttpTransferEncodingField : HttpField, IEquatable<HttpTransferEncodingField>
{
/// <summary>
/// The field name.
/// </summary>
public const string FieldName = "Transfer-Encoding";
/// <summary>
/// The field name in uppercase.
/// </summary>
public const string FieldNameUpper = "TRANSFER-ENCODING";
private const string RegexTransferCodingGroupName = "TransferCoding";
/// <summary>
/// Creates an HTTP transfer encoding field from a set of transfer codings.
/// </summary>
public HttpTransferEncodingField(IList<string> transferCodings)
:base(FieldName, transferCodings.SequenceToString(","))
{
SetTransferCodings(transferCodings);
}
/// <summary>
/// Creates an HTTP transfer encoding field from a set of transfer codings.
/// </summary>
public HttpTransferEncodingField(params string[] transferCodings)
:this((IList<string>)transferCodings)
{
}
/// <summary>
/// Transfer-coding values are used to indicate an encoding transformation that has been, can be,
/// or may need to be applied to an entity-body in order to ensure "safe transport" through the network.
/// This differs from a content coding in that the transfer-coding is a property of the message, not of the original entity.
///
/// <pre>
/// transfer-coding = "chunked" | transfer-extension
/// transfer-extension = token *( ";" parameter )
/// </pre>
///
/// Parameters are in the form of attribute/value pairs.
///
/// <pre>
/// parameter = attribute "=" value
/// attribute = token
/// value = token | quoted-string
/// </pre>
///
/// All transfer-coding values are case-insensitive.
/// Whenever a transfer-coding is applied to a message-body, the set of transfer-codings MUST include "chunked",
/// unless the message is terminated by closing the connection.
/// When the "chunked" transfer-coding is used, it MUST be the last transfer-coding applied to the message-body.
/// The "chunked" transfer-coding MUST NOT be applied more than once to a message-body.
/// These rules allow the recipient to determine the transfer-length of the message.
/// </summary>
public ReadOnlyCollection<string> TransferCodings { get { return _transferCodings; } }
/// <summary>
/// True iff the two HTTP transfer encoding fields are of equal value.
/// Two HTTP transfer encoding fields are equal iff they have the same transfer codings.
/// </summary>
public bool Equals(HttpTransferEncodingField other)
{
return other != null &&
......@@ -32,17 +96,10 @@ namespace PcapDotNet.Packets.Http
TransferCodings != null && other.TransferCodings != null && TransferCodings.SequenceEqual(other.TransferCodings));
}
public ReadOnlyCollection<string> TransferCodings{get{return _transferCodings;}}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
private void SetTransferCodings(IList<string> transferCodings)
{
if (transferCodings.Any(coding => coding.Any(c => c.IsUppercaseAlpha())))
_transferCodings = transferCodings.Select(coding => coding.ToLowerInvariant()).ToArray().AsReadOnly();
else
_transferCodings = transferCodings.AsReadOnly();
}
/// <summary>
/// True iff the two HTTP transfer encoding fields are of equal value.
/// Two HTTP transfer encoding fields are equal iff they have the same transfer codings.
/// </summary>
public override bool Equals(HttpField other)
{
return Equals(other as HttpTransferEncodingField);
......@@ -59,6 +116,15 @@ namespace PcapDotNet.Packets.Http
SetTransferCodings(match.GroupCapturesValues(RegexTransferCodingGroupName).ToArray());
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
private void SetTransferCodings(IList<string> transferCodings)
{
if (transferCodings.Any(coding => coding.Any(c => c.IsUppercaseAlpha())))
_transferCodings = transferCodings.Select(coding => coding.ToLowerInvariant()).ToArray().AsReadOnly();
else
_transferCodings = transferCodings.AsReadOnly();
}
private ReadOnlyCollection<string> _transferCodings;
private static readonly Regex _transferExtensionRegex = HttpRegex.Concat(HttpRegex.Token, HttpRegex.OptionalParameters);
......
......@@ -10,28 +10,58 @@ namespace PcapDotNet.Packets.Http
/// </summary>
public class HttpVersion : IEquatable<HttpVersion>
{
/// <summary>
/// Creates a version from the major and minor version numbers.
/// </summary>
/// <param name="major">The major version number. 0 for 0.9, 1 for 1.0 or 1.1.</param>
/// <param name="minor">The minor version number. 9 for 0.9, 0, for 1.0 and 1 for 1.1.</param>
public HttpVersion(uint major, uint minor)
{
Major = major;
Minor = minor;
}
/// <summary>
/// A built version for HTTP/1.0.
/// </summary>
public static HttpVersion Version10 { get { return _version10; } }
/// <summary>
/// A built version for HTTP/1.1.
/// </summary>
public static HttpVersion Version11 { get { return _version11; } }
/// <summary>
/// The major version number.
/// </summary>
public uint Major { get; private set; }
/// <summary>
/// The minor version number.
/// </summary>
public uint Minor { get; private set; }
/// <summary>
/// The number of bytes this version takes.
/// </summary>
public int Length
{
get { return _httpSlashBytes.Length + Major.DigitsCount(10) + 1 + Minor.DigitsCount(10); }
}
/// <summary>
/// A string represneting the version.
/// Example: &quot;HTTP/1.1&quot;.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "HTTP/{0}.{1}", Major, Minor);
}
/// <summary>
/// Two HTTP versions are equal iff they have the same major and minor versions.
/// </summary>
public bool Equals(HttpVersion other)
{
return other != null &&
......@@ -39,11 +69,17 @@ namespace PcapDotNet.Packets.Http
Minor == other.Minor;
}
/// <summary>
/// Two HTTP versions are equal iff they have the same major and minor versions.
/// </summary>
public override bool Equals(object obj)
{
return Equals(obj as HttpVersion);
}
/// <summary>
/// The hash code of an http version is the xor of the hash codes of the minor version and the major version.
/// </summary>
public override int GetHashCode()
{
return Minor.GetHashCode() ^ Major.GetHashCode();
......
......@@ -75,6 +75,9 @@ namespace PcapDotNet.Packets.Igmp
/// </summary>
LeaveGroupVersion2 = 0x17,
/// <summary>
/// Multicast Traceroute Response.
/// </summary>
MulticastTraceRouteResponse = 0x1E,
}
}
\ No newline at end of file
......@@ -80,6 +80,9 @@ namespace PcapDotNet.Packets.IpV4
get { return EthernetType.IpV4; }
}
/// <summary>
/// The protocol that should be written in the previous (IPv4) layer (in this case: IP).
/// </summary>
public IpV4Protocol PreviousLayerProtocol
{
get { return IpV4Protocol.Ip; }
......
......@@ -256,6 +256,9 @@ namespace PcapDotNet.Packets.Transport
};
}
/// <summary>
/// The first HTTP message in this TCP datagram.
/// </summary>
public HttpDatagram Http
{
get
......@@ -264,6 +267,9 @@ namespace PcapDotNet.Packets.Transport
}
}
/// <summary>
/// All of the available HTTP messages in this TCP datagram.
/// </summary>
public ReadOnlyCollection<HttpDatagram> HttpCollection
{
get
......
......@@ -69,6 +69,10 @@ namespace PcapDotNet.Packets.Transport
/// The minimum number of bytes this option take.
/// </summary>
public const int OptionMinimumLength = OptionHeaderLength + OptionValueMinimumLength;
/// <summary>
/// The maximum number of bytes this option take.
/// </summary>
public const int OptionMaximumLength = OptionHeaderLength + OptionValueMaximumLength;
/// <summary>
......@@ -103,6 +107,9 @@ namespace PcapDotNet.Packets.Transport
/// </summary>
public TcpOptionMoodEmotion Emotion { get; private set; }
/// <summary>
/// The ASCII string of the emotion.
/// </summary>
public string EmotionString
{
get
......@@ -123,11 +130,6 @@ namespace PcapDotNet.Packets.Transport
get { return OptionHeaderLength + ValueLength; }
}
public int ValueLength
{
get { return EmotionString.Length; }
}
/// <summary>
/// True iff this option may appear at most once in a datagram.
/// </summary>
......@@ -189,6 +191,11 @@ namespace PcapDotNet.Packets.Transport
buffer.Write(ref offset, Encoding.ASCII.GetBytes(EmotionString));
}
private int ValueLength
{
get { return EmotionString.Length; }
}
private static TcpOptionMoodEmotion StringToEmotion(string emotionString)
{
TcpOptionMoodEmotion emotion;
......
......@@ -65,6 +65,9 @@
/// </summary>
Evil,
/// <summary>
/// An unknown emotion.
/// </summary>
None,
}
}
\ No newline at end of file
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