Commit f320867a authored by titanium007's avatar titanium007

#87 Use header dictionary

parent 216b8c90
......@@ -17,10 +17,10 @@ namespace Titanium.Web.Proxy.Examples.Basic
public void StartProxy()
{
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//proxyServer.BeforeRequest += OnRequest;
//proxyServer.BeforeResponse += OnResponse;
//proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
//proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
......@@ -141,7 +141,9 @@ namespace Titanium.Web.Proxy.Examples.Basic
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
{
e.IsValid = true;
}
return Task.FromResult(0);
}
......
......@@ -47,7 +47,9 @@ namespace Titanium.Web.Proxy
}
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
//By default
//do not allow this client to communicate with unauthenticated servers.
......@@ -82,13 +84,17 @@ namespace Titanium.Web.Proxy
{
string issuer = certificate.Issuer;
if (Array.IndexOf(acceptableIssuers, issuer) != -1)
{
clientCertificate = certificate;
}
}
}
if (localCertificates != null &&
localCertificates.Count > 0)
{
clientCertificate = localCertificates[0];
}
//If user call back is registered
if (ClientCertificateSelectionCallback != null)
......@@ -115,7 +121,6 @@ namespace Titanium.Web.Proxy
}
return clientCertificate;
}
}
}
......@@ -72,7 +72,8 @@ namespace Titanium.Web.Proxy.EventArguments
if ((WebSession.Request.Method.ToUpper() != "POST" && WebSession.Request.Method.ToUpper() != "PUT"))
{
throw new BodyNotFoundException("Request don't have a body." +
"Please verify that this request is a Http POST/PUT and request content length is greater than zero before accessing the body.");
"Please verify that this request is a Http POST/PUT and request " +
"content length is greater than zero before accessing the body.");
}
//Caching check
......@@ -80,7 +81,6 @@ namespace Titanium.Web.Proxy.EventArguments
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header
using (var requestBodyStream = new MemoryStream())
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
......@@ -94,13 +94,17 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Request.ContentLength > 0)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await this.ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream, WebSession.Request.ContentLength);
await this.ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream,
WebSession.Request.ContentLength);
}
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue);
}
}
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray());
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding,
requestBodyStream.ToArray());
}
//Now set the flag to true
......@@ -130,14 +134,18 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Response.ContentLength > 0)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, WebSession.Response.ContentLength);
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream,
WebSession.Response.ContentLength);
}
else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue);
}
}
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray());
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding,
responseBodyStream.ToArray());
}
//set this to true for caching
......@@ -154,7 +162,9 @@ namespace Titanium.Web.Proxy.EventArguments
if (!WebSession.Request.RequestBodyRead)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
await ReadRequestBody();
}
......@@ -169,7 +179,9 @@ namespace Titanium.Web.Proxy.EventArguments
if (!WebSession.Request.RequestBodyRead)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
await ReadRequestBody();
}
......@@ -184,7 +196,9 @@ namespace Titanium.Web.Proxy.EventArguments
public async Task SetRequestBody(byte[] body)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
//syphon out the request body from client before setting the new body
if (!WebSession.Request.RequestBodyRead)
......@@ -195,9 +209,13 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Request.RequestBody = body;
if (WebSession.Request.IsChunked == false)
{
WebSession.Request.ContentLength = body.Length;
}
else
{
WebSession.Request.ContentLength = -1;
}
}
/// <summary>
......@@ -207,7 +225,9 @@ namespace Titanium.Web.Proxy.EventArguments
public async Task SetRequestBodyString(string body)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
//syphon out the request body from client before setting the new body
if (!WebSession.Request.RequestBodyRead)
......@@ -226,7 +246,9 @@ namespace Titanium.Web.Proxy.EventArguments
public async Task<byte[]> GetResponseBody()
{
if (!WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
await ReadResponseBody();
return WebSession.Response.ResponseBody;
......@@ -239,11 +261,14 @@ namespace Titanium.Web.Proxy.EventArguments
public async Task<string> GetResponseBodyAsString()
{
if (!WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
await GetResponseBody();
return WebSession.Response.ResponseBodyString ?? (WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
return WebSession.Response.ResponseBodyString ??
(WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
}
/// <summary>
......@@ -253,7 +278,9 @@ namespace Titanium.Web.Proxy.EventArguments
public async Task SetResponseBody(byte[] body)
{
if (!WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
//syphon out the response body from server before setting the new body
if (WebSession.Response.ResponseBody == null)
......@@ -265,9 +292,13 @@ namespace Titanium.Web.Proxy.EventArguments
//If there is a content length header update it
if (WebSession.Response.IsChunked == false)
{
WebSession.Response.ContentLength = body.Length;
}
else
{
WebSession.Response.ContentLength = -1;
}
}
/// <summary>
......@@ -277,7 +308,9 @@ namespace Titanium.Web.Proxy.EventArguments
public async Task SetResponseBodyString(string body)
{
if (!WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
//syphon out the response body from server before setting the new body
if (WebSession.Response.ResponseBody == null)
......@@ -308,10 +341,14 @@ namespace Titanium.Web.Proxy.EventArguments
public async Task Ok(string html)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
if (html == null)
{
html = string.Empty;
}
var result = Encoding.Default.GetBytes(html);
......@@ -341,7 +378,7 @@ namespace Titanium.Web.Proxy.EventArguments
var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add(new Models.HttpHeader("Location", url));
response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response);
......
......@@ -20,7 +20,9 @@ namespace Titanium.Web.Proxy.Extensions
{
//return default if not specified
if (request.ContentType == null)
{
return Encoding.GetEncoding("ISO-8859-1");
}
//extract the encoding by finding the charset
var contentTypes = request.ContentType.Split(ProxyConstants.SemiColonSplit);
......
......@@ -17,7 +17,9 @@ namespace Titanium.Web.Proxy.Extensions
{
//return default if not specified
if (response.ContentType == null)
{
return Encoding.GetEncoding("ISO-8859-1");
}
//extract the encoding by finding the charset
var contentTypes = response.ContentType.Split(ProxyConstants.SemiColonSplit);
......
......@@ -26,6 +26,7 @@ namespace Titanium.Web.Proxy.Extensions
var bytes = Encoding.ASCII.GetBytes(initialData);
await output.WriteAsync(bytes, 0, bytes.Length);
}
await input.CopyToAsync(output);
}
/// <summary>
......@@ -45,7 +46,9 @@ namespace Titanium.Web.Proxy.Extensions
bytesToRead = totalBytesToRead;
}
else
{
bytesToRead = bufferSize;
}
while (totalbytesRead < totalBytesToRead)
......@@ -53,7 +56,9 @@ namespace Titanium.Web.Proxy.Extensions
var buffer = await streamReader.ReadBytesAsync(bufferSize, bytesToRead);
if (buffer.Length == 0)
{
break;
}
totalbytesRead += buffer.Length;
......@@ -62,6 +67,7 @@ namespace Titanium.Web.Proxy.Extensions
{
bytesToRead = remainingBytes;
}
await stream.WriteAsync(buffer, 0, buffer.Length);
}
}
......@@ -107,7 +113,9 @@ namespace Titanium.Web.Proxy.Extensions
await clientStream.WriteAsync(data, 0, data.Length);
}
else
{
await WriteResponseBodyChunked(data, clientStream);
}
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
......@@ -124,12 +132,16 @@ namespace Titanium.Web.Proxy.Extensions
{
//http 1.0
if (ContentLength == -1)
{
ContentLength = long.MaxValue;
}
int bytesToRead = bufferSize;
if (ContentLength < bufferSize)
{
bytesToRead = (int)ContentLength;
}
var buffer = new byte[bufferSize];
......@@ -150,7 +162,9 @@ namespace Titanium.Web.Proxy.Extensions
}
}
else
{
await WriteResponseBodyChunked(inStreamReader, bufferSize, outStream);
}
}
/// <summary>
......
......@@ -13,6 +13,7 @@ namespace Titanium.Web.Proxy.Extensions
{
// This is how you can determine whether a socket is still connected.
bool blockingState = client.Blocking;
try
{
byte[] tmp = new byte[1];
......@@ -25,7 +26,9 @@ namespace Titanium.Web.Proxy.Extensions
{
// 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035))
{
return true;
}
else
{
return false;
......
......@@ -27,9 +27,13 @@ namespace Titanium.Web.Proxy.Helpers
public override string ToString()
{
if (!IsHttps)
{
return "http=" + HostName + ":" + Port;
}
else
{
return "https=" + HostName + ":" + Port;
}
}
}
/// <summary>
......@@ -44,6 +48,7 @@ namespace Titanium.Web.Proxy.Helpers
{
var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
if (reg != null)
{
prepareRegistry(reg);
......
......@@ -27,7 +27,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="tunnelPort"></param>
/// <param name="isHttps"></param>
/// <returns></returns>
internal static async Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
internal static async Task SendRaw(Stream clientStream, string httpCmd, Dictionary<string, HttpHeader> requestHeaders, string hostName,
int tunnelPort, bool isHttps, SslProtocols supportedProtocols)
{
//prepare the prefix content
......@@ -35,17 +35,22 @@ namespace Titanium.Web.Proxy.Helpers
if (httpCmd != null || requestHeaders != null)
{
sb = new StringBuilder();
if (httpCmd != null)
{
sb.Append(httpCmd);
sb.Append(Environment.NewLine);
}
if (requestHeaders != null)
foreach (var header in requestHeaders.Select(t => t.ToString()))
{
foreach (var header in requestHeaders.Select(t => t.Value.ToString()))
{
sb.Append(header);
sb.Append(Environment.NewLine);
}
}
sb.Append(Environment.NewLine);
}
......@@ -70,7 +75,9 @@ namespace Titanium.Web.Proxy.Helpers
catch
{
if (sslStream != null)
{
sslStream.Dispose();
}
throw;
}
......@@ -80,9 +87,13 @@ namespace Titanium.Web.Proxy.Helpers
//Now async relay all server=>client & client=>server data
if (sb != null)
{
sendRelay = clientStream.CopyToAsync(sb.ToString(), tunnelStream);
}
else
{
sendRelay = clientStream.CopyToAsync(string.Empty, tunnelStream);
}
var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream);
......@@ -98,7 +109,9 @@ namespace Titanium.Web.Proxy.Helpers
}
if (tunnelClient != null)
{
tunnelClient.Close();
}
throw;
}
......
......@@ -58,7 +58,7 @@ namespace Titanium.Web.Proxy.Http
Stream stream = ServerConnection.Stream;
StringBuilder requestLines = new StringBuilder();
//prepare the request & headers
requestLines.AppendLine(string.Join(" ", new string[3]
{
......@@ -66,10 +66,22 @@ namespace Titanium.Web.Proxy.Http
this.Request.RequestUri.PathAndQuery,
string.Format("HTTP/{0}.{1}",this.Request.HttpVersion.Major, this.Request.HttpVersion.Minor)
}));
//write request headers
foreach (HttpHeader httpHeader in this.Request.RequestHeaders)
foreach (var headerItem in this.Request.RequestHeaders)
{
requestLines.AppendLine(httpHeader.Name + ':' + httpHeader.Value);
var header = headerItem.Value;
requestLines.AppendLine(header.Name + ':' + header.Value);
}
//write non unique request headers
foreach (var headerItem in this.Request.NonUniqueRequestHeaders)
{
var headers = headerItem.Value;
foreach (var header in headers)
{
requestLines.AppendLine(header.Name + ':' + header.Value);
}
}
requestLines.AppendLine();
......@@ -80,6 +92,7 @@ namespace Titanium.Web.Proxy.Http
await stream.FlushAsync();
if (enable100ContinueBehaviour)
{
if (this.Request.ExpectContinue)
{
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
......@@ -100,6 +113,7 @@ namespace Titanium.Web.Proxy.Http
await ServerConnection.StreamReader.ReadLineAsync();
}
}
}
}
/// <summary>
......@@ -121,7 +135,7 @@ namespace Titanium.Web.Proxy.Http
var httpVersion = httpResult[0].Trim().ToLower();
var version = new Version(1,1);
var version = new Version(1, 1);
if (httpVersion == "http/1.0")
{
version = new Version(1, 0);
......@@ -155,13 +169,34 @@ namespace Titanium.Web.Proxy.Http
return;
}
//read response headers
List<string> responseLines = await ServerConnection.StreamReader.ReadAllLinesAsync();
for (int index = 0; index < responseLines.Count; ++index)
//Read the Response headers
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync()))
{
string[] strArray = responseLines[index].Split(ProxyConstants.ColonSplit, 2);
this.Response.ResponseHeaders.Add(new HttpHeader(strArray[0], strArray[1]));
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
Response.NonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
else if (Response.ResponseHeaders.ContainsKey(newHeader.Name))
{
var existing = Response.ResponseHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader>();
nonUniqueHeaders.Add(existing);
nonUniqueHeaders.Add(newHeader);
Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
Response.ResponseHeaders.Remove(newHeader.Name);
}
else
{
Response.ResponseHeaders.Add(newHeader.Name, newHeader);
}
}
}
}
......
......@@ -34,18 +34,26 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var host = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "host");
if (host != null)
return host.Value;
var hasHeader = RequestHeaders.ContainsKey("host");
if (hasHeader)
{
return RequestHeaders["host"].Value;
}
return null;
}
set
{
var host = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "host");
if (host != null)
host.Value = value;
var hasHeader = RequestHeaders.ContainsKey("host");
if (hasHeader)
{
RequestHeaders["host"].Value = value;
}
else
RequestHeaders.Add(new HttpHeader("Host", value));
{
RequestHeaders.Add("Host", new HttpHeader("Host", value));
}
}
}
......@@ -56,11 +64,11 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = this.RequestHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-encoding"));
var hasHeader = RequestHeaders.ContainsKey("content-encoding");
if (header != null)
if (hasHeader)
{
return header.Value.Trim();
return RequestHeaders["content-encoding"].Value;
}
return null;
......@@ -74,36 +82,52 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
var hasHeader = RequestHeaders.ContainsKey("content-length");
if (header == null)
if (hasHeader == false)
{
return -1;
}
var header = RequestHeaders["content-length"];
long contentLen;
long.TryParse(header.Value, out contentLen);
if (contentLen >=0)
if (contentLen >= 0)
{
return contentLen;
}
return -1;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
var hasHeader = RequestHeaders.ContainsKey("content-length");
var header = RequestHeaders["content-length"];
if (value >= 0)
{
if (header != null)
if (hasHeader)
{
header.Value = value.ToString();
}
else
RequestHeaders.Add(new HttpHeader("content-length", value.ToString()));
{
RequestHeaders.Add("content-length", new HttpHeader("content-length", value.ToString()));
}
IsChunked = false;
}
else
{
if (header != null)
this.RequestHeaders.Remove(header);
if (hasHeader)
{
RequestHeaders.Remove("content-length");
}
}
}
}
......@@ -114,19 +138,29 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-type");
if (header != null)
var hasHeader = RequestHeaders.ContainsKey("content-type");
if (hasHeader)
{
var header = RequestHeaders["content-type"];
return header.Value;
}
return null;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-type");
var hasHeader = RequestHeaders.ContainsKey("content-type");
if (header != null)
if (hasHeader)
{
var header = RequestHeaders["content-type"];
header.Value = value.ToString();
}
else
RequestHeaders.Add(new HttpHeader("content-type", value.ToString()));
{
RequestHeaders.Add("content-type", new HttpHeader("content-type", value.ToString()));
}
}
}
......@@ -138,29 +172,41 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "transfer-encoding");
if (header != null) return header.Value.ToLower().Contains("chunked");
var hasHeader = RequestHeaders.ContainsKey("transfer-encoding");
if (hasHeader)
{
var header = RequestHeaders["transfer-encoding"];
return header.Value.ToLower().Contains("chunked");
}
return false;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "transfer-encoding");
var hasHeader = RequestHeaders.ContainsKey("transfer-encoding");
if (value)
{
if (header != null)
if (hasHeader)
{
var header = RequestHeaders["transfer-encoding"];
header.Value = "chunked";
}
else
RequestHeaders.Add(new HttpHeader("transfer-encoding", "chunked"));
{
RequestHeaders.Add("transfer-encoding", new HttpHeader("transfer-encoding", "chunked"));
}
this.ContentLength = -1;
ContentLength = -1;
}
else
{
if (header != null)
RequestHeaders.Remove(header);
if (hasHeader)
{
RequestHeaders.Remove("transfer-encoding");
}
}
}
}
......@@ -172,8 +218,15 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "expect");
if (header != null) return header.Value.Equals("100-continue");
var hasHeader = RequestHeaders.ContainsKey("expect");
if (hasHeader)
{
var header = RequestHeaders["expect"];
return header.Value.Equals("100-continue");
}
return false;
}
}
......@@ -213,12 +266,19 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "upgrade");
if (header == null)
var hasHeader = RequestHeaders.ContainsKey("upgrade");
if (hasHeader == false)
{
return false;
}
var header = RequestHeaders["upgrade"];
if (header.Value.ToLower() == "websocket")
{
return true;
}
return false;
......@@ -226,9 +286,14 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Request heade collection
/// Unique Request header collection
/// </summary>
public Dictionary<string, HttpHeader> RequestHeaders { get; set; }
/// <summary>
/// Non Unique headers
/// </summary>
public List<HttpHeader> RequestHeaders { get; set; }
public Dictionary<string, List<HttpHeader>> NonUniqueRequestHeaders { get; set; }
/// <summary>
/// Does server responsed positively for 100 continue request
......@@ -242,7 +307,8 @@ namespace Titanium.Web.Proxy.Http
public Request()
{
this.RequestHeaders = new List<HttpHeader>();
RequestHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
NonUniqueRequestHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
}
}
......
......@@ -25,10 +25,12 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-encoding"));
var hasHeader = ResponseHeaders.ContainsKey("content-encoding");
if (header != null)
if (hasHeader)
{
var header = ResponseHeaders["content-encoding"];
return header.Value.Trim();
}
......@@ -45,11 +47,16 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("connection"));
var hasHeader = ResponseHeaders.ContainsKey("connection");
if (header != null && header.Value.ToLower().Contains("close"))
if (hasHeader)
{
return false;
var header = ResponseHeaders["connection"];
if (header.Value.ToLower().Contains("close"))
{
return false;
}
}
return true;
......@@ -64,10 +71,12 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-type"));
var hasHeader = ResponseHeaders.ContainsKey("content-type");
if (header != null)
if (hasHeader)
{
var header = ResponseHeaders["content-type"];
return header.Value;
}
......@@ -83,36 +92,49 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-length"));
var hasHeader = ResponseHeaders.ContainsKey("content-length");
if (header == null)
if (hasHeader == false)
{
return -1;
}
var header = ResponseHeaders["content-length"];
long contentLen;
long.TryParse(header.Value, out contentLen);
if (contentLen >= 0)
{
return contentLen;
}
return -1;
}
set
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-length"));
var hasHeader = ResponseHeaders.ContainsKey("content-length");
if (value >= 0)
{
if (header != null)
if (hasHeader)
{
var header = ResponseHeaders["content-length"];
header.Value = value.ToString();
}
else
ResponseHeaders.Add(new HttpHeader("content-length", value.ToString()));
{
ResponseHeaders.Add("content-length", new HttpHeader("content-length", value.ToString()));
}
IsChunked = false;
}
else
{
if (header != null)
this.ResponseHeaders.Remove(header);
if (hasHeader)
{
ResponseHeaders.Remove("content-length");
}
}
}
}
......@@ -124,11 +146,16 @@ namespace Titanium.Web.Proxy.Http
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("transfer-encoding"));
var hasHeader = ResponseHeaders.ContainsKey("transfer-encoding");
if (header != null && header.Value.ToLower().Contains("chunked"))
if (hasHeader)
{
return true;
var header = ResponseHeaders["transfer-encoding"];
if (header.Value.ToLower().Contains("chunked"))
{
return true;
}
}
return false;
......@@ -136,23 +163,29 @@ namespace Titanium.Web.Proxy.Http
}
set
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("transfer-encoding"));
var hasHeader = ResponseHeaders.ContainsKey("transfer-encoding");
if (value)
{
if (header != null)
if (hasHeader)
{
var header = ResponseHeaders["transfer-encoding"];
header.Value = "chunked";
}
else
ResponseHeaders.Add(new HttpHeader("transfer-encoding", "chunked"));
{
ResponseHeaders.Add("transfer-encoding", new HttpHeader("transfer-encoding", "chunked"));
}
this.ContentLength = -1;
ContentLength = -1;
}
else
{
if (header != null)
ResponseHeaders.Remove(header);
if (hasHeader)
{
ResponseHeaders.Remove("transfer-encoding");
}
}
}
......@@ -161,7 +194,13 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Collection of all response headers
/// </summary>
public List<HttpHeader> ResponseHeaders { get; set; }
public Dictionary<string, HttpHeader> ResponseHeaders { get; set; }
/// <summary>
/// Non Unique headers
/// </summary>
public Dictionary<string, List<HttpHeader>> NonUniqueResponseHeaders { get; set; }
/// <summary>
/// Response network stream
......@@ -193,7 +232,8 @@ namespace Titanium.Web.Proxy.Http
public Response()
{
this.ResponseHeaders = new List<HttpHeader>();
this.ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
this.NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
}
}
......
......@@ -9,7 +9,10 @@ namespace Titanium.Web.Proxy.Models
{
public HttpHeader(string name, string value)
{
if (string.IsNullOrEmpty(name)) throw new Exception("Name cannot be null");
if (string.IsNullOrEmpty(name))
{
throw new Exception("Name cannot be null");
}
Name = name.Trim();
Value = value.Trim();
......
......@@ -114,7 +114,9 @@ namespace Titanium.Web.Proxy.Network
certificates = FindCertificates(store, certificateSubject);
if (certificates != null)
{
certificate = certificates[0];
}
}
if (certificate == null)
......@@ -127,16 +129,22 @@ namespace Titanium.Web.Proxy.Network
//remove it from store
if (!isRootCertificate)
{
DestroyCertificate(certificateName);
}
if (certificates != null)
{
certificate = certificates[0];
}
}
store.Close();
if (certificate != null && !certificateCache.ContainsKey(certificateName))
{
certificateCache.Add(certificateName, new CachedCertificate() { Certificate = certificate });
}
return certificate;
}
......@@ -162,7 +170,9 @@ namespace Titanium.Web.Proxy.Network
string file = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "makecert.exe");
if (!File.Exists(file))
{
throw new Exception("Unable to locate 'makecert.exe'.");
}
process.StartInfo.Verb = "runas";
process.StartInfo.Arguments = args != null ? args[0] : string.Empty;
......@@ -178,6 +188,7 @@ namespace Titanium.Web.Proxy.Network
};
bool started = process.Start();
if (!started)
{
//you may allow for the process to be re-used (started = false)
......@@ -214,6 +225,7 @@ namespace Titanium.Web.Proxy.Network
string certificateSubject = string.Format("CN={0}, O={1}", certificateName, Issuer);
certificates = FindCertificates(store, certificateSubject);
if (certificates != null)
{
store.RemoveRange(certificates);
......@@ -226,6 +238,7 @@ namespace Titanium.Web.Proxy.Network
{
certificateCache.Remove(certificateName);
}
return certificates == null;
}
/// <summary>
......@@ -279,7 +292,9 @@ namespace Titanium.Web.Proxy.Network
foreach (var cache in outdated)
certificateCache.Remove(cache.Key);
}
finally { semaphoreLock.Release(); }
finally {
semaphoreLock.Release();
}
//after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60);
......@@ -289,10 +304,14 @@ namespace Titanium.Web.Proxy.Network
public void Dispose()
{
if (MyStore != null)
{
MyStore.Close();
}
if (RootStore != null)
{
RootStore.Close();
}
}
}
}
\ No newline at end of file
......@@ -51,6 +51,7 @@ namespace Titanium.Web.Proxy.Network
while (true)
{
await connectionAccessLock.WaitAsync();
try
{
connectionCache.TryGetValue(key, out cachedConnections);
......@@ -65,7 +66,9 @@ namespace Titanium.Web.Proxy.Network
cached = null;
}
}
finally { connectionAccessLock.Release(); }
finally {
connectionAccessLock.Release();
}
if (cached != null && !cached.TcpClient.Client.IsConnected())
{
......@@ -75,14 +78,18 @@ namespace Titanium.Web.Proxy.Network
}
if (cached == null)
{
break;
}
}
if (cached == null)
{
cached = await CreateClient(hostname, port, isHttps, version, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
remoteCertificateValidationCallBack, localCertificateSelectionCallback);
}
if (cachedConnections == null || cachedConnections.Count() <= 2)
{
......@@ -147,7 +154,9 @@ namespace Titanium.Web.Proxy.Network
var result = await reader.ReadLineAsync();
if (!result.ToLower().Contains("200 connection established"))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
await reader.ReadAllLinesAsync();
}
......@@ -162,13 +171,18 @@ namespace Titanium.Web.Proxy.Network
{
sslStream = new SslStream(stream, true, remoteCertificateValidationCallBack,
localCertificateSelectionCallback);
await sslStream.AuthenticateAsClientAsync(hostname, null, supportedSslProtocols, false);
stream = (Stream)sslStream;
}
catch
{
if (sslStream != null)
{
sslStream.Dispose();
}
throw;
}
}
......@@ -209,19 +223,25 @@ namespace Titanium.Web.Proxy.Network
connection.LastAccess = DateTime.Now;
var key = GetConnectionKey(connection.HostName, connection.port, connection.IsHttps, connection.Version);
await connectionAccessLock.WaitAsync();
try
{
List<TcpConnectionCache> cachedConnections;
connectionCache.TryGetValue(key, out cachedConnections);
if (cachedConnections != null)
{
cachedConnections.Add(connection);
}
else
{
connectionCache.Add(key, new List<TcpConnectionCache>() { connection });
}
}
finally { connectionAccessLock.Release(); }
finally {
connectionAccessLock.Release();
}
}
private bool clearConenctions { get; set; }
......@@ -240,9 +260,11 @@ namespace Titanium.Web.Proxy.Network
internal async void ClearIdleConnections(int connectionCacheTimeOutMinutes)
{
clearConenctions = true;
while (clearConenctions)
{
await connectionAccessLock.WaitAsync();
try
{
var cutOff = DateTime.Now.AddMinutes(-1 * connectionCacheTimeOutMinutes);
......@@ -255,7 +277,9 @@ namespace Titanium.Web.Proxy.Network
connectionCache.ToList().ForEach(x => x.Value.RemoveAll(y => y.LastAccess < cutOff));
}
finally { connectionAccessLock.Release(); }
finally {
connectionAccessLock.Release();
}
//every minute run this
await Task.Delay(1000 * 60);
......
......@@ -143,13 +143,17 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param>
public void AddEndPoint(ProxyEndPoint endPoint)
{
if (ProxyEndPoints.Any(x=>x.IpAddress == endPoint.IpAddress && x.Port == endPoint.Port))
if (ProxyEndPoints.Any(x => x.IpAddress == endPoint.IpAddress && x.Port == endPoint.Port))
{
throw new Exception("Cannot add another endpoint to same port & ip address");
}
ProxyEndPoints.Add(endPoint);
if (proxyRunning)
{
Listen(endPoint);
}
}
/// <summary>
......@@ -160,12 +164,16 @@ namespace Titanium.Web.Proxy
public void RemoveEndPoint(ProxyEndPoint endPoint)
{
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot remove endPoints not added to proxy");
}
ProxyEndPoints.Remove(endPoint);
if (proxyRunning)
{
QuitListen(endPoint);
}
}
/// <summary>
......@@ -254,7 +262,9 @@ namespace Titanium.Web.Proxy
public void Start()
{
if (proxyRunning)
{
throw new Exception("Proxy is already running.");
}
certTrusted = certificateCacheManager.CreateTrustedRootCertificate().Result;
......@@ -275,7 +285,9 @@ namespace Titanium.Web.Proxy
public void Stop()
{
if (!proxyRunning)
{
throw new Exception("Proxy is not running.");
}
var setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy);
......@@ -332,10 +344,14 @@ namespace Titanium.Web.Proxy
private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot set endPoints not added to proxy as system proxy");
}
if (!proxyRunning)
{
throw new Exception("Cannot set system proxy settings before proxy has been started.");
}
}
/// <summary>
......@@ -375,7 +391,9 @@ namespace Titanium.Web.Proxy
public void Dispose()
{
if (proxyRunning)
{
Stop();
}
certificateCacheManager.Dispose();
}
......
This diff is collapsed.
......@@ -8,6 +8,7 @@ using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Compression;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy
{
......@@ -17,7 +18,7 @@ namespace Titanium.Web.Proxy
partial class ProxyServer
{
//Called asynchronously when a request was successfully and we received the response
public async Task HandleHttpSessionResponse(SessionEventArgs args)
public async Task HandleHttpSessionResponse(SessionEventArgs args)
{
//read response & headers from server
await args.WebSession.ReceiveResponse();
......@@ -25,7 +26,9 @@ namespace Titanium.Web.Proxy
try
{
if (!args.WebSession.Response.ResponseBodyRead)
{
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
}
//If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
......@@ -71,26 +74,39 @@ namespace Titanium.Web.Proxy
args.WebSession.Response.ResponseBody = await GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody);
if (isChunked == false)
{
args.WebSession.Response.ContentLength = args.WebSession.Response.ResponseBody.Length;
}
else
{
args.WebSession.Response.ContentLength = -1;
}
}
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response.ResponseHeaders);
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response);
await args.ProxyClient.ClientStream.WriteResponseBody(args.WebSession.Response.ResponseBody, isChunked);
}
else
{
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response.ResponseHeaders);
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response);
//Write body only if response is chunked or content length >0
//Is none are true then check if connection:close header exist, if so write response until server or client terminates the connection
if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 || !args.WebSession.Response.ResponseKeepAlive)
await args.WebSession.ServerConnection.StreamReader.WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength);
if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0
|| !args.WebSession.Response.ResponseKeepAlive)
{
await args.WebSession.ServerConnection.StreamReader
.WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked,
args.WebSession.Response.ContentLength);
}
//write response if connection:keep-alive header exist and when version is http/1.0
//Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream)
else if (args.WebSession.Response.ResponseKeepAlive && args.WebSession.Response.HttpVersion.Minor == 0)
await args.WebSession.ServerConnection.StreamReader.WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength);
{
await args.WebSession.ServerConnection.StreamReader
.WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked,
args.WebSession.Response.ContentLength);
}
}
await args.ProxyClient.ClientStream.FlushAsync();
......@@ -98,7 +114,8 @@ namespace Titanium.Web.Proxy
}
catch
{
Dispose(args.ProxyClient.TcpClient, args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
Dispose(args.ProxyClient.TcpClient, args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args);
}
finally
{
......@@ -112,7 +129,7 @@ namespace Titanium.Web.Proxy
/// <param name="encodingType"></param>
/// <param name="responseBodyStream"></param>
/// <returns></returns>
private async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream)
private async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream)
{
var compressionFactory = new CompressionFactory();
var compressor = compressionFactory.Create(encodingType);
......@@ -127,7 +144,7 @@ namespace Titanium.Web.Proxy
/// <param name="description"></param>
/// <param name="responseWriter"></param>
/// <returns></returns>
private async Task WriteResponseStatus(Version version, string code, string description,
private async Task WriteResponseStatus(Version version, string code, string description,
StreamWriter responseWriter)
{
await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description));
......@@ -139,43 +156,56 @@ namespace Titanium.Web.Proxy
/// <param name="responseWriter"></param>
/// <param name="headers"></param>
/// <returns></returns>
private async Task WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers)
private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response)
{
if (headers != null)
FixProxyHeaders(response.ResponseHeaders);
foreach (var header in response.ResponseHeaders)
{
FixResponseProxyHeaders(headers);
await responseWriter.WriteLineAsync(header.Value.ToString());
}
//write non unique request headers
foreach (var headerItem in response.NonUniqueResponseHeaders)
{
var headers = headerItem.Value;
foreach (var header in headers)
{
await responseWriter.WriteLineAsync(header.ToString());
}
}
await responseWriter.WriteLineAsync();
await responseWriter.FlushAsync();
}
/// <summary>
/// Fix the proxy specific headers before sending response headers to client
/// Fix proxy specific headers
/// </summary>
/// <param name="headers"></param>
private void FixResponseProxyHeaders(List<HttpHeader> headers)
private void FixProxyHeaders(Dictionary<string, HttpHeader> headers)
{
//If proxy-connection close was returned inform to close the connection
var proxyHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "proxy-connection");
var connectionHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "connection");
var hasProxyHeader = headers.ContainsKey("proxy-connection");
var hasConnectionheader = headers.ContainsKey("connection");
if (proxyHeader != null)
if (connectionHeader == null)
if (hasProxyHeader)
{
var proxyHeader = headers["proxy-connection"];
if (hasConnectionheader == false)
{
headers.Add(new HttpHeader("connection", proxyHeader.Value));
headers.Add("connection", new HttpHeader("connection", proxyHeader.Value));
}
else
{
var connectionHeader = headers["connection"];
connectionHeader.Value = proxyHeader.Value;
}
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
headers.Remove("proxy-connection");
}
}
/// <summary>
......@@ -212,7 +242,6 @@ namespace Titanium.Web.Proxy
if (clientStreamWriter != null)
clientStreamWriter.Dispose();
}
}
}
\ 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