Commit af254c32 authored by justcoding121's avatar justcoding121

Merge pull request #73 from justcoding121/release

External Proxy, Async & Server Certificate Validation
parents aeb9f31e e1cd5fcd
using System;
using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Test
namespace Titanium.Web.Proxy.Examples.Basic
{
public class Program
{
......
......@@ -2,10 +2,11 @@
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Test
namespace Titanium.Web.Proxy.Examples.Basic
{
public class ProxyTestController
{
......@@ -13,6 +14,7 @@ namespace Titanium.Web.Proxy.Test
{
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
......@@ -34,13 +36,14 @@ namespace Titanium.Web.Proxy.Test
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
//Please read about it before asking questions!
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
GenericCertificateName = "google.com"
};
ProxyServer.AddEndPoint(transparentEndPoint);
//ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in ProxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
......@@ -59,32 +62,31 @@ namespace Titanium.Web.Proxy.Test
ProxyServer.Stop();
}
//Test On Request, intecept requests
//Read browser URL send back to proxy by the injection script in OnResponse event
public void OnRequest(object sender, SessionEventArgs e)
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.ProxySession.Request.Url);
Console.WriteLine(e.WebSession.Request.Url);
//read request headers
var requestHeaders = e.ProxySession.Request.RequestHeaders;
////read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders;
if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT"))
if ((e.WebSession.Request.Method.ToUpper() == "POST" || e.WebSession.Request.Method.ToUpper() == "PUT"))
{
//Get/Set request body bytes
byte[] bodyBytes = e.GetRequestBody();
e.SetRequestBody(bodyBytes);
byte[] bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes);
//Get/Set request body as string
string bodyString = e.GetRequestBodyAsString();
e.SetRequestBodyString(bodyString);
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);
}
//To cancel a request with a custom HTML content
//Filter URL
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
e.Ok("<!DOCTYPE html>" +
await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
......@@ -93,35 +95,47 @@ namespace Titanium.Web.Proxy.Test
"</html>");
}
//Redirect example
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
e.Redirect("https://www.paypal.com");
await e.Redirect("https://www.paypal.com");
}
}
//Test script injection
//Insert script to read the Browser URL and send it back to proxy
public void OnResponse(object sender, SessionEventArgs e)
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
//read response headers
var responseHeaders = e.ProxySession.Response.ResponseHeaders;
var responseHeaders = e.WebSession.Response.ResponseHeaders;
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.RequestMethod == "GET" || e.RequestMethod == "POST")
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{
if (e.ProxySession.Response.ResponseStatusCode == "200")
if (e.WebSession.Response.ResponseStatusCode == "200")
{
if (e.ProxySession.Response.ContentType.Trim().ToLower().Contains("text/html"))
if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
byte[] bodyBytes = e.GetResponseBody();
e.SetResponseBody(bodyBytes);
byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);
string body = e.GetResponseBodyAsString();
e.SetResponseBodyString(body);
string body = await e.GetResponseBodyAsString();
await e.SetResponseBodyString(body);
}
}
}
}
/// <summary>
/// Allows overriding default certificate validation logic
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public async Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
else
await e.Session.Ok("Cannot validate server certificate! Not safe to proceed.");
}
}
}
\ No newline at end of file
......@@ -8,8 +8,8 @@
<ProjectGuid>{F3B7E553-1904-4E80-BDC7-212342B5C952}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.Test</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.Test</AssemblyName>
<RootNamespace>Titanium.Web.Proxy.Examples.Basic</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.Examples.Basic</AssemblyName>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
......@@ -65,7 +65,7 @@
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
......
......@@ -6,7 +6,7 @@ A light weight http(s) proxy server written in C#
Kindly report only issues/bugs here . For programming help or questions use [StackOverflow](http://stackoverflow.com/questions/tagged/titanium-web-proxy) with the tag Titanium-Web-Proxy.
![alt tag](https://raw.githubusercontent.com/titanium007/Titanium/master/Titanium.Web.Proxy.Test/Capture.PNG)
![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/release/Examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG)
Features
========
......@@ -32,29 +32,47 @@ After installing nuget package mark following files to be copied to app director
Setup HTTP proxy:
```csharp
// listen to client request & server response events
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true){
//Exclude Https addresses you don't want to proxy/cannot be proxied
//for example exclude dropbox client which use certificate pinning
ExcludedHttpsHostNameRegex = new List<string>() { "dropbox.com" }
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//for example dropbox.com
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
};
//Add an explicit endpoint where the client is aware of the proxy
//So client would send request in a proxy friendly manner
//An explicit endpoint is where the client knows about the existance of a proxy
//So client sends request in a proxy friendly manner
ProxyServer.AddEndPoint(explicitEndPoint);
ProxyServer.Start();
//Only explicit proxies can be set as a system proxy!
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint);
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint
//Currently do not support Server Name Indication (It is not currently supported by SslStream class)
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
GenericCertificateName = "google.com"
};
ProxyServer.AddEndPoint(transparentEndPoint);
//ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in ProxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
//Only explicit proxies can be set as system proxy!
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint);
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();
......@@ -68,66 +86,81 @@ Sample request and response event handlers
```csharp
//Test On Request, intecept requests
//Read browser URL send back to proxy by the injection script in OnResponse event
public void OnRequest(object sender, SessionEventArgs e)
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.ProxySession.Request.Url);
Console.WriteLine(e.WebSession.Request.Url);
//read request headers
var requestHeaders = e.ProxySession.Request.RequestHeaders;
////read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders;
if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT"))
if ((e.WebSession.Request.Method.ToUpper() == "POST" || e.WebSession.Request.Method.ToUpper() == "PUT"))
{
//Get/Set request body bytes
byte[] bodyBytes = e.GetRequestBody();
e.SetRequestBody(bodyBytes);
byte[] bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes);
//Get/Set request body as string
string bodyString = e.GetRequestBodyAsString();
e.SetRequestBodyString(bodyString);
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);
}
//To cancel a request with a custom HTML content
//Filter URL
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
e.Ok("<!DOCTYPE html>"+
"<html><body><h1>"+
"Website Blocked"+
"</h1>"+
"<p>Blocked by titanium web proxy.</p>"+
"</body>"+
await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
//Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
await e.Redirect("https://www.paypal.com");
}
}
//Test script injection
//Insert script to read the Browser URL and send it back to proxy
public void OnResponse(object sender, SessionEventArgs e)
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
//read response headers
var responseHeaders = e.ProxySession.Response.ResponseHeaders;
var responseHeaders = e.WebSession.Response.ResponseHeaders;
if (e.RequestMethod == "GET" || e.RequestMethod == "POST")
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{
if (e.ProxySession.Response.ResponseStatusCode == "200")
if (e.WebSession.Response.ResponseStatusCode == "200")
{
if (e.ProxySession.Response.ContentType.Trim().ToLower().Contains("text/html"))
if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
string body = e.GetResponseBodyAsString();
byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);
string body = await e.GetResponseBodyAsString();
await e.SetResponseBodyString(body);
}
}
}
}
/// Allows overriding default certificate validation logic
public async Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
else
await e.Session.Ok("Cannot validate server certificate! Not safe to proceed.");
}
```
Future roadmap
============
* Add callbacks for client/server certificate validation/selection
* Support mutual authentication
* Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0
......
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectView>ProjectFiles</ProjectView>
</PropertyGroup>
</Project>
\ No newline at end of file
......@@ -3,9 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Test", "Titanium.Web.Proxy.Test\Titanium.Web.Proxy.Test.csproj", "{F3B7E553-1904-4E80-BDC7-212342B5C952}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy", "Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}"
EndProject
......@@ -16,20 +14,35 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6FD3B8
.nuget\NuGet.targets = .nuget\NuGet.targets
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Examples.Basic", "Examples\Titanium.Web.Proxy.Examples.Basic\Titanium.Web.Proxy.Examples.Basic.csproj", "{F3B7E553-1904-4E80-BDC7-212342B5C952}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{38EA62D0-D2CB-465D-AF4F-407C5B4D4A1E}"
ProjectSection(SolutionItems) = preProject
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A-3059-4FDB-9A5C-363AD86F2EEF}"
ProjectSection(SolutionItems) = preProject
.build\Bootstrap.ps1 = .build\Bootstrap.ps1
.build\Common.psm1 = .build\Common.psm1
.build\default.ps1 = .build\default.ps1
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.Build.0 = Release|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.Build.0 = Release|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
class DeflateCompression : ICompression
{
public byte[] Compress(byte[] responseBody)
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
using (var zip = new DeflateStream(ms, CompressionMode.Compress, true))
{
zip.Write(responseBody, 0, responseBody.Length);
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
}
return ms.ToArray();
......
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
class GZipCompression : ICompression
{
public byte[] Compress(byte[] responseBody)
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
{
zip.Write(responseBody, 0, responseBody.Length);
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
}
return ms.ToArray();
......
namespace Titanium.Web.Proxy.Compression
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
interface ICompression
{
byte[] Compress(byte[] responseBody);
Task<byte[]> Compress(byte[] responseBody);
}
}
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
class ZlibCompression : ICompression
{
public byte[] Compress(byte[] responseBody)
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
using (var zip = new ZlibStream(ms, CompressionMode.Compress, true))
{
zip.Write(responseBody, 0, responseBody.Length);
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
}
return ms.ToArray();
......
namespace Titanium.Web.Proxy.Decompression
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
class DefaultDecompression : IDecompression
{
public byte[] Decompress(byte[] compressedArray)
public Task<byte[]> Decompress(byte[] compressedArray)
{
return compressedArray;
return Task.FromResult(compressedArray);
}
}
}
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
class DeflateDecompression : IDecompression
{
public byte[] Decompress(byte[] compressedArray)
public async Task<byte[]> Decompress(byte[] compressedArray)
{
var stream = new MemoryStream(compressedArray);
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{
var buffer = new byte[ProxyServer.BUFFER_SIZE];
var buffer = new byte[Constants.BUFFER_SIZE];
using (var output = new MemoryStream())
{
int read;
while ((read = decompressor.Read(buffer, 0, buffer.Length)) > 0)
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
{
output.Write(buffer, 0, read);
await output.WriteAsync(buffer, 0, read).ConfigureAwait(false);
}
return output.ToArray();
......
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
class GZipDecompression : IDecompression
{
public byte[] Decompress(byte[] compressedArray)
public async Task<byte[]> Decompress(byte[] compressedArray)
{
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
{
var buffer = new byte[ProxyServer.BUFFER_SIZE];
var buffer = new byte[Constants.BUFFER_SIZE];
using (var output = new MemoryStream())
{
int read;
while ((read = decompressor.Read(buffer, 0, buffer.Length)) > 0)
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
{
output.Write(buffer, 0, read);
await output.WriteAsync(buffer, 0, read).ConfigureAwait(false);
}
return output.ToArray();
}
......
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
interface IDecompression
{
byte[] Decompress(byte[] compressedArray);
Task<byte[]> Decompress(byte[] compressedArray);
}
}
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
class ZlibDecompression : IDecompression
{
public byte[] Decompress(byte[] compressedArray)
public async Task<byte[]> Decompress(byte[] compressedArray)
{
var memoryStream = new MemoryStream(compressedArray);
using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress))
{
var buffer = new byte[ProxyServer.BUFFER_SIZE];
var buffer = new byte[Constants.BUFFER_SIZE];
using (var output = new MemoryStream())
{
int read;
while ((read = decompressor.Read(buffer, 0, buffer.Length)) > 0)
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
{
output.Write(buffer, 0, read);
await output.WriteAsync(buffer, 0, read).ConfigureAwait(false);
}
return output.ToArray();
}
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.EventArguments
{
public class CertificateValidationEventArgs : EventArgs, IDisposable
{
public string HostName => Session.WebSession.Request.Host;
public SessionEventArgs Session { get; internal set; }
public X509Certificate Certificate { get; internal set; }
public X509Chain Chain { get; internal set; }
public SslPolicyErrors SslPolicyErrors { get; internal set; }
public bool IsValid { get; set; }
public void Dispose()
{
}
}
}
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Responses;
using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.EventArguments
{
......@@ -24,7 +25,7 @@ namespace Titanium.Web.Proxy.EventArguments
internal SessionEventArgs()
{
Client = new ProxyClient();
ProxySession = new HttpWebSession();
WebSession = new HttpWebSession();
}
/// <summary>
......@@ -41,45 +42,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// A web session corresponding to a single request/response sequence
/// within a proxy connection
/// </summary>
public HttpWebSession ProxySession { get; set; }
public HttpWebSession WebSession { get; set; }
/// <summary>
/// A shortcut to get the request content length
/// </summary>
public int RequestContentLength
{
get
{
return ProxySession.Request.ContentLength;
}
}
/// <summary>
/// A shortcut to get the request Method (GET/POST/PUT etc)
/// </summary>
public string RequestMethod
{
get { return ProxySession.Request.Method; }
}
/// <summary>
/// A shortcut to get the response status code (200 OK, 404 etc)
/// </summary>
public string ResponseStatusCode
{
get { return ProxySession.Response.ResponseStatusCode; }
}
/// <summary>
/// A shortcut to get the response content type
/// </summary>
public string ResponseContentType
{
get
{
return ProxySession.Response.ContentType;
}
}
/// <summary>
/// implement any cleanup here
......@@ -92,132 +56,82 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
private void ReadRequestBody()
private async Task ReadRequestBody()
{
//GET request don't have a request body to read
if ((ProxySession.Request.Method.ToUpper() != "POST" && ProxySession.Request.Method.ToUpper() != "PUT"))
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.");
}
//Caching check
if (ProxySession.Request.RequestBody == null)
if (WebSession.Request.RequestBody == null)
{
var isChunked = false;
string requestContentEncoding = null;
//get compression method (gzip, zlib etc)
if (ProxySession.Request.RequestHeaders.Any(x => x.Name.ToLower() == "content-encoding"))
{
requestContentEncoding = ProxySession.Request.RequestHeaders.First(x => x.Name.ToLower() == "content-encoding").Value;
}
//check if the request have chunked body (body send chunck by chunck without a fixed length)
if (ProxySession.Request.RequestHeaders.Any(x => x.Name.ToLower() == "transfer-encoding"))
{
var transferEncoding =
ProxySession.Request.RequestHeaders.First(x => x.Name.ToLower() == "transfer-encoding").Value.ToLower();
if (transferEncoding.Contains("chunked"))
{
isChunked = true;
}
}
//If chunked then its easy just read the whole body with the content length mentioned in the request header
//If not chunked then its easy just read the whole body with the content length mentioned in the request header
if (requestContentEncoding == null && !isChunked)
ProxySession.Request.RequestBody = this.Client.ClientStreamReader.ReadBytes(RequestContentLength);
else
{
using (var requestBodyStream = new MemoryStream())
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (isChunked)
{
while (true)
{
var chuchkHead = this.Client.ClientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
if (WebSession.Request.IsChunked)
{
var buffer = this.Client.ClientStreamReader.ReadBytes(chunkSize);
requestBodyStream.Write(buffer, 0, buffer.Length);
//chunk trail
this.Client.ClientStreamReader.ReadLine();
await this.Client.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream).ConfigureAwait(false);
}
else
{
//chunk end
this.Client.ClientStreamReader.ReadLine();
break;
}
}
}
try
{
ProxySession.Request.RequestBody = GetDecompressedResponseBody(requestContentEncoding, requestBodyStream.ToArray());
}
catch
//If not chunked then its easy just read the whole body with the content length mentioned in the request header
if (WebSession.Request.ContentLength > 0)
{
//if decompression fails, just assign the body stream as it it
//Not a safe option
ProxySession.Request.RequestBody = requestBodyStream.ToArray();
}
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await this.Client.ClientStreamReader.CopyBytesToStream(requestBodyStream, WebSession.Request.ContentLength).ConfigureAwait(false);
}
else if (WebSession.Request.HttpVersion.ToLower().Trim().Equals("http/1.0"))
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue).ConfigureAwait(false);
}
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray()).ConfigureAwait(false);
}
//Now set the flag to true
//So that next time we can deliver body from cache
ProxySession.Request.RequestBodyRead = true;
WebSession.Request.RequestBodyRead = true;
}
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
private void ReadResponseBody()
private async Task ReadResponseBody()
{
//If not already read (not cached yet)
if (ProxySession.Response.ResponseBody == null)
if (WebSession.Response.ResponseBody == null)
{
using (var responseBodyStream = new MemoryStream())
{
//If chuncked the read chunk by chunk until we hit chunk end symbol
if (ProxySession.Response.IsChunked)
{
while (true)
{
var chuchkHead = ProxySession.ProxyClient.ServerStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
if (WebSession.Response.IsChunked)
{
var buffer = ProxySession.ProxyClient.ServerStreamReader.ReadBytes(chunkSize);
responseBodyStream.Write(buffer, 0, buffer.Length);
//chunk trail
ProxySession.ProxyClient.ServerStreamReader.ReadLine();
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(responseBodyStream).ConfigureAwait(false);
}
else
{
//chuck end
ProxySession.ProxyClient.ServerStreamReader.ReadLine();
break;
}
}
}
else
if (WebSession.Response.ContentLength > 0)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
var buffer = ProxySession.ProxyClient.ServerStreamReader.ReadBytes(ProxySession.Response.ContentLength);
responseBodyStream.Write(buffer, 0, buffer.Length);
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, WebSession.Response.ContentLength).ConfigureAwait(false);
}
else if(WebSession.Response.HttpVersion.ToLower().Trim().Equals("http/1.0"))
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue).ConfigureAwait(false);
}
ProxySession.Response.ResponseBody = GetDecompressedResponseBody(ProxySession.Response.ContentEncoding, responseBodyStream.ToArray());
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray()).ConfigureAwait(false);
}
//set this to true for caching
ProxySession.Response.ResponseBodyRead = true;
WebSession.Response.ResponseBodyRead = true;
}
}
......@@ -225,130 +139,149 @@ namespace Titanium.Web.Proxy.EventArguments
/// Gets the request body as bytes
/// </summary>
/// <returns></returns>
public byte[] GetRequestBody()
public async Task<byte[]> GetRequestBody()
{
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function after request is made to server.");
ReadRequestBody();
return ProxySession.Request.RequestBody;
await ReadRequestBody().ConfigureAwait(false);
return WebSession.Request.RequestBody;
}
/// <summary>
/// Gets the request body as string
/// </summary>
/// <returns></returns>
public string GetRequestBodyAsString()
public async Task<string> GetRequestBodyAsString()
{
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function after request is made to server.");
ReadRequestBody();
await ReadRequestBody().ConfigureAwait(false);
//Use the encoding specified in request to decode the byte[] data to string
return ProxySession.Request.RequestBodyString ?? (ProxySession.Request.RequestBodyString = ProxySession.Request.Encoding.GetString(ProxySession.Request.RequestBody));
return WebSession.Request.RequestBodyString ?? (WebSession.Request.RequestBodyString = WebSession.Request.Encoding.GetString(WebSession.Request.RequestBody));
}
/// <summary>
/// Sets the request body
/// </summary>
/// <param name="body"></param>
public void SetRequestBody(byte[] body)
public async Task SetRequestBody(byte[] body)
{
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
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 (!ProxySession.Request.RequestBodyRead)
if (!WebSession.Request.RequestBodyRead)
{
ReadRequestBody();
await ReadRequestBody().ConfigureAwait(false);
}
ProxySession.Request.RequestBody = body;
ProxySession.Request.RequestBodyRead = true;
WebSession.Request.RequestBody = body;
if (WebSession.Request.IsChunked == false)
WebSession.Request.ContentLength = body.Length;
else
WebSession.Request.ContentLength = -1;
}
/// <summary>
/// Sets the body with the specified string
/// </summary>
/// <param name="body"></param>
public void SetRequestBodyString(string body)
public async Task SetRequestBodyString(string body)
{
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
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 (!ProxySession.Request.RequestBodyRead)
if (!WebSession.Request.RequestBodyRead)
{
ReadRequestBody();
await ReadRequestBody().ConfigureAwait(false);
}
ProxySession.Request.RequestBody = ProxySession.Request.Encoding.GetBytes(body);
ProxySession.Request.RequestBodyRead = true;
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
}
/// <summary>
/// Gets the response body as byte array
/// </summary>
/// <returns></returns>
public byte[] GetResponseBody()
public async Task<byte[]> GetResponseBody()
{
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
if (!WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function before request is made to server.");
ReadResponseBody();
return ProxySession.Response.ResponseBody;
await ReadResponseBody().ConfigureAwait(false);
return WebSession.Response.ResponseBody;
}
/// <summary>
/// Gets the response body as string
/// </summary>
/// <returns></returns>
public string GetResponseBodyAsString()
public async Task<string> GetResponseBodyAsString()
{
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
if (!WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function before request is made to server.");
GetResponseBody();
await GetResponseBody().ConfigureAwait(false);
return ProxySession.Response.ResponseBodyString ?? (ProxySession.Response.ResponseBodyString = ProxySession.Response.Encoding.GetString(ProxySession.Response.ResponseBody));
return WebSession.Response.ResponseBodyString ?? (WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
}
/// <summary>
/// Set the response body bytes
/// </summary>
/// <param name="body"></param>
public void SetResponseBody(byte[] body)
public async Task SetResponseBody(byte[] body)
{
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
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 (ProxySession.Response.ResponseBody == null)
if (WebSession.Response.ResponseBody == null)
{
GetResponseBody();
await GetResponseBody().ConfigureAwait(false);
}
ProxySession.Response.ResponseBody = body;
WebSession.Response.ResponseBody = body;
//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>
/// Replace the response body with the specified string
/// </summary>
/// <param name="body"></param>
public void SetResponseBodyString(string body)
public async Task SetResponseBodyString(string body)
{
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
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 (ProxySession.Response.ResponseBody == null)
if (WebSession.Response.ResponseBody == null)
{
GetResponseBody();
await GetResponseBody().ConfigureAwait(false);
}
var bodyBytes = ProxySession.Response.Encoding.GetBytes(body);
SetResponseBody(bodyBytes);
var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
await SetResponseBody(bodyBytes).ConfigureAwait(false);
}
private byte[] GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream)
private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream)
{
var decompressionFactory = new DecompressionFactory();
var decompressor = decompressionFactory.Create(encodingType);
return decompressor.Decompress(responseBodyStream);
return await decompressor.Decompress(responseBodyStream).ConfigureAwait(false);
}
......@@ -358,16 +291,17 @@ namespace Titanium.Web.Proxy.EventArguments
/// and ignore the request
/// </summary>
/// <param name="html"></param>
public void Ok(string html)
public async Task Ok(string html)
{
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
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);
Ok(result);
await Ok(result).ConfigureAwait(false);
}
/// <summary>
......@@ -376,42 +310,42 @@ namespace Titanium.Web.Proxy.EventArguments
/// and ignore the request
/// </summary>
/// <param name="body"></param>
public void Ok(byte[] result)
public async Task Ok(byte[] result)
{
var response = new OkResponse();
response.HttpVersion = ProxySession.Request.HttpVersion;
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result;
Respond(response);
await Respond(response).ConfigureAwait(false);
ProxySession.Request.CancelRequest = true;
WebSession.Request.CancelRequest = true;
}
public void Redirect(string url)
public async Task Redirect(string url)
{
var response = new RedirectResponse();
response.HttpVersion = ProxySession.Request.HttpVersion;
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add(new Models.HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
Respond(response);
await Respond(response).ConfigureAwait(false);
ProxySession.Request.CancelRequest = true;
WebSession.Request.CancelRequest = true;
}
/// a generic responder method
public void Respond(Response response)
public async Task Respond(Response response)
{
ProxySession.Request.RequestLocked = true;
WebSession.Request.RequestLocked = true;
response.ResponseLocked = true;
response.ResponseBodyRead = true;
ProxySession.Response = response;
WebSession.Response = response;
ProxyServer.HandleHttpSessionResponse(this);
await ProxyServer.HandleHttpSessionResponse(this).ConfigureAwait(false);
}
}
......
using System.Net;
using System.Text;
using Titanium.Web.Proxy.Network;
using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions
{
......@@ -15,10 +15,11 @@ namespace Titanium.Web.Proxy.Extensions
try
{
//return default if not specified
if (request.ContentType == null) return Encoding.GetEncoding("ISO-8859-1");
if (request.ContentType == null)
return Encoding.GetEncoding("ISO-8859-1");
//extract the encoding by finding the charset
var contentTypes = request.ContentType.Split(';');
var contentTypes = request.ContentType.Split(Constants.SemiColonSplit);
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
......
using System.Net;
using System.Text;
using Titanium.Web.Proxy.Network;
using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions
{
public static class HttpWebResponseExtensions
{
public static Encoding GetResponseEncoding(this Response response)
public static Encoding GetResponseCharacterEncoding(this Response response)
{
if (string.IsNullOrEmpty(response.CharacterSet))
try
{
//return default if not specified
if (response.ContentType == null)
return Encoding.GetEncoding("ISO-8859-1");
try
//extract the encoding by finding the charset
var contentTypes = response.ContentType.Split(Constants.SemiColonSplit);
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset")
{
return Encoding.GetEncoding(encodingSplit[1]);
}
}
}
catch
{
return Encoding.GetEncoding(response.CharacterSet.Replace(@"""", string.Empty));
//parsing errors
// ignored
}
catch { return Encoding.GetEncoding("ISO-8859-1"); }
//return default if not specified
return Encoding.GetEncoding("ISO-8859-1");
}
}
}
\ No newline at end of file
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions
{
public static class StreamHelper
{
public static void CopyToAsync(this Stream input, string initialData, Stream output, int bufferSize)
public static async Task CopyToAsync(this Stream input, string initialData, Stream output)
{
if(!string.IsNullOrEmpty(initialData))
if (!string.IsNullOrEmpty(initialData))
{
var bytes = Encoding.ASCII.GetBytes(initialData);
output.Write(bytes, 0, bytes.Length);
await output.WriteAsync(bytes, 0, bytes.Length);
}
CopyToAsync(input, output, bufferSize);
await input.CopyToAsync(output);
}
//http://stackoverflow.com/questions/1540658/net-asynchronous-stream-read-write
private static void CopyToAsync(this Stream input, Stream output, int bufferSize)
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead)
{
try
var totalbytesRead = 0;
long bytesToRead;
if (totalBytesToRead < Constants.BUFFER_SIZE)
{
if (!input.CanRead) throw new InvalidOperationException("input must be open for reading");
if (!output.CanWrite) throw new InvalidOperationException("output must be open for writing");
bytesToRead = totalBytesToRead;
}
else
bytesToRead = Constants.BUFFER_SIZE;
byte[][] buf = {new byte[bufferSize], new byte[bufferSize]};
int[] bufl = {0, 0};
var bufno = 0;
var read = input.BeginRead(buf[bufno], 0, buf[bufno].Length, null, null);
IAsyncResult write = null;
while (true)
while (totalbytesRead < totalBytesToRead)
{
// wait for the read operation to complete
read.AsyncWaitHandle.WaitOne();
bufl[bufno] = input.EndRead(read);
var buffer = await streamReader.ReadBytesAsync(bytesToRead);
// if zero bytes read, the copy is complete
if (bufl[bufno] == 0)
{
if (buffer.Length == 0)
break;
}
// wait for the in-flight write operation, if one exists, to complete
// the only time one won't exist is after the very first read operation completes
if (write != null)
totalbytesRead += buffer.Length;
var remainingBytes = totalBytesToRead - totalbytesRead;
if (remainingBytes < bytesToRead)
{
write.AsyncWaitHandle.WaitOne();
output.EndWrite(write);
bytesToRead = remainingBytes;
}
// start the new write operation
write = output.BeginWrite(buf[bufno], 0, bufl[bufno], null, null);
// toggle the current, in-use buffer
// and start the read operation on the new buffer.
//
// Changed to use XOR to toggle between 0 and 1.
// A little speedier than using a ternary expression.
bufno ^= 1; // bufno = ( bufno == 0 ? 1 : 0 ) ;
read = input.BeginRead(buf[bufno], 0, buf[bufno].Length, null, null);
await stream.WriteAsync(buffer, 0, buffer.Length);
}
// wait for the final in-flight write operation, if one exists, to complete
// the only time one won't exist is if the input stream is empty.
if (write != null)
{
write.AsyncWaitHandle.WaitOne();
output.EndWrite(write);
}
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
{
while (true)
{
var chuchkHead = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
output.Flush();
if (chunkSize != 0)
{
var buffer = await clientStreamReader.ReadBytesAsync(chunkSize);
await stream.WriteAsync(buffer, 0, buffer.Length);
//chunk trail
await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
}
catch
else
{
// ignored
await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
break;
}
}
// return to the caller ;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Extensions
......
......@@ -4,6 +4,8 @@ using System.Diagnostics;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Reflection;
using System.Threading.Tasks;
using System.Threading;
namespace Titanium.Web.Proxy.Helpers
{
......@@ -13,6 +15,7 @@ namespace Titanium.Web.Proxy.Helpers
"-ss {0} -n \"CN={1}, O={2}\" -sky {3} -cy {4} -m 120 -a sha256 -eku 1.3.6.1.5.5.7.3.1 {5}";
private readonly IDictionary<string, X509Certificate2> _certificateCache;
private static SemaphoreSlim semaphoreLock = new SemaphoreSlim(1);
public string Issuer { get; private set; }
public string RootCertificateName { get; private set; }
......@@ -35,10 +38,10 @@ namespace Titanium.Web.Proxy.Helpers
/// Attempts to move a self-signed certificate to the root store.
/// </summary>
/// <returns>true if succeeded, else false</returns>
public bool CreateTrustedRootCertificate()
public async Task<bool> CreateTrustedRootCertificate()
{
X509Certificate2 rootCertificate =
CreateCertificate(RootStore, RootCertificateName);
await CreateCertificate(RootStore, RootCertificateName);
return rootCertificate != null;
}
......@@ -46,9 +49,9 @@ namespace Titanium.Web.Proxy.Helpers
/// Attempts to remove the self-signed certificate from the root store.
/// </summary>
/// <returns>true if succeeded, else false</returns>
public bool DestroyTrustedRootCertificate()
public async Task<bool> DestroyTrustedRootCertificate()
{
return DestroyCertificate(RootStore, RootCertificateName);
return await DestroyCertificate(RootStore, RootCertificateName);
}
public X509Certificate2Collection FindCertificates(string certificateSubject)
......@@ -64,18 +67,18 @@ namespace Titanium.Web.Proxy.Helpers
discoveredCertificates : null;
}
public X509Certificate2 CreateCertificate(string certificateName)
public async Task<X509Certificate2> CreateCertificate(string certificateName)
{
return CreateCertificate(MyStore, certificateName);
return await CreateCertificate(MyStore, certificateName);
}
protected virtual X509Certificate2 CreateCertificate(X509Store store, string certificateName)
protected async virtual Task<X509Certificate2> CreateCertificate(X509Store store, string certificateName)
{
if (_certificateCache.ContainsKey(certificateName))
return _certificateCache[certificateName];
lock (store)
{
await semaphoreLock.WaitAsync();
X509Certificate2 certificate = null;
try
{
......@@ -93,26 +96,33 @@ namespace Titanium.Web.Proxy.Helpers
string[] args = new[] {
GetCertificateCreateArgs(store, certificateName) };
CreateCertificate(args);
await CreateCertificate(args);
certificates = FindCertificates(store, certificateSubject);
return certificates != null ?
certificates[0] : null;
}
return certificate;
}
finally
{
store.Close();
if (certificate != null && !_certificateCache.ContainsKey(certificateName))
_certificateCache.Add(certificateName, certificate);
return certificate;
}
finally
{
semaphoreLock.Release();
}
}
protected virtual void CreateCertificate(string[] args)
{
using (var process = new Process())
protected virtual Task<int> CreateCertificate(string[] args)
{
// there is no non-generic TaskCompletionSource
var tcs = new TaskCompletionSource<int>();
var process = new Process();
string file = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "makecert.exe");
if (!File.Exists(file))
......@@ -123,20 +133,34 @@ namespace Titanium.Web.Proxy.Helpers
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = file;
process.EnableRaisingEvents = true;
process.Start();
process.WaitForExit();
process.Exited += (sender, processArgs) =>
{
tcs.SetResult(process.ExitCode);
process.Dispose();
};
bool started = process.Start();
if (!started)
{
//you may allow for the process to be re-used (started = false)
//but I'm not sure about the guarantees of the Exited event in such a case
throw new InvalidOperationException("Could not start process: " + process);
}
return tcs.Task;
}
public bool DestroyCertificate(string certificateName)
public async Task<bool> DestroyCertificate(string certificateName)
{
return DestroyCertificate(MyStore, certificateName);
return await DestroyCertificate(MyStore, certificateName);
}
protected virtual bool DestroyCertificate(X509Store store, string certificateName)
{
lock (store)
protected virtual async Task<bool> DestroyCertificate(X509Store store, string certificateName)
{
await semaphoreLock.WaitAsync();
X509Certificate2Collection certificates = null;
try
{
......@@ -149,18 +173,20 @@ namespace Titanium.Web.Proxy.Helpers
store.RemoveRange(certificates);
certificates = FindCertificates(store, certificateSubject);
}
return certificates == null;
}
finally
{
store.Close();
if (certificates == null &&
_certificateCache.ContainsKey(certificateName))
{
_certificateCache.Remove(certificateName);
}
return certificates == null;
}
finally
{
semaphoreLock.Release();
}
}
protected virtual string GetCertificateCreateArgs(X509Store store, string certificateName)
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
......@@ -10,27 +15,33 @@ namespace Titanium.Web.Proxy.Helpers
/// using the specified encoding
/// as well as to read bytes as required
/// </summary>
public class CustomBinaryReader : BinaryReader
public class CustomBinaryReader : IDisposable
{
internal CustomBinaryReader(Stream stream, Encoding encoding)
: base(stream, encoding)
private Stream stream;
internal CustomBinaryReader(Stream stream)
{
this.stream = stream;
}
public Stream BaseStream => stream;
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
internal string ReadLine()
internal async Task<string> ReadLineAsync()
{
var readBuffer = new StringBuilder();
try
{
var lastChar = default(char);
var buffer = new char[1];
var buffer = new byte[1];
while (Read(buffer, 0, 1) > 0)
while (await this.stream.ReadAsync(buffer, 0, 1).ConfigureAwait(false) > 0)
{
if (lastChar == '\r' && buffer[0] == '\n')
{
......@@ -40,8 +51,8 @@ namespace Titanium.Web.Proxy.Helpers
{
return readBuffer.ToString();
}
readBuffer.Append(buffer);
lastChar = buffer[0];
readBuffer.Append((char)buffer[0]);
lastChar = (char)buffer[0];
}
return readBuffer.ToString();
......@@ -56,15 +67,52 @@ namespace Titanium.Web.Proxy.Helpers
/// Read until the last new line
/// </summary>
/// <returns></returns>
internal List<string> ReadAllLines()
internal async Task<List<string>> ReadAllLinesAsync()
{
string tmpLine;
var requestLines = new List<string>();
while (!string.IsNullOrEmpty(tmpLine = ReadLine()))
while (!string.IsNullOrEmpty(tmpLine = await ReadLineAsync().ConfigureAwait(false)))
{
requestLines.Add(tmpLine);
}
return requestLines;
}
internal async Task<byte[]> ReadBytesAsync(long totalBytesToRead)
{
int bytesToRead = Constants.BUFFER_SIZE;
if (totalBytesToRead < Constants.BUFFER_SIZE)
bytesToRead = (int)totalBytesToRead;
var buffer = new byte[Constants.BUFFER_SIZE];
var bytesRead = 0;
var totalBytesRead = 0;
using (var outStream = new MemoryStream())
{
while ((bytesRead += await this.stream.ReadAsync(buffer, 0, bytesToRead).ConfigureAwait(false)) > 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
totalBytesRead += bytesRead;
if (totalBytesRead == totalBytesToRead)
break;
bytesRead = 0;
var remainingBytes = (totalBytesToRead - totalBytesRead);
bytesToRead = remainingBytes > (long)Constants.BUFFER_SIZE ? Constants.BUFFER_SIZE : (int)remainingBytes;
}
return outStream.ToArray();
}
}
public void Dispose()
{
}
}
}
\ No newline at end of file
......@@ -8,14 +8,13 @@ using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
public class TcpHelper
{
private static readonly int BUFFER_SIZE = 8192;
public static void SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
public async static Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
int tunnelPort, bool isHttps)
{
StringBuilder sb = null;
......@@ -51,7 +50,7 @@ namespace Titanium.Web.Proxy.Helpers
try
{
sslStream = new SslStream(tunnelStream);
sslStream.AuthenticateAsClient(hostName, null, ProxyServer.SupportedProtocols, false);
await sslStream.AuthenticateAsClientAsync(hostName, null, Constants.SupportedProtocols, false);
tunnelStream = sslStream;
}
catch
......@@ -63,18 +62,17 @@ namespace Titanium.Web.Proxy.Helpers
}
}
Task sendRelay;
var sendRelay = Task.Factory.StartNew(() =>
{
if (sb != null)
clientStream.CopyToAsync(sb.ToString(), tunnelStream, BUFFER_SIZE);
sendRelay = clientStream.CopyToAsync(sb.ToString(), tunnelStream);
else
clientStream.CopyToAsync(string.Empty, tunnelStream, BUFFER_SIZE);
});
sendRelay = clientStream.CopyToAsync(string.Empty, tunnelStream);
var receiveRelay = Task.Factory.StartNew(() => tunnelStream.CopyToAsync(string.Empty, clientStream, BUFFER_SIZE));
var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream);
Task.WaitAll(sendRelay, receiveRelay);
await Task.WhenAll(sendRelay, receiveRelay).ConfigureAwait(false);
}
catch
{
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
public class HttpWebSession
{
internal TcpConnection ServerConnection { get; set; }
public Request Request { get; set; }
public Response Response { get; set; }
public bool IsSecure
{
get
{
return this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
}
}
internal void SetConnection(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ServerConnection = Connection;
}
internal HttpWebSession()
{
this.Request = new Request();
this.Response = new Response();
}
internal async Task SendRequest()
{
Stream stream = ServerConnection.Stream;
StringBuilder requestLines = new StringBuilder();
requestLines.AppendLine(string.Join(" ", new string[3]
{
this.Request.Method,
this.Request.RequestUri.PathAndQuery,
this.Request.HttpVersion
}));
foreach (HttpHeader httpHeader in this.Request.RequestHeaders)
{
requestLines.AppendLine(httpHeader.Name + ':' + httpHeader.Value);
}
requestLines.AppendLine();
string request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync();
if (ProxyServer.Enable100ContinueBehaviour)
if (this.Request.ExpectContinue)
{
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(Constants.SpaceSplit, 3);
var responseStatusCode = httpResult[1].Trim();
var responseStatusDescription = httpResult[2].Trim();
//find if server is willing for expect continue
if (responseStatusCode.Equals("100")
&& responseStatusDescription.ToLower().Equals("continue"))
{
this.Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false);
}
else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false);
}
}
}
internal async Task ReceiveResponse()
{
//return if this is already read
if (this.Response.ResponseStatusCode != null) return;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(Constants.SpaceSplit, 3);
if (string.IsNullOrEmpty(httpResult[0]))
{
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false);
}
this.Response.HttpVersion = httpResult[0].Trim();
this.Response.ResponseStatusCode = httpResult[1].Trim();
this.Response.ResponseStatusDescription = httpResult[2].Trim();
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (this.Response.ResponseStatusCode.Equals("100")
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
{
this.Response.Is100Continue = true;
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false);
await ReceiveResponse();
return;
}
else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false);
await ReceiveResponse();
return;
}
List<string> responseLines = await ServerConnection.StreamReader.ReadAllLinesAsync().ConfigureAwait(false);
for (int index = 0; index < responseLines.Count; ++index)
{
string[] strArray = responseLines[index].Split(Constants.ColonSplit, 2);
this.Response.ResponseHeaders.Add(new HttpHeader(strArray[0], strArray[1]));
}
}
}
}
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using System.Linq;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Network
namespace Titanium.Web.Proxy.Http
{
public class Request
{
......@@ -39,30 +32,56 @@ namespace Titanium.Web.Proxy.Network
}
}
public int ContentLength
internal string ContentEncoding
{
get
{
var header = this.RequestHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-encoding"));
if (header != null)
{
return header.Value.Trim();
}
return null;
}
}
public long ContentLength
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
if (header == null)
return 0;
return -1;
int contentLen;
int.TryParse(header.Value, out contentLen);
if (contentLen != 0)
long contentLen;
long.TryParse(header.Value, out contentLen);
if (contentLen >=0)
return contentLen;
return 0;
return -1;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
if (value >= 0)
{
if (header != null)
header.Value = value.ToString();
else
RequestHeaders.Add(new HttpHeader("content-length", value.ToString()));
IsChunked = false;
}
else
{
if (header != null)
this.RequestHeaders.Remove(header);
}
}
}
......@@ -87,7 +106,7 @@ namespace Titanium.Web.Proxy.Network
}
public bool SendChunked
public bool IsChunked
{
get
{
......@@ -95,6 +114,27 @@ namespace Titanium.Web.Proxy.Network
if (header != null) return header.Value.ToLower().Contains("chunked");
return false;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "transfer-encoding");
if (value)
{
if (header != null)
{
header.Value = "chunked";
}
else
RequestHeaders.Add(new HttpHeader("transfer-encoding", "chunked"));
this.ContentLength = -1;
}
else
{
if (header != null)
RequestHeaders.Remove(header);
}
}
}
public bool ExpectContinue
......@@ -146,253 +186,4 @@ namespace Titanium.Web.Proxy.Network
}
}
public class Response
{
public string ResponseStatusCode { get; set; }
public string ResponseStatusDescription { get; set; }
internal Encoding Encoding { get { return this.GetResponseEncoding(); } }
internal string CharacterSet
{
get
{
if (this.ContentType.Contains(";"))
{
return this.ContentType.Split(';')[1].Substring(9).Trim();
}
return null;
}
}
internal string ContentEncoding
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-encoding"));
if (header != null)
{
return header.Value.Trim().ToLower();
}
return null;
}
}
internal string HttpVersion { get; set; }
internal bool ResponseKeepAlive
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("connection"));
if (header != null && header.Value.ToLower().Contains("close"))
{
return false;
}
return true;
}
}
public string ContentType
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-type"));
if (header != null)
{
if (header.Value.Contains(";"))
{
return header.Value.Split(';')[0].Trim();
}
else
return header.Value.ToLower().Trim();
}
return null;
}
}
internal int ContentLength
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-length"));
if (header != null)
{
return int.Parse(header.Value.Trim());
}
return -1;
}
}
internal bool IsChunked
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("transfer-encoding"));
if (header != null && header.Value.ToLower().Contains("chunked"))
{
return true;
}
return false;
}
}
public List<HttpHeader> ResponseHeaders { get; set; }
internal Stream ResponseStream { get; set; }
internal byte[] ResponseBody { get; set; }
internal string ResponseBodyString { get; set; }
internal bool ResponseBodyRead { get; set; }
internal bool ResponseLocked { get; set; }
public bool Is100Continue { get; internal set; }
public bool ExpectationFailed { get; internal set; }
public Response()
{
this.ResponseHeaders = new List<HttpHeader>();
}
}
public class HttpWebSession
{
private const string Space = " ";
public bool IsSecure
{
get
{
return this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
}
}
public Request Request { get; set; }
public Response Response { get; set; }
internal TcpConnection ProxyClient { get; set; }
public void SetConnection(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ProxyClient = Connection;
}
public HttpWebSession()
{
this.Request = new Request();
this.Response = new Response();
}
public void SendRequest()
{
Stream stream = ProxyClient.Stream;
StringBuilder requestLines = new StringBuilder();
requestLines.AppendLine(string.Join(" ", new string[3]
{
this.Request.Method,
this.Request.RequestUri.PathAndQuery,
this.Request.HttpVersion
}));
foreach (HttpHeader httpHeader in this.Request.RequestHeaders)
{
requestLines.AppendLine(httpHeader.Name + ':' + httpHeader.Value);
}
requestLines.AppendLine();
string request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request);
stream.Write(requestBytes, 0, requestBytes.Length);
stream.Flush();
if (ProxyServer.Enable100ContinueBehaviour)
if (this.Request.ExpectContinue)
{
var httpResult = ProxyClient.ServerStreamReader.ReadLine().Split(new char[] { ' ' }, 3);
var responseStatusCode = httpResult[1].Trim();
var responseStatusDescription = httpResult[2].Trim();
//find if server is willing for expect continue
if (responseStatusCode.Equals("100")
&& responseStatusDescription.ToLower().Equals("continue"))
{
this.Request.Is100Continue = true;
ProxyClient.ServerStreamReader.ReadLine();
}
else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Request.ExpectationFailed = true;
ProxyClient.ServerStreamReader.ReadLine();
}
}
}
public void ReceiveResponse()
{
//return if this is already read
if (this.Response.ResponseStatusCode != null) return;
var httpResult = ProxyClient.ServerStreamReader.ReadLine().Split(new char[] { ' ' }, 3);
if (string.IsNullOrEmpty(httpResult[0]))
{
var s = ProxyClient.ServerStreamReader.ReadLine();
}
this.Response.HttpVersion = httpResult[0].Trim();
this.Response.ResponseStatusCode = httpResult[1].Trim();
this.Response.ResponseStatusDescription = httpResult[2].Trim();
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (this.Response.ResponseStatusCode.Equals("100")
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
{
this.Response.Is100Continue = true;
this.Response.ResponseStatusCode = null;
ProxyClient.ServerStreamReader.ReadLine();
ReceiveResponse();
return;
}
else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null;
ProxyClient.ServerStreamReader.ReadLine();
ReceiveResponse();
return;
}
List<string> responseLines = ProxyClient.ServerStreamReader.ReadAllLines();
for (int index = 0; index < responseLines.Count; ++index)
{
string[] strArray = responseLines[index].Split(new char[] { ':' }, 2);
this.Response.ResponseHeaders.Add(new HttpHeader(strArray[0], strArray[1]));
}
}
}
}
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Http
{
public class Response
{
public string ResponseStatusCode { get; set; }
public string ResponseStatusDescription { get; set; }
internal Encoding Encoding { get { return this.GetResponseCharacterEncoding(); } }
internal string ContentEncoding
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-encoding"));
if (header != null)
{
return header.Value.Trim();
}
return null;
}
}
internal string HttpVersion { get; set; }
internal bool ResponseKeepAlive
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("connection"));
if (header != null && header.Value.ToLower().Contains("close"))
{
return false;
}
return true;
}
}
public string ContentType
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-type"));
if (header != null)
{
return header.Value;
}
return null;
}
}
internal long ContentLength
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-length"));
if (header == null)
return -1;
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"));
if (value >= 0)
{
if (header != null)
header.Value = value.ToString();
else
ResponseHeaders.Add(new HttpHeader("content-length", value.ToString()));
IsChunked = false;
}
else
{
if (header != null)
this.ResponseHeaders.Remove(header);
}
}
}
internal bool IsChunked
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("transfer-encoding"));
if (header != null && header.Value.ToLower().Contains("chunked"))
{
return true;
}
return false;
}
set
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("transfer-encoding"));
if (value)
{
if (header != null)
{
header.Value = "chunked";
}
else
ResponseHeaders.Add(new HttpHeader("transfer-encoding", "chunked"));
this.ContentLength = -1;
}
else
{
if (header != null)
ResponseHeaders.Remove(header);
}
}
}
public List<HttpHeader> ResponseHeaders { get; set; }
internal Stream ResponseStream { get; set; }
internal byte[] ResponseBody { get; set; }
internal string ResponseBodyString { get; set; }
internal bool ResponseBodyRead { get; set; }
internal bool ResponseLocked { get; set; }
public bool Is100Continue { get; internal set; }
public bool ExpectationFailed { get; internal set; }
public Response()
{
this.ResponseHeaders = new List<HttpHeader>();
}
}
}
using System;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Responses
namespace Titanium.Web.Proxy.Http.Responses
{
public class OkResponse : Response
{
......
using System;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Responses
namespace Titanium.Web.Proxy.Http.Responses
{
public class RedirectResponse : Response
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Titanium.Web.Proxy.Models
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Models
{
public class ExternalProxy
{
public string HostName { get; set; }
public int Port { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// Used to pass in Session object for ServerCertificateValidation Callback
/// </summary>
internal class CustomSslStream : SslStream
{
/// <summary>
/// Holds the current session
/// </summary>
internal SessionEventArgs Session { get; set; }
public CustomSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback)
:base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback)
{
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.IO;
using System.Net.Sockets;
using System.Text;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.EventArguments
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// This class wraps Tcp connection to Server
......
......@@ -3,30 +3,33 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.IO;
using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using System.Threading;
using System.Security.Authentication;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
using System.Security.Cryptography.X509Certificates;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Network
{
public class TcpConnection
{
public string HostName { get; set; }
public int port { get; set; }
public bool IsSecure { get; set; }
internal string HostName { get; set; }
internal int port { get; set; }
internal bool IsSecure { get; set; }
internal Version Version { get; set; }
public TcpClient TcpClient { get; set; }
public CustomBinaryReader ServerStreamReader { get; set; }
public Stream Stream { get; set; }
internal TcpClient TcpClient { get; set; }
internal CustomBinaryReader StreamReader { get; set; }
internal Stream Stream { get; set; }
public DateTime LastAccess { get; set; }
internal DateTime LastAccess { get; set; }
public TcpConnection()
internal TcpConnection()
{
LastAccess = DateTime.Now;
}
......@@ -36,14 +39,15 @@ namespace Titanium.Web.Proxy.Network
{
static List<TcpConnection> ConnectionCache = new List<TcpConnection>();
public static TcpConnection GetClient(string Hostname, int port, bool IsSecure)
internal static async Task<TcpConnection> GetClient(SessionEventArgs sessionArgs, string hostname, int port, bool isSecure, Version version)
{
TcpConnection cached = null;
while (true)
{
lock (ConnectionCache)
{
cached = ConnectionCache.FirstOrDefault(x => x.HostName == Hostname && x.port == port && x.IsSecure == IsSecure && x.TcpClient.Connected);
cached = ConnectionCache.FirstOrDefault(x => x.HostName == hostname && x.port == port &&
x.IsSecure == isSecure && x.TcpClient.Connected && x.Version.Equals(version));
if (cached != null)
ConnectionCache.Remove(cached);
......@@ -57,28 +61,64 @@ namespace Titanium.Web.Proxy.Network
}
if (cached == null)
cached = CreateClient(Hostname, port, IsSecure);
cached = await CreateClient(sessionArgs, hostname, port, isSecure, version).ConfigureAwait(false);
if (ConnectionCache.Where(x => x.HostName == Hostname && x.port == port && x.IsSecure == IsSecure && x.TcpClient.Connected).Count() < 2)
//just create one more preemptively
if (ConnectionCache.Where(x => x.HostName == hostname && x.port == port &&
x.IsSecure == isSecure && x.TcpClient.Connected && x.Version.Equals(version)).Count() < 2)
{
Task.Factory.StartNew(() => ReleaseClient(CreateClient(Hostname, port, IsSecure)));
var task = CreateClient(sessionArgs, hostname, port, isSecure, version)
.ContinueWith(x => { if (x.Status == TaskStatus.RanToCompletion) ReleaseClient(x.Result); });
}
return cached;
}
private static TcpConnection CreateClient(string Hostname, int port, bool IsSecure)
private static async Task<TcpConnection> CreateClient(SessionEventArgs sessionArgs, string hostname, int port, bool isSecure, Version version)
{
TcpClient client;
Stream stream;
if (isSecure)
{
CustomSslStream sslStream = null;
if (ProxyServer.UpStreamHttpsProxy != null)
{
client = new TcpClient(ProxyServer.UpStreamHttpsProxy.HostName, ProxyServer.UpStreamHttpsProxy.Port);
stream = (Stream)client.GetStream();
using (var writer = new StreamWriter(stream, Encoding.ASCII, Constants.BUFFER_SIZE, true))
{
var client = new TcpClient(Hostname, port);
var stream = (Stream)client.GetStream();
await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port, sessionArgs.WebSession.Request.HttpVersion));
await writer.WriteLineAsync(string.Format("Host: {0}:{1}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port));
await writer.WriteLineAsync("Connection: Keep-Alive");
await writer.WriteLineAsync();
await writer.FlushAsync();
writer.Close();
}
if (IsSecure)
using (var reader = new CustomBinaryReader(stream))
{
var sslStream = (SslStream)null;
var result = await reader.ReadLineAsync().ConfigureAwait(false);
if (!result.ToLower().Contains("200 connection established"))
throw new Exception("Upstream proxy failed to create a secure tunnel");
await reader.ReadAllLinesAsync().ConfigureAwait(false);
}
}
else
{
client = new TcpClient(hostname, port);
stream = (Stream)client.GetStream();
}
try
{
sslStream = new SslStream(stream);
sslStream.AuthenticateAsClient(Hostname, null, ProxyServer.SupportedProtocols , false);
sslStream = new CustomSslStream(stream, true, new RemoteCertificateValidationCallback(ProxyServer.ValidateServerCertificate));
sslStream.Session = sessionArgs;
await sslStream.AuthenticateAsClientAsync(hostname, null, Constants.SupportedProtocols, false).ConfigureAwait(false);
stream = (Stream)sslStream;
}
catch
......@@ -88,25 +128,40 @@ namespace Titanium.Web.Proxy.Network
throw;
}
}
else
{
if (ProxyServer.UpStreamHttpProxy != null)
{
client = new TcpClient(ProxyServer.UpStreamHttpProxy.HostName, ProxyServer.UpStreamHttpProxy.Port);
stream = (Stream)client.GetStream();
}
else
{
client = new TcpClient(hostname, port);
stream = (Stream)client.GetStream();
}
}
return new TcpConnection()
{
HostName = Hostname,
HostName = hostname,
port = port,
IsSecure = IsSecure,
IsSecure = isSecure,
TcpClient = client,
ServerStreamReader = new CustomBinaryReader(stream, Encoding.ASCII),
Stream = stream
StreamReader = new CustomBinaryReader(stream),
Stream = stream,
Version = version
};
}
public static void ReleaseClient(TcpConnection Connection)
internal static void ReleaseClient(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ConnectionCache.Add(Connection);
}
public static void ClearIdleConnections()
internal async static void ClearIdleConnections()
{
while (true)
{
......@@ -122,11 +177,10 @@ namespace Titanium.Web.Proxy.Network
ConnectionCache.RemoveAll(x => x.LastAccess < cutOff);
}
Thread.Sleep(1000 * 60 * 3);
await Task.Delay(1000 * 60 * 3).ConfigureAwait(false);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using System.Linq;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
namespace Titanium.Web.Proxy
{
......@@ -22,35 +19,14 @@ namespace Titanium.Web.Proxy
public partial class ProxyServer
{
private static readonly char[] SemiSplit = { ';' };
private static readonly string[] ColonSpaceSplit = { ": " };
private static readonly char[] SpaceSplit = { ' ' };
private static readonly Regex CookieSplitRegEx = new Regex(@",(?! )");
private static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(Environment.NewLine);
private static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
public static readonly int BUFFER_SIZE = 8192;
#if NET45
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
#else
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Ssl3;
#endif
static ProxyServer()
{
ProxyEndPoints = new List<ProxyEndPoint>();
Initialize();
}
private static CertificateManager CertManager { get; set; }
private static bool EnableSsl { get; set; }
private static bool certTrusted { get; set; }
private static bool proxyRunning { get; set; }
......@@ -58,14 +34,30 @@ namespace Titanium.Web.Proxy
public static string RootCertificateName { get; set; }
public static bool Enable100ContinueBehaviour { get; set; }
public static event EventHandler<SessionEventArgs> BeforeRequest;
public static event EventHandler<SessionEventArgs> BeforeResponse;
public static event Func<object, SessionEventArgs, Task> BeforeRequest;
public static event Func<object, SessionEventArgs, Task> BeforeResponse;
/// <summary>
/// External proxy for Http
/// </summary>
public static ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary>
/// External proxy for Http
/// </summary>
public static ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary>
public static event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback;
public static List<ProxyEndPoint> ProxyEndPoints { get; set; }
public static void Initialize()
{
Task.Factory.StartNew(() => TcpConnectionManager.ClearIdleConnections());
TcpConnectionManager.ClearIdleConnections();
}
public static void AddEndPoint(ProxyEndPoint endPoint)
......@@ -162,10 +154,7 @@ namespace Titanium.Web.Proxy
CertManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName);
EnableSsl = ProxyEndPoints.Any(x => x.EnableSsl);
if (EnableSsl)
certTrusted = CertManager.CreateTrustedRootCertificate();
certTrusted = CertManager.CreateTrustedRootCertificate().Result;
foreach (var endPoint in ProxyEndPoints)
{
......@@ -233,19 +222,21 @@ namespace Titanium.Web.Proxy
{
var client = endPoint.listener.EndAcceptTcpClient(asyn);
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
Task.Factory.StartNew(() => HandleClient(endPoint as TransparentProxyEndPoint, client));
HandleClient(endPoint as TransparentProxyEndPoint, client);
else
Task.Factory.StartNew(() => HandleClient(endPoint as ExplicitProxyEndPoint, client));
HandleClient(endPoint as ExplicitProxyEndPoint, client);
// Get the listener that handles the client request.
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
catch
catch (ObjectDisposedException)
{
// ignored
// The listener was Stop()'d, disposing the underlying socket and
// triggering the completion of the callback. We're already exiting,
// so just return.
return;
}
}
}
......
......@@ -3,29 +3,30 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models;
using System.Security.Cryptography.X509Certificates;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy
{
partial class ProxyServer
{
//This is called when client is aware of proxy
private static void HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client)
private static async void HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client)
{
Stream clientStream = client.GetStream();
var clientStreamReader = new CustomBinaryReader(clientStream, Encoding.ASCII);
var clientStreamReader = new CustomBinaryReader(clientStream);
var clientStreamWriter = new StreamWriter(clientStream);
Uri httpRemoteUri;
......@@ -33,7 +34,7 @@ namespace Titanium.Web.Proxy
{
//read the first line HTTP command
var httpCmd = clientStreamReader.ReadLine();
var httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
if (string.IsNullOrEmpty(httpCmd))
{
......@@ -42,7 +43,7 @@ namespace Titanium.Web.Proxy
}
//break up the line into three components (method, remote URL & Http Version)
var httpCmdSplit = httpCmd.Split(SpaceSplit, 3);
var httpCmdSplit = httpCmd.Split(Constants.SpaceSplit, 3);
var httpVerb = httpCmdSplit[0];
......@@ -51,7 +52,10 @@ namespace Titanium.Web.Proxy
else
httpRemoteUri = new Uri(httpCmdSplit[1]);
var httpVersion = httpCmdSplit[2];
string httpVersion = "HTTP/1.1";
if (httpCmdSplit.Length == 3)
httpVersion = httpCmdSplit[2];
var excluded = endPoint.ExcludedHttpsHostNameRegex != null ? endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false;
......@@ -59,11 +63,11 @@ namespace Titanium.Web.Proxy
if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
{
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
clientStreamReader.ReadAllLines();
await clientStreamReader.ReadAllLinesAsync().ConfigureAwait(false);
WriteConnectResponse(clientStreamWriter, httpVersion);
await WriteConnectResponse(clientStreamWriter, httpVersion).ConfigureAwait(false);
var certificate = CertManager.CreateCertificate(httpRemoteUri.Host);
var certificate = await CertManager.CreateCertificate(httpRemoteUri.Host);
SslStream sslStream = null;
......@@ -72,10 +76,10 @@ namespace Titanium.Web.Proxy
sslStream = new SslStream(clientStream, true);
//Successfully managed to authenticate the client using the fake certificate
sslStream.AuthenticateAsServer(certificate, false,
SupportedProtocols, false);
await sslStream.AuthenticateAsServerAsync(certificate, false,
Constants.SupportedProtocols, false).ConfigureAwait(false);
clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII);
clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamWriter = new StreamWriter(sslStream);
//HTTPS server created - we can now decrypt the client's traffic
clientStream = sslStream;
......@@ -91,16 +95,16 @@ namespace Titanium.Web.Proxy
}
httpCmd = clientStreamReader.ReadLine();
httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
}
else if (httpVerb.ToUpper() == "CONNECT")
{
clientStreamReader.ReadAllLines();
WriteConnectResponse(clientStreamWriter, httpVersion);
await clientStreamReader.ReadAllLinesAsync().ConfigureAwait(false);
await WriteConnectResponse(clientStreamWriter, httpVersion).ConfigureAwait(false);
TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port,
false);
await TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port,
false).ConfigureAwait(false);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
return;
......@@ -108,8 +112,8 @@ namespace Titanium.Web.Proxy
//Now create the request
HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? true : false);
await HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? true : false).ConfigureAwait(false);
}
catch
{
......@@ -119,7 +123,7 @@ namespace Titanium.Web.Proxy
//This is called when requests are routed through router to this endpoint
//For ssl requests
private static void HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
private static async void HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{
Stream clientStream = tcpClient.GetStream();
CustomBinaryReader clientStreamReader = null;
......@@ -135,15 +139,15 @@ namespace Titanium.Web.Proxy
// certificate = CertManager.CreateCertificate(hostName);
//}
//else
certificate = CertManager.CreateCertificate(endPoint.GenericCertificateName);
certificate = await CertManager.CreateCertificate(endPoint.GenericCertificateName);
try
{
//Successfully managed to authenticate the client using the fake certificate
sslStream.AuthenticateAsServer(certificate, false,
SslProtocols.Tls, false);
await sslStream.AuthenticateAsServerAsync(certificate, false,
SslProtocols.Tls, false).ConfigureAwait(false);
clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII);
clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamWriter = new StreamWriter(sslStream);
//HTTPS server created - we can now decrypt the client's traffic
......@@ -160,17 +164,17 @@ namespace Titanium.Web.Proxy
}
else
{
clientStreamReader = new CustomBinaryReader(clientStream, Encoding.ASCII);
clientStreamReader = new CustomBinaryReader(clientStream);
}
var httpCmd = clientStreamReader.ReadLine();
var httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
//Now create the request
HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
true);
await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
true).ConfigureAwait(false);
}
private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
private static async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, bool IsHttps)
{
TcpConnection connection = null;
......@@ -190,11 +194,9 @@ namespace Titanium.Web.Proxy
try
{
//break up the line into three components (method, remote URL & Http Version)
var httpCmdSplit = httpCmd.Split(SpaceSplit, 3);
var httpCmdSplit = httpCmd.Split(Constants.SpaceSplit, 3);
var httpMethod = httpCmdSplit[0];
var httpVersion = httpCmdSplit[2];
Version version;
......@@ -207,111 +209,125 @@ namespace Titanium.Web.Proxy
version = new Version(1, 0);
}
args.ProxySession.Request.RequestHeaders = new List<HttpHeader>();
args.WebSession.Request.RequestHeaders = new List<HttpHeader>();
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine()))
while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync().ConfigureAwait(false)))
{
var header = tmpLine.Split(new char[] { ':' }, 2);
args.ProxySession.Request.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
args.WebSession.Request.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
}
var httpRemoteUri = new Uri(!IsHttps ? httpCmdSplit[1] : (string.Concat("https://", args.ProxySession.Request.Host, httpCmdSplit[1])));
var httpRemoteUri = new Uri(!IsHttps ? httpCmdSplit[1] : (string.Concat("https://", args.WebSession.Request.Host, httpCmdSplit[1])));
args.IsHttps = IsHttps;
args.ProxySession.Request.RequestUri = httpRemoteUri;
args.WebSession.Request.RequestUri = httpRemoteUri;
args.ProxySession.Request.Method = httpMethod;
args.ProxySession.Request.HttpVersion = httpVersion;
args.WebSession.Request.Method = httpMethod;
args.WebSession.Request.HttpVersion = httpVersion;
args.Client.ClientStream = clientStream;
args.Client.ClientStreamReader = clientStreamReader;
args.Client.ClientStreamWriter = clientStreamWriter;
if (args.ProxySession.Request.UpgradeToWebSocket)
if (args.WebSession.Request.UpgradeToWebSocket)
{
TcpHelper.SendRaw(clientStream, httpCmd, args.ProxySession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps);
await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps).ConfigureAwait(false);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
}
PrepareRequestHeaders(args.ProxySession.Request.RequestHeaders, args.ProxySession);
PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Host;
//If requested interception
if (BeforeRequest != null)
{
Delegate[] invocationList = BeforeRequest.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args);
}
await Task.WhenAll(handlerTasks).ConfigureAwait(false);
}
//construct the web request that we are going to issue on behalf of the client.
connection = connection == null ?
TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps)
: lastRequestHostName != args.ProxySession.Request.RequestUri.Host ? TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps)
await TcpConnectionManager.GetClient(args, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version).ConfigureAwait(false)
: lastRequestHostName != args.WebSession.Request.RequestUri.Host ? await TcpConnectionManager.GetClient(args, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version).ConfigureAwait(false)
: connection;
lastRequestHostName = args.ProxySession.Request.RequestUri.Host;
args.ProxySession.Request.Host = args.ProxySession.Request.RequestUri.Host;
lastRequestHostName = args.WebSession.Request.RequestUri.Host;
args.WebSession.Request.RequestLocked = true;
//If requested interception
if (BeforeRequest != null)
if (args.WebSession.Request.CancelRequest)
{
BeforeRequest(null, args);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
break;
}
args.ProxySession.Request.RequestLocked = true;
if (args.ProxySession.Request.ExpectContinue)
if (args.WebSession.Request.ExpectContinue)
{
args.ProxySession.SetConnection(connection);
args.ProxySession.SendRequest();
args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest().ConfigureAwait(false);
}
if (Enable100ContinueBehaviour)
if (args.ProxySession.Request.Is100Continue)
if (args.WebSession.Request.Is100Continue)
{
WriteResponseStatus(args.ProxySession.Response.HttpVersion, "100",
WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.Client.ClientStreamWriter);
args.Client.ClientStreamWriter.WriteLine();
await args.Client.ClientStreamWriter.WriteLineAsync();
}
else if (args.ProxySession.Request.ExpectationFailed)
else if (args.WebSession.Request.ExpectationFailed)
{
WriteResponseStatus(args.ProxySession.Response.HttpVersion, "417",
WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.Client.ClientStreamWriter);
args.Client.ClientStreamWriter.WriteLine();
await args.Client.ClientStreamWriter.WriteLineAsync();
}
if (args.ProxySession.Request.CancelRequest)
if (!args.WebSession.Request.ExpectContinue)
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
break;
args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest().ConfigureAwait(false);
}
if (!args.ProxySession.Request.ExpectContinue)
//If request was modified by user
if (args.WebSession.Request.RequestBodyRead)
{
args.ProxySession.SetConnection(connection);
args.ProxySession.SendRequest();
if(args.WebSession.Request.ContentEncoding!=null)
{
args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
}
//chunked send is not supported as of now
args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
//If request was modified by user
if (args.ProxySession.Request.RequestBodyRead)
{
args.ProxySession.Request.ContentLength = args.ProxySession.Request.RequestBody.Length;
var newStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream;
newStream.Write(args.ProxySession.Request.RequestBody, 0, args.ProxySession.Request.RequestBody.Length);
var newStream = args.WebSession.ServerConnection.Stream;
await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length).ConfigureAwait(false);
}
else
{
if (!args.ProxySession.Request.ExpectationFailed)
if (!args.WebSession.Request.ExpectationFailed)
{
//If its a post/put request, then read the client html body and send it to server
if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT")
{
SendClientRequestBody(args);
await SendClientRequestBody(args).ConfigureAwait(false);
}
}
}
if (!args.ProxySession.Request.ExpectationFailed)
if (!args.WebSession.Request.ExpectationFailed)
{
HandleHttpSessionResponse(args);
await HandleHttpSessionResponse(args).ConfigureAwait(false);
}
//if connection is closing exit
if (args.ProxySession.Response.ResponseKeepAlive == false)
if (args.WebSession.Response.ResponseKeepAlive == false)
{
connection.TcpClient.Close();
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
......@@ -319,7 +335,7 @@ namespace Titanium.Web.Proxy
}
// read the next request
httpCmd = clientStreamReader.ReadLine();
httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
}
catch
......@@ -334,12 +350,12 @@ namespace Titanium.Web.Proxy
TcpConnectionManager.ReleaseClient(connection);
}
private static void WriteConnectResponse(StreamWriter clientStreamWriter, string httpVersion)
private static async Task WriteConnectResponse(StreamWriter clientStreamWriter, string httpVersion)
{
clientStreamWriter.WriteLine(httpVersion + " 200 Connection established");
clientStreamWriter.WriteLine("Timestamp: {0}", DateTime.Now);
clientStreamWriter.WriteLine();
clientStreamWriter.Flush();
await clientStreamWriter.WriteLineAsync(httpVersion + " 200 Connection established").ConfigureAwait(false);
await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now)).ConfigureAwait(false);
await clientStreamWriter.WriteLineAsync().ConfigureAwait(false);
await clientStreamWriter.FlushAsync().ConfigureAwait(false);
}
private static void PrepareRequestHeaders(List<HttpHeader> requestHeaders, HttpWebSession webRequest)
......@@ -379,40 +395,16 @@ namespace Titanium.Web.Proxy
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
}
//This is called when the request is PUT/POST to read the body
private static void SendClientRequestBody(SessionEventArgs args)
private static async Task SendClientRequestBody(SessionEventArgs args)
{
// End the operation
var postStream = args.ProxySession.ProxyClient.Stream;
var postStream = args.WebSession.ServerConnection.Stream;
if (args.ProxySession.Request.ContentLength > 0)
if (args.WebSession.Request.ContentLength > 0)
{
//args.ProxyRequest.AllowWriteStreamBuffering = true;
try
{
var totalbytesRead = 0;
int bytesToRead;
if (args.ProxySession.Request.ContentLength < BUFFER_SIZE)
{
bytesToRead = (int)args.ProxySession.Request.ContentLength;
}
else
bytesToRead = BUFFER_SIZE;
while (totalbytesRead < (int)args.ProxySession.Request.ContentLength)
{
var buffer = args.Client.ClientStreamReader.ReadBytes(bytesToRead);
totalbytesRead += buffer.Length;
var remainingBytes = (int)args.ProxySession.Request.ContentLength - totalbytesRead;
if (remainingBytes < bytesToRead)
{
bytesToRead = remainingBytes;
}
postStream.Write(buffer, 0, buffer.Length);
}
await args.Client.ClientStreamReader.CopyBytesToStream(postStream, args.WebSession.Request.ContentLength).ConfigureAwait(false);
}
catch
{
......@@ -420,35 +412,71 @@ namespace Titanium.Web.Proxy
}
}
//Need to revist, find any potential bugs
else if (args.ProxySession.Request.SendChunked)
else if (args.WebSession.Request.IsChunked)
{
try
{
while (true)
await args.Client.ClientStreamReader.CopyBytesToStreamChunked(postStream).ConfigureAwait(false);
}
catch
{
var chuchkHead = args.Client.ClientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
throw;
}
}
}
if (chunkSize != 0)
/// <summary>
/// Call back to override server certificate validation
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
internal static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
var buffer = args.Client.ClientStreamReader.ReadBytes(chunkSize);
postStream.Write(buffer, 0, buffer.Length);
//chunk trail
args.Client.ClientStreamReader.ReadLine();
}
else
var param = sender as CustomSslStream;
if (ServerCertificateValidationCallback != null)
{
args.Client.ClientStreamReader.ReadLine();
var args = new CertificateValidationEventArgs();
break;
}
}
args.Session = param.Session;
args.Certificate = certificate;
args.Chain = chain;
args.SslPolicyErrors = sslPolicyErrors;
Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>)invocationList[i])(null, args);
}
catch
Task.WhenAll(handlerTasks).Wait();
if (!args.IsValid)
{
throw;
param.Session.WebSession.Request.CancelRequest = true;
}
return args.IsValid;
}
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
//By default
//do not allow this client to communicate with unauthenticated servers.
return false;
}
}
}
\ No newline at end of file
......@@ -3,78 +3,88 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Shared;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy
{
partial class ProxyServer
{
//Called asynchronously when a request was successfully and we received the response
public static void HandleHttpSessionResponse(SessionEventArgs args)
public static async Task HandleHttpSessionResponse(SessionEventArgs args)
{
args.ProxySession.ReceiveResponse();
await args.WebSession.ReceiveResponse().ConfigureAwait(false);
try
{
if (!args.ProxySession.Response.ResponseBodyRead)
args.ProxySession.Response.ResponseStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream;
if (!args.WebSession.Response.ResponseBodyRead)
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
if (BeforeResponse != null && !args.ProxySession.Response.ResponseLocked)
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{
Delegate[] invocationList = BeforeResponse.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
BeforeResponse(null, args);
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args);
}
args.ProxySession.Response.ResponseLocked = true;
await Task.WhenAll(handlerTasks).ConfigureAwait(false);
}
args.WebSession.Response.ResponseLocked = true;
if (args.ProxySession.Response.Is100Continue)
if (args.WebSession.Response.Is100Continue)
{
WriteResponseStatus(args.ProxySession.Response.HttpVersion, "100",
WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.Client.ClientStreamWriter);
args.Client.ClientStreamWriter.WriteLine();
await args.Client.ClientStreamWriter.WriteLineAsync();
}
else if (args.ProxySession.Response.ExpectationFailed)
else if (args.WebSession.Response.ExpectationFailed)
{
WriteResponseStatus(args.ProxySession.Response.HttpVersion, "417",
WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.Client.ClientStreamWriter);
args.Client.ClientStreamWriter.WriteLine();
await args.Client.ClientStreamWriter.WriteLineAsync();
}
WriteResponseStatus(args.ProxySession.Response.HttpVersion, args.ProxySession.Response.ResponseStatusCode,
args.ProxySession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter);
WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode,
args.WebSession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter);
if (args.ProxySession.Response.ResponseBodyRead)
if (args.WebSession.Response.ResponseBodyRead)
{
var isChunked = args.ProxySession.Response.IsChunked;
var contentEncoding = args.ProxySession.Response.ContentEncoding;
var isChunked = args.WebSession.Response.IsChunked;
var contentEncoding = args.WebSession.Response.ContentEncoding;
if (contentEncoding != null)
{
args.ProxySession.Response.ResponseBody = GetCompressedResponseBody(contentEncoding, args.ProxySession.Response.ResponseBody);
args.WebSession.Response.ResponseBody = await GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody).ConfigureAwait(false);
if (isChunked == false)
args.WebSession.Response.ContentLength = args.WebSession.Response.ResponseBody.Length;
else
args.WebSession.Response.ContentLength = -1;
}
WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders, args.ProxySession.Response.ResponseBody.Length,
isChunked);
WriteResponseBody(args.Client.ClientStream, args.ProxySession.Response.ResponseBody, isChunked);
await WriteResponseHeaders(args.Client.ClientStreamWriter, args.WebSession.Response.ResponseHeaders).ConfigureAwait(false);
await WriteResponseBody(args.Client.ClientStream, args.WebSession.Response.ResponseBody, isChunked).ConfigureAwait(false);
}
else
{
WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders);
await WriteResponseHeaders(args.Client.ClientStreamWriter, args.WebSession.Response.ResponseHeaders);
if (args.ProxySession.Response.IsChunked || args.ProxySession.Response.ContentLength > 0)
WriteResponseBody(args.ProxySession.ProxyClient.ServerStreamReader, args.Client.ClientStream, args.ProxySession.Response.IsChunked, args.ProxySession.Response.ContentLength);
if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 || args.WebSession.Response.HttpVersion.ToLower().Trim() == "http/1.0")
await WriteResponseBody(args.WebSession.ServerConnection.StreamReader, args.Client.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength).ConfigureAwait(false);
}
args.Client.ClientStream.Flush();
await args.Client.ClientStream.FlushAsync();
}
catch
......@@ -87,21 +97,21 @@ namespace Titanium.Web.Proxy
}
}
private static byte[] GetCompressedResponseBody(string encodingType, byte[] responseBodyStream)
private static async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream)
{
var compressionFactory = new CompressionFactory();
var compressor = compressionFactory.Create(encodingType);
return compressor.Compress(responseBodyStream);
return await compressor.Compress(responseBodyStream).ConfigureAwait(false);
}
private static void WriteResponseStatus(string version, string code, string description,
StreamWriter responseWriter)
{
responseWriter.WriteLine(string.Format("{0} {1} {2}", version, code, description));
responseWriter.WriteLineAsync(string.Format("{0} {1} {2}", version, code, description));
}
private static void WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers)
private static async Task WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers)
{
if (headers != null)
{
......@@ -109,12 +119,12 @@ namespace Titanium.Web.Proxy
foreach (var header in headers)
{
responseWriter.WriteLine(header.ToString());
await responseWriter.WriteLineAsync(header.ToString());
}
}
responseWriter.WriteLine();
responseWriter.Flush();
await responseWriter.WriteLineAsync();
await responseWriter.FlushAsync();
}
private static void FixResponseProxyHeaders(List<HttpHeader> headers)
{
......@@ -135,118 +145,92 @@ namespace Titanium.Web.Proxy
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
}
private static void WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers, int length,
bool isChunked)
{
FixResponseProxyHeaders(headers);
if (!isChunked)
{
if (headers.Any(x => x.Name.ToLower() == "content-length") == false)
{
headers.Add(new HttpHeader("Content-Length", length.ToString()));
}
}
if (headers != null)
{
foreach (var header in headers)
{
if (!isChunked && header.Name.ToLower() == "content-length")
header.Value = length.ToString();
responseWriter.WriteLine(header.ToString());
}
}
responseWriter.WriteLine();
responseWriter.Flush();
}
private static void WriteResponseBody(Stream clientStream, byte[] data, bool isChunked)
private static async Task WriteResponseBody(Stream clientStream, byte[] data, bool isChunked)
{
if (!isChunked)
{
clientStream.Write(data, 0, data.Length);
await clientStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
}
else
WriteResponseBodyChunked(data, clientStream);
await WriteResponseBodyChunked(data, clientStream).ConfigureAwait(false);
}
private static void WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, int BodyLength)
private static async Task WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, long ContentLength)
{
if (!isChunked)
{
int bytesToRead = BUFFER_SIZE;
//http 1.0
if (ContentLength == -1)
ContentLength = long.MaxValue;
if (BodyLength < BUFFER_SIZE)
bytesToRead = BodyLength;
int bytesToRead = Constants.BUFFER_SIZE;
var buffer = new byte[BUFFER_SIZE];
if (ContentLength < Constants.BUFFER_SIZE)
bytesToRead = (int)ContentLength;
var buffer = new byte[Constants.BUFFER_SIZE];
var bytesRead = 0;
var totalBytesRead = 0;
while ((bytesRead += inStreamReader.BaseStream.Read(buffer, 0, bytesToRead)) > 0)
while ((bytesRead += await inStreamReader.BaseStream.ReadAsync(buffer, 0, bytesToRead).ConfigureAwait(false)) > 0)
{
outStream.Write(buffer, 0, bytesRead);
await outStream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
totalBytesRead += bytesRead;
if (totalBytesRead == BodyLength)
if (totalBytesRead == ContentLength)
break;
bytesRead = 0;
var remainingBytes = (BodyLength - totalBytesRead);
bytesToRead = remainingBytes > BUFFER_SIZE ? BUFFER_SIZE : remainingBytes;
var remainingBytes = (ContentLength - totalBytesRead);
bytesToRead = remainingBytes > (long)Constants.BUFFER_SIZE ? Constants.BUFFER_SIZE : (int)remainingBytes;
}
}
else
WriteResponseBodyChunked(inStreamReader, outStream);
await WriteResponseBodyChunked(inStreamReader, outStream).ConfigureAwait(false);
}
//Send chunked response
private static void WriteResponseBodyChunked(CustomBinaryReader inStreamReader, Stream outStream)
private static async Task WriteResponseBodyChunked(CustomBinaryReader inStreamReader, Stream outStream)
{
while (true)
{
var chuchkHead = inStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
var chunkHead = await inStreamReader.ReadLineAsync().ConfigureAwait(false);
var chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = inStreamReader.ReadBytes(chunkSize);
var buffer = await inStreamReader.ReadBytesAsync(chunkSize).ConfigureAwait(false);
var chunkHead = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
await outStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length).ConfigureAwait(false);
await outStream.WriteAsync(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length).ConfigureAwait(false);
outStream.Write(buffer, 0, chunkSize);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
await outStream.WriteAsync(buffer, 0, chunkSize).ConfigureAwait(false);
await outStream.WriteAsync(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length).ConfigureAwait(false);
inStreamReader.ReadLine();
await inStreamReader.ReadLineAsync().ConfigureAwait(false);
}
else
{
inStreamReader.ReadLine();
outStream.Write(ChunkEnd, 0, ChunkEnd.Length);
await inStreamReader.ReadLineAsync().ConfigureAwait(false);
await outStream.WriteAsync(Constants.ChunkEnd, 0, Constants.ChunkEnd.Length).ConfigureAwait(false);
break;
}
}
}
private static void WriteResponseBodyChunked(byte[] data, Stream outStream)
private static async Task WriteResponseBodyChunked(byte[] data, Stream outStream)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(data, 0, data.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
await outStream.WriteAsync(chunkHead, 0, chunkHead.Length).ConfigureAwait(false);
await outStream.WriteAsync(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length).ConfigureAwait(false);
await outStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
await outStream.WriteAsync(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length).ConfigureAwait(false);
outStream.Write(ChunkEnd, 0, ChunkEnd.Length);
await outStream.WriteAsync(Constants.ChunkEnd, 0, Constants.ChunkEnd.Length).ConfigureAwait(false);
}
......
using System;
using System.Security.Authentication;
using System.Text;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Shared
{
/// <summary>
/// Literals shared by Proxy Server
/// </summary>
internal class Constants
{
public static readonly int BUFFER_SIZE = 8192;
internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(Environment.NewLine);
internal static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
}
}
......@@ -60,7 +60,8 @@
<Compile Include="Decompression\GZipDecompression.cs" />
<Compile Include="Decompression\IDecompression.cs" />
<Compile Include="Decompression\ZlibDecompression.cs" />
<Compile Include="EventArguments\ProxyClient.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Network\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" />
<Compile Include="Extensions\HttpWebRequestExtensions.cs" />
......@@ -69,9 +70,13 @@
<Compile Include="Helpers\SystemProxy.cs" />
<Compile Include="Models\EndPoint.cs" />
<Compile Include="Extensions\TcpExtensions.cs" />
<Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" />
<Compile Include="Models\ExternalProxy.cs" />
<Compile Include="Network\CustomSslStream.cs" />
<Compile Include="Network\TcpConnectionManager.cs" />
<Compile Include="Models\HttpHeader.cs" />
<Compile Include="Network\HttpWebClient.cs" />
<Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RequestHandler.cs" />
<Compile Include="ResponseHandler.cs" />
......@@ -80,8 +85,9 @@
<Compile Include="EventArguments\SessionEventArgs.cs" />
<Compile Include="Helpers\Tcp.cs" />
<Compile Include="Extensions\StreamExtensions.cs" />
<Compile Include="Responses\OkResponse.cs" />
<Compile Include="Responses\RedirectResponse.cs" />
<Compile Include="Http\Responses\OkResponse.cs" />
<Compile Include="Http\Responses\RedirectResponse.cs" />
<Compile Include="Shared\Constants.cs" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
......
......@@ -7,7 +7,7 @@
# - Section names should be unique on each level.
# version format
version: 2.1.{build}
version: 2.2{build}
shallow_clone: true
......
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