Commit 5a683862 authored by justcoding121's avatar justcoding121

Merge with Beta

Since Chrome HTTPS is broken in stable due to #199, lets merge with beta
asap
parent 45d3a905
......@@ -5,7 +5,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
{
public class Program
{
private static readonly ProxyTestController Controller = new ProxyTestController();
private static readonly ProxyTestController controller = new ProxyTestController();
public static void Main(string[] args)
{
......@@ -15,13 +15,13 @@ namespace Titanium.Web.Proxy.Examples.Basic
//Start proxy controller
Controller.StartProxy();
controller.StartProxy();
Console.WriteLine("Hit any key to exit..");
Console.WriteLine();
Console.Read();
Controller.Stop();
controller.Stop();
}
......@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
if (eventType != 2) return false;
try
{
Controller.Stop();
controller.Stop();
}
catch
{
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
......@@ -9,16 +10,29 @@ namespace Titanium.Web.Proxy.Examples.Basic
{
public class ProxyTestController
{
private ProxyServer proxyServer;
private readonly ProxyServer proxyServer;
//share requestBody outside handlers
private Dictionary<Guid, string> requestBodyHistory;
private readonly Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>();
public ProxyTestController()
{
proxyServer = new ProxyServer();
//generate root certificate without storing it in file system
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//proxyServer.CertificateManager.CreateTrustedRootCertificate(false);
//proxyServer.CertificateManager.TrustRootCertificate();
proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message);
proxyServer.TrustRootCertificate = true;
requestBodyHistory = new Dictionary<Guid, string>();
//optionally set the Certificate Engine
//Under Mono only BouncyCastle will be supported
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//optionally set the Root Certificate
//proxyServer.RootCertificate = new X509Certificate2("myCert.pfx", string.Empty, X509KeyStorageFlags.Exportable);
}
public void StartProxy()
......@@ -28,21 +42,32 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//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" }
//Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example google.com and dropbox.com
ExcludedHttpsHostNameRegex = new List<string>() { "dropbox.com" }
//Include Https addresses you want to proxy (others will be excluded)
//for example github.com
// IncludedHttpsHostNameRegex = new List<string>() { "github.com" }
//You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required by proxy clients
// GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//An explicit endpoint is where the client knows about the existance of a proxy
//An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
//Transparent endpoint is useful for reverse proxying (client is not aware of the existence 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
......@@ -74,18 +99,22 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
//remove the generated certificates
//proxyServer.CertificateManager.RemoveTrustedRootCertificates();
}
//intecept & cancel, redirect or update requests
//intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Client Connections:" + ((ProxyServer) sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url);
////read request headers
//read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders;
var method = e.WebSession.Request.Method.ToUpper();
if ((method == "POST" || method == "PUT" || method == "PATCH"))
if (method == "POST" || method == "PUT" || method == "PATCH")
{
//Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody();
......@@ -98,45 +127,49 @@ namespace Titanium.Web.Proxy.Examples.Basic
requestBodyHistory[e.Id] = bodyString;
}
//To cancel a request with a custom HTML content
//Filter URL
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
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");
}
////To cancel a request with a custom HTML content
////Filter URL
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
//{
// 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");
//}
}
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
if(requestBodyHistory.ContainsKey(e.Id))
Console.WriteLine("Active Server Connections:" + (sender as ProxyServer).ServerConnectionCount);
if (requestBodyHistory.ContainsKey(e.Id))
{
//access request body by looking up the shared dictionary using requestId
var requestBody = requestBodyHistory[e.Id];
}
//read response headers
var responseHeaders = e.WebSession.Response.ResponseHeaders;
// print out process id of current session
Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
//Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{
if (e.WebSession.Response.ResponseStatusCode == "200")
{
if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
if (e.WebSession.Response.ContentType != null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);
......
......@@ -25,7 +25,7 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;NET45</DefineConstants>
......@@ -33,6 +33,7 @@
<WarningLevel>4</WarningLevel>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<Prefer32Bit>false</Prefer32Bit>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
......
Doneness:
- [ ] Build is okay - I made sure that this change is building successfully.
- [ ] No Bugs - I made sure that this change is working properly as expected. It does'nt have any bugs that you are aware of.
- [ ] No Bugs - I made sure that this change is working properly as expected. It doesn't have any bugs that you are aware of.
- [ ] Branching - If this is not a hotfix, I am making this request against develop branch
Titanium
========
A light weight http(s) proxy server written in C#
A light weight HTTP(S) proxy server written in C#
![Build Status](https://ci.appveyor.com/api/projects/status/rvlxv8xgj0m7lkr4?svg=true)
......@@ -11,10 +11,10 @@ Kindly report only issues/bugs here . For programming help or questions use [Sta
Features
========
* Supports Http(s) and most features of HTTP 1.1
* Supports HTTP(S) and most features of HTTP 1.1
* Support redirect/block/update requests
* Supports updating response
* Safely relays WebSocket requests over Http
* Safely relays Web Socket requests over HTTP
* Support mutual SSL authentication
* Fully asynchronous proxy
* Supports proxy authentication
......@@ -23,7 +23,7 @@ Features
Usage
=====
Refer the HTTP Proxy Server library in your project, look up Test project to learn usage.
Refer the HTTP Proxy Server library in your project, look up Test project to learn usage. ([Wiki & Contribution guidelines](https://github.com/justcoding121/Titanium-Web-Proxy/wiki))
Install by nuget:
......@@ -38,71 +38,84 @@ For stable releases on [stable branch](https://github.com/justcoding121/Titanium
Setup HTTP proxy:
```csharp
var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy
proxyServer.TrustRootCertificate = true;
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//for example dropbox.com
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
};
//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();
//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} ",
var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy
proxyServer.TrustRootCertificate = true;
//optionally set the Certificate Engine
//Under Mono only BouncyCastle will be supported
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//Exclude HTTPS addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
//Use self-issued generic certificate on all HTTPS requests
//Optimizes performance by not creating a certificate for each HTTPS-enabled domain
//Useful when certificate trust is not required by proxy clients
// GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
//Warning! Transparent endpoint is not tested end to end
//Transparent endpoint is useful for reverse proxy (client is not aware of the existence 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);
//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();
//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();
//Unsubscribe & Quit
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
//Unsubscribe & Quit
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
proxyServer.Stop();
```
Sample request and response event handlers
```csharp
public async Task OnRequest(object sender, SessionEventArgs e)
{
//To access requestBody from OnResponse handler
private Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>();
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.WebSession.Request.Url);
////read request headers
......@@ -119,6 +132,9 @@ Sample request and response event handlers
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);
//store request Body/request headers etc with request Id as key
//so that you can find it from response handler using request Id
requestBodyHistory[e.Id] = bodyString;
}
//To cancel a request with a custom HTML content
......@@ -138,11 +154,11 @@ Sample request and response event handlers
{
await e.Redirect("https://www.paypal.com");
}
}
}
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
//read response headers
var responseHeaders = e.WebSession.Response.ResponseHeaders;
......@@ -161,27 +177,32 @@ Sample request and response event handlers
}
}
}
}
/// Allows overriding default certificate validation logic
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
//access request body/request headers etc by looking up using requestId
if(requestBodyHistory.ContainsKey(e.Id))
{
var requestBody = requestBodyHistory[e.Id];
}
}
/// Allows overriding default certificate validation logic
public 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;
return Task.FromResult(0);
}
}
/// Allows overriding default client certificate selection logic during mutual authentication
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
/// Allows overriding default client certificate selection logic during mutual authentication
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
//set e.clientCertificate to override
return Task.FromResult(0);
}
}
```
Future roadmap
Future road map (Pull requests are welcome!)
============
* Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0
......
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Titanium.Web.Proxy.IntegrationTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Titanium.Web.Proxy.IntegrationTests")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("32231301-b0fb-4f9e-98df-b3e8a88f4c16")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
using System.Net.Http;
using System.Diagnostics;
namespace Titanium.Web.Proxy.IntegrationTests
{
[TestClass]
public class SslTests
{
[TestMethod]
public void TestSsl()
{
//expand this to stress test to find
//why in long run proxy becomes unresponsive as per issue #184
var testUrl = "https://google.com";
int proxyPort = 8086;
var proxy = new ProxyTestController();
proxy.StartProxy(proxyPort);
using (var client = CreateHttpClient(testUrl, proxyPort))
{
var response = client.GetAsync(new Uri(testUrl)).Result;
}
}
private HttpClient CreateHttpClient(string url, int localProxyPort)
{
var handler = new HttpClientHandler
{
Proxy = new WebProxy($"http://localhost:{localProxyPort}", false),
UseProxy = true,
};
var client = new HttpClient(handler);
return client;
}
}
public class ProxyTestController
{
private readonly ProxyServer proxyServer;
public ProxyTestController()
{
proxyServer = new ProxyServer();
proxyServer.TrustRootCertificate = true;
}
public void StartProxy(int proxyPort)
{
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, proxyPort, true);
//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();
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);
}
public void Stop()
{
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
}
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Debug.WriteLine(e.WebSession.Request.Url);
await Task.FromResult(0);
}
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
await Task.FromResult(0);
}
/// <summary>
/// Allows overriding default certificate validation logic
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public 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;
}
return Task.FromResult(0);
}
/// <summary>
/// Allows overriding default client certificate selection logic during mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
//set e.clientCertificate to override
return Task.FromResult(0);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.IntegrationTests</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.IntegrationTests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Net.Http" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="SslTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
......@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.UnitTests
[TestClass]
public class CertificateManagerTests
{
private readonly static string[] hostNames
private static readonly string[] hostNames
= new string[] { "facebook.com", "youtube.com", "google.com",
"bing.com", "yahoo.com"};
......@@ -20,8 +20,7 @@ namespace Titanium.Web.Proxy.UnitTests
{
var tasks = new List<Task>();
var mgr = new CertificateManager("Titanium", "Titanium Root Certificate Authority",
new Lazy<Action<Exception>>(() => (e => { })).Value);
var mgr = new CertificateManager(new Lazy<Action<Exception>>(() => (e => { })).Value);
mgr.ClearIdleCertificates(1);
......
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
......
......@@ -34,6 +34,12 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
......@@ -60,6 +66,9 @@
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="StrongNameKey.snk" />
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
......

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject
......@@ -33,6 +33,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.UnitTests", "Tests\Titanium.Web.Proxy.UnitTests\Titanium.Web.Proxy.UnitTests.csproj", "{B517E3D0-D03B-436F-AB03-34BA0D5321AF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.IntegrationTests", "Tests\Titanium.Web.Proxy.IntegrationTests\Titanium.Web.Proxy.IntegrationTests.csproj", "{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -51,6 +53,10 @@ Global
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.Build.0 = Release|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -58,6 +64,7 @@ Global
GlobalSection(NestedProjects) = preSolution
{F3B7E553-1904-4E80-BDC7-212342B5C952} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
{B517E3D0-D03B-436F-AB03-34BA0D5321AF} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35
......
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BC/@EntryIndexedValue">BC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=dda2ffa1_002D435c_002D4111_002D88eb_002D1a7c93c382f0/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Property (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="PROPERTY" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String></wpf:ResourceDictionary>
\ No newline at end of file
using System;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
......@@ -26,11 +25,12 @@ namespace Titanium.Web.Proxy
//if user callback is registered then do it
if (ServerCertificateValidationCallback != null)
{
var args = new CertificateValidationEventArgs();
args.Certificate = certificate;
args.Chain = chain;
args.SslPolicyErrors = sslPolicyErrors;
var args = new CertificateValidationEventArgs
{
Certificate = certificate,
Chain = chain,
SslPolicyErrors = sslPolicyErrors
};
Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList();
......@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>)invocationList[i])(null, args);
handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>) invocationList[i])(null, args);
}
Task.WhenAll(handlerTasks).Wait();
......@@ -73,7 +73,6 @@ namespace Titanium.Web.Proxy
string[] acceptableIssuers)
{
X509Certificate clientCertificate = null;
var customSslStream = sender as SslStream;
if (acceptableIssuers != null &&
acceptableIssuers.Length > 0 &&
......@@ -100,20 +99,22 @@ namespace Titanium.Web.Proxy
//If user call back is registered
if (ClientCertificateSelectionCallback != null)
{
var args = new CertificateSelectionEventArgs();
var args = new CertificateSelectionEventArgs
{
TargetHost = targetHost,
LocalCertificates = localCertificates,
RemoteCertificate = remoteCertificate,
AcceptableIssuers = acceptableIssuers,
ClientCertificate = clientCertificate
};
args.TargetHost = targetHost;
args.LocalCertificates = localCertificates;
args.RemoteCertificate = remoteCertificate;
args.AcceptableIssuers = acceptableIssuers;
args.ClientCertificate = clientCertificate;
Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateSelectionEventArgs, Task>)invocationList[i])(null, args);
handlerTasks[i] = ((Func<object, CertificateSelectionEventArgs, Task>) invocationList[i])(null, args);
}
Task.WhenAll(handlerTasks).Wait();
......
......@@ -13,8 +13,6 @@
return new GZipCompression();
case "deflate":
return new DeflateCompression();
case "zlib":
return new ZlibCompression();
default:
return null;
}
......
using Ionic.Zlib;
using System.IO;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
......
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
/// <summary>
/// concrete implementation of zlib compression
/// </summary>
internal class ZlibCompression : ICompression
{
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
using (var zip = new ZlibStream(ms, CompressionMode.Compress, true))
{
await zip.WriteAsync(responseBody, 0, responseBody.Length);
}
return ms.ToArray();
}
}
}
}
......@@ -7,14 +7,12 @@
{
internal IDecompression Create(string type)
{
switch(type)
switch (type)
{
case "gzip":
return new GZipDecompression();
case "deflate":
return new DeflateDecompression();
case "zlib":
return new ZlibDecompression();
default:
return new DefaultDecompression();
}
......
......@@ -2,7 +2,6 @@
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// When no compression is specified just return the byte array
/// </summary>
......
using Ionic.Zlib;
using System.IO;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
......@@ -12,8 +11,7 @@ namespace Titanium.Web.Proxy.Decompression
{
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{
var stream = new MemoryStream(compressedArray);
using (var stream = new MemoryStream(compressedArray))
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{
var buffer = new byte[bufferSize];
......@@ -23,7 +21,7 @@ namespace Titanium.Web.Proxy.Decompression
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await output.WriteAsync(buffer, 0, read);
output.Write(buffer, 0, read);
}
return output.ToArray();
......
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
......@@ -20,8 +19,9 @@ namespace Titanium.Web.Proxy.Decompression
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await output.WriteAsync(buffer, 0, read);
output.Write(buffer, 0, read);
}
return output.ToArray();
}
}
......
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// concrete implemetation of zlib de-compression
/// </summary>
internal class ZlibDecompression : IDecompression
{
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{
var memoryStream = new MemoryStream(compressedArray);
using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress))
{
var buffer = new byte[bufferSize];
using (var output = new MemoryStream())
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await output.WriteAsync(buffer, 0, read);
}
return output.ToArray();
}
}
}
}
}
......@@ -8,13 +8,34 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public class CertificateSelectionEventArgs : EventArgs
{
/// <summary>
/// Sender object.
/// </summary>
public object Sender { get; internal set; }
/// <summary>
/// Target host.
/// </summary>
public string TargetHost { get; internal set; }
/// <summary>
/// Local certificates.
/// </summary>
public X509CertificateCollection LocalCertificates { get; internal set; }
/// <summary>
/// Remote certificate.
/// </summary>
public X509Certificate RemoteCertificate { get; internal set; }
/// <summary>
/// Acceptable issuers.
/// </summary>
public string[] AcceptableIssuers { get; internal set; }
/// <summary>
/// Client Certificate.
/// </summary>
public X509Certificate ClientCertificate { get; set; }
}
}
......@@ -7,17 +7,26 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// An argument passed on to the user for validating the server certificate during SSL authentication
/// </summary>
public class CertificateValidationEventArgs : EventArgs, IDisposable
public class CertificateValidationEventArgs : EventArgs
{
/// <summary>
/// Certificate
/// </summary>
public X509Certificate Certificate { get; internal set; }
/// <summary>
/// Certificate chain
/// </summary>
public X509Chain Chain { get; internal set; }
/// <summary>
/// SSL policy errors.
/// </summary>
public SslPolicyErrors SslPolicyErrors { get; internal set; }
/// <summary>
/// is a valid certificate?
/// </summary>
public bool IsValid { get; set; }
public void Dispose()
{
}
}
}
......@@ -22,7 +22,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public class SessionEventArgs : EventArgs, IDisposable
{
/// <summary>
/// Size of Buffers used by this object
/// </summary>
......@@ -44,20 +43,20 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public Guid Id => WebSession.RequestId;
//Should we send a rerequest
public bool ReRequest
{
get;
set;
}
/// <summary>
/// Should we send a rerequest
/// </summary>
public bool ReRequest { get; set; }
/// <summary>
/// Does this session uses SSL
/// </summary>
public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary>
/// Client End Point.
/// </summary>
public IPEndPoint ClientEndPoint => (IPEndPoint) ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary>
/// A web session corresponding to a single request/response sequence
......@@ -65,8 +64,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public HttpWebClient WebSession { get; set; }
/// <summary>
/// Are we using a custom upstream HTTP proxy?
/// </summary>
public ExternalProxy CustomUpStreamHttpProxyUsed { get; set; }
/// <summary>
/// Are we using a custom upstream HTTPS proxy?
/// </summary>
public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
/// <summary>
......@@ -88,7 +93,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
//GET request don't have a request body to read
var method = WebSession.Request.Method.ToUpper();
if ((method != "POST" && method != "PUT" && method != "PATCH"))
if (method != "POST" && method != "PUT" && method != "PATCH")
{
throw new BodyNotFoundException("Request don't have a body. " +
"Please verify that this request is a Http POST/PUT/PATCH and request " +
......@@ -98,14 +103,13 @@ namespace Titanium.Web.Proxy.EventArguments
//Caching check
if (WebSession.Request.RequestBody == null)
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header
using (var requestBodyStream = new MemoryStream())
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (WebSession.Request.IsChunked)
{
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream);
await ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream);
}
else
{
......@@ -113,9 +117,8 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Request.ContentLength > 0)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await this.ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream,
await ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream,
WebSession.Request.ContentLength);
}
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
{
......@@ -130,7 +133,6 @@ namespace Titanium.Web.Proxy.EventArguments
//So that next time we can deliver body from cache
WebSession.Request.RequestBodyRead = true;
}
}
/// <summary>
......@@ -146,7 +148,7 @@ namespace Titanium.Web.Proxy.EventArguments
//If chuncked the read chunk by chunk until we hit chunk end symbol
if (WebSession.Response.IsChunked)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream);
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(responseBodyStream);
}
else
{
......@@ -155,7 +157,6 @@ namespace Titanium.Web.Proxy.EventArguments
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream,
WebSession.Response.ContentLength);
}
else if ((WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) || WebSession.Response.ContentLength == -1)
{
......@@ -165,7 +166,6 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding,
responseBodyStream.ToArray());
}
//set this to true for caching
WebSession.Response.ResponseBodyRead = true;
......@@ -189,6 +189,7 @@ namespace Titanium.Web.Proxy.EventArguments
}
return WebSession.Request.RequestBody;
}
/// <summary>
/// Gets the request body as string
/// </summary>
......@@ -255,7 +256,6 @@ namespace Titanium.Web.Proxy.EventArguments
}
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
}
/// <summary>
......@@ -406,11 +406,79 @@ namespace Titanium.Web.Proxy.EventArguments
public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers)
{
var response = new OkResponse();
if (headers != null && headers.Count > 0)
{
response.ResponseHeaders = headers;
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result;
await Respond(response);
WebSession.Request.CancelRequest = true;
}
/// <summary>
/// Before request is made to server 
/// Respond with the specified HTML string to client
/// and ignore the request 
/// </summary>
/// <param name="html"></param>
/// <param name="status"></param>
public async Task GenericResponse(string html, HttpStatusCode status)
{
await GenericResponse(html, null, status);
}
/// <summary>
/// Before request is made to server 
/// Respond with the specified HTML string to client
/// and the specified status
/// and ignore the request 
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
/// <param name="status"></param>
public async Task GenericResponse(string html, Dictionary<string, HttpHeader> headers, HttpStatusCode status)
{
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);
await GenericResponse(result, headers, status);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and the specified status
/// and ignore the request
/// </summary>
/// <param name="result"></param>
/// <param name="headers"></param>
/// <param name="status"></param>
/// <returns></returns>
public async Task GenericResponse(byte[] result, Dictionary<string, HttpHeader> headers, HttpStatusCode status)
{
var response = new GenericResponse(status);
if (headers != null && headers.Count > 0)
{
response.ResponseHeaders = headers;
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result;
await Respond(response);
......@@ -418,12 +486,17 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Request.CancelRequest = true;
}
/// <summary>
/// Redirect to URL.
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task Redirect(string url)
{
var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url));
response.ResponseHeaders.Add("Location", new HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response);
......@@ -449,7 +522,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public void Dispose()
{
}
}
}
using System;
namespace Titanium.Web.Proxy.Exceptions
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// An expception thrown when body is unexpectedly empty
/// </summary>
public class BodyNotFoundException : ProxyException
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="message"></param>
public BodyNotFoundException(string message)
: base(message)
{
......
......@@ -2,7 +2,10 @@
namespace Titanium.Web.Proxy.Extensions
{
public static class ByteArrayExtensions
/// <summary>
/// Extension methods for Byte Arrays.
/// </summary>
internal static class ByteArrayExtensions
{
/// <summary>
/// Get the sub array from byte of data
......@@ -12,12 +15,11 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="index"></param>
/// <param name="length"></param>
/// <returns></returns>
public static T[] SubArray<T>(this T[] data, int index, int length)
internal static T[] SubArray<T>(this T[] data, int index, int length)
{
T[] result = new T[length];
var result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
}
}
using System.Text;
using System;
using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
......@@ -29,7 +30,7 @@ namespace Titanium.Web.Proxy.Extensions
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset")
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
return Encoding.GetEncoding(encodingSplit[1]);
}
......
using System.Text;
using System;
using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
......@@ -26,7 +27,7 @@ namespace Titanium.Web.Proxy.Extensions
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset")
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
return Encoding.GetEncoding(encodingSplit[1]);
}
......
......@@ -46,7 +46,7 @@ namespace Titanium.Web.Proxy.Extensions
while (totalbytesRead < totalBytesToRead)
{
var buffer = await streamReader.ReadBytesAsync(bufferSize, bytesToRead);
var buffer = await streamReader.ReadBytesAsync(bytesToRead);
if (buffer.Length == 0)
{
......@@ -69,10 +69,9 @@ namespace Titanium.Web.Proxy.Extensions
/// Copies the stream chunked
/// </summary>
/// <param name="clientStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="stream"></param>
/// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, int bufferSize, Stream stream)
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
{
while (true)
{
......@@ -81,7 +80,7 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0)
{
var buffer = await clientStreamReader.ReadBytesAsync(bufferSize, chunkSize);
var buffer = await clientStreamReader.ReadBytesAsync(chunkSize);
await stream.WriteAsync(buffer, 0, buffer.Length);
//chunk trail
await clientStreamReader.ReadLineAsync();
......@@ -93,6 +92,7 @@ namespace Titanium.Web.Proxy.Extensions
}
}
}
/// <summary>
/// Writes the byte array body to the given stream; optionally chunked
/// </summary>
......@@ -136,7 +136,7 @@ namespace Titanium.Web.Proxy.Extensions
if (contentLength < bufferSize)
{
bytesToRead = (int)contentLength;
bytesToRead = (int) contentLength;
}
var buffer = new byte[bufferSize];
......@@ -154,12 +154,12 @@ namespace Titanium.Web.Proxy.Extensions
bytesRead = 0;
var remainingBytes = (contentLength - totalBytesRead);
bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
bytesToRead = remainingBytes > (long) bufferSize ? bufferSize : (int) remainingBytes;
}
}
else
{
await WriteResponseBodyChunked(inStreamReader, bufferSize, outStream);
await WriteResponseBodyChunked(inStreamReader, outStream);
}
}
......@@ -167,10 +167,9 @@ namespace Titanium.Web.Proxy.Extensions
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream)
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, Stream outStream)
{
while (true)
{
......@@ -179,7 +178,7 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0)
{
var buffer = await inStreamReader.ReadBytesAsync(bufferSize, chunkSize);
var buffer = await inStreamReader.ReadBytesAsync(chunkSize);
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
......@@ -199,6 +198,7 @@ namespace Titanium.Web.Proxy.Extensions
}
}
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
......@@ -216,6 +216,5 @@ namespace Titanium.Web.Proxy.Extensions
await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
}
}
}
using System.Globalization;
namespace Titanium.Web.Proxy.Extensions
{
internal static class StringExtensions
{
internal static bool ContainsIgnoreCase(this string str, string value)
{
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0;
}
}
}
using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Extensions
{
......@@ -12,11 +13,11 @@ namespace Titanium.Web.Proxy.Extensions
internal static bool IsConnected(this Socket client)
{
// This is how you can determine whether a socket is still connected.
bool blockingState = client.Blocking;
var blockingState = client.Blocking;
try
{
byte[] tmp = new byte[1];
var tmp = new byte[1];
client.Blocking = false;
client.Send(tmp, 0, 0);
......@@ -25,20 +26,32 @@ namespace Titanium.Web.Proxy.Extensions
catch (SocketException e)
{
// 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035))
{
return true;
}
else
{
return false;
}
return e.NativeErrorCode.Equals(10035);
}
finally
{
client.Blocking = blockingState;
}
}
/// <summary>
/// Gets the local port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The local port</returns>
internal static int GetLocalPort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.localPort1 << 8) + tcpRow.localPort2 + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
}
/// <summary>
/// Gets the remote port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The remote port</returns>
internal static int GetRemotePort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
}
}
}
......@@ -3,12 +3,9 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// A custom binary reader that would allo us to read string line by line
/// using the specified encoding
......@@ -16,15 +13,20 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
internal class CustomBinaryReader : IDisposable
{
private Stream stream;
private Encoding encoding;
private readonly CustomBufferedStream stream;
private readonly int bufferSize;
private readonly byte[] staticBuffer;
private readonly Encoding encoding;
internal CustomBinaryReader(Stream stream)
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{
this.stream = stream;
staticBuffer = new byte[bufferSize];
this.bufferSize = bufferSize;
//default to UTF-8
this.encoding = Encoding.UTF8;
encoding = Encoding.UTF8;
}
internal Stream BaseStream => stream;
......@@ -35,33 +37,41 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns>
internal async Task<string> ReadLineAsync()
{
using (var readBuffer = new MemoryStream())
{
var lastChar = default(char);
var buffer = new byte[1];
var lastChar = default(byte);
int bufferDataLength = 0;
while ((await this.stream.ReadAsync(buffer, 0, 1)) > 0)
// try to use the thread static buffer, usually it is enough
var buffer = staticBuffer;
while (stream.DataAvailable || await stream.FillBufferAsync())
{
var newChar = stream.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
//if new line
if (lastChar == '\r' && buffer[0] == '\n')
if (lastChar == '\r' && newChar == '\n')
{
var result = readBuffer.ToArray();
return encoding.GetString(result.SubArray(0, result.Length - 1));
return encoding.GetString(buffer, 0, bufferDataLength - 1);
}
//end of stream
if (buffer[0] == '\0')
if (newChar == '\0')
{
return encoding.GetString(readBuffer.ToArray());
return encoding.GetString(buffer, 0, bufferDataLength);
}
await readBuffer.WriteAsync(buffer,0,1);
bufferDataLength++;
//store last char for new line comparison
lastChar = (char)buffer[0];
}
lastChar = newChar;
return encoding.GetString(readBuffer.ToArray());
if (bufferDataLength == buffer.Length)
{
ResizeBuffer(ref buffer, bufferDataLength * 2);
}
}
return encoding.GetString(buffer, 0, bufferDataLength);
}
/// <summary>
......@@ -79,46 +89,72 @@ namespace Titanium.Web.Proxy.Helpers
return requestLines;
}
/// <summary>
/// Read until the last new line, ignores the result
/// </summary>
/// <returns></returns>
internal async Task ReadAndIgnoreAllLinesAsync()
{
while (!string.IsNullOrEmpty(await ReadLineAsync()))
{
}
}
/// <summary>
/// Read the specified number of raw bytes from the base stream
/// </summary>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal async Task<byte[]> ReadBytesAsync(int bufferSize, long totalBytesToRead)
internal async Task<byte[]> ReadBytesAsync(long totalBytesToRead)
{
int bytesToRead = bufferSize;
var buffer = staticBuffer;
if (totalBytesToRead < bufferSize)
bytesToRead = (int)totalBytesToRead;
var buffer = new byte[bufferSize];
{
bytesToRead = (int) totalBytesToRead;
buffer = new byte[bytesToRead];
}
var bytesRead = 0;
int bytesRead;
var totalBytesRead = 0;
using (var outStream = new MemoryStream())
while ((bytesRead = await stream.ReadAsync(buffer, totalBytesRead, bytesToRead)) > 0)
{
while ((bytesRead += await this.stream.ReadAsync(buffer, 0, bytesToRead)) > 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == totalBytesToRead)
break;
bytesRead = 0;
var remainingBytes = (totalBytesToRead - totalBytesRead);
bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
var remainingBytes = totalBytesToRead - totalBytesRead;
bytesToRead = Math.Min(bufferSize, (int) remainingBytes);
if (totalBytesRead + bytesToRead > buffer.Length)
{
ResizeBuffer(ref buffer, Math.Min(totalBytesToRead, buffer.Length * 2));
}
}
return outStream.ToArray();
if (totalBytesRead != buffer.Length)
{
//Normally this should not happen. Resize the buffer anyway
var newBuffer = new byte[totalBytesRead];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, totalBytesRead);
buffer = newBuffer;
}
return buffer;
}
public void Dispose()
{
}
private void ResizeBuffer(ref byte[] buffer, long size)
{
var newBuffer = new byte[size];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
buffer = newBuffer;
}
}
}
This diff is collapsed.
......@@ -6,9 +6,12 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// A helper class to set proxy settings for firefox
/// </summary>
public class FireFoxProxySettingsManager
internal class FireFoxProxySettingsManager
{
public void AddFirefox()
/// <summary>
/// Add Firefox settings.
/// </summary>
internal void AddFirefox()
{
try
{
......@@ -16,29 +19,28 @@ namespace Titanium.Web.Proxy.Helpers
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (File.Exists(myFfPrefFile))
{
if (!File.Exists(myFfPrefFile)) return;
// We have a pref file so let''s make sure it has the proxy setting
var myReader = new StreamReader(myFfPrefFile);
var myPrefContents = myReader.ReadToEnd();
myReader.Close();
if (myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);"))
{
if (!myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);")) return;
// Add the proxy enable line and write it back to the file
myPrefContents = myPrefContents.Replace("user_pref(\"network.proxy.type\", 0);", "");
File.Delete(myFfPrefFile);
File.WriteAllText(myFfPrefFile, myPrefContents);
}
}
}
catch (Exception)
{
// Only exception should be a read/write error because the user opened up FireFox so they can be ignored.
}
}
public void RemoveFirefox()
/// <summary>
/// Remove firefox settings.
/// </summary>
internal void RemoveFirefox()
{
try
{
......@@ -46,8 +48,7 @@ namespace Titanium.Web.Proxy.Helpers
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (File.Exists(myFfPrefFile))
{
if (!File.Exists(myFfPrefFile)) return;
// We have a pref file so let''s make sure it has the proxy setting
var myReader = new StreamReader(myFfPrefFile);
var myPrefContents = myReader.ReadToEnd();
......@@ -61,7 +62,6 @@ namespace Titanium.Web.Proxy.Helpers
File.WriteAllText(myFfPrefFile, myPrefContents);
}
}
}
catch (Exception)
{
// Only exception should be a read/write error because the user opened up FireFox so they can be ignored.
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Helpers
{
......@@ -11,8 +8,7 @@ namespace Titanium.Web.Proxy.Helpers
{
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{
var tcpRow = TcpHelper.GetExtendedTcpTable(ipVersion).FirstOrDefault(
row => row.LocalEndPoint.Port == port);
var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port);
return tcpRow?.ProcessId ?? 0;
}
......@@ -33,28 +29,50 @@ namespace Titanium.Web.Proxy.Helpers
/// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary>
/// <param name="address></param>
/// <param name="address"></param>
/// <returns></returns>
internal static bool IsLocalIpAddress(IPAddress address)
{
try
{
// get local IP addresses
IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
var localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// test if any host IP equals to any local IP or to localhost
return IPAddress.IsLoopback(address) || localIPs.Contains(address);
}
// is localhost
if (IPAddress.IsLoopback(address)) return true;
// is local address
foreach (IPAddress localIP in localIPs)
internal static bool IsLocalIpAddress(string hostName)
{
if (address.Equals(localIP)) return true;
bool isLocalhost = false;
IPHostEntry localhost = Dns.GetHostEntry("127.0.0.1");
if (hostName == localhost.HostName)
{
IPHostEntry hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = hostEntry.AddressList.Any(IPAddress.IsLoopback);
}
if (!isLocalhost)
{
localhost = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ipAddress;
if (IPAddress.TryParse(hostName, out ipAddress))
isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress));
if (!isLocalhost)
{
try
{
var hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = localhost.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals));
}
catch { }
return false;
catch (SocketException)
{
}
}
}
return isLocalhost;
}
}
}
using System;
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// Run time helpers
/// </summary>
internal class RunTime
{
/// <summary>
/// Checks if current run time is Mono
/// </summary>
/// <returns></returns>
internal static bool IsRunningOnMono()
{
return Type.GetType("Mono.Runtime") != null;
}
}
}
......@@ -4,11 +4,8 @@ using Microsoft.Win32;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
/// <summary>
/// Helper classes for setting system proxy settings
/// </summary>
// Helper classes for setting system proxy settings
namespace Titanium.Web.Proxy.Helpers
{
internal enum ProxyProtocolType
......@@ -128,7 +125,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Removes all types of proxy settings (both http & https)
/// Removes all types of proxy settings (both http and https)
/// </summary>
internal void DisableAllProxy()
{
......@@ -184,7 +181,7 @@ namespace Titanium.Web.Proxy.Helpers
if (tmp.StartsWith("http=") || tmp.StartsWith("https="))
{
var endPoint = tmp.Substring(5);
return new HttpSystemProxyValue()
return new HttpSystemProxyValue
{
HostName = endPoint.Split(':')[0],
Port = int.Parse(endPoint.Split(':')[1]),
......@@ -193,8 +190,8 @@ namespace Titanium.Web.Proxy.Helpers
}
return null;
}
/// <summary>
/// Prepares the proxy server registry (create empty values if they don't exist)
/// </summary>
......@@ -210,7 +207,6 @@ namespace Titanium.Web.Proxy.Helpers
{
reg.SetValue("ProxyServer", string.Empty);
}
}
/// <summary>
......
......@@ -42,7 +42,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpTable
......@@ -52,7 +52,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpRow
......@@ -72,7 +72,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
......@@ -124,28 +124,70 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// Gets the TCP row by local port number.
/// </summary>
/// <returns><see cref="TcpRow"/>.</returns>
internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort)
{
IntPtr tcpTable = IntPtr.Zero;
int tcpTableLength = 0;
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{
try
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i)
{
var tcpRow = (NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow));
if (tcpRow.GetLocalPort() == localPort)
{
return new TcpRow(tcpRow);
}
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
}
}
}
finally
{
if (tcpTable != IntPtr.Zero)
{
Marshal.FreeHGlobal(tcpTable);
}
}
}
return null;
}
/// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="server"></param>
/// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps, SslProtocols supportedProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory, IPEndPoint upStreamEndPoint)
internal static async Task SendRaw(ProxyServer server,
string remoteHostName, int remotePort,
string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
{
//prepare the prefix content
StringBuilder sb = null;
......@@ -171,11 +213,10 @@ namespace Titanium.Web.Proxy.Helpers
sb.Append(ProxyConstants.NewLine);
}
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds,
var tcpConnection = await tcpConnectionFactory.CreateClient(server,
remoteHostName, remotePort,
httpVersion, isHttps,
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
null, null, clientStream, upStreamEndPoint);
null, null, clientStream);
try
{
......@@ -191,6 +232,7 @@ namespace Titanium.Web.Proxy.Helpers
finally
{
tcpConnection.Dispose();
server.ServerConnectionCount--;
}
}
}
......
using System.Collections.Generic;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
internal static class HeaderParser
{
internal static async Task ReadHeaders(CustomBinaryReader reader,
Dictionary<string, List<HttpHeader>> nonUniqueResponseHeaders,
Dictionary<string, HttpHeader> headers)
{
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (nonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
nonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (headers.ContainsKey(newHeader.Name))
{
var existing = headers[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> { existing, newHeader };
nonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
headers.Remove(newHeader.Name);
}
//add to unique header collection
else
{
headers.Add(newHeader.Name, newHeader);
}
}
}
}
}
This diff is collapsed.
......@@ -34,12 +34,7 @@ namespace Titanium.Web.Proxy.Http
get
{
var hasHeader = RequestHeaders.ContainsKey("host");
if (hasHeader)
{
return RequestHeaders["host"].Value;
}
return null;
return hasHeader ? RequestHeaders["host"].Value : null;
}
set
{
......@@ -52,7 +47,6 @@ namespace Titanium.Web.Proxy.Http
{
RequestHeaders.Add("Host", new HttpHeader("Host", value));
}
}
}
......@@ -124,9 +118,7 @@ namespace Titanium.Web.Proxy.Http
{
RequestHeaders.Remove("content-length");
}
}
}
}
......@@ -154,14 +146,13 @@ namespace Titanium.Web.Proxy.Http
if (hasHeader)
{
var header = RequestHeaders["content-type"];
header.Value = value.ToString();
header.Value = value;
}
else
{
RequestHeaders.Add("content-type", new HttpHeader("content-type", value.ToString()));
RequestHeaders.Add("content-type", new HttpHeader("content-type", value));
}
}
}
/// <summary>
......@@ -177,7 +168,7 @@ namespace Titanium.Web.Proxy.Http
{
var header = RequestHeaders["transfer-encoding"];
return header.Value.ToLower().Contains("chunked");
return header.Value.ContainsIgnoreCase("chunked");
}
return false;
......@@ -219,15 +210,11 @@ namespace Titanium.Web.Proxy.Http
{
var hasHeader = RequestHeaders.ContainsKey("expect");
if (hasHeader)
{
if (!hasHeader) return false;
var header = RequestHeaders["expect"];
return header.Value.Equals("100-continue");
}
return false;
}
}
/// <summary>
......@@ -255,8 +242,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
internal string RequestBodyString { get; set; }
internal bool RequestBodyRead { get; set; }
internal bool RequestLocked { get; set; }
/// <summary>
......@@ -275,13 +262,7 @@ namespace Titanium.Web.Proxy.Http
var header = RequestHeaders["upgrade"];
if (header.Value.ToLower() == "websocket")
{
return true;
}
return false;
return header.Value.Equals("websocket", StringComparison.CurrentCultureIgnoreCase);
}
}
......@@ -305,11 +286,13 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public bool ExpectationFailed { get; internal set; }
/// <summary>
/// Constructor.
/// </summary>
public Request()
{
RequestHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
NonUniqueRequestHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
}
}
}
......@@ -12,7 +12,14 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public class Response
{
/// <summary>
/// Response Status Code.
/// </summary>
public string ResponseStatusCode { get; set; }
/// <summary>
/// Response Status description.
/// </summary>
public string ResponseStatusDescription { get; set; }
internal Encoding Encoding => this.GetResponseCharacterEncoding();
......@@ -26,15 +33,11 @@ namespace Titanium.Web.Proxy.Http
{
var hasHeader = ResponseHeaders.ContainsKey("content-encoding");
if (hasHeader)
{
if (!hasHeader) return null;
var header = ResponseHeaders["content-encoding"];
return header.Value.Trim();
}
return null;
}
}
internal Version HttpVersion { get; set; }
......@@ -52,14 +55,13 @@ namespace Titanium.Web.Proxy.Http
{
var header = ResponseHeaders["connection"];
if (header.Value.ToLower().Contains("close"))
if (header.Value.ContainsIgnoreCase("close"))
{
return false;
}
}
return true;
}
}
......@@ -80,7 +82,6 @@ namespace Titanium.Web.Proxy.Http
}
return null;
}
}
......@@ -108,7 +109,6 @@ namespace Titanium.Web.Proxy.Http
}
return -1;
}
set
{
......@@ -151,14 +151,13 @@ namespace Titanium.Web.Proxy.Http
{
var header = ResponseHeaders["transfer-encoding"];
if (header.Value.ToLower().Contains("chunked"))
if (header.Value.ContainsIgnoreCase("chunked"))
{
return true;
}
}
return false;
}
set
{
......@@ -184,9 +183,7 @@ namespace Titanium.Web.Proxy.Http
{
ResponseHeaders.Remove("transfer-encoding");
}
}
}
}
......@@ -229,11 +226,13 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public bool ExpectationFailed { get; internal set; }
/// <summary>
/// Constructor.
/// </summary>
public Response()
{
this.ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
this.NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
}
}
}
using System.Net;
namespace Titanium.Web.Proxy.Http.Responses
{
/// <summary>
/// Anything other than a 200 or 302 response
/// </summary>
public class GenericResponse : Response
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="status"></param>
public GenericResponse(HttpStatusCode status)
{
ResponseStatusCode = ((int) status).ToString();
ResponseStatusDescription = status.ToString();
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="statusCode"></param>
/// <param name="statusDescription"></param>
public GenericResponse(string statusCode, string statusDescription)
{
ResponseStatusCode = statusCode;
ResponseStatusDescription = statusDescription;
}
}
}
......@@ -5,6 +5,9 @@
/// </summary>
public sealed class OkResponse : Response
{
/// <summary>
/// Constructor.
/// </summary>
public OkResponse()
{
ResponseStatusCode = "200";
......
......@@ -5,6 +5,9 @@
/// </summary>
public sealed class RedirectResponse : Response
{
/// <summary>
/// Constructor.
/// </summary>
public RedirectResponse()
{
ResponseStatusCode = "302";
......
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Models
{
......@@ -9,22 +13,42 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public abstract class ProxyEndPoint
{
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
protected ProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
{
this.IpAddress = IpAddress;
this.Port = Port;
this.EnableSsl = EnableSsl;
IpAddress = ipAddress;
Port = port;
EnableSsl = enableSsl;
}
/// <summary>
/// Ip Address.
/// </summary>
public IPAddress IpAddress { get; internal set; }
/// <summary>
/// Port.
/// </summary>
public int Port { get; internal set; }
/// <summary>
/// Enable SSL?
/// </summary>
public bool EnableSsl { get; internal set; }
public bool IpV6Enabled => IpAddress == IPAddress.IPv6Any
|| IpAddress == IPAddress.IPv6Loopback
|| IpAddress == IPAddress.IPv6None;
/// <summary>
/// Is IPv6 enabled?
/// </summary>
public bool IpV6Enabled => Equals(IpAddress, IPAddress.IPv6Any)
|| Equals(IpAddress, IPAddress.IPv6Loopback)
|| Equals(IpAddress, IPAddress.IPv6None);
internal TcpListener listener { get; set; }
internal TcpListener Listener { get; set; }
}
/// <summary>
......@@ -33,15 +57,61 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
{
internal List<Regex> ExcludedHttpsHostNameRegexList;
internal List<Regex> IncludedHttpsHostNameRegexList;
internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; }
public List<string> ExcludedHttpsHostNameRegex { get; set; }
/// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
public IEnumerable<string> ExcludedHttpsHostNameRegex
{
get { return ExcludedHttpsHostNameRegexList?.Select(x => x.ToString()).ToList(); }
set
{
if (IncludedHttpsHostNameRegex != null)
{
throw new ArgumentException("Cannot set excluded when included is set");
}
ExcludedHttpsHostNameRegexList = value?.Select(x => new Regex(x, RegexOptions.Compiled)).ToList();
}
}
public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
/// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
public IEnumerable<string> IncludedHttpsHostNameRegex
{
get { return IncludedHttpsHostNameRegexList?.Select(x => x.ToString()).ToList(); }
set
{
if (ExcludedHttpsHostNameRegex != null)
{
throw new ArgumentException("Cannot set included when excluded is set");
}
IncludedHttpsHostNameRegexList = value?.Select(x => new Regex(x, RegexOptions.Compiled)).ToList();
}
}
/// <summary>
/// Generic certificate to use for SSL decryption.
/// </summary>
public X509Certificate2 GenericCertificate { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
: base(ipAddress, port, enableSsl)
{
}
}
......@@ -51,18 +121,22 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//This is valid only when UseServerNameIndication is set to false
/// <summary>
/// Name of the Certificate need to be sent (same as the hostname we want to proxy)
/// This is valid only when UseServerNameIndication is set to false
/// </summary>
public string GenericCertificateName { get; set; }
// public bool UseServerNameIndication { get; set; }
public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
: base(ipAddress, port, enableSsl)
{
this.GenericCertificateName = "localhost";
GenericCertificateName = "localhost";
}
}
}
......@@ -8,41 +8,63 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class ExternalProxy
{
private static readonly Lazy<NetworkCredential> DefaultCredentials = new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private static readonly Lazy<NetworkCredential> defaultCredentials = new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private string userName;
private string password;
/// <summary>
/// Use default windows credentials?
/// </summary>
public bool UseDefaultCredentials { get; set; }
public string UserName {
get { return UseDefaultCredentials ? DefaultCredentials.Value.UserName : userName; }
/// <summary>
/// Bypass this proxy for connections to localhost?
/// </summary>
public bool BypassForLocalhost { get; set; }
/// <summary>
/// Username.
/// </summary>
public string UserName
{
get { return UseDefaultCredentials ? defaultCredentials.Value.UserName : userName; }
set
{
userName = value;
if (DefaultCredentials.Value.UserName != userName)
if (defaultCredentials.Value.UserName != userName)
{
UseDefaultCredentials = false;
}
}
}
/// <summary>
/// Password.
/// </summary>
public string Password
{
get { return UseDefaultCredentials ? DefaultCredentials.Value.Password : password; }
get { return UseDefaultCredentials ? defaultCredentials.Value.Password : password; }
set
{
password = value;
if (DefaultCredentials.Value.Password != password)
if (defaultCredentials.Value.Password != password)
{
UseDefaultCredentials = false;
}
}
}
/// <summary>
/// Host name.
/// </summary>
public string HostName { get; set; }
/// <summary>
/// Port.
/// </summary>
public int Port { get; set; }
}
}
using System;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Models
{
......@@ -7,6 +9,16 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class HttpHeader
{
internal static Version Version10 = new Version(1, 0);
internal static Version Version11 = new Version(1, 1);
/// <summary>
/// Constructor.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <exception cref="Exception"></exception>
public HttpHeader(string name, string value)
{
if (string.IsNullOrEmpty(name))
......@@ -18,7 +30,14 @@ namespace Titanium.Web.Proxy.Models
Value = value.Trim();
}
/// <summary>
/// Header Name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Header Value.
/// </summary>
public string Value { get; set; }
/// <summary>
......@@ -27,7 +46,14 @@ namespace Titanium.Web.Proxy.Models
/// <returns></returns>
public override string ToString()
{
return string.Format("{0}: {1}", Name, Value);
return $"{Name}: {Value}";
}
internal async Task WriteToStream(StreamWriter writer)
{
await writer.WriteAsync(Name);
await writer.WriteAsync(": ");
await writer.WriteLineAsync(Value);
}
}
}
......@@ -20,6 +20,5 @@ namespace Titanium.Web.Proxy.Network
{
LastAccess = DateTime.Now;
}
}
}
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Implements certificate generation operations.
/// </summary>
internal class BCCertificateMaker : ICertificateMaker
{
private const int certificateValidDays = 1825;
private const int certificateGraceDays = 366;
/// <summary>
/// Makes the certificate.
/// </summary>
/// <param name="sSubjectCn">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="signingCert">The signing cert.</param>
/// <returns>X509Certificate2 instance.</returns>
public X509Certificate2 MakeCertificate(string sSubjectCn, bool isRoot, X509Certificate2 signingCert = null)
{
return MakeCertificateInternal(sSubjectCn, isRoot, true, signingCert);
}
/// <summary>
/// Generates the certificate.
/// </summary>
/// <param name="subjectName">Name of the subject.</param>
/// <param name="issuerName">Name of the issuer.</param>
/// <param name="validFrom">The valid from.</param>
/// <param name="validTo">The valid to.</param>
/// <param name="keyStrength">The key strength.</param>
/// <param name="signatureAlgorithm">The signature algorithm.</param>
/// <param name="issuerPrivateKey">The issuer private key.</param>
/// <param name="hostName">The host name</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="PemException">Malformed sequence in RSA private key</exception>
private static X509Certificate2 GenerateCertificate(string hostName,
string subjectName,
string issuerName, DateTime validFrom,
DateTime validTo, int keyStrength = 2048,
string signatureAlgorithm = "SHA256WithRSA",
AsymmetricKeyParameter issuerPrivateKey = null)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var secureRandom = new SecureRandom(randomGenerator);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), secureRandom);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
certificateGenerator.SetNotBefore(validFrom);
certificateGenerator.SetNotAfter(validTo);
if (hostName != null)
{
//add subject alternative names
var subjectAlternativeNames = new Asn1Encodable[]
{
new GeneralName(GeneralName.DnsName, hostName),
};
var subjectAlternativeNamesExtension = new DerSequence(subjectAlternativeNames);
certificateGenerator.AddExtension(
X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
}
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// Set certificate intended purposes to only Server Authentication
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));
var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerPrivateKey ?? subjectKeyPair.Private, secureRandom);
// Self-sign the certificate
var certificate = certificateGenerator.Generate(signatureFactory);
var x509Certificate = new X509Certificate2(certificate.GetEncoded());
// Corresponding private key
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
var seq = (Asn1Sequence) Asn1Object.FromByteArray(privateKeyInfo.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException("Malformed sequence in RSA private key");
}
var rsa = RsaPrivateKeyStructure.GetInstance(seq);
var rsaparams = new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);
// Set private key onto certificate instance
x509Certificate.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
x509Certificate.FriendlyName = subjectName;
return x509Certificate;
}
/// <summary>
/// Makes the certificate internal.
/// </summary>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="hostName">hostname for certificate</param>
/// <param name="subjectName">The full subject.</param>
/// <param name="validFrom">The valid from.</param>
/// <param name="validTo">The valid to.</param>
/// <param name="signingCertificate">The signing certificate.</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="System.ArgumentException">You must specify a Signing Certificate if and only if you are not creating a root.</exception>
private X509Certificate2 MakeCertificateInternal(bool isRoot,
string hostName, string subjectName,
DateTime validFrom, DateTime validTo, X509Certificate2 signingCertificate)
{
if (isRoot != (null == signingCertificate))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", nameof(signingCertificate));
}
return isRoot
? GenerateCertificate(null, subjectName, subjectName, validFrom, validTo)
: GenerateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo, issuerPrivateKey: DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey).Private);
}
/// <summary>
/// Makes the certificate internal.
/// </summary>
/// <param name="subject">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="switchToMtaIfNeeded">if set to <c>true</c> [switch to MTA if needed].</param>
/// <param name="signingCert">The signing cert.</param>
/// <param name="cancellationToken">Task cancellation token</param>
/// <returns>X509Certificate2.</returns>
private X509Certificate2 MakeCertificateInternal(string subject, bool isRoot,
bool switchToMtaIfNeeded, X509Certificate2 signingCert = null,
CancellationToken cancellationToken = default(CancellationToken))
{
X509Certificate2 certificate = null;
if (switchToMtaIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
using (var manualResetEvent = new ManualResetEventSlim(false))
{
ThreadPool.QueueUserWorkItem(o =>
{
certificate = MakeCertificateInternal(subject, isRoot, false, signingCert);
if (!cancellationToken.IsCancellationRequested)
{
manualResetEvent?.Set();
}
});
manualResetEvent.Wait(TimeSpan.FromMinutes(1), cancellationToken);
}
return certificate;
}
return MakeCertificateInternal(isRoot, subject, $"CN={subject}", DateTime.UtcNow.AddDays(-certificateGraceDays), DateTime.UtcNow.AddDays(certificateValidDays), isRoot ? null : signingCert);
}
}
}
using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Abstract interface for different Certificate Maker Engines
/// </summary>
internal interface ICertificateMaker
{
X509Certificate2 MakeCertificate(string sSubjectCn, bool isRoot, X509Certificate2 signingCert);
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -7,7 +7,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// This class wraps Tcp connection to client
/// </summary>
public class ProxyClient
internal class ProxyClient
{
/// <summary>
/// TcpClient used to communicate with client
......@@ -28,6 +28,5 @@ namespace Titanium.Web.Proxy.Network
/// used to write line by line to client
/// </summary>
internal StreamWriter ClientStreamWriter { get; set; }
}
}
......@@ -7,15 +7,16 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// An object that holds TcpConnection to a particular server & port
/// An object that holds TcpConnection to a particular server and port
/// </summary>
public class TcpConnection : IDisposable
internal class TcpConnection : IDisposable
{
internal ExternalProxy UpStreamHttpProxy { get; set; }
internal ExternalProxy UpStreamHttpsProxy { get; set; }
internal string HostName { get; set; }
internal int Port { get; set; }
internal bool IsHttps { get; set; }
......@@ -47,18 +48,17 @@ namespace Titanium.Web.Proxy.Network.Tcp
LastAccess = DateTime.Now;
}
/// <summary>
/// Dispose.
/// </summary>
public void Dispose()
{
Stream.Close();
Stream.Dispose();
TcpClient.LingerState = new LingerOption(true, 0);
TcpClient.Client.Shutdown(SocketShutdown.Both);
TcpClient.Client.Close();
TcpClient.Client.Dispose();
Stream?.Close();
Stream?.Dispose();
TcpClient.Close();
StreamReader?.Dispose();
TcpClient?.Close();
}
}
}
......@@ -6,14 +6,12 @@ using System.IO;
using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
using System.Linq;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network.Tcp
{
using System.Net;
/// <summary>
/// A class that manages Tcp Connection to server used by this proxy server
/// </summary>
......@@ -23,44 +21,41 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary>
/// Creates a TCP connection to server
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="server"></param>
/// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param>
/// <param name="remotePort"></param>
/// <param name="httpVersion"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedSslProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param>
/// <param name="clientStream"></param>
/// <param name="upStreamEndPoint"></param>
/// <returns></returns>
internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
internal async Task<TcpConnection> CreateClient(ProxyServer server,
string remoteHostName, int remotePort, Version httpVersion,
bool isHttps, SslProtocols supportedSslProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
Stream clientStream, EndPoint upStreamEndPoint)
Stream clientStream)
{
TcpClient client;
Stream stream;
CustomBufferedStream stream;
bool isLocalhost = (externalHttpsProxy == null && externalHttpProxy == null) ? false : NetworkHelper.IsLocalIpAddress(remoteHostName);
bool useHttpsProxy = externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName && (externalHttpsProxy.BypassForLocalhost && !isLocalhost);
bool useHttpProxy = externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName && (externalHttpProxy.BypassForLocalhost && !isLocalhost);
if (isHttps)
{
SslStream sslStream = null;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName)
if (useHttpsProxy)
{
client = new TcpClient();
client.Client.Bind(upStreamEndPoint);
client.Client.Connect(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = client.GetStream();
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true) { NewLine = ProxyConstants.NewLine })
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) {NewLine = ProxyConstants.NewLine})
{
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
......@@ -69,42 +64,40 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
}
await writer.WriteLineAsync();
await writer.FlushAsync();
writer.Close();
}
using (var reader = new CustomBinaryReader(stream))
using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{
var result = await reader.ReadLineAsync();
if (!new string[] { "200 OK", "connection established" }.Any(s => result.ToLower().Contains(s.ToLower())))
if (!new[] {"200 OK", "connection established"}.Any(s => result.ContainsIgnoreCase(s)))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
await reader.ReadAllLinesAsync();
await reader.ReadAndIgnoreAllLinesAsync();
}
}
else
{
client = new TcpClient();
client.Client.Bind(upStreamEndPoint);
client.Client.Connect(remoteHostName, remotePort);
stream = client.GetStream();
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
try
{
sslStream = new SslStream(stream, true, remoteCertificateValidationCallback,
localCertificateSelectionCallback);
sslStream = new SslStream(stream, true, server.ValidateServerCertificate,
server.SelectClientCertificate);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, false);
stream = sslStream;
stream = new CustomBufferedStream(sslStream, server.BufferSize);
}
catch
{
......@@ -115,30 +108,28 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
else
{
if (externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName)
if (useHttpProxy)
{
client = new TcpClient();
client.Client.Bind(upStreamEndPoint);
client.Client.Connect(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = client.GetStream();
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
else
{
client = new TcpClient();
client.Client.Bind(upStreamEndPoint);
client.Client.Connect(remoteHostName, remotePort);
stream = client.GetStream();
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
}
client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000;
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
stream.ReadTimeout = connectionTimeOutSeconds * 1000;
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
client.LingerState = new LingerOption(true, 0);
server.ServerConnectionCount++;
return new TcpConnection()
return new TcpConnection
{
UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy,
......@@ -146,7 +137,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
Port = remotePort,
IsHttps = isHttps,
TcpClient = client,
StreamReader = new CustomBinaryReader(stream),
StreamReader = new CustomBinaryReader(stream, server.BufferSize),
Stream = stream,
Version = httpVersion
};
......
using System.Net;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
internal class TcpRow
{
......@@ -13,32 +14,50 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// Initializes a new instance of the <see cref="TcpRow"/> class.
/// </summary>
/// <param name="tcpRow">TcpRow struct.</param>
public TcpRow(NativeMethods.TcpRow tcpRow)
internal TcpRow(NativeMethods.TcpRow tcpRow)
{
ProcessId = tcpRow.owningPid;
int localPort = (tcpRow.localPort1 << 8) + (tcpRow.localPort2) + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
long localAddress = tcpRow.localAddr;
LocalEndPoint = new IPEndPoint(localAddress, localPort);
LocalPort = tcpRow.GetLocalPort();
LocalAddress = tcpRow.localAddr;
int remotePort = (tcpRow.remotePort1 << 8) + (tcpRow.remotePort2) + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
long remoteAddress = tcpRow.remoteAddr;
RemoteEndPoint = new IPEndPoint(remoteAddress, remotePort);
RemotePort = tcpRow.GetRemotePort();
RemoteAddress = tcpRow.remoteAddr;
}
/// <summary>
/// Gets the local end point address.
/// </summary>
internal long LocalAddress { get; }
/// <summary>
/// Gets the local end point port.
/// </summary>
internal int LocalPort { get; }
/// <summary>
/// Gets the local end point.
/// </summary>
public IPEndPoint LocalEndPoint { get; private set; }
internal IPEndPoint LocalEndPoint => new IPEndPoint(LocalAddress, LocalPort);
/// <summary>
/// Gets the remote end point address.
/// </summary>
internal long RemoteAddress { get; }
/// <summary>
/// Gets the remote end point port.
/// </summary>
internal int RemotePort { get; }
/// <summary>
/// Gets the remote end point.
/// </summary>
public IPEndPoint RemoteEndPoint { get; private set; }
internal IPEndPoint RemoteEndPoint => new IPEndPoint(RemoteAddress, RemotePort);
/// <summary>
/// Gets the process identifier.
/// </summary>
public int ProcessId { get; private set; }
internal int ProcessId { get; }
}
}
......@@ -6,7 +6,9 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary>
/// Represents collection of TcpRows
/// </summary>
/// <seealso cref="System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}" />
/// <seealso>
/// <cref>System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}</cref>
/// </seealso>
internal class TcpTable : IEnumerable<TcpRow>
{
private readonly IEnumerable<TcpRow> tcpRows;
......@@ -15,7 +17,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// Initializes a new instance of the <see cref="TcpTable"/> class.
/// </summary>
/// <param name="tcpRows">TcpRow collection to initialize with.</param>
public TcpTable(IEnumerable<TcpRow> tcpRows)
internal TcpTable(IEnumerable<TcpRow> tcpRows)
{
this.tcpRows = tcpRows;
}
......@@ -23,7 +25,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary>
/// Gets the TCP rows.
/// </summary>
public IEnumerable<TcpRow> TcpRows => tcpRows;
internal IEnumerable<TcpRow> TcpRows => tcpRows;
/// <summary>
/// Returns an enumerator that iterates through the collection.
......
......@@ -14,7 +14,12 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: InternalsVisibleTo("Titanium.Web.Proxy.UnitTests")]
[assembly: InternalsVisibleTo("Titanium.Web.Proxy.UnitTests, PublicKey=" +
"0024000004800000940000000602000000240000525341310004000001000100e7368e0ccc717e" +
"eb4d57d35ad6a8305cbbed14faa222e13869405e92c83856266d400887d857005f1393ffca2b92" +
"de7f3ba0bdad35ec2d6057ee1846091b34be2abc3f97dc7e72c16fd4958c15126b12923df76964" +
"7d84922c3f4f3b80ee0ae8e4cb40bc1973b782afb90bb00519fd16adf960f217e23696e7c31654" +
"01d0acd6")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
......
......@@ -12,87 +12,71 @@ namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> Headers)
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> headers)
{
if (AuthenticateUserFunc == null)
{
return true;
}
var httpHeaders = Headers as HttpHeader[] ?? Headers.ToArray();
var httpHeaders = headers as ICollection<HttpHeader> ?? headers.ToArray();
try
{
if (!httpHeaders.Where(t => t.Name == "Proxy-Authorization").Any())
if (httpHeaders.All(t => t.Name != "Proxy-Authorization"))
{
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Required", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required");
return false;
}
else
{
var headerValue = httpHeaders.Where(t => t.Name == "Proxy-Authorization").FirstOrDefault().Value.Trim();
if (!headerValue.ToLower().StartsWith("basic"))
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization");
if (header == null) throw new NullReferenceException();
var headerValue = header.Value.Trim();
if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase))
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
headerValue = headerValue.Substring(5).Trim();
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
if (decoded.Contains(":") == false)
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
var username = decoded.Substring(0, decoded.IndexOf(':'));
var password = decoded.Substring(decoded.IndexOf(':') + 1);
return await AuthenticateUserFunc(username, password).ConfigureAwait(false);
}
return await AuthenticateUserFunc(username, password);
}
catch (Exception e)
{
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders));
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
//Return not authorized
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
}
private async Task SendAuthentication407Response(StreamWriter clientStreamWriter, string description)
{
await WriteResponseStatus(HttpHeader.Version11, "407", description, clientStreamWriter);
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{"Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
using System;
using System.Text;
using System.Text;
namespace Titanium.Web.Proxy.Shared
{
......@@ -8,9 +7,9 @@ namespace Titanium.Web.Proxy.Shared
/// </summary>
internal class ProxyConstants
{
internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly char[] SpaceSplit = {' '};
internal static readonly char[] ColonSplit = {':'};
internal static readonly char[] SemiColonSplit = {';'};
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine);
......
......@@ -14,7 +14,7 @@
<copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright>
<tags></tags>
<dependencies>
<dependency id="DotNetZip" version="1.9.8" />
<dependency id="BouncyCastle" version="1.8.1" />
</dependencies>
</metadata>
<files>
......
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
......@@ -12,4 +12,7 @@
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
</configuration>
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DotNetZip" version="1.9.8" targetFramework="net45" />
<package id="BouncyCastle" version="1.8.1" targetFramework="net45" />
</packages>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment