8,14 → 8,13 |
using System.Text.RegularExpressions; |
using System.Xml; |
|
// 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 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 |
// FIXME: |
// - deny write access to all public properties |
// - for text/xml get encoding from body if not specified in header |
// - option to not store parsed data, just raw packets and reparse on demand |
// - implement all others (not 'identity' and 'chunked') transfer- and content- encodings |
// - benchmark one-by-one byte iterator vs. block read |
// - locks for HttpMessage in parser |
namespace TCPproxy |
{ |
public enum BinLogTypes |
296,6 → 295,7 |
private IPEndPoint localPoint; |
private IPEndPoint remotePoint; |
private LinkedList messages = new LinkedList(); |
private LinkedListReadOnly messagesReadOnly; |
private SocketState localState = SocketState.None; |
private SocketState remoteState = SocketState.None; |
private SocketWorker worker; |
332,9 → 332,9 |
get { return remotePoint; } |
} |
|
public LinkedList Messages |
public LinkedListReadOnly Messages |
{ |
get { return messages; } // FIXME return read-only object |
get { return new LinkedListReadOnly(messages); } |
} |
|
public SocketState LocalState |
373,6 → 373,7 |
{ |
this.id = id; |
this.httpParser = HttpParser.Parse(this); |
this.messagesReadOnly = new LinkedListReadOnly(messages); |
} |
|
internal void Continue(IPAddress resendHost, int resendPort, Socket localSocket) |
1511,23 → 1512,23 |
|
messages.AddHttpMessage(http); |
SkipEmptyLines(requestPos); |
http.RequestStartTimestamp = requestPos.CurrentMessage.Timestamp; |
http.Request.StartTimestamp = requestPos.CurrentMessage.Timestamp; |
|
ParseRequestLine(requestPos, http); |
http.UpdateHttpMessage(); |
|
ParseHeaders(requestPos, http, true); |
SetRequestProperties(http); |
ParseHeaders(requestPos, http, http.Request); |
SetRequestProperties(http, http.Request); |
http.UpdateHttpMessage(); |
|
ParseBody(requestPos, http, true); |
if("text" == http.RequestContentType && "xml" == http.RequestContentSubtype) |
ParseBody(requestPos, http, http.Request); |
if("text" == http.Request.ContentType && "xml" == http.Request.ContentSubtype) |
{ |
http.RequestXml = new XmlMessage(http.RequestText); |
http.Request.Xml = new XmlMessage(http.Request.Text); |
} |
http.UpdateHttpMessage(); |
|
http.RequestComplete = true; |
http.Request.Complete = true; |
http.UpdateHttpMessage(); |
|
SkipEmptyLines(requestPos); |
1571,29 → 1572,23 |
lock(https) |
{ |
if(!httpEnum.MoveNext()) |
throw new Exception("Tried to find response by no HTTP message available"); |
throw new Exception("Tried to find response but no HTTP message available"); |
} |
} |
|
HttpMessage http = (HttpMessage)httpEnum.Current; |
|
ParseResponseLine(responsePos, http); |
http.ResponseStartTimestamp = responsePos.CurrentMessage.Timestamp; |
http.UpdateHttpMessage(); |
ParseResponse(responsePos, http); |
|
ParseHeaders(responsePos, http, false); |
SetResponseProperties(http); |
http.UpdateHttpMessage(); |
|
ParseBody(responsePos, http, false); |
if("text" == http.ResponseContentType && "xml" == http.ResponseContentSubtype) |
if(http.Response.StatusCode == 100) // "100 (Continue)" response |
{ |
http.ResponseXml = new XmlMessage(http.ResponseText); |
http.GotContinueResponse(); |
ParseResponse(responsePos, http); // once again |
} |
http.UpdateHttpMessage(); |
|
http.ResponseComplete = true; |
http.Response.Complete = true; |
http.UpdateHttpMessage(); |
responsePos.NextOctetAndBack(); // go forward to see end of connection |
} |
} |
catch(Exception ex) |
1672,14 → 1667,14 |
private void ParseRequestLine(ParsePosition pos, HttpMessage http) |
{ |
// method |
http.RequestMethod = GetToken(pos, 1024); |
if(http.RequestMethod == null || http.RequestMethod.Length == 0) |
http.Request.Method = GetToken(pos, 1024); |
if(http.Request.Method == null || http.Request.Method.Length == 0) |
throw new HttpParseException("Request method name expected"); |
ExpectSpace(pos); |
|
// URI |
http.RequestUri = GetUntilSpace(pos, 1024); |
if(http.RequestUri == null || http.RequestUri.Length == 0) |
http.Request.Uri = GetUntilSpace(pos, 1024); |
if(http.Request.Uri == null || http.Request.Uri.Length == 0) |
throw new HttpParseException("Request URI expected"); |
|
if(pos.IsEnd) |
1691,7 → 1686,7 |
{ |
pos.Back(); |
ExpectCRLF(pos); |
http.RequestVersion = HttpVersion.V0_9; |
http.Request.Version = HttpVersion.V0_9; |
} |
else if(b != 32) |
{ |
1705,11 → 1700,11 |
|
if(versionStr == "HTTP/1.0") |
{ |
http.RequestVersion = HttpVersion.V1_0; |
http.Request.Version = HttpVersion.V1_0; |
} |
else if(versionStr == "HTTP/1.1") |
{ |
http.RequestVersion = HttpVersion.V1_1; |
http.Request.Version = HttpVersion.V1_1; |
} |
else |
{ |
1719,7 → 1714,7 |
ExpectCRLF(pos); |
} |
|
private void ParseHeaders(ParsePosition pos, HttpMessage http, bool request) |
private void ParseHeaders(ParsePosition pos, HttpMessage http, HttpHalfMessage half) |
{ |
if(pos.IsEnd) return; // end of TCP messages |
|
1737,19 → 1732,16 |
|
string name = GetToken(pos, 0); |
if(name == null || name.Length == 0) |
throw new HttpParseException("Request header name expected"); |
throw new HttpParseException("Header name expected"); |
|
if(pos.IsEnd || pos.NextOctet() != 58) // : |
throw new HttpParseException("Request header value expected"); |
throw new HttpParseException("Header value expected"); |
|
string s = TrimHeaderValue(GetUntilEoL(pos, 0)); |
|
ExpectCRLF(pos); |
|
if(request) |
http.AddRequestHeader(name, s); |
else |
http.AddResponseHeader(name, s); |
half.AddHeader(name, s); |
} |
} |
|
1877,77 → 1869,89 |
return HttpContentEncoding.Unknown; |
} |
|
private void SetRequestProperties(HttpMessage http) |
private void SetHttpProperties(HttpMessage http, HttpHalfMessage half) |
{ |
// length |
string contentLength = (string)http.RequestHeadersHash["content-length"]; |
HttpHeader contentLength = half.GetHeader("content-length"); |
if(contentLength != null) |
{ |
http.RequestLength = int.Parse(contentLength); |
half.Length = int.Parse(contentLength.Values[0]); |
} |
|
// transfer encoding |
http.RequestTransferEncoding = ParseTransferEncoding((string)http.RequestHeadersHash["transfer-encoding"]); |
HttpHeader transferEncoding = half.GetHeader("transfer-encoding"); |
half.TransferEncoding = ParseTransferEncoding((transferEncoding == null) ? null : transferEncoding.Values[0]); |
if(HasBody(http, half) && half.TransferEncoding == HttpTransferEncoding.None) |
half.TransferEncoding = HttpTransferEncoding.Identity; |
|
// content encoding |
http.RequestContentEncoding = ParseContentEncoding((string)http.RequestHeadersHash["content-encoding"]); |
HttpHeader contentEncoding = half.GetHeader("content-encoding"); |
half.ContentEncoding = ParseContentEncoding((contentEncoding == null) ? null : contentEncoding.Values[0]); |
if(HasBody(http, half) && half.ContentEncoding == HttpContentEncoding.None) |
half.ContentEncoding = HttpContentEncoding.Identity; |
|
// type & charset |
string contentType = (string)http.RequestHeadersHash["content-type"]; |
HttpHeader contentType = half.GetHeader("content-type"); |
if(contentType != null) |
{ |
Match match = Regex.Match(contentType, @"^\s*(\S+)/(\S+)\s*($|;\s*(charset=""?(\S+)""?)?)"); |
Match match = Regex.Match(contentType.Values[0], @"^\s*(\S+)/(\S+)\s*($|;\s*(charset=""?(\S+)""?)?)"); |
if(match.Success) |
{ |
http.RequestContentType = match.Groups[1].Captures[0].Value; |
http.RequestContentSubtype = match.Groups[2].Captures[0].Value; |
if(match.Groups.Count >= 6) http.RequestCharset = match.Groups[5].Captures[0].Value.Trim('"'); |
half.ContentType = match.Groups[1].Captures[0].Value; |
half.ContentSubtype = match.Groups[2].Captures[0].Value; |
if(match.Groups.Count >= 6 && match.Groups[5].Captures.Count > 0) |
half.Charset = match.Groups[5].Captures[0].Value.Trim('"'); |
} |
} |
} |
|
private void SetRequestProperties(HttpMessage http, HttpRequest request) |
{ |
SetHttpProperties(http, request); |
|
// soap action |
string soapAction = (string)http.RequestHeadersHash["soapaction"]; |
HttpHeader soapAction = request.GetHeader("soapaction"); |
if(soapAction != null) |
{ |
http.SoapAction = soapAction.Trim('"'); |
request.SoapAction = soapAction.Values[0]; |
} |
} |
|
// RFC 2616: 4.3 |
private bool WaitForBody(HttpMessage http, bool isRequest) |
private bool HasBody(HttpMessage http, HttpHalfMessage half) |
{ |
if(isRequest) |
if(half.IsRequest) |
{ |
return (http.RequestLength > 0) |
|| (http.RequestTransferEncoding != HttpTransferEncoding.None); |
return (http.Request.Length > 0) |
|| (http.Request.TransferEncoding != 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; |
if(http.Request.Method == "HEAD") return false; |
if(http.Response.StatusCode < 200) return false; |
if(http.Response.StatusCode == 204) return false; |
if(http.Response.StatusCode == 304) return false; |
|
return true; |
} |
} |
|
private void ParseBody(ParsePosition pos, HttpMessage http, bool isRequest) |
private void ParseBody(ParsePosition pos, HttpMessage http, HttpHalfMessage half) |
{ |
if(!WaitForBody(http, isRequest)) return; |
if(!HasBody(http, half)) return; |
|
// FIXME parse and save on-the-fly, dont wait util end of message |
|
byte[] bin = new byte[8*1024]; |
int len = 0; // current bin biffer length |
int limit = (isRequest ? http.RequestLength : http.ResponseLength); |
int limit = half.Length; |
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); |
HttpTransferEncoding transferEncoding = half.TransferEncoding; |
HttpContentEncoding contentEncoding = half.ContentEncoding; |
string contentType = half.ContentType; |
string contentSubtype = half.ContentSubtype; |
string charset = half.Charset; |
|
for(byte b = pos.NextOctet(); !pos.IsEnd; b = pos.NextOctet()) |
{ |
2029,18 → 2033,27 |
} |
} |
|
if(isRequest) |
{ |
http.RequestLength = len; |
http.RequestBody = bin; |
http.RequestText = text; |
half.Length = len; |
half.Body = bin; |
half.Text = text; |
} |
else |
|
private void ParseResponse(ParsePosition pos, HttpMessage http) |
{ |
http.ResponseLength = len; |
http.ResponseBody = bin; |
http.ResponseText = text; |
ParseResponseLine(pos, http); |
http.Response.StartTimestamp = pos.CurrentMessage.Timestamp; |
http.UpdateHttpMessage(); |
|
ParseHeaders(pos, http, http.Response); |
SetHttpProperties(http, http.Response); |
http.UpdateHttpMessage(); |
|
ParseBody(pos, http, http.Response); |
if("text" == http.Response.ContentType && "xml" == http.Response.ContentSubtype) |
{ |
http.Response.Xml = new XmlMessage(http.Response.Text); |
} |
http.UpdateHttpMessage(); |
} |
|
private void ParseResponseLine(ParsePosition pos, HttpMessage http) |
2052,11 → 2065,11 |
|
if(versionStr == "HTTP/1.0") |
{ |
http.ResponseVersion = HttpVersion.V1_0; |
http.Response.Version = HttpVersion.V1_0; |
} |
else if(versionStr == "HTTP/1.1") |
{ |
http.ResponseVersion = HttpVersion.V1_1; |
http.Response.Version = HttpVersion.V1_1; |
} |
else |
{ |
2073,7 → 2086,7 |
{ |
int c = int.Parse(code); |
if(c < 100 || c >= 1000) throw new HttpParseException("Status code expected"); |
http.ResponseStatusCode = c; |
http.Response.StatusCode = c; |
} |
catch(FormatException) |
{ |
2082,7 → 2095,7 |
ExpectSpace(pos); |
|
// status message |
http.ResponseStatusMessage = GetUntilEoL(pos, 0); |
http.Response.StatusMessage = GetUntilEoL(pos, 0); |
|
if(pos.IsEnd) |
throw new HttpParseException("Unexpected end of message"); |
2089,40 → 2102,8 |
|
ExpectCRLF(pos); |
} |
|
private void SetResponseProperties(HttpMessage http) |
{ |
// length |
HttpHeader contentLength = (HttpHeader)http.ResponseHeadersHash["content-length"]; |
if(contentLength != null) |
{ |
http.ResponseLength = int.Parse(contentLength.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"]; |
if(contentType != null) |
{ |
Match match = Regex.Match(contentType.Values[0], @"^\s*(\S+)/(\S+)\s*($|;\s*(charset=""?(\S+)""?)?)"); |
if(match.Success) |
{ |
http.ResponseContentType = match.Groups[1].Captures[0].Value; |
http.ResponseContentSubtype = match.Groups[2].Captures[0].Value; |
if(match.Groups.Count >= 6 && match.Groups[5].Captures.Count > 0) |
http.ResponseCharset = match.Groups[5].Captures[0].Value.Trim('"'); |
} |
} |
} |
} |
|
public class HttpHeader |
{ |
private string name; |
2165,257 → 2146,199 |
} |
} |
|
public class HttpMessage |
public abstract class HttpHalfMessage |
{ |
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 complete = false; |
private HttpVersion version; |
protected LinkedList headers = new LinkedList(); |
protected Hashtable headersHash = new Hashtable(); |
private int length = -1; // -1 == unknown |
private HttpTransferEncoding transferEncoding = HttpTransferEncoding.None; |
private HttpContentEncoding contentEncoding = HttpContentEncoding.None; |
private string contentType; |
private string contentSubtype; |
private string charset; |
private byte[] body; |
private string text; |
private XmlMessage xml; |
private DateTime startTimestamp = 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 |
public abstract bool IsRequest |
{ |
get { return requestComplete; } |
set { requestComplete = value; } |
get; |
} |
|
public HttpVersion RequestVersion |
public bool Complete |
{ |
get { return requestVersion; } |
set { requestVersion = value; } |
get { return complete; } |
set { complete = value; } |
} |
|
public string RequestMethod |
public HttpVersion Version |
{ |
get { return requestMethod; } |
set { requestMethod = value; } |
get { return version; } |
set { version = value; } |
} |
|
public string RequestUri |
public LinkedList Headers |
{ |
get { return requestUri; } |
set { requestUri = value; } |
get { return headers; } |
} |
|
public LinkedList RequestHeaders |
public int Length |
{ |
get { return requestHeaders; } |
get { return length; } |
set { length = value; } |
} |
|
public IDictionary RequestHeadersHash |
public HttpTransferEncoding TransferEncoding |
{ |
get { return requestHeadersHash; } |
get { return transferEncoding; } |
set { transferEncoding = value; } |
} |
|
public int RequestLength |
public HttpContentEncoding ContentEncoding |
{ |
get { return requestLength; } |
set { requestLength = value; } |
get { return contentEncoding; } |
set { contentEncoding = value; } |
} |
|
public HttpTransferEncoding RequestTransferEncoding |
public string ContentType |
{ |
get { return requestTransferEncoding; } |
set { requestTransferEncoding = value; } |
get { return contentType; } |
set { contentType = value; } |
} |
|
public HttpContentEncoding RequestContentEncoding |
public string ContentSubtype |
{ |
get { return requestContentEncoding; } |
set { requestContentEncoding = value; } |
get { return contentSubtype; } |
set { contentSubtype = value; } |
} |
|
public string RequestContentType |
public string Charset |
{ |
get { return requestContentType; } |
set { requestContentType = value; } |
get { return charset; } |
set { charset = value; } |
} |
|
public string RequestContentSubtype |
public byte[] Body |
{ |
get { return requestContentSubtype; } |
set { requestContentSubtype = value; } |
get { return body; } |
set { body = value; } |
} |
|
public string RequestCharset |
public string Text |
{ |
get { return requestCharset; } |
set { requestCharset = value; } |
get { return text; } |
set { text = value; } |
} |
|
public string SoapAction |
public XmlMessage Xml |
{ |
get { return soapAction; } |
set { soapAction = value; } |
get { return xml; } |
set { xml = value; } |
} |
|
public byte[] RequestBody |
public DateTime StartTimestamp |
{ |
get { return requestBody; } |
set { requestBody = value; } |
get { return startTimestamp; } |
set { startTimestamp = value; } |
} |
|
public string RequestText |
public void AddHeader(string name, string headerValue) |
{ |
get { return requestText; } |
set { requestText = value; } |
} |
|
public XmlMessage RequestXml |
HttpHeader header = (HttpHeader)headersHash[name]; |
if(header == null) |
{ |
get { return requestXml; } |
set { requestXml = value; } |
header = new HttpHeader(name, headerValue); |
headers.Add(header); |
headersHash.Add(name.ToLower(), header); |
} |
|
public DateTime RequestStartTimestamp |
else |
{ |
get { return requestStartTimestamp; } |
set { requestStartTimestamp = value; } |
header.AddValue(headerValue); |
} |
|
public bool ResponseComplete |
{ |
get { return responseComplete; } |
set { responseComplete = value; } |
} |
|
public HttpVersion ResponseVersion |
public HttpHeader GetHeader(string name) |
{ |
get { return responseVersion; } |
set { responseVersion = value; } |
return (HttpHeader)headersHash[name.ToLower()]; |
} |
|
public int ResponseStatusCode |
{ |
get { return responseStatusCode; } |
set { responseStatusCode = value; } |
} |
|
public string ResponseStatusMessage |
public class HttpRequest : HttpHalfMessage |
{ |
get { return responseStatusMessage; } |
set { responseStatusMessage = value; } |
} |
private string method; |
private string uri; |
private string soapAction; |
|
public LinkedList ResponseHeaders |
public override bool IsRequest |
{ |
get { return responseHeaders; } |
get { return true; } |
} |
|
public IDictionary ResponseHeadersHash |
public string Method |
{ |
get { return responseHeadersHash; } |
get { return method; } |
set { method = value; } |
} |
|
public int ResponseLength |
public string Uri |
{ |
get { return responseLength; } |
set { responseLength = value; } |
get { return uri; } |
set { uri = value; } |
} |
|
public HttpTransferEncoding ResponseTransferEncoding |
public string SoapAction |
{ |
get { return responseTransferEncoding; } |
set { responseTransferEncoding = value; } |
get { return soapAction; } |
set { soapAction = value; } |
} |
|
public HttpContentEncoding ResponseContentEncoding |
{ |
get { return responseContentEncoding; } |
set { responseContentEncoding = value; } |
} |
|
public string ResponseContentType |
public class HttpResponse : HttpHalfMessage |
{ |
get { return responseContentType; } |
set { responseContentType = value; } |
} |
private int statusCode; |
private string statusMessage; |
|
public string ResponseContentSubtype |
public override bool IsRequest |
{ |
get { return responseContentSubtype; } |
set { responseContentSubtype = value; } |
get { return false; } |
} |
|
public string ResponseCharset |
public int StatusCode |
{ |
get { return responseCharset; } |
set { responseCharset = value; } |
get { return statusCode; } |
set { statusCode = value; } |
} |
|
public byte[] ResponseBody |
public string StatusMessage |
{ |
get { return responseBody; } |
set { responseBody = value; } |
get { return statusMessage; } |
set { statusMessage = value; } |
} |
|
public string ResponseText |
{ |
get { return responseText; } |
set { responseText = value; } |
} |
|
public XmlMessage ResponseXml |
public class HttpMessage |
{ |
get { return responseXml; } |
set { responseXml = value; } |
} |
private HttpRequest request = new HttpRequest(); |
private HttpResponse continueResponse = null; |
private HttpResponse response = new HttpResponse(); |
|
public DateTime ResponseStartTimestamp |
public HttpRequest Request |
{ |
get { return responseStartTimestamp; } |
set { responseStartTimestamp = value; } |
get { return request; } |
} |
|
public void AddRequestHeader(string name, string headerValue) |
public HttpResponse ContinueResponse |
{ |
requestHeaders.Add(new HttpHeader(name, headerValue)); |
requestHeadersHash.Add(name.ToLower(), headerValue); |
get { return continueResponse; } |
} |
|
public void AddResponseHeader(string name, string headerValue) |
public HttpResponse Response |
{ |
HttpHeader header = (HttpHeader)responseHeadersHash[name]; |
if(header == null) |
{ |
header = new HttpHeader(name, headerValue); |
responseHeaders.Add(header); |
responseHeadersHash.Add(name.ToLower(), header); |
get { return response; } |
} |
else |
{ |
header.AddValue(headerValue); |
} |
} |
|
public event TcpEventHandler Update; |
|
2431,7 → 2354,13 |
{ |
OnUpdate(new TcpEventArgs()); |
} |
|
internal void GotContinueResponse() |
{ |
continueResponse = response; |
response = new HttpResponse(); |
} |
} |
|
internal class HttpParseException : Exception |
{ |