Commit 95877cde authored by titanium007's avatar titanium007

issues #67 #69 #70 #71 #72

parent e4c68b0b
using System;
using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Test
namespace Titanium.Web.Proxy.Examples.Basic
{
public class Program
{
......
......@@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Test
namespace Titanium.Web.Proxy.Examples.Basic
{
public class ProxyTestController
{
......@@ -13,6 +13,7 @@ namespace Titanium.Web.Proxy.Test
{
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.RemoteCertificateValidationCallback += OnCertificateValidation;
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
......@@ -34,13 +35,14 @@ namespace Titanium.Web.Proxy.Test
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
//Please read about it before asking questions!
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
GenericCertificateName = "google.com"
};
ProxyServer.AddEndPoint(transparentEndPoint);
//ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in ProxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
......@@ -63,12 +65,12 @@ namespace Titanium.Web.Proxy.Test
//Read browser URL send back to proxy by the injection script in OnResponse event
public void OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.ProxySession.Request.Url);
Console.WriteLine(e.WebSession.Request.Url);
//read request headers
var requestHeaders = e.ProxySession.Request.RequestHeaders;
////read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders;
if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT"))
if ((e.WebSession.Request.Method.ToUpper() == "POST" || e.WebSession.Request.Method.ToUpper() == "PUT"))
{
//Get/Set request body bytes
byte[] bodyBytes = e.GetRequestBody();
......@@ -82,7 +84,7 @@ namespace Titanium.Web.Proxy.Test
//To cancel a request with a custom HTML content
//Filter URL
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
......@@ -93,7 +95,7 @@ namespace Titanium.Web.Proxy.Test
"</html>");
}
//Redirect example
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
e.Redirect("https://www.paypal.com");
}
......@@ -105,14 +107,14 @@ namespace Titanium.Web.Proxy.Test
{
//read response headers
var responseHeaders = e.ProxySession.Response.ResponseHeaders;
var responseHeaders = e.WebSession.Response.ResponseHeaders;
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.RequestMethod == "GET" || e.RequestMethod == "POST")
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{
if (e.ProxySession.Response.ResponseStatusCode == "200")
if (e.WebSession.Response.ResponseStatusCode == "200")
{
if (e.ProxySession.Response.ContentType.Trim().ToLower().Contains("text/html"))
if (e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
byte[] bodyBytes = e.GetResponseBody();
e.SetResponseBody(bodyBytes);
......@@ -123,5 +125,19 @@ namespace Titanium.Web.Proxy.Test
}
}
}
/// <summary>
/// Allows overriding default certificate validation logic
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
else
e.Session.Ok("Cannot validate server certificate! Not safe to proceed.");
}
}
}
\ No newline at end of file
......@@ -8,8 +8,8 @@
<ProjectGuid>{F3B7E553-1904-4E80-BDC7-212342B5C952}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.Test</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.Test</AssemblyName>
<RootNamespace>Titanium.Web.Proxy.Examples.Basic</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.Examples.Basic</AssemblyName>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
......@@ -65,7 +65,7 @@
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
......
......@@ -6,7 +6,7 @@ A light weight http(s) proxy server written in C#
Kindly report only issues/bugs here . For programming help or questions use [StackOverflow](http://stackoverflow.com/questions/tagged/titanium-web-proxy) with the tag Titanium-Web-Proxy.
![alt tag](https://raw.githubusercontent.com/titanium007/Titanium/master/Titanium.Web.Proxy.Test/Capture.PNG)
![alt tag](https://raw.githubusercontent.com/titanium007/Titanium/master/Titanium-Web-Proxy/Examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG)
Features
========
......@@ -32,52 +32,69 @@ After installing nuget package mark following files to be copied to app director
Setup HTTP proxy:
```csharp
// listen to client request & server response events
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true){
//Exclude Https addresses you don't want to proxy/cannot be proxied
//for example exclude dropbox client which use certificate pinning
ExcludedHttpsHostNameRegex = new List<string>() { "dropbox.com" }
};
//Add an explicit endpoint where the client is aware of the proxy
//So client would send request in a proxy friendly manner
ProxyServer.AddEndPoint(explicitEndPoint);
ProxyServer.Start();
//Only explicit proxies can be set as a system proxy!
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint);
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.RemoteCertificateValidationCallback += OnCertificateValidation;
//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);
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);
//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);
//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();
//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();
//Unsubscribe & Quit
ProxyServer.BeforeRequest -= OnRequest;
ProxyServer.BeforeResponse -= OnResponse;
ProxyServer.Stop();
//Unsubscribe & Quit
ProxyServer.BeforeRequest -= OnRequest;
ProxyServer.BeforeResponse -= OnResponse;
ProxyServer.Stop();
```
Sample request and response event handlers
```csharp
//Test On Request, intecept requests
//Read browser URL send back to proxy by the injection script in OnResponse event
public void OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.ProxySession.Request.Url);
Console.WriteLine(e.WebSession.Request.Url);
//read request headers
var requestHeaders = e.ProxySession.Request.RequestHeaders;
////read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders;
if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT"))
if ((e.WebSession.Request.Method.ToUpper() == "POST" || e.WebSession.Request.Method.ToUpper() == "PUT"))
{
//Get/Set request body bytes
byte[] bodyBytes = e.GetRequestBody();
......@@ -91,43 +108,58 @@ Sample request and response event handlers
//To cancel a request with a custom HTML content
//Filter URL
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
//Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
e.Ok("<!DOCTYPE html>"+
"<html><body><h1>"+
"Website Blocked"+
"</h1>"+
"<p>Blocked by titanium web proxy.</p>"+
"</body>"+
"</html>");
e.Redirect("https://www.paypal.com");
}
}
//Test script injection
//Insert script to read the Browser URL and send it back to proxy
public void OnResponse(object sender, SessionEventArgs e)
{
//read response headers
var responseHeaders = e.ProxySession.Response.ResponseHeaders;
var responseHeaders = e.WebSession.Response.ResponseHeaders;
if (e.RequestMethod == "GET" || e.RequestMethod == "POST")
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{
if (e.ProxySession.Response.ResponseStatusCode == "200")
if (e.WebSession.Response.ResponseStatusCode == "200")
{
if (e.ProxySession.Response.ContentType.Trim().ToLower().Contains("text/html"))
if (e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
byte[] bodyBytes = e.GetResponseBody();
e.SetResponseBody(bodyBytes);
string body = e.GetResponseBodyAsString();
e.SetResponseBodyString(body);
}
}
}
}
// Allows overriding default certificate validation logic
public void OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
else
e.Session.Ok("Cannot validate server certificate! Not safe to proceed.");
}
```
Future roadmap
============
* Add callbacks for client/server certificate validation/selection
* Support mutual authentication
* Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0
......
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectView>ProjectFiles</ProjectView>
</PropertyGroup>
</Project>
\ No newline at end of file
......@@ -3,9 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Test", "Titanium.Web.Proxy.Test\Titanium.Web.Proxy.Test.csproj", "{F3B7E553-1904-4E80-BDC7-212342B5C952}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy", "Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}"
EndProject
......@@ -16,20 +14,35 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{6FD3B8
.nuget\NuGet.targets = .nuget\NuGet.targets
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Examples.Basic", "Examples\Titanium.Web.Proxy.Examples.Basic\Titanium.Web.Proxy.Examples.Basic.csproj", "{F3B7E553-1904-4E80-BDC7-212342B5C952}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{38EA62D0-D2CB-465D-AF4F-407C5B4D4A1E}"
ProjectSection(SolutionItems) = preProject
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{AC9AE37A-3059-4FDB-9A5C-363AD86F2EEF}"
ProjectSection(SolutionItems) = preProject
.build\Bootstrap.ps1 = .build\Bootstrap.ps1
.build\Common.psm1 = .build\Common.psm1
.build\default.ps1 = .build\default.ps1
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.Build.0 = Release|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D73A1BE-868C-42D2-9ECE-F32CC1A02906}.Release|Any CPU.Build.0 = Release|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3B7E553-1904-4E80-BDC7-212342B5C952}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
using Ionic.Zlib;
using System.IO;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
......@@ -11,7 +12,7 @@ namespace Titanium.Web.Proxy.Decompression
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{
var buffer = new byte[ProxyServer.BUFFER_SIZE];
var buffer = new byte[Constants.BUFFER_SIZE];
using (var output = new MemoryStream())
{
......
using System.IO;
using System.IO.Compression;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
......@@ -9,7 +10,7 @@ namespace Titanium.Web.Proxy.Decompression
{
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
{
var buffer = new byte[ProxyServer.BUFFER_SIZE];
var buffer = new byte[Constants.BUFFER_SIZE];
using (var output = new MemoryStream())
{
int read;
......
using Ionic.Zlib;
using System.IO;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
......@@ -10,7 +11,7 @@ namespace Titanium.Web.Proxy.Decompression
var memoryStream = new MemoryStream(compressedArray);
using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress))
{
var buffer = new byte[ProxyServer.BUFFER_SIZE];
var buffer = new byte[Constants.BUFFER_SIZE];
using (var output = new MemoryStream())
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.EventArguments
{
public class CertificateValidationEventArgs : EventArgs, IDisposable
{
public string HostName => Session.WebSession.Request.Host;
public SessionEventArgs Session { get; internal set; }
public X509Certificate Certificate { get; internal set; }
public X509Chain Chain { get; internal set; }
public SslPolicyErrors SslPolicyErrors { get; internal set; }
public bool IsValid { get; set; }
public void Dispose()
{
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.IO;
using System.Net.Sockets;
using System.Text;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.EventArguments
......
using System.Net;
using System.Text;
using Titanium.Web.Proxy.Network;
using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions
{
......@@ -18,7 +18,7 @@ namespace Titanium.Web.Proxy.Extensions
if (request.ContentType == null) return Encoding.GetEncoding("ISO-8859-1");
//extract the encoding by finding the charset
var contentTypes = request.ContentType.Split(';');
var contentTypes = request.ContentType.Split(Constants.SemiColonSplit);
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
......
using System.Net;
using System.Text;
using Titanium.Web.Proxy.Network;
using System.Text;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Extensions
{
......
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions
{
public static class StreamHelper
{
public static void CopyToAsync(this Stream input, string initialData, Stream output, int bufferSize)
public static async Task CopyToAsync(this Stream input, string initialData, Stream output)
{
if(!string.IsNullOrEmpty(initialData))
if (!string.IsNullOrEmpty(initialData))
{
var bytes = Encoding.ASCII.GetBytes(initialData);
output.Write(bytes, 0, bytes.Length);
}
CopyToAsync(input, output, bufferSize);
await input.CopyToAsync(output);
}
//http://stackoverflow.com/questions/1540658/net-asynchronous-stream-read-write
private static void CopyToAsync(this Stream input, Stream output, int bufferSize)
internal static void CopyBytesToStream(this CustomBinaryReader clientStreamReader, Stream stream, long totalBytesToRead)
{
try
{
if (!input.CanRead) throw new InvalidOperationException("input must be open for reading");
if (!output.CanWrite) throw new InvalidOperationException("output must be open for writing");
byte[][] buf = {new byte[bufferSize], new byte[bufferSize]};
int[] bufl = {0, 0};
var bufno = 0;
var read = input.BeginRead(buf[bufno], 0, buf[bufno].Length, null, null);
IAsyncResult write = null;
while (true)
{
// wait for the read operation to complete
read.AsyncWaitHandle.WaitOne();
bufl[bufno] = input.EndRead(read);
// if zero bytes read, the copy is complete
if (bufl[bufno] == 0)
{
break;
}
var totalbytesRead = 0;
// wait for the in-flight write operation, if one exists, to complete
// the only time one won't exist is after the very first read operation completes
if (write != null)
{
write.AsyncWaitHandle.WaitOne();
output.EndWrite(write);
}
int bytesToRead;
if (totalBytesToRead < Constants.BUFFER_SIZE)
{
bytesToRead = (int)totalBytesToRead;
}
else
bytesToRead = Constants.BUFFER_SIZE;
// start the new write operation
write = output.BeginWrite(buf[bufno], 0, bufl[bufno], null, null);
// toggle the current, in-use buffer
// and start the read operation on the new buffer.
//
// Changed to use XOR to toggle between 0 and 1.
// A little speedier than using a ternary expression.
bufno ^= 1; // bufno = ( bufno == 0 ? 1 : 0 ) ;
read = input.BeginRead(buf[bufno], 0, buf[bufno].Length, null, null);
}
while (totalbytesRead < (int)totalBytesToRead)
{
var buffer = clientStreamReader.ReadBytes(bytesToRead);
totalbytesRead += buffer.Length;
// wait for the final in-flight write operation, if one exists, to complete
// the only time one won't exist is if the input stream is empty.
if (write != null)
var remainingBytes = (int)totalBytesToRead - totalbytesRead;
if (remainingBytes < bytesToRead)
{
write.AsyncWaitHandle.WaitOne();
output.EndWrite(write);
bytesToRead = remainingBytes;
}
output.Flush();
stream.Write(buffer, 0, buffer.Length);
}
catch
}
internal static void CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
{
while (true)
{
// ignored
var chuchkHead = clientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = clientStreamReader.ReadBytes(chunkSize);
stream.Write(buffer, 0, buffer.Length);
//chunk trail
clientStreamReader.ReadLine();
}
else
{
clientStreamReader.ReadLine();
break;
}
}
// return to the caller ;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Extensions
......
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Text;
......
......@@ -8,13 +8,12 @@ using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
public class TcpHelper
{
private static readonly int BUFFER_SIZE = 8192;
public static void SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
int tunnelPort, bool isHttps)
{
......@@ -51,7 +50,7 @@ namespace Titanium.Web.Proxy.Helpers
try
{
sslStream = new SslStream(tunnelStream);
sslStream.AuthenticateAsClient(hostName, null, ProxyServer.SupportedProtocols, false);
sslStream.AuthenticateAsClient(hostName, null, Constants.SupportedProtocols, false);
tunnelStream = sslStream;
}
catch
......@@ -63,16 +62,15 @@ namespace Titanium.Web.Proxy.Helpers
}
}
var sendRelay = Task.Factory.StartNew(() =>
{
if (sb != null)
clientStream.CopyToAsync(sb.ToString(), tunnelStream, BUFFER_SIZE);
clientStream.CopyToAsync(sb.ToString(), tunnelStream).Wait();
else
clientStream.CopyToAsync(string.Empty, tunnelStream, BUFFER_SIZE);
clientStream.CopyToAsync(string.Empty, tunnelStream).Wait();
});
var receiveRelay = Task.Factory.StartNew(() => tunnelStream.CopyToAsync(string.Empty, clientStream, BUFFER_SIZE));
var receiveRelay = Task.Factory.StartNew(() =>tunnelStream.CopyToAsync(string.Empty, clientStream).Wait());
Task.WaitAll(sendRelay, receiveRelay);
}
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Http
{
public class Request
{
public string Method { get; set; }
public Uri RequestUri { get; set; }
public string HttpVersion { get; set; }
internal string Host
{
get
{
var host = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "host");
if (host != null)
return host.Value;
return null;
}
set
{
var host = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "host");
if (host != null)
host.Value = value;
else
RequestHeaders.Add(new HttpHeader("Host", value));
}
}
internal string ContentEncoding
{
get
{
var header = this.RequestHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-encoding"));
if (header != null)
{
return header.Value.Trim();
}
return null;
}
}
public long ContentLength
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
if (header == null)
return -1;
long contentLen;
long.TryParse(header.Value, out contentLen);
if (contentLen >=0)
return contentLen;
return -1;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
if (value >= 0)
{
if (header != null)
header.Value = value.ToString();
else
RequestHeaders.Add(new HttpHeader("content-length", value.ToString()));
}
else
{
if (header != null)
this.RequestHeaders.Remove(header);
}
}
}
public string ContentType
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-type");
if (header != null)
return header.Value;
return null;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-type");
if (header != null)
header.Value = value.ToString();
else
RequestHeaders.Add(new HttpHeader("content-type", value.ToString()));
}
}
public bool IsChunked
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "transfer-encoding");
if (header != null) return header.Value.ToLower().Contains("chunked");
return false;
}
set
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "transfer-encoding");
if (value)
{
if (header != null)
{
header.Value = "chunked";
}
else
RequestHeaders.Add(new HttpHeader("transfer-encoding", "chunked"));
this.ContentLength = -1;
}
else
{
if (header != null)
RequestHeaders.Remove(header);
}
}
}
public bool ExpectContinue
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "expect");
if (header != null) return header.Value.Equals("100-continue");
return false;
}
}
public string Url { get { return RequestUri.OriginalString; } }
internal Encoding Encoding { get { return this.GetEncoding(); } }
/// <summary>
/// Terminates the underlying Tcp Connection to client after current request
/// </summary>
internal bool CancelRequest { get; set; }
internal byte[] RequestBody { get; set; }
internal string RequestBodyString { get; set; }
internal bool RequestBodyRead { get; set; }
internal bool RequestLocked { get; set; }
internal bool UpgradeToWebSocket
{
get
{
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "upgrade");
if (header == null)
return false;
if (header.Value.ToLower() == "websocket")
return true;
return false;
}
}
public List<HttpHeader> RequestHeaders { get; set; }
public bool Is100Continue { get; internal set; }
public bool ExpectationFailed { get; internal set; }
public Request()
{
this.RequestHeaders = new List<HttpHeader>();
}
}
}
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Http
{
public class Response
{
public string ResponseStatusCode { get; set; }
public string ResponseStatusDescription { get; set; }
internal Encoding Encoding { get { return this.GetResponseEncoding(); } }
internal string CharacterSet
{
get
{
if (this.ContentType.Contains(";"))
{
return this.ContentType.Split(';')[1].Substring(9).Trim();
}
return null;
}
}
internal string ContentEncoding
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-encoding"));
if (header != null)
{
return header.Value.Trim();
}
return null;
}
}
internal string HttpVersion { get; set; }
internal bool ResponseKeepAlive
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("connection"));
if (header != null && header.Value.ToLower().Contains("close"))
{
return false;
}
return true;
}
}
public string ContentType
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-type"));
if (header != null)
{
if (header.Value.Contains(";"))
{
return header.Value.Split(';')[0].Trim();
}
else
return header.Value.ToLower().Trim();
}
return null;
}
}
internal long ContentLength
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-length"));
if (header == null)
return -1;
long contentLen;
long.TryParse(header.Value, out contentLen);
if (contentLen >= 0)
return contentLen;
return -1;
}
set
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("content-length"));
if (value >= 0)
{
if (header != null)
header.Value = value.ToString();
else
ResponseHeaders.Add(new HttpHeader("content-length", value.ToString()));
}
else
{
if (header != null)
this.ResponseHeaders.Remove(header);
}
}
}
internal bool IsChunked
{
get
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("transfer-encoding"));
if (header != null && header.Value.ToLower().Contains("chunked"))
{
return true;
}
return false;
}
set
{
var header = this.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower().Equals("transfer-encoding"));
if (value)
{
if (header != null)
{
header.Value = "chunked";
}
else
ResponseHeaders.Add(new HttpHeader("transfer-encoding", "chunked"));
this.ContentLength = -1;
}
else
{
if (header != null)
ResponseHeaders.Remove(header);
}
}
}
public List<HttpHeader> ResponseHeaders { get; set; }
internal Stream ResponseStream { get; set; }
internal byte[] ResponseBody { get; set; }
internal string ResponseBodyString { get; set; }
internal bool ResponseBodyRead { get; set; }
internal bool ResponseLocked { get; set; }
public bool Is100Continue { get; internal set; }
public bool ExpectationFailed { get; internal set; }
public Response()
{
this.ResponseHeaders = new List<HttpHeader>();
}
}
}
using System;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Responses
namespace Titanium.Web.Proxy.Http.Responses
{
public class OkResponse : Response
{
......
using System;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Responses
namespace Titanium.Web.Proxy.Http.Responses
{
public class RedirectResponse : Response
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Titanium.Web.Proxy.Models
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Models
{
public class ExternalProxy
{
public string HostName { get; set; }
public int Port { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// Used to pass in Session object for ServerCertificateValidation Callback
/// </summary>
internal class CustomSslStream : SslStream
{
/// <summary>
/// Holds the current session
/// </summary>
internal SessionEventArgs Session { get; set; }
public CustomSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback)
:base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback)
{
}
}
}
......@@ -3,30 +3,33 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.IO;
using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using System.Threading;
using System.Security.Authentication;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
using System.Security.Cryptography.X509Certificates;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Network
{
public class TcpConnection
{
public string HostName { get; set; }
public int port { get; set; }
public bool IsSecure { get; set; }
internal string HostName { get; set; }
internal int port { get; set; }
internal bool IsSecure { get; set; }
internal Version Version { get; set; }
public TcpClient TcpClient { get; set; }
public CustomBinaryReader ServerStreamReader { get; set; }
public Stream Stream { get; set; }
internal TcpClient TcpClient { get; set; }
internal CustomBinaryReader ServerStreamReader { get; set; }
internal Stream Stream { get; set; }
public DateTime LastAccess { get; set; }
internal DateTime LastAccess { get; set; }
public TcpConnection()
internal TcpConnection()
{
LastAccess = DateTime.Now;
}
......@@ -36,14 +39,15 @@ namespace Titanium.Web.Proxy.Network
{
static List<TcpConnection> ConnectionCache = new List<TcpConnection>();
public static TcpConnection GetClient(string Hostname, int port, bool IsSecure)
internal static TcpConnection GetClient(SessionEventArgs sessionArgs, string hostname, int port, bool isSecure, Version version)
{
TcpConnection cached = null;
while (true)
{
lock (ConnectionCache)
{
cached = ConnectionCache.FirstOrDefault(x => x.HostName == Hostname && x.port == port && x.IsSecure == IsSecure && x.TcpClient.Connected);
cached = ConnectionCache.FirstOrDefault(x => x.HostName == hostname && x.port == port &&
x.IsSecure == isSecure && x.TcpClient.Connected && x.Version.Equals(version));
if (cached != null)
ConnectionCache.Remove(cached);
......@@ -57,28 +61,58 @@ namespace Titanium.Web.Proxy.Network
}
if (cached == null)
cached = CreateClient(Hostname, port, IsSecure);
cached = CreateClient(sessionArgs,hostname, port, isSecure, version);
if (ConnectionCache.Where(x => x.HostName == Hostname && x.port == port && x.IsSecure == IsSecure && x.TcpClient.Connected).Count() < 2)
if (ConnectionCache.Where(x => x.HostName == hostname && x.port == port &&
x.IsSecure == isSecure && x.TcpClient.Connected && x.Version.Equals(version)).Count() < 2)
{
Task.Factory.StartNew(() => ReleaseClient(CreateClient(Hostname, port, IsSecure)));
Task.Factory.StartNew(() => CreateClient(sessionArgs, hostname, port, isSecure, version));
}
return cached;
}
private static TcpConnection CreateClient(string Hostname, int port, bool IsSecure)
private static TcpConnection CreateClient(SessionEventArgs sessionArgs, string hostname, int port, bool isSecure, Version version)
{
var client = new TcpClient(Hostname, port);
var stream = (Stream)client.GetStream();
TcpClient client;
Stream stream;
if (IsSecure)
if (isSecure)
{
var sslStream = (SslStream)null;
CustomSslStream sslStream = null;
if(ProxyServer.UpStreamHttpsProxy!=null)
{
client = new TcpClient(ProxyServer.UpStreamHttpsProxy.HostName, ProxyServer.UpStreamHttpsProxy.Port);
stream = (Stream)client.GetStream();
var writer = new StreamWriter(stream);
writer.WriteLine(string.Format("CONNECT {0}:{1} {2}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port, sessionArgs.WebSession.Request.HttpVersion));
writer.WriteLine(string.Format("Host: {0}:{1}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port));
writer.WriteLine("Connection: Keep-Alive");
writer.WriteLine();
writer.Flush();
var reader = new CustomBinaryReader(stream, Encoding.ASCII);
var result = reader.ReadLine();
if (!result.ToLower().Contains("200 connection established"))
throw new Exception("Upstream proxy failed to create a secure tunnel");
reader.ReadAllLines();
}
else
{
client = new TcpClient(hostname, port);
stream = (Stream)client.GetStream();
}
try
{
sslStream = new SslStream(stream);
sslStream.AuthenticateAsClient(Hostname, null, ProxyServer.SupportedProtocols , false);
sslStream = new CustomSslStream(stream, true, ProxyServer.ValidateServerCertificate);
sslStream.Session = sessionArgs;
sslStream.AuthenticateAsClient(hostname, null, Constants.SupportedProtocols, false);
stream = (Stream)sslStream;
}
catch
......@@ -88,25 +122,40 @@ namespace Titanium.Web.Proxy.Network
throw;
}
}
else
{
if (ProxyServer.UpStreamHttpProxy != null)
{
client = new TcpClient(ProxyServer.UpStreamHttpProxy.HostName, ProxyServer.UpStreamHttpProxy.Port);
stream = (Stream)client.GetStream();
}
else
{
client = new TcpClient(hostname, port);
stream = (Stream)client.GetStream();
}
}
return new TcpConnection()
{
HostName = Hostname,
HostName = hostname,
port = port,
IsSecure = IsSecure,
IsSecure = isSecure,
TcpClient = client,
ServerStreamReader = new CustomBinaryReader(stream, Encoding.ASCII),
Stream = stream
Stream = stream,
Version = version
};
}
public static void ReleaseClient(TcpConnection Connection)
internal static void ReleaseClient(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ConnectionCache.Add(Connection);
}
public static void ClearIdleConnections()
internal static void ClearIdleConnections()
{
while (true)
{
......
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using System.Linq;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
namespace Titanium.Web.Proxy
{
......@@ -21,36 +18,15 @@ namespace Titanium.Web.Proxy
/// </summary>
public partial class ProxyServer
{
private static readonly char[] SemiSplit = { ';' };
private static readonly string[] ColonSpaceSplit = { ": " };
private static readonly char[] SpaceSplit = { ' ' };
private static readonly Regex CookieSplitRegEx = new Regex(@",(?! )");
private static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(Environment.NewLine);
private static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
public static readonly int BUFFER_SIZE = 8192;
#if NET45
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
#else
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Ssl3;
#endif
static ProxyServer()
{
ProxyEndPoints = new List<ProxyEndPoint>();
Initialize();
}
private static CertificateManager CertManager { get; set; }
private static bool EnableSsl { get; set; }
private static bool certTrusted { get; set; }
private static bool proxyRunning { get; set; }
......@@ -61,6 +37,22 @@ namespace Titanium.Web.Proxy
public static event EventHandler<SessionEventArgs> BeforeRequest;
public static event EventHandler<SessionEventArgs> BeforeResponse;
/// <summary>
/// External proxy for Http
/// </summary>
public static ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary>
/// External proxy for Http
/// </summary>
public static ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary>
public static event EventHandler<CertificateValidationEventArgs> RemoteCertificateValidationCallback;
public static List<ProxyEndPoint> ProxyEndPoints { get; set; }
public static void Initialize()
......@@ -162,10 +154,7 @@ namespace Titanium.Web.Proxy
CertManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName);
EnableSsl = ProxyEndPoints.Any(x => x.EnableSsl);
if (EnableSsl)
certTrusted = CertManager.CreateTrustedRootCertificate();
certTrusted = CertManager.CreateTrustedRootCertificate();
foreach (var endPoint in ProxyEndPoints)
{
......@@ -228,7 +217,7 @@ namespace Titanium.Web.Proxy
private static void OnAcceptConnection(IAsyncResult asyn)
{
var endPoint = (ProxyEndPoint)asyn.AsyncState;
try
{
var client = endPoint.listener.EndAcceptTcpClient(asyn);
......@@ -245,8 +234,52 @@ namespace Titanium.Web.Proxy
// ignored
}
}
/// <summary>
/// Call back to override server certificate validation
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
var param = sender as CustomSslStream;
if (RemoteCertificateValidationCallback != null)
{
var args = new CertificateValidationEventArgs();
args.Session = param.Session;
args.Certificate = certificate;
args.Chain = chain;
args.SslPolicyErrors = sslPolicyErrors;
RemoteCertificateValidationCallback.Invoke(null, args);
if(!args.IsValid)
{
param.Session.WebSession.Request.CancelRequest = true;
}
return args.IsValid;
}
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
//By default
//do not allow this client to communicate with unauthenticated servers.
return false;
}
}
}
\ No newline at end of file
This diff is collapsed.
......@@ -3,16 +3,13 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy
{
......@@ -21,57 +18,57 @@ namespace Titanium.Web.Proxy
//Called asynchronously when a request was successfully and we received the response
public static void HandleHttpSessionResponse(SessionEventArgs args)
{
args.ProxySession.ReceiveResponse();
args.WebSession.ReceiveResponse();
try
{
if (!args.ProxySession.Response.ResponseBodyRead)
args.ProxySession.Response.ResponseStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream;
if (!args.WebSession.Response.ResponseBodyRead)
args.WebSession.Response.ResponseStream = args.WebSession.ProxyClient.ServerStreamReader.BaseStream;
if (BeforeResponse != null && !args.ProxySession.Response.ResponseLocked)
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{
BeforeResponse(null, args);
}
args.ProxySession.Response.ResponseLocked = true;
args.WebSession.Response.ResponseLocked = true;
if (args.ProxySession.Response.Is100Continue)
if (args.WebSession.Response.Is100Continue)
{
WriteResponseStatus(args.ProxySession.Response.HttpVersion, "100",
WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.Client.ClientStreamWriter);
args.Client.ClientStreamWriter.WriteLine();
}
else if (args.ProxySession.Response.ExpectationFailed)
else if (args.WebSession.Response.ExpectationFailed)
{
WriteResponseStatus(args.ProxySession.Response.HttpVersion, "417",
WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.Client.ClientStreamWriter);
args.Client.ClientStreamWriter.WriteLine();
}
WriteResponseStatus(args.ProxySession.Response.HttpVersion, args.ProxySession.Response.ResponseStatusCode,
args.ProxySession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter);
WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode,
args.WebSession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter);
if (args.ProxySession.Response.ResponseBodyRead)
if (args.WebSession.Response.ResponseBodyRead)
{
var isChunked = args.ProxySession.Response.IsChunked;
var contentEncoding = args.ProxySession.Response.ContentEncoding;
var isChunked = args.WebSession.Response.IsChunked;
var contentEncoding = args.WebSession.Response.ContentEncoding;
if (contentEncoding != null)
{
args.ProxySession.Response.ResponseBody = GetCompressedResponseBody(contentEncoding, args.ProxySession.Response.ResponseBody);
args.WebSession.Response.ResponseBody = GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody);
}
WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders, args.ProxySession.Response.ResponseBody.Length,
WriteResponseHeaders(args.Client.ClientStreamWriter, args.WebSession.Response.ResponseHeaders, args.WebSession.Response.ResponseBody.Length,
isChunked);
WriteResponseBody(args.Client.ClientStream, args.ProxySession.Response.ResponseBody, isChunked);
WriteResponseBody(args.Client.ClientStream, args.WebSession.Response.ResponseBody, isChunked);
}
else
{
WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders);
WriteResponseHeaders(args.Client.ClientStreamWriter, args.WebSession.Response.ResponseHeaders);
if (args.ProxySession.Response.IsChunked || args.ProxySession.Response.ContentLength > 0)
WriteResponseBody(args.ProxySession.ProxyClient.ServerStreamReader, args.Client.ClientStream, args.ProxySession.Response.IsChunked, args.ProxySession.Response.ContentLength);
if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0)
WriteResponseBody(args.WebSession.ProxyClient.ServerStreamReader, args.Client.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength);
}
args.Client.ClientStream.Flush();
......@@ -173,16 +170,16 @@ namespace Titanium.Web.Proxy
WriteResponseBodyChunked(data, clientStream);
}
private static void WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, int BodyLength)
private static void WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, long ContentLength)
{
if (!isChunked)
{
int bytesToRead = BUFFER_SIZE;
int bytesToRead = Constants.BUFFER_SIZE;
if (BodyLength < BUFFER_SIZE)
bytesToRead = BodyLength;
if (ContentLength < Constants.BUFFER_SIZE)
bytesToRead = (int)ContentLength;
var buffer = new byte[BUFFER_SIZE];
var buffer = new byte[Constants.BUFFER_SIZE];
var bytesRead = 0;
var totalBytesRead = 0;
......@@ -192,12 +189,12 @@ namespace Titanium.Web.Proxy
outStream.Write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == BodyLength)
if (totalBytesRead == ContentLength)
break;
bytesRead = 0;
var remainingBytes = (BodyLength - totalBytesRead);
bytesToRead = remainingBytes > BUFFER_SIZE ? BUFFER_SIZE : remainingBytes;
var remainingBytes = (ContentLength - totalBytesRead);
bytesToRead = remainingBytes > (long)Constants.BUFFER_SIZE ? Constants.BUFFER_SIZE : (int)remainingBytes;
}
}
else
......@@ -219,22 +216,20 @@ namespace Titanium.Web.Proxy
var chunkHead = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length);
outStream.Write(buffer, 0, chunkSize);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length);
inStreamReader.ReadLine();
}
else
{
inStreamReader.ReadLine();
outStream.Write(ChunkEnd, 0, ChunkEnd.Length);
outStream.Write(Constants.ChunkEnd, 0, Constants.ChunkEnd.Length);
break;
}
}
}
private static void WriteResponseBodyChunked(byte[] data, Stream outStream)
......@@ -242,11 +237,11 @@ namespace Titanium.Web.Proxy
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length);
outStream.Write(data, 0, data.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length);
outStream.Write(ChunkEnd, 0, ChunkEnd.Length);
outStream.Write(Constants.ChunkEnd, 0, Constants.ChunkEnd.Length);
}
......
using System;
using System.Security.Authentication;
using System.Text;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Shared
{
/// <summary>
/// Literals shared by Proxy Server
/// </summary>
internal class Constants
{
public static readonly int BUFFER_SIZE = 8192;
internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(Environment.NewLine);
internal static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
}
}
......@@ -60,6 +60,7 @@
<Compile Include="Decompression\GZipDecompression.cs" />
<Compile Include="Decompression\IDecompression.cs" />
<Compile Include="Decompression\ZlibDecompression.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="EventArguments\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" />
......@@ -69,9 +70,13 @@
<Compile Include="Helpers\SystemProxy.cs" />
<Compile Include="Models\EndPoint.cs" />
<Compile Include="Extensions\TcpExtensions.cs" />
<Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" />
<Compile Include="Models\ExternalProxy.cs" />
<Compile Include="Network\CustomSslStream.cs" />
<Compile Include="Network\TcpConnectionManager.cs" />
<Compile Include="Models\HttpHeader.cs" />
<Compile Include="Network\HttpWebClient.cs" />
<Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RequestHandler.cs" />
<Compile Include="ResponseHandler.cs" />
......@@ -80,8 +85,9 @@
<Compile Include="EventArguments\SessionEventArgs.cs" />
<Compile Include="Helpers\Tcp.cs" />
<Compile Include="Extensions\StreamExtensions.cs" />
<Compile Include="Responses\OkResponse.cs" />
<Compile Include="Responses\RedirectResponse.cs" />
<Compile Include="Http\Responses\OkResponse.cs" />
<Compile Include="Http\Responses\RedirectResponse.cs" />
<Compile Include="Shared\Constants.cs" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
......
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