Subversion Repositories general

Compare Revisions

Ignore whitespace Rev 1232 → Rev 1233

/TCPproxy/trunk/Network.cs
11,7 → 11,11
// FIXME deny write access to all public properties
// FIXME for text/xml get encoding from body if not specified in header
// FIXME option to not store parsed data, just raw packets and reparse on demand
// FIXME implement chunk-encoding
// FIXME implement all others (not 'identity' and 'chunked') transfer-encodings
// FIXME benchmark one-by-one byte iterator vs. block read
// FIXME recongize "100 (Continue)" response, use next (real) response as part of same
// request-response interaction
// FIXME split HttpMessage class to request and response
namespace TCPproxy
{
public enum BinLogTypes
54,9 → 58,11
V1_1
}
 
public enum HttpEncoding
public enum HttpTransferEncoding
{
Identify,
None, // there is no body
Identity,
Chunked,
Gzip,
Compress,
Deflate,
63,6 → 69,16
Unknown
}
 
public enum HttpContentEncoding
{
None, // there is no body
Identity,
Gzip,
Compress,
Deflate,
Unknown
}
 
public class TcpLogEventArgs : EventArgs
{
private readonly LogLevel level;
407,11 → 423,6
}
}
 
public override string ToString() // FIXME delete the method
{
return id + " " + startTimestamp.ToString("HH:mm:ss.ffff");
}
 
protected void SetLocalPoint(IPEndPoint localPoint)
{
this.localPoint = localPoint;
1382,17 → 1393,14
{
tcp = newTcp;
tcpLen = tcp.Length;
tcpPos = 0;
tcpPos = -1;
if(nextMessageEvent != null) nextMessageEvent.Set();
}
}
 
public byte CurrentOctet()
public void Back()
{
if(tcp == null || tcpPos >= tcpLen) NextTcp();
if(tcpEnd) return 0;
 
return tcp.Bytes[tcpPos];
tcpPos--; // FIXME double check if it's always possible to go to _one_ step back
}
 
public byte NextOctet()
1399,12 → 1407,24
{
tcpPos++;
 
if(tcp == null || tcpPos >= tcpLen) NextTcp();
if(tcp == null || tcpPos >= tcpLen)
{
NextTcp();
tcpPos++;
}
if(tcpEnd) return 0;
 
return tcp.Bytes[tcpPos];
}
 
public byte NextOctetAndBack()
{
byte b = NextOctet();
if(!tcpEnd) Back();
 
return b;
}
 
public void SetDirection(TcpMessageDirection direction)
{
do
1422,7 → 1442,7
if(!tcpEnd)
{
tcpLen = tcp.Length;
tcpPos = 0;
tcpPos = -1;
}
}
}
1500,7 → 1520,7
SetRequestProperties(http);
http.UpdateHttpMessage();
 
bool fullLength = ParseBody(requestPos, http, true);
ParseBody(requestPos, http, true);
if("text" == http.RequestContentType && "xml" == http.RequestContentSubtype)
{
http.RequestXml = new XmlMessage(http.RequestText);
1507,7 → 1527,6
}
http.UpdateHttpMessage();
 
if(fullLength) requestPos.NextOctet();
http.RequestComplete = true;
http.UpdateHttpMessage();
 
1566,7 → 1585,7
SetResponseProperties(http);
http.UpdateHttpMessage();
 
bool fullLength = ParseBody(responsePos, http, false);
ParseBody(responsePos, http, false);
if("text" == http.ResponseContentType && "xml" == http.ResponseContentSubtype)
{
http.ResponseXml = new XmlMessage(http.ResponseText);
1573,7 → 1592,6
}
http.UpdateHttpMessage();
 
if(fullLength) responsePos.NextOctet();
http.ResponseComplete = true;
http.UpdateHttpMessage();
}
1589,11 → 1607,12
StringBuilder res = new StringBuilder(100);
int len = 0;
 
for(byte b = pos.CurrentOctet(); !pos.IsEnd && tokenChars[b]; b = pos.NextOctet())
for(byte b = pos.NextOctet(); !pos.IsEnd && tokenChars[b]; b = pos.NextOctet())
{
res.Append(charValues[b]);
if(limit > 0 && limit < ++len) return null; // length limit
}
pos.Back();
 
return res.ToString();
}
1603,11 → 1622,12
StringBuilder res = new StringBuilder(1024);
int len = 0;
 
for(byte b = pos.CurrentOctet(); !pos.IsEnd && b != 32 && b != 13 && b != 10; b = pos.NextOctet())
for(byte b = pos.NextOctet(); !pos.IsEnd && b != 32 && b != 13 && b != 10; b = pos.NextOctet())
{ // <space> <cr> <lf>
res.Append(charValues[b]);
if(limit > 0 && limit < ++len) return null; // length limit
}
pos.Back();
 
return res.ToString();
}
1617,11 → 1637,12
StringBuilder res = new StringBuilder(1024);
int len = 0;
 
for(byte b = pos.CurrentOctet(); !pos.IsEnd && b != 13; b = pos.NextOctet())
for(byte b = pos.NextOctet(); !pos.IsEnd && b != 13; b = pos.NextOctet())
{ // <cr>
res.Append(charValues[b]);
if(limit > 0 && limit < ++len) return null; // length limit
}
pos.Back();
 
return res.ToString();
}
1628,26 → 1649,24
 
private void ExpectSpace(ParsePosition pos)
{
if(pos.IsEnd || pos.CurrentOctet() != 32)
if(pos.IsEnd || pos.NextOctet() != 32)
throw new HttpParseException("Space expected");
 
pos.NextOctet();
}
 
private void ExpectCRLF(ParsePosition pos)
{
if(pos.IsEnd || pos.CurrentOctet() != 13)
if(pos.IsEnd || pos.NextOctet() != 13)
throw new HttpParseException("Carriage return expected");
if(pos.IsEnd || pos.NextOctet() != 10)
throw new HttpParseException("Linefeed expected");
 
pos.NextOctet();
}
 
private void SkipEmptyLines(ParsePosition pos)
{
while(pos.CurrentOctet() == 13)
while(pos.NextOctetAndBack() == 13)
{
ExpectCRLF(pos);
}
}
 
private void ParseRequestLine(ParsePosition pos, HttpMessage http)
1667,25 → 1686,17
throw new HttpParseException("Unexpected end of message");
 
// EoL or version
byte b = pos.CurrentOctet();
byte b = pos.NextOctet();
if(b == 13)
{
if(pos.IsEnd || pos.NextOctet() != 10)
{
throw new HttpParseException("Linefeed expected");
}
else
{
if(!pos.IsEnd) ExpectCRLF(pos);
http.RequestVersion = HttpVersion.V0_9;
return;
}
pos.Back();
ExpectCRLF(pos);
http.RequestVersion = HttpVersion.V0_9;
}
else if(b != 32)
{
throw new HttpParseException("HTTP version expected");
}
pos.NextOctet();
 
// check version
string versionStr = GetUntilEoL(pos, 20);
1717,17 → 1728,10
if(pos.IsEnd)
throw new HttpParseException("Unexpected end of message");
 
if(pos.CurrentOctet() == 13)
if(pos.NextOctetAndBack() == 13)
{
if(pos.IsEnd || pos.NextOctet() != 10)
{
throw new HttpParseException("Linefeed expected");
}
else
{
pos.NextOctet(); // end of header, move to body
return;
}
ExpectCRLF(pos);
return; // end of header, move to body
}
if(pos.IsEnd) return; // end of TCP messages
 
1735,10 → 1739,9
if(name == null || name.Length == 0)
throw new HttpParseException("Request header name expected");
 
if(pos.IsEnd || pos.CurrentOctet() != 58) // :
if(pos.IsEnd || pos.NextOctet() != 58) // :
throw new HttpParseException("Request header value expected");
 
pos.NextOctet();
string s = TrimHeaderValue(GetUntilEoL(pos, 0));
 
ExpectCRLF(pos);
1838,34 → 1841,59
return buf.ToString();
}
 
private HttpEncoding ParseEncoding(string encoding)
private HttpTransferEncoding ParseTransferEncoding(string encoding)
{
if(encoding == null || encoding == "identity")
return HttpEncoding.Identify;
if(encoding == null) return HttpTransferEncoding.None;
encoding = encoding.ToLower();
 
if(encoding == "identity")
return HttpTransferEncoding.Identity;
else if(encoding == "chunked")
return HttpTransferEncoding.Chunked;
else if(encoding == "gzip")
return HttpEncoding.Gzip;
return HttpTransferEncoding.Gzip;
else if(encoding == "compress")
return HttpEncoding.Compress;
return HttpTransferEncoding.Compress;
else if(encoding == "deflate")
return HttpEncoding.Deflate;
return HttpTransferEncoding.Deflate;
else
return HttpEncoding.Unknown;
return HttpTransferEncoding.Unknown;
}
 
private HttpContentEncoding ParseContentEncoding(string encoding)
{
if(encoding == null) return HttpContentEncoding.None;
encoding = encoding.ToLower();
 
if(encoding == "identity")
return HttpContentEncoding.Identity;
else if(encoding == "gzip")
return HttpContentEncoding.Gzip;
else if(encoding == "compress")
return HttpContentEncoding.Compress;
else if(encoding == "deflate")
return HttpContentEncoding.Deflate;
else
return HttpContentEncoding.Unknown;
}
 
private void SetRequestProperties(HttpMessage http)
{
// length
string contentLength = (string)http.RequestHeadersHash["Content-Length"];
string contentLength = (string)http.RequestHeadersHash["content-length"];
if(contentLength != null)
{
http.RequestLength = int.Parse(contentLength);
}
 
// encoding
http.RequestEncoding = ParseEncoding((string)http.RequestHeadersHash["Content-Encoding"]);
// transfer encoding
http.RequestTransferEncoding = ParseTransferEncoding((string)http.RequestHeadersHash["transfer-encoding"]);
 
// content encoding
http.RequestContentEncoding = ParseContentEncoding((string)http.RequestHeadersHash["content-encoding"]);
 
// type & charset
string contentType = (string)http.RequestHeadersHash["Content-Type"];
string contentType = (string)http.RequestHeadersHash["content-type"];
if(contentType != null)
{
Match match = Regex.Match(contentType, @"^\s*(\S+)/(\S+)\s*($|;\s*(charset=""?(\S+)""?)?)");
1885,22 → 1913,76
}
}
 
private bool ParseBody(ParsePosition pos, HttpMessage http, bool request)
// RFC 2616: 4.3
private bool WaitForBody(HttpMessage http, bool isRequest)
{
if(request && http.RequestMethod != "POST") return false;
if(isRequest)
{
return (http.RequestLength > 0)
|| (http.RequestTransferEncoding != HttpTransferEncoding.None);
}
else
{
if(http.RequestMethod == "HEAD") return false;
if(http.ResponseStatusCode < 200) return false;
if(http.ResponseStatusCode == 204) return false;
if(http.ResponseStatusCode == 304) return false;
 
return true;
}
}
 
private void ParseBody(ParsePosition pos, HttpMessage http, bool isRequest)
{
if(!WaitForBody(http, isRequest)) return;
 
// FIXME parse and save on-the-fly, dont wait util end of message
 
byte[] bin = new byte[8*1024];
int len = 0;
int limit = (request ? http.RequestLength : http.ResponseLength);
HttpEncoding encoding = (request ? http.RequestEncoding : http.ResponseEncoding);
string contentType = (request ? http.RequestContentType : http.ResponseContentType);
string contentSubtype = (request ? http.RequestContentSubtype : http.ResponseContentSubtype);
string charset = (request ? http.RequestCharset : http.ResponseCharset);
byte[] bin = new byte[8*1024];
int len = 0; // current bin biffer length
int limit = (isRequest ? http.RequestLength : http.ResponseLength);
int chunkLen = -1; // current length of current chunk
int chunkLimit = -1;
HttpTransferEncoding transferEncoding = (isRequest ? http.RequestTransferEncoding : http.ResponseTransferEncoding);
HttpContentEncoding contentEncoding = (isRequest ? http.RequestContentEncoding : http.ResponseContentEncoding);
string contentType = (isRequest ? http.RequestContentType : http.ResponseContentType);
string contentSubtype = (isRequest ? http.RequestContentSubtype : http.ResponseContentSubtype);
string charset = (isRequest ? http.RequestCharset : http.ResponseCharset);
 
for(byte b = pos.CurrentOctet(); !pos.IsEnd; b = pos.NextOctet())
for(byte b = pos.NextOctet(); !pos.IsEnd; b = pos.NextOctet())
{
// RFC 2616: 3.6.1
if(transferEncoding == HttpTransferEncoding.Chunked && chunkLimit < 0)
{
// FIXME recognize chunk-extension here
 
// get chunk length
pos.Back();
string chunkLimitStr = GetUntilEoL(pos, 40);
if(pos.IsEnd || chunkLimitStr == null || chunkLimitStr.Length == 0)
throw new HttpParseException("Chunk length expected");
 
try
{
chunkLimit = Convert.ToInt32(chunkLimitStr, 16);
}
catch(Exception)
{
throw new HttpParseException("Cannot parse chunk length");
}
 
ExpectCRLF(pos);
 
if(chunkLimit == 0) { // the end marker
ExpectCRLF(pos);
break;
}
 
chunkLen = 0;
b = pos.NextOctet();
}
 
// grow array if full
if(len >= bin.Length)
{
byte[] newBin = new byte[bin.Length*2];
1909,14 → 1991,32
}
 
bin[len++] = b;
if(limit > 0 && limit <= len) // full length
chunkLen++;
if(chunkLimit > 0)
{
break;
if(chunkLen >= chunkLimit) // full chunk
{
ExpectCRLF(pos);
chunkLimit = -1; // try to find length of next chunk on next iteration
}
}
else
{
if(limit > 0 && len >= limit) // full length
{
break;
}
}
}
if(transferEncoding == HttpTransferEncoding.Chunked && chunkLimit > 0 && chunkLen != chunkLimit)
{
throw new HttpParseException("Incomplete chunk found");
}
 
// FIXME parse entity-headers for chunked encoding here
 
string text = null;
if(encoding == HttpEncoding.Identify && contentType == "text")
if(contentEncoding == HttpContentEncoding.Identity && contentType == "text")
{
try
{
1925,11 → 2025,11
}
catch(NotSupportedException)
{
Console.WriteLine("Unsupported encoding: " + charset);
Console.WriteLine("Unsupported charset: " + charset);
}
}
 
if(request)
if(isRequest)
{
http.RequestLength = len;
http.RequestBody = bin;
1941,8 → 2041,6
http.ResponseBody = bin;
http.ResponseText = text;
}
 
return (limit > 0 && limit <= len); // full length reached, need to go to next octet
}
 
private void ParseResponseLine(ParsePosition pos, HttpMessage http)
1995,18 → 2093,22
private void SetResponseProperties(HttpMessage http)
{
// length
HttpHeader contentLength = (HttpHeader)http.ResponseHeadersHash["Content-Length"];
HttpHeader contentLength = (HttpHeader)http.ResponseHeadersHash["content-length"];
if(contentLength != null)
{
http.ResponseLength = int.Parse(contentLength.Values[0]);
}
 
// encoding
HttpHeader contentEncoding = (HttpHeader)http.ResponseHeadersHash["Content-Encoding"];
http.ResponseEncoding = ParseEncoding((contentEncoding == null) ? null : contentEncoding.Values[0]);
// transfer encoding
HttpHeader transferEncoding = (HttpHeader)http.ResponseHeadersHash["transfer-encoding"];
http.ResponseTransferEncoding = ParseTransferEncoding((transferEncoding == null) ? null : transferEncoding.Values[0]);
 
// content encoding
HttpHeader contentEncoding = (HttpHeader)http.ResponseHeadersHash["content-encoding"];
http.ResponseContentEncoding = ParseContentEncoding((contentEncoding == null) ? null : contentEncoding.Values[0]);
 
// type & charset
HttpHeader contentType = (HttpHeader)http.ResponseHeadersHash["Content-Type"];
HttpHeader contentType = (HttpHeader)http.ResponseHeadersHash["content-type"];
if(contentType != null)
{
Match match = Regex.Match(contentType.Values[0], @"^\s*(\S+)/(\S+)\s*($|;\s*(charset=""?(\S+)""?)?)");
2065,38 → 2167,40
 
public class HttpMessage
{
private bool requestComplete = false;
private HttpVersion requestVersion;
private string requestMethod;
private string requestUri;
private LinkedList requestHeaders = new LinkedList();
private Hashtable requestHeadersHash = new Hashtable();
private int requestLength = -1; // -1 == unknown
private HttpEncoding requestEncoding = HttpEncoding.Identify;
private string requestContentType;
private string requestContentSubtype;
private string requestCharset;
private string soapAction;
private byte[] requestBody;
private string requestText;
private XmlMessage requestXml;
private DateTime requestStartTimestamp = DateTime.MinValue;
private bool requestComplete = false;
private HttpVersion requestVersion;
private string requestMethod;
private string requestUri;
private LinkedList requestHeaders = new LinkedList();
private Hashtable requestHeadersHash = new Hashtable();
private int requestLength = -1; // -1 == unknown
private HttpTransferEncoding requestTransferEncoding = HttpTransferEncoding.None;
private HttpContentEncoding requestContentEncoding = HttpContentEncoding.None;
private string requestContentType;
private string requestContentSubtype;
private string requestCharset;
private string soapAction;
private byte[] requestBody;
private string requestText;
private XmlMessage requestXml;
private DateTime requestStartTimestamp = DateTime.MinValue;
 
private bool responseComplete = false;
private HttpVersion responseVersion;
private int responseStatusCode;
private string responseStatusMessage;
private LinkedList responseHeaders = new LinkedList();
private Hashtable responseHeadersHash = new Hashtable();
private int responseLength = -1; // -1 == unknown
private HttpEncoding responseEncoding = HttpEncoding.Identify;
private string responseContentType;
private string responseContentSubtype;
private string responseCharset;
private byte[] responseBody;
private string responseText;
private XmlMessage responseXml;
private DateTime responseStartTimestamp = DateTime.MinValue;
private bool responseComplete = false;
private HttpVersion responseVersion;
private int responseStatusCode;
private string responseStatusMessage;
private LinkedList responseHeaders = new LinkedList();
private Hashtable responseHeadersHash = new Hashtable();
private int responseLength = -1; // -1 == unknown
private HttpTransferEncoding responseTransferEncoding = HttpTransferEncoding.None;
private HttpContentEncoding responseContentEncoding = HttpContentEncoding.None;
private string responseContentType;
private string responseContentSubtype;
private string responseCharset;
private byte[] responseBody;
private string responseText;
private XmlMessage responseXml;
private DateTime responseStartTimestamp = DateTime.MinValue;
 
public bool RequestComplete
{
2138,12 → 2242,18
set { requestLength = value; }
}
 
public HttpEncoding RequestEncoding
public HttpTransferEncoding RequestTransferEncoding
{
get { return requestEncoding; }
set { requestEncoding = value; }
get { return requestTransferEncoding; }
set { requestTransferEncoding = value; }
}
 
public HttpContentEncoding RequestContentEncoding
{
get { return requestContentEncoding; }
set { requestContentEncoding = value; }
}
 
public string RequestContentType
{
get { return requestContentType; }
2232,12 → 2342,18
set { responseLength = value; }
}
 
public HttpEncoding ResponseEncoding
public HttpTransferEncoding ResponseTransferEncoding
{
get { return responseEncoding; }
set { responseEncoding = value; }
get { return responseTransferEncoding; }
set { responseTransferEncoding = value; }
}
 
public HttpContentEncoding ResponseContentEncoding
{
get { return responseContentEncoding; }
set { responseContentEncoding = value; }
}
 
public string ResponseContentType
{
get { return responseContentType; }
2283,7 → 2399,7
public void AddRequestHeader(string name, string headerValue)
{
requestHeaders.Add(new HttpHeader(name, headerValue));
requestHeadersHash.Add(name, headerValue);
requestHeadersHash.Add(name.ToLower(), headerValue);
}
 
public void AddResponseHeader(string name, string headerValue)
2293,7 → 2409,7
{
header = new HttpHeader(name, headerValue);
responseHeaders.Add(header);
responseHeadersHash.Add(name, header);
responseHeadersHash.Add(name.ToLower(), header);
}
else
{
2301,12 → 2417,6
}
}
 
public override string ToString() // FIXME delete the method
{
return (soapAction != null ? soapAction
: (requestMethod == null ? "" : requestMethod) + " " + (requestUri == null ? "" : requestUri));
}
 
public event TcpEventHandler Update;
 
protected virtual void OnUpdate(TcpEventArgs e)