Commit f5a6fe91 authored by Jehonathan's avatar Jehonathan Committed by GitHub

Merge pull request #254 from justcoding121/beta

Stable
parents 2f89234f 8063d024
...@@ -13,7 +13,10 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -13,7 +13,10 @@ namespace Titanium.Web.Proxy.Examples.Basic
private readonly ProxyServer proxyServer; private readonly ProxyServer proxyServer;
//share requestBody outside handlers //share requestBody outside handlers
private readonly Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>(); //Using a dictionary is not a good idea since it can cause memory overflow
//ideally the data should be moved out of memory
//private readonly Dictionary<Guid, string> requestBodyHistory
// = new Dictionary<Guid, string>();
public ProxyTestController() public ProxyTestController()
{ {
...@@ -42,6 +45,8 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -42,6 +45,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation; proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection; proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//proxyServer.EnableWinAuth = true;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{ {
//Exclude Https addresses you don't want to proxy //Exclude Https addresses you don't want to proxy
...@@ -113,8 +118,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -113,8 +118,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//read request headers //read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders; var requestHeaders = e.WebSession.Request.RequestHeaders;
var method = e.WebSession.Request.Method.ToUpper(); if (e.WebSession.Request.HasBody)
if (method == "POST" || method == "PUT" || method == "PATCH")
{ {
//Get/Set request body bytes //Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody(); byte[] bodyBytes = await e.GetRequestBody();
...@@ -124,7 +128,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -124,7 +128,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
string bodyString = await e.GetRequestBodyAsString(); string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString); await e.SetRequestBodyString(bodyString);
requestBodyHistory[e.Id] = bodyString; //requestBodyHistory[e.Id] = bodyString;
} }
////To cancel a request with a custom HTML content ////To cancel a request with a custom HTML content
...@@ -152,11 +156,11 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -152,11 +156,11 @@ namespace Titanium.Web.Proxy.Examples.Basic
{ {
Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount); Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
if (requestBodyHistory.ContainsKey(e.Id)) //if (requestBodyHistory.ContainsKey(e.Id))
{ //{
//access request body by looking up the shared dictionary using requestId // //access request body by looking up the shared dictionary using requestId
var requestBody = requestBodyHistory[e.Id]; // var requestBody = requestBodyHistory[e.Id];
} //}
//read response headers //read response headers
var responseHeaders = e.WebSession.Response.ResponseHeaders; var responseHeaders = e.WebSession.Response.ResponseHeaders;
......
...@@ -18,6 +18,7 @@ Features ...@@ -18,6 +18,7 @@ Features
* Support mutual SSL authentication * Support mutual SSL authentication
* Fully asynchronous proxy * Fully asynchronous proxy
* Supports proxy authentication & automatic proxy detection * Supports proxy authentication & automatic proxy detection
* Kerberos/NTLM authentication over HTTP protocols for windows domain
Usage Usage
===== =====
...@@ -203,7 +204,6 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs ...@@ -203,7 +204,6 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
``` ```
Future road map (Pull requests are welcome!) Future road map (Pull requests are welcome!)
============ ============
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
* Support Server Name Indication (SNI) for transparent endpoints * Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0 * Support HTTP 2.0
* Support SOCKS protocol * Support SOCKS protocol
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
...@@ -59,6 +60,7 @@ ...@@ -59,6 +60,7 @@
<Compile Include="CertificateManagerTests.cs" /> <Compile Include="CertificateManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyServerTests.cs" /> <Compile Include="ProxyServerTests.cs" />
<Compile Include="WinAuthTests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj"> <ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
......
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using Titanium.Web.Proxy.Network.WinAuth;
namespace Titanium.Web.Proxy.UnitTests
{
[TestClass]
public class WinAuthTests
{
[TestMethod]
public void Test_Acquire_Client_Token()
{
var token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com", "NTLM", Guid.NewGuid());
Assert.IsTrue(token.Length > 1);
}
}
}
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
...@@ -14,17 +15,23 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -14,17 +15,23 @@ namespace Titanium.Web.Proxy.Decompression
using (var stream = new MemoryStream(compressedArray)) using (var stream = new MemoryStream(compressedArray))
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress)) using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{ {
var buffer = new byte[bufferSize]; var buffer = BufferPool.GetBuffer(bufferSize);
try
using (var output = new MemoryStream())
{ {
int read; using (var output = new MemoryStream())
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{ {
output.Write(buffer, 0, read); int read;
} while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
return output.ToArray(); return output.ToArray();
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
} }
} }
} }
......
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
...@@ -13,16 +14,23 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -13,16 +14,23 @@ namespace Titanium.Web.Proxy.Decompression
{ {
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress)) using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
{ {
var buffer = new byte[bufferSize]; var buffer = BufferPool.GetBuffer(bufferSize);
using (var output = new MemoryStream()) try
{ {
int read; using (var output = new MemoryStream())
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{ {
output.Write(buffer, 0, read); int read;
} while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
return output.ToArray(); return output.ToArray();
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
} }
} }
} }
......
...@@ -32,6 +32,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -32,6 +32,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
private Func<SessionEventArgs, Task> httpResponseHandler; private Func<SessionEventArgs, Task> httpResponseHandler;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private bool reRequest;
/// <summary> /// <summary>
/// Holds a reference to client /// Holds a reference to client
/// </summary> /// </summary>
...@@ -43,10 +48,24 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -43,10 +48,24 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public Guid Id => WebSession.RequestId; public Guid Id => WebSession.RequestId;
/// <summary> /// <summary>
/// Should we send a rerequest /// Should we send the request again
/// </summary> /// </summary>
public bool ReRequest { get; set; } public bool ReRequest
{
get { return reRequest; }
set
{
if (WebSession.Response.ResponseStatusCode == null)
{
throw new Exception("Response status code is null. Cannot request again a request "
+ "which was never send to server.");
}
reRequest = value;
}
}
/// <summary> /// <summary>
/// Does this session uses SSL /// Does this session uses SSL
...@@ -92,8 +111,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -92,8 +111,7 @@ namespace Titanium.Web.Proxy.EventArguments
private async Task ReadRequestBody() private async Task ReadRequestBody()
{ {
//GET request don't have a request body to read //GET request don't have a request body to read
var method = WebSession.Request.Method.ToUpper(); if (!WebSession.Request.HasBody)
if (method != "POST" && method != "PUT" && method != "PATCH")
{ {
throw new BodyNotFoundException("Request don't have a body. " + throw new BodyNotFoundException("Request don't have a body. " +
"Please verify that this request is a Http POST/PUT/PATCH and request " + "Please verify that this request is a Http POST/PUT/PATCH and request " +
...@@ -117,12 +135,12 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -117,12 +135,12 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Request.ContentLength > 0) if (WebSession.Request.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream, await ProxyClient.ClientStreamReader.CopyBytesToStream(requestBodyStream,
WebSession.Request.ContentLength); WebSession.Request.ContentLength);
} }
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0) else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue);
} }
} }
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding,
...@@ -135,6 +153,18 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -135,6 +153,18 @@ namespace Titanium.Web.Proxy.EventArguments
} }
} }
/// <summary>
/// reinit response object
/// </summary>
internal async Task ClearResponse()
{
//siphon out the body
await ReadResponseBody();
WebSession.Response.Dispose();
WebSession.Response = new Response();
}
/// <summary> /// <summary>
/// Read response body as byte[] for current response /// Read response body as byte[] for current response
/// </summary> /// </summary>
...@@ -155,12 +185,12 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -155,12 +185,12 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Response.ContentLength > 0) if (WebSession.Response.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //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, await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream,
WebSession.Response.ContentLength); WebSession.Response.ContentLength);
} }
else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0 || WebSession.Response.ContentLength == -1) else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0 || WebSession.Response.ContentLength == -1)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue);
} }
} }
...@@ -420,27 +450,29 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -420,27 +450,29 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
} }
/// <summary> /// <summary>
/// Before request is made to server  /// Before request is made to server 
/// Respond with the specified HTML string to client /// Respond with the specified HTML string to client
/// and ignore the request  /// and ignore the request 
/// </summary> /// </summary>
/// <param name="html"></param> /// <param name="html"></param>
/// <param name="status"></param> /// <param name="status"></param>
/// <returns></returns>
public async Task GenericResponse(string html, HttpStatusCode status) public async Task GenericResponse(string html, HttpStatusCode status)
{ {
await GenericResponse(html, null, status); await GenericResponse(html, null, status);
} }
/// <summary> /// <summary>
/// Before request is made to server  /// Before request is made to server 
/// Respond with the specified HTML string to client /// Respond with the specified HTML string to client
/// and the specified status /// and the specified status
/// and ignore the request  /// and ignore the request 
/// </summary> /// </summary>
/// <param name="html"></param> /// <param name="html"></param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <param name="status"></param> /// <param name="status"></param>
/// <returns></returns>
public async Task GenericResponse(string html, Dictionary<string, HttpHeader> headers, HttpStatusCode status) public async Task GenericResponse(string html, Dictionary<string, HttpHeader> headers, HttpStatusCode status)
{ {
if (WebSession.Request.RequestLocked) if (WebSession.Request.RequestLocked)
......
using System; using System;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
...@@ -17,33 +17,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -17,33 +17,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <returns></returns> /// <returns></returns>
internal static Encoding GetEncoding(this Request request) internal static Encoding GetEncoding(this Request request)
{ {
try return HttpHelper.GetEncodingFromContentType(request.ContentType);
{
//return default if not specified
if (request.ContentType == null)
{
return Encoding.GetEncoding("ISO-8859-1");
}
//extract the encoding by finding the charset
var contentTypes = request.ContentType.Split(ProxyConstants.SemiColonSplit);
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
return Encoding.GetEncoding(encodingSplit[1]);
}
}
}
catch
{
//parsing errors
// ignored
}
//return default if not specified
return Encoding.GetEncoding("ISO-8859-1");
} }
} }
} }
using System; using System;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
...@@ -14,33 +14,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -14,33 +14,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <returns></returns> /// <returns></returns>
internal static Encoding GetResponseCharacterEncoding(this Response response) internal static Encoding GetResponseCharacterEncoding(this Response response)
{ {
try return HttpHelper.GetEncodingFromContentType(response.ContentType);
{
//return default if not specified
if (response.ContentType == null)
{
return Encoding.GetEncoding("ISO-8859-1");
}
//extract the encoding by finding the charset
var contentTypes = response.ContentType.Split(ProxyConstants.SemiColonSplit);
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
return Encoding.GetEncoding(encodingSplit[1]);
}
}
}
catch
{
//parsing errors
// ignored
}
//return default if not specified
return Encoding.GetEncoding("ISO-8859-1");
} }
} }
} }
...@@ -34,34 +34,31 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -34,34 +34,31 @@ namespace Titanium.Web.Proxy.Extensions
/// copies the specified bytes to the stream from the input stream /// copies the specified bytes to the stream from the input stream
/// </summary> /// </summary>
/// <param name="streamReader"></param> /// <param name="streamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="stream"></param> /// <param name="stream"></param>
/// <param name="totalBytesToRead"></param> /// <param name="totalBytesToRead"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, int bufferSize, Stream stream, long totalBytesToRead) internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead)
{ {
var totalbytesRead = 0; byte[] buffer = streamReader.Buffer;
long remainingBytes = totalBytesToRead;
long bytesToRead = totalBytesToRead < bufferSize ? totalBytesToRead : bufferSize;
while (totalbytesRead < totalBytesToRead) while (remainingBytes > 0)
{ {
var buffer = await streamReader.ReadBytesAsync(bytesToRead); int bytesToRead = buffer.Length;
if (remainingBytes < bytesToRead)
if (buffer.Length == 0)
{ {
break; bytesToRead = (int)remainingBytes;
} }
totalbytesRead += buffer.Length; int bytesRead = await streamReader.ReadBytesAsync(buffer, bytesToRead);
if (bytesRead == 0)
var remainingBytes = totalBytesToRead - totalbytesRead;
if (remainingBytes < bytesToRead)
{ {
bytesToRead = remainingBytes; break;
} }
await stream.WriteAsync(buffer, 0, buffer.Length); remainingBytes -= bytesRead;
await stream.WriteAsync(buffer, 0, bytesRead);
} }
} }
...@@ -80,8 +77,8 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -80,8 +77,8 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0) if (chunkSize != 0)
{ {
var buffer = await clientStreamReader.ReadBytesAsync(chunkSize); await CopyBytesToStream(clientStreamReader, stream, chunkSize);
await stream.WriteAsync(buffer, 0, buffer.Length);
//chunk trail //chunk trail
await clientStreamReader.ReadLineAsync(); await clientStreamReader.ReadLineAsync();
} }
...@@ -132,30 +129,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -132,30 +129,7 @@ namespace Titanium.Web.Proxy.Extensions
contentLength = long.MaxValue; contentLength = long.MaxValue;
} }
int bytesToRead = bufferSize; await CopyBytesToStream(inStreamReader, outStream, contentLength);
if (contentLength < bufferSize)
{
bytesToRead = (int)contentLength;
}
var buffer = new byte[bufferSize];
var bytesRead = 0;
var totalBytesRead = 0;
while ((bytesRead += await inStreamReader.BaseStream.ReadAsync(buffer, 0, bytesToRead)) > 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == contentLength)
break;
bytesRead = 0;
var remainingBytes = contentLength - totalBytesRead;
bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
}
} }
else else
{ {
...@@ -178,14 +152,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -178,14 +152,11 @@ namespace Titanium.Web.Proxy.Extensions
if (chunkSize != 0) if (chunkSize != 0)
{ {
var buffer = await inStreamReader.ReadBytesAsync(chunkSize);
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2")); var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
await outStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length); await outStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length); await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await outStream.WriteAsync(buffer, 0, chunkSize); await CopyBytesToStream(inStreamReader, outStream, chunkSize);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length); await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await inStreamReader.ReadLineAsync(); await inStreamReader.ReadLineAsync();
......
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
internal static class BufferPool
{
private static readonly ConcurrentQueue<byte[]> buffers = new ConcurrentQueue<byte[]>();
internal static byte[] GetBuffer(int bufferSize)
{
byte[] buffer;
if (!buffers.TryDequeue(out buffer) || buffer.Length != bufferSize)
{
buffer = new byte[bufferSize];
}
return buffer;
}
internal static void ReturnBuffer(byte[] buffer)
{
if (buffer != null)
{
buffers.Enqueue(buffer);
}
}
}
}
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -16,30 +14,22 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -16,30 +14,22 @@ namespace Titanium.Web.Proxy.Helpers
{ {
private readonly CustomBufferedStream stream; private readonly CustomBufferedStream stream;
private readonly int bufferSize; private readonly int bufferSize;
private readonly byte[] staticBuffer;
private readonly Encoding encoding; private readonly Encoding encoding;
private static readonly ConcurrentQueue<byte[]> buffers
= new ConcurrentQueue<byte[]>();
private volatile bool disposed; private volatile bool disposed;
internal byte[] Buffer { get; }
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize) internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{ {
this.stream = stream; this.stream = stream;
if (!buffers.TryDequeue(out staticBuffer) || staticBuffer.Length != bufferSize) Buffer = BufferPool.GetBuffer(bufferSize);
{
staticBuffer = new byte[bufferSize];
}
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
//default to UTF-8 //default to UTF-8
encoding = Encoding.UTF8; encoding = Encoding.UTF8;
} }
internal Stream BaseStream => stream;
/// <summary> /// <summary>
/// Read a line from the byte stream /// Read a line from the byte stream
/// </summary> /// </summary>
...@@ -51,7 +41,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -51,7 +41,7 @@ namespace Titanium.Web.Proxy.Helpers
int bufferDataLength = 0; int bufferDataLength = 0;
// try to use the thread static buffer, usually it is enough // try to use the thread static buffer, usually it is enough
var buffer = staticBuffer; var buffer = Buffer;
while (stream.DataAvailable || await stream.FillBufferAsync()) while (stream.DataAvailable || await stream.FillBufferAsync())
{ {
...@@ -63,6 +53,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -63,6 +53,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
return encoding.GetString(buffer, 0, bufferDataLength - 1); return encoding.GetString(buffer, 0, bufferDataLength - 1);
} }
//end of stream //end of stream
if (newChar == '\0') if (newChar == '\0')
{ {
...@@ -80,6 +71,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -80,6 +71,11 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
if (bufferDataLength == 0)
{
return null;
}
return encoding.GetString(buffer, 0, bufferDataLength); return encoding.GetString(buffer, 0, bufferDataLength);
} }
...@@ -95,6 +91,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -95,6 +91,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
requestLines.Add(tmpLine); requestLines.Add(tmpLine);
} }
return requestLines; return requestLines;
} }
...@@ -110,49 +107,14 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -110,49 +107,14 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Read the specified number of raw bytes from the base stream /// Read the specified number (or less) of raw bytes from the base stream to the given buffer
/// </summary> /// </summary>
/// <param name="totalBytesToRead"></param> /// <param name="buffer"></param>
/// <returns></returns> /// <param name="bytesToRead"></param>
internal async Task<byte[]> ReadBytesAsync(long totalBytesToRead) /// <returns>The number of bytes read</returns>
internal Task<int> ReadBytesAsync(byte[] buffer, int bytesToRead)
{ {
int bytesToRead = bufferSize; return stream.ReadAsync(buffer, 0, bytesToRead);
var buffer = staticBuffer;
if (totalBytesToRead < bufferSize)
{
bytesToRead = (int)totalBytesToRead;
buffer = new byte[bytesToRead];
}
int bytesRead;
var totalBytesRead = 0;
while ((bytesRead = await stream.ReadAsync(buffer, totalBytesRead, bytesToRead)) > 0)
{
totalBytesRead += bytesRead;
if (totalBytesRead == totalBytesToRead)
break;
var remainingBytes = totalBytesToRead - totalBytesRead;
bytesToRead = Math.Min(bufferSize, (int)remainingBytes);
if (totalBytesRead + bytesToRead > buffer.Length)
{
ResizeBuffer(ref buffer, Math.Min(totalBytesToRead, buffer.Length * 2));
}
}
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() public void Dispose()
...@@ -160,7 +122,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -160,7 +122,7 @@ namespace Titanium.Web.Proxy.Helpers
if (!disposed) if (!disposed)
{ {
disposed = true; disposed = true;
buffers.Enqueue(staticBuffer); BufferPool.ReturnBuffer(Buffer);
} }
} }
...@@ -172,7 +134,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -172,7 +134,7 @@ namespace Titanium.Web.Proxy.Helpers
private void ResizeBuffer(ref byte[] buffer, long size) private void ResizeBuffer(ref byte[] buffer, long size)
{ {
var newBuffer = new byte[size]; var newBuffer = new byte[size];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length); System.Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
buffer = newBuffer; buffer = newBuffer;
} }
} }
......
...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
private readonly Stream baseStream; private readonly Stream baseStream;
private readonly byte[] streamBuffer; private byte[] streamBuffer;
private int bufferLength; private int bufferLength;
...@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -30,7 +30,7 @@ namespace Titanium.Web.Proxy.Helpers
public CustomBufferedStream(Stream baseStream, int bufferSize) public CustomBufferedStream(Stream baseStream, int bufferSize)
{ {
this.baseStream = baseStream; this.baseStream = baseStream;
streamBuffer = new byte[bufferSize]; streamBuffer = BufferPool.GetBuffer(bufferSize);
} }
/// <summary> /// <summary>
...@@ -146,14 +146,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -146,14 +146,6 @@ namespace Titanium.Web.Proxy.Helpers
return baseStream.BeginWrite(buffer, offset, count, callback, state); return baseStream.BeginWrite(buffer, offset, count, callback, state);
} }
/// <summary>
/// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. Instead of calling this method, ensure that the stream is properly disposed.
/// </summary>
public override void Close()
{
baseStream.Close();
}
/// <summary> /// <summary>
/// Asynchronously reads the bytes from the current stream and writes them to another stream, using a specified buffer size and cancellation token. /// Asynchronously reads the bytes from the current stream and writes them to another stream, using a specified buffer size and cancellation token.
/// </summary> /// </summary>
...@@ -238,14 +230,24 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -238,14 +230,24 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Asynchronously reads a sequence of bytes from the current stream, advances the position within the stream by the number of bytes read, and monitors cancellation requests. /// Asynchronously reads a sequence of bytes from the current stream,
/// advances the position within the stream by the number of bytes read,
/// and monitors cancellation requests.
/// </summary> /// </summary>
/// <param name="buffer">The buffer to write the data into.</param> /// <param name="buffer">The buffer to write the data into.</param>
/// <param name="offset">The byte offset in <paramref name="buffer" /> at which to begin writing data from the stream.</param> /// <param name="offset">The byte offset in <paramref name="buffer" /> at which
/// to begin writing data from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param> /// <param name="count">The maximum number of bytes to read.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.
/// The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns> /// <returns>
/// A task that represents the asynchronous read operation. The value of the <paramref name="TResult" /> parameter contains the total number of bytes read into the buffer. The result value can be less than the number of bytes requested if the number of bytes currently available is less than the requested number, or it can be 0 (zero) if the end of the stream has been reached. /// A task that represents the asynchronous read operation.
/// The value of the parameter contains the total
/// number of bytes read into the buffer.
/// The result value can be less than the number of bytes
/// requested if the number of bytes currently available is
/// less than the requested number, or it can be 0 (zero)
/// if the end of the stream has been reached.
/// </returns> /// </returns>
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
...@@ -330,9 +332,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -330,9 +332,10 @@ namespace Titanium.Web.Proxy.Helpers
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
baseStream.Dispose(); baseStream.Dispose();
BufferPool.ReturnBuffer(streamBuffer);
streamBuffer = null;
} }
/// <summary> /// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading. /// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
/// </summary> /// </summary>
......
...@@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// Add Firefox settings. /// Add Firefox settings.
/// </summary> /// </summary>
internal void AddFirefox() internal void UseSystemProxy()
{ {
try try
{ {
...@@ -19,48 +19,30 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -19,48 +19,30 @@ namespace Titanium.Web.Proxy.Helpers
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default"); "\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js"; var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (!File.Exists(myFfPrefFile)) return; if (!File.Exists(myFfPrefFile))
// We have a pref file so let''s make sure it has the proxy setting {
var myReader = new StreamReader(myFfPrefFile); return;
var myPrefContents = myReader.ReadToEnd(); }
myReader.Close();
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.
}
}
/// <summary>
/// Remove firefox settings.
/// </summary>
internal void RemoveFirefox()
{
try
{
var myProfileDirectory =
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (!File.Exists(myFfPrefFile)) return;
// We have a pref file so let''s make sure it has the proxy setting // We have a pref file so let''s make sure it has the proxy setting
var myReader = new StreamReader(myFfPrefFile); var myReader = new StreamReader(myFfPrefFile);
var myPrefContents = myReader.ReadToEnd(); var myPrefContents = myReader.ReadToEnd();
myReader.Close(); myReader.Close();
if (!myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);"))
for (int i = 0; i <= 4; i++)
{ {
// Add the proxy enable line and write it back to the file var searchStr = $"user_pref(\"network.proxy.type\", {i});";
myPrefContents = myPrefContents + "\n\r" + "user_pref(\"network.proxy.type\", 0);";
File.Delete(myFfPrefFile); if (myPrefContents.Contains(searchStr))
File.WriteAllText(myFfPrefFile, myPrefContents); {
// Add the proxy enable line and write it back to the file
myPrefContents = myPrefContents.Replace(searchStr,
"user_pref(\"network.proxy.type\", 5);");
}
} }
File.Delete(myFfPrefFile);
File.WriteAllText(myFfPrefFile, myPrefContents);
} }
catch (Exception) catch (Exception)
{ {
......
using System;
using System.Text;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
internal static class HttpHelper
{
private static readonly Encoding defaultEncoding = Encoding.GetEncoding("ISO-8859-1");
public static Encoding GetEncodingFromContentType(string contentType)
{
try
{
//return default if not specified
if (contentType == null)
{
return defaultEncoding;
}
//extract the encoding by finding the charset
var parameters = contentType.Split(ProxyConstants.SemiColonSplit);
foreach (var parameter in parameters)
{
var encodingSplit = parameter.Split(ProxyConstants.EqualSplit, 2);
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
{
string value = encodingSplit[1];
if (value.Equals("x-user-defined", StringComparison.OrdinalIgnoreCase))
{
//todo: what is this?
continue;
}
if (value.Length > 2 && value[0] == '"' && value[value.Length - 1] == '"')
{
value = value.Substring(1, value.Length - 2);
}
return Encoding.GetEncoding(value);
}
}
}
catch
{
//parsing errors
// ignored
}
//return default if not specified
return defaultEncoding;
}
/// <summary>
/// Tries to get root domain from a given hostname
/// Adapted from below answer
/// https://stackoverflow.com/questions/16473838/get-domain-name-of-a-url-in-c-sharp-net
/// </summary>
/// <param name="hostname"></param>
/// <returns></returns>
internal static string GetWildCardDomainName(string hostname)
{
//only for subdomains we need wild card
//example www.google.com or gstatic.google.com
//but NOT for google.com
if (hostname.Split(ProxyConstants.DotSplit).Length > 2)
{
int idx = hostname.IndexOf(ProxyConstants.DotSplit);
var rootDomain = hostname.Substring(idx + 1);
return "*." + rootDomain;
}
//return as it is
return hostname;
}
}
}
...@@ -8,12 +8,15 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -8,12 +8,15 @@ namespace Titanium.Web.Proxy.Helpers
internal class RunTime internal class RunTime
{ {
/// <summary> /// <summary>
/// Checks if current run time is Mono /// cache for mono runtime check
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal static bool IsRunningOnMono() private static Lazy<bool> isRunningOnMono
{ = new Lazy<bool>(()=> Type.GetType("Mono.Runtime") != null);
return Type.GetType("Mono.Runtime") != null;
} /// <summary>
/// Is running on Mono?
/// </summary>
internal static bool IsRunningOnMono => isRunningOnMono.Value;
} }
} }
...@@ -179,12 +179,14 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -179,12 +179,14 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param> /// <param name="tcpConnectionFactory"></param>
/// <param name="connection"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRaw(ProxyServer server, internal static async Task SendRaw(ProxyServer server,
string remoteHostName, int remotePort, string remoteHostName, int remotePort,
string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps, bool isHttps,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory) Stream clientStream, TcpConnectionFactory tcpConnectionFactory,
TcpConnection connection = null)
{ {
//prepare the prefix content //prepare the prefix content
StringBuilder sb = null; StringBuilder sb = null;
...@@ -210,10 +212,23 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -210,10 +212,23 @@ namespace Titanium.Web.Proxy.Helpers
sb.Append(ProxyConstants.NewLine); sb.Append(ProxyConstants.NewLine);
} }
var tcpConnection = await tcpConnectionFactory.CreateClient(server, bool connectionCreated = false;
remoteHostName, remotePort, TcpConnection tcpConnection;
httpVersion, isHttps,
null, null, clientStream); //create new connection if connection is null
if (connection == null)
{
tcpConnection = await tcpConnectionFactory.CreateClient(server,
remoteHostName, remotePort,
httpVersion, isHttps,
null, null);
connectionCreated = true;
}
else
{
tcpConnection = connection;
}
try try
{ {
...@@ -228,8 +243,14 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -228,8 +243,14 @@ namespace Titanium.Web.Proxy.Helpers
} }
finally finally
{ {
tcpConnection.Dispose(); //if connection was null
Interlocked.Decrement(ref server.serverConnectionCount); //then a new connection was created
//so dispose the new connection
if (connectionCreated)
{
tcpConnection.Dispose();
Interlocked.Decrement(ref server.serverConnectionCount);
}
} }
} }
} }
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -160,7 +161,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -160,7 +161,13 @@ namespace Titanium.Web.Proxy.Http
//return if this is already read //return if this is already read
if (Response.ResponseStatusCode != null) return; if (Response.ResponseStatusCode != null) return;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); string line = await ServerConnection.StreamReader.ReadLineAsync();
if (line == null)
{
throw new IOException();
}
var httpResult = line.Split(ProxyConstants.SpaceSplit, 3);
if (string.IsNullOrEmpty(httpResult[0])) if (string.IsNullOrEmpty(httpResult[0]))
{ {
...@@ -168,7 +175,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -168,7 +175,7 @@ namespace Titanium.Web.Proxy.Http
httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
} }
var httpVersion = httpResult[0].Trim().ToLower(); var httpVersion = httpResult[0];
var version = HttpHeader.Version11; var version = HttpHeader.Version11;
if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase)) if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
......
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
...@@ -26,6 +27,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -26,6 +27,11 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public Version HttpVersion { get; set; } public Version HttpVersion { get; set; }
/// <summary>
/// Has request body?
/// </summary>
public bool HasBody => Method == "POST" || Method == "PUT" || Method == "PATCH";
/// <summary> /// <summary>
/// Request Http hostanem /// Request Http hostanem
/// </summary> /// </summary>
...@@ -242,8 +248,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -242,8 +248,14 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
internal string RequestBodyString { get; set; } internal string RequestBodyString { get; set; }
/// <summary>
/// Request body was read by user?
/// </summary>
internal bool RequestBodyRead { get; set; } internal bool RequestBodyRead { get; set; }
/// <summary>
/// Request is ready to be sent (user callbacks are complete?)
/// </summary>
internal bool RequestLocked { get; set; } internal bool RequestLocked { get; set; }
/// <summary> /// <summary>
...@@ -295,6 +307,143 @@ namespace Titanium.Web.Proxy.Http ...@@ -295,6 +307,143 @@ namespace Titanium.Web.Proxy.Http
NonUniqueRequestHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase); NonUniqueRequestHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
} }
/// <summary>
/// True if header exists
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool HeaderExists(string name)
{
if (RequestHeaders.ContainsKey(name)
|| NonUniqueRequestHeaders.ContainsKey(name))
{
return true;
}
return false;
}
/// <summary>
/// Returns all headers with given name if exists
/// Returns null if does'nt exist
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public List<HttpHeader> GetHeaders(string name)
{
if (RequestHeaders.ContainsKey(name))
{
return new List<HttpHeader>() { RequestHeaders[name] };
}
else if (NonUniqueRequestHeaders.ContainsKey(name))
{
return new List<HttpHeader>(NonUniqueRequestHeaders[name]);
}
return null;
}
/// <summary>
/// Returns all headers
/// </summary>
/// <returns></returns>
public List<HttpHeader> GetAllHeaders()
{
var result = new List<HttpHeader>();
result.AddRange(RequestHeaders.Select(x => x.Value));
result.AddRange(NonUniqueRequestHeaders.SelectMany(x => x.Value));
return result;
}
/// <summary>
/// Add a new header with given name and value
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void AddHeader(string name, string value)
{
AddHeader(new HttpHeader(name, value));
}
/// <summary>
/// Adds the given header object to Request
/// </summary>
/// <param name="newHeader"></param>
public void AddHeader(HttpHeader newHeader)
{
if (NonUniqueRequestHeaders.ContainsKey(newHeader.Name))
{
NonUniqueRequestHeaders[newHeader.Name].Add(newHeader);
return;
}
if (RequestHeaders.ContainsKey(newHeader.Name))
{
var existing = RequestHeaders[newHeader.Name];
RequestHeaders.Remove(newHeader.Name);
NonUniqueRequestHeaders.Add(newHeader.Name,
new List<HttpHeader>() { existing, newHeader });
}
else
{
RequestHeaders.Add(newHeader.Name, newHeader);
}
}
/// <summary>
/// removes all headers with given name
/// </summary>
/// <param name="headerName"></param>
/// <returns>True if header was removed
/// False if no header exists with given name</returns>
public bool RemoveHeader(string headerName)
{
if (RequestHeaders.ContainsKey(headerName))
{
RequestHeaders.Remove(headerName);
return true;
}
else if (NonUniqueRequestHeaders.ContainsKey(headerName))
{
NonUniqueRequestHeaders.Remove(headerName);
return true;
}
return false;
}
/// <summary>
/// Removes given header object if it exist
/// </summary>
/// <param name="header">Returns true if header exists and was removed </param>
public bool RemoveHeader(HttpHeader header)
{
if (RequestHeaders.ContainsKey(header.Name))
{
if (RequestHeaders[header.Name].Equals(header))
{
RequestHeaders.Remove(header.Name);
return true;
}
}
else if (NonUniqueRequestHeaders.ContainsKey(header.Name))
{
if (NonUniqueRequestHeaders[header.Name]
.RemoveAll(x => x.Equals(header)) > 0)
{
return true;
}
}
return false;
}
/// <summary> /// <summary>
/// Dispose off /// Dispose off
/// </summary> /// </summary>
...@@ -309,5 +458,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -309,5 +458,7 @@ namespace Titanium.Web.Proxy.Http
RequestBody = null; RequestBody = null;
RequestBody = null; RequestBody = null;
} }
} }
} }
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
...@@ -213,7 +214,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -213,7 +214,14 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
internal string ResponseBodyString { get; set; } internal string ResponseBodyString { get; set; }
/// <summary>
/// Was response body read by user
/// </summary>
internal bool ResponseBodyRead { get; set; } internal bool ResponseBodyRead { get; set; }
/// <summary>
/// Is response is no more modifyable by user (user callbacks complete?)
/// </summary>
internal bool ResponseLocked { get; set; } internal bool ResponseLocked { get; set; }
/// <summary> /// <summary>
...@@ -235,6 +243,141 @@ namespace Titanium.Web.Proxy.Http ...@@ -235,6 +243,141 @@ namespace Titanium.Web.Proxy.Http
NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase); NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
} }
/// <summary>
/// True if header exists
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool HeaderExists(string name)
{
if(ResponseHeaders.ContainsKey(name)
|| NonUniqueResponseHeaders.ContainsKey(name))
{
return true;
}
return false;
}
/// <summary>
/// Returns all headers with given name if exists
/// Returns null if does'nt exist
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public List<HttpHeader> GetHeaders(string name)
{
if (ResponseHeaders.ContainsKey(name))
{
return new List<HttpHeader>() { ResponseHeaders[name] };
}
else if (NonUniqueResponseHeaders.ContainsKey(name))
{
return new List<HttpHeader>(NonUniqueResponseHeaders[name]);
}
return null;
}
/// <summary>
/// Returns all headers
/// </summary>
/// <returns></returns>
public List<HttpHeader> GetAllHeaders()
{
var result = new List<HttpHeader>();
result.AddRange(ResponseHeaders.Select(x => x.Value));
result.AddRange(NonUniqueResponseHeaders.SelectMany(x => x.Value));
return result;
}
/// <summary>
/// Add a new header with given name and value
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void AddHeader(string name, string value)
{
AddHeader(new HttpHeader(name, value));
}
/// <summary>
/// Adds the given header object to Response
/// </summary>
/// <param name="newHeader"></param>
public void AddHeader(HttpHeader newHeader)
{
if (NonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
NonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
return;
}
if (ResponseHeaders.ContainsKey(newHeader.Name))
{
var existing = ResponseHeaders[newHeader.Name];
ResponseHeaders.Remove(newHeader.Name);
NonUniqueResponseHeaders.Add(newHeader.Name,
new List<HttpHeader>() { existing, newHeader });
}
else
{
ResponseHeaders.Add(newHeader.Name, newHeader);
}
}
/// <summary>
/// removes all headers with given name
/// </summary>
/// <param name="headerName"></param>
/// <returns>True if header was removed
/// False if no header exists with given name</returns>
public bool RemoveHeader(string headerName)
{
if(ResponseHeaders.ContainsKey(headerName))
{
ResponseHeaders.Remove(headerName);
return true;
}
else if (NonUniqueResponseHeaders.ContainsKey(headerName))
{
NonUniqueResponseHeaders.Remove(headerName);
return true;
}
return false;
}
/// <summary>
/// Removes given header object if it exist
/// </summary>
/// <param name="header">Returns true if header exists and was removed </param>
public bool RemoveHeader(HttpHeader header)
{
if (ResponseHeaders.ContainsKey(header.Name))
{
if (ResponseHeaders[header.Name].Equals(header))
{
ResponseHeaders.Remove(header.Name);
return true;
}
}
else if (NonUniqueResponseHeaders.ContainsKey(header.Name))
{
if (NonUniqueResponseHeaders[header.Name]
.RemoveAll(x => x.Equals(header)) > 0)
{
return true;
}
}
return false;
}
/// <summary> /// <summary>
/// Dispose off /// Dispose off
/// </summary> /// </summary>
......
...@@ -26,13 +26,19 @@ namespace Titanium.Web.Proxy.Models ...@@ -26,13 +26,19 @@ namespace Titanium.Web.Proxy.Models
EnableSsl = enableSsl; EnableSsl = enableSsl;
} }
/// <summary>
/// underlying TCP Listener object
/// </summary>
internal TcpListener Listener { get; set; }
/// <summary> /// <summary>
/// Ip Address. /// Ip Address we are listening.
/// </summary> /// </summary>
public IPAddress IpAddress { get; internal set; } public IPAddress IpAddress { get; internal set; }
/// <summary> /// <summary>
/// Port. /// Port we are listening.
/// </summary> /// </summary>
public int Port { get; internal set; } public int Port { get; internal set; }
...@@ -48,7 +54,6 @@ namespace Titanium.Web.Proxy.Models ...@@ -48,7 +54,6 @@ namespace Titanium.Web.Proxy.Models
|| Equals(IpAddress, IPAddress.IPv6Loopback) || Equals(IpAddress, IPAddress.IPv6Loopback)
|| Equals(IpAddress, IPAddress.IPv6None); || Equals(IpAddress, IPAddress.IPv6None);
internal TcpListener Listener { get; set; }
} }
/// <summary> /// <summary>
...@@ -64,6 +69,12 @@ namespace Titanium.Web.Proxy.Models ...@@ -64,6 +69,12 @@ namespace Titanium.Web.Proxy.Models
internal bool IsSystemHttpsProxy { get; set; } internal bool IsSystemHttpsProxy { get; set; }
/// <summary>
/// Remote HTTPS ports we are allowed to communicate with
/// CONNECT request to ports other than these will not be decrypted
/// </summary>
public List<int> RemoteHttpsPorts { get; set; }
/// <summary> /// <summary>
/// List of host names to exclude using Regular Expressions. /// List of host names to exclude using Regular Expressions.
/// </summary> /// </summary>
...@@ -112,6 +123,8 @@ namespace Titanium.Web.Proxy.Models ...@@ -112,6 +123,8 @@ namespace Titanium.Web.Proxy.Models
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
: base(ipAddress, port, enableSsl) : base(ipAddress, port, enableSsl)
{ {
//init to well known HTTPS ports
RemoteHttpsPorts = new List<int> { 443, 8443 };
} }
} }
......
...@@ -39,7 +39,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -39,7 +39,7 @@ namespace Titanium.Web.Proxy.Network
set set
{ {
//For Mono only Bouncy Castle is supported //For Mono only Bouncy Castle is supported
if (RunTime.IsRunningOnMono()) if (RunTime.IsRunningOnMono)
{ {
value = CertificateEngine.BouncyCastle; value = CertificateEngine.BouncyCastle;
} }
...@@ -226,7 +226,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -226,7 +226,7 @@ namespace Titanium.Web.Proxy.Network
/// <returns></returns> /// <returns></returns>
public bool TrustRootCertificateAsAdministrator() public bool TrustRootCertificateAsAdministrator()
{ {
if (RunTime.IsRunningOnMono()) if (RunTime.IsRunningOnMono)
{ {
return false; return false;
} }
......
...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
internal Version Version { get; set; } internal Version Version { get; set; }
internal TcpClient TcpClient { get; set; } internal TcpClient TcpClient { private get; set; }
/// <summary> /// <summary>
/// used to read lines from server /// used to read lines from server
...@@ -54,7 +54,6 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -54,7 +54,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
public void Dispose() public void Dispose()
{ {
Stream?.Close(); Stream?.Close();
Stream?.Dispose();
StreamReader?.Dispose(); StreamReader?.Dispose();
......
...@@ -28,17 +28,12 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -28,17 +28,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="externalHttpProxy"></param> /// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param> /// <param name="externalHttpsProxy"></param>
/// <param name="clientStream"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpConnection> CreateClient(ProxyServer server, internal async Task<TcpConnection> CreateClient(ProxyServer server,
string remoteHostName, int remotePort, Version httpVersion, string remoteHostName, int remotePort, Version httpVersion,
bool isHttps, bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
Stream clientStream)
{ {
TcpClient client;
CustomBufferedStream stream;
bool useHttpProxy = false; bool useHttpProxy = false;
//check if external proxy is set for HTTP //check if external proxy is set for HTTP
if (!isHttps && externalHttpProxy != null if (!isHttps && externalHttpProxy != null
...@@ -71,88 +66,86 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -71,88 +66,86 @@ namespace Titanium.Web.Proxy.Network.Tcp
} }
} }
if (isHttps) TcpClient client = null;
{ CustomBufferedStream stream = null;
SslStream sslStream = null;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections try
if (useHttpsProxy) {
if (isHttps)
{ {
client = new TcpClient(server.UpStreamEndPoint); //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port); if (useHttpsProxy)
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) { NewLine = ProxyConstants.NewLine })
{ {
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); client = new TcpClient(server.UpStreamEndPoint);
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
await writer.WriteLineAsync("Connection: Keep-Alive"); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null) using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) { NewLine = ProxyConstants.NewLine })
{ {
await writer.WriteLineAsync("Proxy-Connection: keep-alive"); await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password))); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
}
await writer.WriteLineAsync();
await writer.FlushAsync();
writer.Close();
} }
await writer.WriteLineAsync();
await writer.FlushAsync();
writer.Close();
}
using (var reader = new CustomBinaryReader(stream, server.BufferSize)) using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{
var result = await reader.ReadLineAsync();
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
{ {
throw new Exception("Upstream proxy failed to create a secure tunnel"); var result = await reader.ReadLineAsync();
}
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
await reader.ReadAndIgnoreAllLinesAsync(); await reader.ReadAndIgnoreAllLinesAsync();
}
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
} }
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
try var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate);
{ stream = new CustomBufferedStream(sslStream, server.BufferSize);
sslStream = new SslStream(stream, true, server.ValidateServerCertificate,
server.SelectClientCertificate);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation); await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation);
stream = new CustomBufferedStream(sslStream, server.BufferSize);
} }
catch else
{ {
sslStream?.Close(); if (useHttpProxy)
sslStream?.Dispose(); {
client = new TcpClient(server.UpStreamEndPoint);
throw; await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
} }
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
} }
else catch (Exception)
{ {
if (useHttpProxy) stream?.Dispose();
{ client?.Close();
client = new TcpClient(server.UpStreamEndPoint); throw;
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
} }
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
Interlocked.Increment(ref server.serverConnectionCount); Interlocked.Increment(ref server.serverConnectionCount);
return new TcpConnection return new TcpConnection
......
This diff is collapsed.
//
// Mono.Security.BitConverterLE.cs
// Like System.BitConverter but always little endian
//
// Author:
// Bernie Solomon
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
internal sealed class LittleEndian
{
private LittleEndian ()
{
}
unsafe private static byte[] GetUShortBytes (byte *bytes)
{
if (BitConverter.IsLittleEndian)
{
return new byte[] { bytes[0], bytes[1] };
}
else
{
return new byte[] { bytes[1], bytes[0] };
}
}
unsafe private static byte[] GetUIntBytes (byte *bytes)
{
if (BitConverter.IsLittleEndian)
{
return new byte[] { bytes[0], bytes[1], bytes[2], bytes[3] };
}
else
{
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
}
}
unsafe private static byte[] GetULongBytes (byte *bytes)
{
if (BitConverter.IsLittleEndian)
{
return new byte[] { bytes [0], bytes [1], bytes [2], bytes [3],
bytes [4], bytes [5], bytes [6], bytes [7] };
}
else
{
return new byte[] { bytes [7], bytes [6], bytes [5], bytes [4],
bytes [3], bytes [2], bytes [1], bytes [0] };
}
}
unsafe internal static byte[] GetBytes (bool value)
{
return new byte [] { value ? (byte)1 : (byte)0 };
}
unsafe internal static byte[] GetBytes (char value)
{
return GetUShortBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (short value)
{
return GetUShortBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (int value)
{
return GetUIntBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (long value)
{
return GetULongBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (ushort value)
{
return GetUShortBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (uint value)
{
return GetUIntBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (ulong value)
{
return GetULongBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (float value)
{
return GetUIntBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (double value)
{
return GetULongBytes ((byte *) &value);
}
unsafe private static void UShortFromBytes (byte *dst, byte[] src, int startIndex)
{
if (BitConverter.IsLittleEndian)
{
dst [0] = src [startIndex];
dst [1] = src [startIndex + 1];
}
else
{
dst [0] = src [startIndex + 1];
dst [1] = src [startIndex];
}
}
unsafe private static void UIntFromBytes (byte *dst, byte[] src, int startIndex)
{
if (BitConverter.IsLittleEndian)
{
dst [0] = src [startIndex];
dst [1] = src [startIndex + 1];
dst [2] = src [startIndex + 2];
dst [3] = src [startIndex + 3];
}
else
{
dst [0] = src [startIndex + 3];
dst [1] = src [startIndex + 2];
dst [2] = src [startIndex + 1];
dst [3] = src [startIndex];
}
}
unsafe private static void ULongFromBytes (byte *dst, byte[] src, int startIndex)
{
if (BitConverter.IsLittleEndian) {
for (int i = 0; i < 8; ++i)
dst [i] = src [startIndex + i];
} else {
for (int i = 0; i < 8; ++i)
dst [i] = src [startIndex + (7 - i)];
}
}
unsafe internal static bool ToBoolean (byte[] value, int startIndex)
{
return value [startIndex] != 0;
}
unsafe internal static char ToChar (byte[] value, int startIndex)
{
char ret;
UShortFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static short ToInt16 (byte[] value, int startIndex)
{
short ret;
UShortFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static int ToInt32 (byte[] value, int startIndex)
{
int ret;
UIntFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static long ToInt64 (byte[] value, int startIndex)
{
long ret;
ULongFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static ushort ToUInt16 (byte[] value, int startIndex)
{
ushort ret;
UShortFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static uint ToUInt32 (byte[] value, int startIndex)
{
uint ret;
UIntFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static ulong ToUInt64 (byte[] value, int startIndex)
{
ulong ret;
ULongFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static float ToSingle (byte[] value, int startIndex)
{
float ret;
UIntFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static double ToDouble (byte[] value, int startIndex)
{
double ret;
ULongFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
}
}
//
// Nancy.Authentication.Ntlm.Protocol.Type3Message - Authentication
//
// Author:
// Sebastien Pouliot <sebastien@ximian.com>
//
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// References
// a. NTLM Authentication Scheme for HTTP, Ronald Tschalär
// http://www.innovation.ch/java/ntlm.html
// b. The NTLM Authentication Protocol, Copyright © 2003 Eric Glass
// http://davenport.sourceforge.net/ntlm.html
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
using System.Text;
internal class Message
{
static private byte[] header = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00 };
internal Message (byte[] message)
{
_type = 3;
Decode (message);
}
/// <summary>
/// Domain name
/// </summary>
internal string Domain
{
get;
private set;
}
/// <summary>
/// Username
/// </summary>
internal string Username
{
get;
private set;
}
private int _type;
private Common.NtlmFlags _flags;
internal Common.NtlmFlags Flags
{
get { return _flags; }
set { _flags = value; }
}
// methods
private void Decode (byte[] message)
{
//base.Decode (message);
if (message == null)
throw new ArgumentNullException("message");
if (message.Length < 12)
{
string msg = "Minimum Type3 message length is 12 bytes.";
throw new ArgumentOutOfRangeException("message", message.Length, msg);
}
if (!CheckHeader(message))
{
string msg = "Invalid Type3 message header.";
throw new ArgumentException(msg, "message");
}
if (LittleEndian.ToUInt16 (message, 56) != message.Length)
{
string msg = "Invalid Type3 message length.";
throw new ArgumentException (msg, "message");
}
if (message.Length >= 64)
{
Flags = (Common.NtlmFlags)LittleEndian.ToUInt32(message, 60);
}
else
{
Flags = (Common.NtlmFlags)0x8201;
}
int dom_len = LittleEndian.ToUInt16 (message, 28);
int dom_off = LittleEndian.ToUInt16 (message, 32);
this.Domain = DecodeString (message, dom_off, dom_len);
int user_len = LittleEndian.ToUInt16 (message, 36);
int user_off = LittleEndian.ToUInt16 (message, 40);
this.Username = DecodeString (message, user_off, user_len);
}
string DecodeString (byte[] buffer, int offset, int len)
{
if ((Flags & Common.NtlmFlags.NegotiateUnicode) != 0)
{
return Encoding.Unicode.GetString(buffer, offset, len);
}
else
{
return Encoding.ASCII.GetString(buffer, offset, len);
}
}
protected bool CheckHeader(byte[] message)
{
for (int i = 0; i < header.Length; i++)
{
if (message[i] != header[i])
return false;
}
return (LittleEndian.ToUInt32(message, 8) == _type);
}
}
}
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
/// <summary>
/// Status of authenticated session
/// </summary>
internal class State
{
internal State()
{
this.Credentials = new Common.SecurityHandle(0);
this.Context = new Common.SecurityHandle(0);
this.LastSeen = DateTime.Now;
}
/// <summary>
/// Credentials used to validate NTLM hashes
/// </summary>
internal Common.SecurityHandle Credentials;
/// <summary>
/// Context will be used to validate HTLM hashes
/// </summary>
internal Common.SecurityHandle Context;
/// <summary>
/// Timestamp needed to calculate validity of the authenticated session
/// </summary>
internal DateTime LastSeen;
internal void ResetHandles()
{
this.Credentials.Reset();
this.Context.Reset();
}
internal void UpdatePresence()
{
this.LastSeen = DateTime.Now;
}
}
}
// http://pinvoke.net/default.aspx/secur32/InitializeSecurityContext.html
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Principal;
using static Common;
using System.Threading.Tasks;
internal class WinAuthEndPoint
{
/// <summary>
/// Keep track of auth states for reuse in final challenge response
/// </summary>
private static IDictionary<Guid, State> authStates
= new ConcurrentDictionary<Guid, State>();
/// <summary>
/// Acquire the intial client token to send
/// </summary>
/// <param name="hostname"></param>
/// <param name="authScheme"></param>
/// <param name="requestId"></param>
/// <returns></returns>
internal static byte[] AcquireInitialSecurityToken(string hostname,
string authScheme, Guid requestId)
{
byte[] token = null;
//null for initial call
SecurityBufferDesciption serverToken
= new SecurityBufferDesciption();
SecurityBufferDesciption clientToken
= new SecurityBufferDesciption(MaximumTokenSize);
try
{
int result;
var state = new State();
result = AcquireCredentialsHandle(
WindowsIdentity.GetCurrent().Name,
authScheme,
SecurityCredentialsOutbound,
IntPtr.Zero,
IntPtr.Zero,
0,
IntPtr.Zero,
ref state.Credentials,
ref NewLifeTime);
if (result != SuccessfulResult)
{
// Credentials acquire operation failed.
return null;
}
result = InitializeSecurityContext(ref state.Credentials,
IntPtr.Zero,
hostname,
StandardContextAttributes,
0,
SecurityNativeDataRepresentation,
ref serverToken,
0,
out state.Context,
out clientToken,
out NewContextAttributes,
out NewLifeTime);
if (result != IntermediateResult)
{
// Client challenge issue operation failed.
return null;
}
token = clientToken.GetBytes();
authStates.Add(requestId, state);
}
finally
{
clientToken.Dispose();
serverToken.Dispose();
}
return token;
}
/// <summary>
/// Acquire the final token to send
/// </summary>
/// <param name="hostname"></param>
/// <param name="serverChallenge"></param>
/// <param name="requestId"></param>
/// <returns></returns>
internal static byte[] AcquireFinalSecurityToken(string hostname,
byte[] serverChallenge, Guid requestId)
{
byte[] token = null;
//user server challenge
SecurityBufferDesciption serverToken
= new SecurityBufferDesciption(serverChallenge);
SecurityBufferDesciption clientToken
= new SecurityBufferDesciption(MaximumTokenSize);
try
{
int result;
var state = authStates[requestId];
state.UpdatePresence();
result = InitializeSecurityContext(ref state.Credentials,
ref state.Context,
hostname,
StandardContextAttributes,
0,
SecurityNativeDataRepresentation,
ref serverToken,
0,
out state.Context,
out clientToken,
out NewContextAttributes,
out NewLifeTime);
if (result != SuccessfulResult)
{
// Client challenge issue operation failed.
return null;
}
authStates.Remove(requestId);
token = clientToken.GetBytes();
}
finally
{
clientToken.Dispose();
serverToken.Dispose();
}
return token;
}
/// <summary>
/// Clear any hanging states
/// </summary>
/// <param name="stateCacheTimeOutMinutes"></param>
internal static async void ClearIdleStates(int stateCacheTimeOutMinutes)
{
var cutOff = DateTime.Now.AddMinutes(-1 * stateCacheTimeOutMinutes);
var outdated = authStates
.Where(x => x.Value.LastSeen < cutOff)
.ToList();
foreach (var cache in outdated)
{
authStates.Remove(cache.Key);
}
//after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60);
}
#region Native calls to secur32.dll
[DllImport("secur32.dll", SetLastError = true)]
static extern int InitializeSecurityContext(ref SecurityHandle phCredential,//PCredHandle
IntPtr phContext, //PCtxtHandle
string pszTargetName,
int fContextReq,
int Reserved1,
int TargetDataRep,
ref SecurityBufferDesciption pInput, //PSecBufferDesc SecBufferDesc
int Reserved2,
out SecurityHandle phNewContext, //PCtxtHandle
out SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc
out uint pfContextAttr, //managed ulong == 64 bits!!!
out SecurityInteger ptsExpiry); //PTimeStamp
[DllImport("secur32", CharSet = CharSet.Auto, SetLastError = true)]
static extern int InitializeSecurityContext(ref SecurityHandle phCredential,//PCredHandle
ref SecurityHandle phContext, //PCtxtHandle
string pszTargetName,
int fContextReq,
int Reserved1,
int TargetDataRep,
ref SecurityBufferDesciption SecBufferDesc, //PSecBufferDesc SecBufferDesc
int Reserved2,
out SecurityHandle phNewContext, //PCtxtHandle
out SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc
out uint pfContextAttr, //managed ulong == 64 bits!!!
out SecurityInteger ptsExpiry); //PTimeStamp
[DllImport("secur32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern int AcquireCredentialsHandle(
string pszPrincipal, //SEC_CHAR*
string pszPackage, //SEC_CHAR* //"Kerberos","NTLM","Negotiative"
int fCredentialUse,
IntPtr PAuthenticationID, //_LUID AuthenticationID,//pvLogonID, //PLUID
IntPtr pAuthData, //PVOID
int pGetKeyFn, //SEC_GET_KEY_FN
IntPtr pvGetKeyArgument, //PVOID
ref Common.SecurityHandle phCredential, //SecHandle //PCtxtHandle ref
ref Common.SecurityInteger ptsExpiry); //PTimeStamp //TimeStamp ref
#endregion
}
}
using Titanium.Web.Proxy.Network.WinAuth.Security;
using System;
namespace Titanium.Web.Proxy.Network.WinAuth
{
/// <summary>
/// A handler for NTLM/Kerberos windows authentication challenge from server
/// NTLM process details below
/// https://blogs.msdn.microsoft.com/chiranth/2013/09/20/ntlm-want-to-know-how-it-works/
/// </summary>
public static class WinAuthHandler
{
/// <summary>
/// Get the initial client token for server
/// using credentials of user running the proxy server process
/// </summary>
/// <param name="serverHostname"></param>
/// <param name="authScheme"></param>
/// <param name="requestId"></param>
/// <returns></returns>
public static string GetInitialAuthToken(string serverHostname,
string authScheme, Guid requestId)
{
var tokenBytes = WinAuthEndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes));
}
/// <summary>
/// Get the final token given the server challenge token
/// </summary>
/// <param name="serverHostname"></param>
/// <param name="serverToken"></param>
/// <param name="requestId"></param>
/// <returns></returns>
public static string GetFinalAuthToken(string serverHostname,
string serverToken, Guid requestId)
{
var tokenBytes = WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname,
Convert.FromBase64String(serverToken), requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes));
}
}
}
...@@ -12,6 +12,7 @@ using Titanium.Web.Proxy.Helpers; ...@@ -12,6 +12,7 @@ using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -60,12 +61,13 @@ namespace Titanium.Web.Proxy ...@@ -60,12 +61,13 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private SystemProxyManager systemProxySettingsManager { get; } private SystemProxyManager systemProxySettingsManager { get; }
#if !DEBUG
/// <summary> /// <summary>
/// Set firefox to use default system proxy /// Set firefox to use default system proxy
/// </summary> /// </summary>
private FireFoxProxySettingsManager firefoxProxySettingsManager = new FireFoxProxySettingsManager(); private FireFoxProxySettingsManager firefoxProxySettingsManager
#endif = new FireFoxProxySettingsManager();
/// <summary> /// <summary>
/// Buffer size used throughout this proxy /// Buffer size used throughout this proxy
...@@ -196,6 +198,15 @@ namespace Titanium.Web.Proxy ...@@ -196,6 +198,15 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public bool ForwardToUpstreamGateway { get; set; } public bool ForwardToUpstreamGateway { get; set; }
/// <summary>
/// Enable disable Windows Authentication (NTLM/Kerberos)
/// Note: NTLM/Kerberos will always send local credentials of current user
/// who is running the proxy process. This is because a man
/// in middle attack is not currently supported
/// (which would require windows delegation enabled for this server process)
/// </summary>
public bool EnableWinAuth { get; set; }
/// <summary> /// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary> /// </summary>
...@@ -235,7 +246,7 @@ namespace Titanium.Web.Proxy ...@@ -235,7 +246,7 @@ namespace Titanium.Web.Proxy
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc { get; set; } public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc { get; set; }
/// <summary> /// <summary>
/// A list of IpAddress & port this proxy is listening to /// A list of IpAddress and port this proxy is listening to
/// </summary> /// </summary>
public List<ProxyEndPoint> ProxyEndPoints { get; set; } public List<ProxyEndPoint> ProxyEndPoints { get; set; }
...@@ -277,9 +288,7 @@ namespace Titanium.Web.Proxy ...@@ -277,9 +288,7 @@ namespace Titanium.Web.Proxy
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(); tcpConnectionFactory = new TcpConnectionFactory();
systemProxySettingsManager = new SystemProxyManager(); systemProxySettingsManager = new SystemProxyManager();
#if !DEBUG
new FireFoxProxySettingsManager();
#endif
CertificateManager = new CertificateManager(ExceptionFunc); CertificateManager = new CertificateManager(ExceptionFunc);
if (rootCertificateName != null) if (rootCertificateName != null)
...@@ -339,7 +348,7 @@ namespace Titanium.Web.Proxy ...@@ -339,7 +348,7 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
{ {
if (RunTime.IsRunningOnMono()) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
...@@ -353,9 +362,9 @@ namespace Titanium.Web.Proxy ...@@ -353,9 +362,9 @@ namespace Titanium.Web.Proxy
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), endPoint.Port); Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), endPoint.Port);
endPoint.IsSystemHttpProxy = true; endPoint.IsSystemHttpProxy = true;
#if !DEBUG
firefoxProxySettingsManager.AddFirefox(); firefoxProxySettingsManager.UseSystemProxy();
#endif
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTP Proxy", endPoint.IpAddress, endPoint.Port); Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTP Proxy", endPoint.IpAddress, endPoint.Port);
} }
...@@ -366,7 +375,7 @@ namespace Titanium.Web.Proxy ...@@ -366,7 +375,7 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{ {
if (RunTime.IsRunningOnMono()) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
...@@ -397,9 +406,8 @@ namespace Titanium.Web.Proxy ...@@ -397,9 +406,8 @@ namespace Titanium.Web.Proxy
endPoint.IsSystemHttpsProxy = true; endPoint.IsSystemHttpsProxy = true;
#if !DEBUG firefoxProxySettingsManager.UseSystemProxy();
firefoxProxySettingsManager.AddFirefox();
#endif
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", endPoint.IpAddress, endPoint.Port); Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", endPoint.IpAddress, endPoint.Port);
} }
...@@ -408,7 +416,7 @@ namespace Titanium.Web.Proxy ...@@ -408,7 +416,7 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public void DisableSystemHttpProxy() public void DisableSystemHttpProxy()
{ {
if (RunTime.IsRunningOnMono()) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
...@@ -421,7 +429,7 @@ namespace Titanium.Web.Proxy ...@@ -421,7 +429,7 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public void DisableSystemHttpsProxy() public void DisableSystemHttpsProxy()
{ {
if (RunTime.IsRunningOnMono()) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
...@@ -434,7 +442,7 @@ namespace Titanium.Web.Proxy ...@@ -434,7 +442,7 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public void DisableAllSystemProxies() public void DisableAllSystemProxies()
{ {
if (RunTime.IsRunningOnMono()) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
...@@ -466,6 +474,12 @@ namespace Titanium.Web.Proxy ...@@ -466,6 +474,12 @@ namespace Titanium.Web.Proxy
CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
if (!RunTime.IsRunningOnMono)
{
//clear orphaned windows auth states every 2 minutes
WinAuthEndPoint.ClearIdleStates(2);
}
proxyRunning = true; proxyRunning = true;
} }
...@@ -485,9 +499,6 @@ namespace Titanium.Web.Proxy ...@@ -485,9 +499,6 @@ namespace Titanium.Web.Proxy
if (setAsSystemProxy) if (setAsSystemProxy)
{ {
systemProxySettingsManager.DisableAllProxy(); systemProxySettingsManager.DisableAllProxy();
#if !DEBUG
firefoxProxySettingsManager.RemoveFirefox();
#endif
} }
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
......
...@@ -87,7 +87,8 @@ namespace Titanium.Web.Proxy ...@@ -87,7 +87,8 @@ namespace Titanium.Web.Proxy
List<HttpHeader> connectRequestHeaders = null; List<HttpHeader> connectRequestHeaders = null;
//Client wants to create a secure tcp tunnel (its a HTTPS request) //Client wants to create a secure tcp tunnel (its a HTTPS request)
if (httpVerb == "CONNECT" && !excluded && httpRemoteUri.Port != 80) if (httpVerb == "CONNECT" && !excluded
&& endPoint.RemoteHttpsPorts.Contains(httpRemoteUri.Port))
{ {
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]); httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
connectRequestHeaders = new List<HttpHeader>(); connectRequestHeaders = new List<HttpHeader>();
...@@ -111,10 +112,12 @@ namespace Titanium.Web.Proxy ...@@ -111,10 +112,12 @@ namespace Titanium.Web.Proxy
try try
{ {
sslStream = new SslStream(clientStream, true); sslStream = new SslStream(clientStream);
var certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host);
var certificate = endPoint.GenericCertificate ?? var certificate = endPoint.GenericCertificate ??
CertificateManager.CreateCertificate(httpRemoteUri.Host, false); CertificateManager.CreateCertificate(certName, false);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
...@@ -191,7 +194,7 @@ namespace Titanium.Web.Proxy ...@@ -191,7 +194,7 @@ namespace Titanium.Web.Proxy
{ {
if (endPoint.EnableSsl) if (endPoint.EnableSsl)
{ {
var sslStream = new SslStream(clientStream, true); var sslStream = new SslStream(clientStream);
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = new CustomBufferedStream(sslStream, BufferSize);
//implement in future once SNI supported by SSL stream, for now use the same certificate //implement in future once SNI supported by SSL stream, for now use the same certificate
...@@ -319,6 +322,16 @@ namespace Titanium.Web.Proxy ...@@ -319,6 +322,16 @@ namespace Titanium.Web.Proxy
PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession); PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
//if win auth is enabled
//we need a cache of request body
//so that we can send it after authentication in WinAuthHandler.cs
if (EnableWinAuth
&& !RunTime.IsRunningOnMono
&& args.WebSession.Request.HasBody)
{
await args.GetRequestBody();
}
//If user requested interception do it //If user requested interception do it
if (BeforeRequest != null) if (BeforeRequest != null)
{ {
...@@ -331,7 +344,7 @@ namespace Titanium.Web.Proxy ...@@ -331,7 +344,7 @@ namespace Titanium.Web.Proxy
await TcpHelper.SendRaw(this, await TcpHelper.SendRaw(this,
httpRemoteUri.Host, httpRemoteUri.Port, httpRemoteUri.Host, httpRemoteUri.Port,
httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps, httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
clientStream, tcpConnectionFactory); clientStream, tcpConnectionFactory, connection);
args.Dispose(); args.Dispose();
break; break;
...@@ -440,28 +453,31 @@ namespace Titanium.Web.Proxy ...@@ -440,28 +453,31 @@ namespace Titanium.Web.Proxy
await args.WebSession.SendRequest(Enable100ContinueBehaviour); await args.WebSession.SendRequest(Enable100ContinueBehaviour);
} }
//If request was modified by user //check if content-length is > 0
if (args.WebSession.Request.RequestBodyRead) if (args.WebSession.Request.ContentLength > 0)
{ {
if (args.WebSession.Request.ContentEncoding != null) //If request was modified by user
if (args.WebSession.Request.RequestBodyRead)
{ {
args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody); if (args.WebSession.Request.ContentEncoding != null)
} {
//chunked send is not supported as of now args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length; }
//chunked send is not supported as of now
args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
var newStream = args.WebSession.ServerConnection.Stream; var newStream = args.WebSession.ServerConnection.Stream;
await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length); await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
} }
else else
{
if (!args.WebSession.Request.ExpectationFailed)
{ {
//If its a post/put/patch request, then read the client html body and send it to server if (!args.WebSession.Request.ExpectationFailed)
var method = args.WebSession.Request.Method.ToUpper();
if (method == "POST" || method == "PUT" || method == "PATCH")
{ {
await SendClientRequestBody(args); //If its a post/put/patch request, then read the client html body and send it to server
if (args.WebSession.Request.HasBody)
{
await SendClientRequestBody(args);
}
} }
} }
} }
...@@ -542,8 +558,7 @@ namespace Titanium.Web.Proxy ...@@ -542,8 +558,7 @@ namespace Titanium.Web.Proxy
args.WebSession.Request.HttpVersion, args.WebSession.Request.HttpVersion,
args.IsHttps, args.IsHttps,
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpProxy ?? UpStreamHttpProxy,
customUpStreamHttpsProxy ?? UpStreamHttpsProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy);
args.ProxyClient.ClientStream);
} }
...@@ -599,7 +614,7 @@ namespace Titanium.Web.Proxy ...@@ -599,7 +614,7 @@ namespace Titanium.Web.Proxy
//send the request body bytes to server //send the request body bytes to server
if (args.WebSession.Request.ContentLength > 0) if (args.WebSession.Request.ContentLength > 0)
{ {
await args.ProxyClient.ClientStreamReader.CopyBytesToStream(BufferSize, postStream, args.WebSession.Request.ContentLength); await args.ProxyClient.ClientStreamReader.CopyBytesToStream(postStream, args.WebSession.Request.ContentLength);
} }
//Need to revist, find any potential bugs //Need to revist, find any potential bugs
//send the request body bytes to server in chunks //send the request body bytes to server in chunks
......
...@@ -36,6 +36,19 @@ namespace Titanium.Web.Proxy ...@@ -36,6 +36,19 @@ namespace Titanium.Web.Proxy
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream; args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
} }
//check for windows authentication
if(EnableWinAuth
&& !RunTime.IsRunningOnMono
&& args.WebSession.Response.ResponseStatusCode == "401")
{
var disposed = await Handle401UnAuthorized(args);
if(disposed)
{
return true;
}
}
args.ReRequest = false; args.ReRequest = false;
//If user requested call back then do it //If user requested call back then do it
...@@ -44,16 +57,13 @@ namespace Titanium.Web.Proxy ...@@ -44,16 +57,13 @@ namespace Titanium.Web.Proxy
await BeforeResponse.InvokeParallelAsync(this, args); await BeforeResponse.InvokeParallelAsync(this, args);
} }
//if user requested to send request again
//likely after making modifications from User Response Handler
if (args.ReRequest) if (args.ReRequest)
{ {
if (args.WebSession.ServerConnection != null) //clear current response
{ await args.ClearResponse();
args.WebSession.ServerConnection.Dispose(); var disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false);
Interlocked.Decrement(ref serverConnectionCount);
}
var connection = await GetServerConnection(args);
var disposed = await HandleHttpSessionRequestInternal(null, args, true);
return disposed; return disposed;
} }
......
...@@ -7,9 +7,12 @@ namespace Titanium.Web.Proxy.Shared ...@@ -7,9 +7,12 @@ namespace Titanium.Web.Proxy.Shared
/// </summary> /// </summary>
internal class ProxyConstants internal class ProxyConstants
{ {
internal static readonly char DotSplit = '.';
internal static readonly char[] SpaceSplit = { ' ' }; internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = { ':' }; internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = { ';' }; internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly char[] EqualSplit = { '=' };
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine); internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine);
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
<DocumentationFile>bin\Release\Titanium.Web.Proxy.XML</DocumentationFile> <DocumentationFile>bin\Release\Titanium.Web.Proxy.XML</DocumentationFile>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols> <DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
...@@ -74,7 +75,9 @@ ...@@ -74,7 +75,9 @@
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" /> <Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" /> <Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Extensions\FuncExtensions.cs" /> <Compile Include="Extensions\FuncExtensions.cs" />
<Compile Include="Helpers\HttpHelper.cs" />
<Compile Include="Extensions\StringExtensions.cs" /> <Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Helpers\BufferPool.cs" />
<Compile Include="Helpers\CustomBufferedStream.cs" /> <Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\Network.cs" /> <Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\RunTime.cs" /> <Compile Include="Helpers\RunTime.cs" />
...@@ -103,6 +106,12 @@ ...@@ -103,6 +106,12 @@
<Compile Include="Network\Tcp\TcpConnectionFactory.cs" /> <Compile Include="Network\Tcp\TcpConnectionFactory.cs" />
<Compile Include="Models\HttpHeader.cs" /> <Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" /> <Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Network\WinAuth\Security\Common.cs" />
<Compile Include="Network\WinAuth\Security\WinAuthEndPoint.cs" />
<Compile Include="Network\WinAuth\Security\LittleEndian.cs" />
<Compile Include="Network\WinAuth\Security\Message.cs" />
<Compile Include="Network\WinAuth\Security\State.cs" />
<Compile Include="Network\WinAuth\WinAuthHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyAuthorizationHandler.cs" /> <Compile Include="ProxyAuthorizationHandler.cs" />
<Compile Include="RequestHandler.cs" /> <Compile Include="RequestHandler.cs" />
...@@ -117,6 +126,7 @@ ...@@ -117,6 +126,7 @@
<Compile Include="Shared\ProxyConstants.cs" /> <Compile Include="Shared\ProxyConstants.cs" />
<Compile Include="Network\Tcp\TcpRow.cs" /> <Compile Include="Network\Tcp\TcpRow.cs" />
<Compile Include="Network\Tcp\TcpTable.cs" /> <Compile Include="Network\Tcp\TcpTable.cs" />
<Compile Include="WinAuthHandler.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<COMReference Include="CERTENROLLLib"> <COMReference Include="CERTENROLLLib">
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.WinAuth;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
//possible header names
private static List<string> authHeaderNames
= new List<string>() {
"WWW-Authenticate",
//IIS 6.0 messed up names below
"WWWAuthenticate",
"NTLMAuthorization",
"NegotiateAuthorization",
"KerberosAuthorization"
};
private static List<string> authSchemes
= new List<string>() {
"NTLM",
"Negotiate",
"Kerberos"
};
/// <summary>
/// Handle windows NTLM authentication
/// Can expand this for Kerberos in future
/// Note: NTLM/Kerberos cannot do a man in middle operation
/// we do for HTTPS requests.
/// As such we will be sending local credentials of current
/// User to server to authenticate requests.
/// To disable this set ProxyServer.EnableWinAuth to false
/// </summary>
internal async Task<bool> Handle401UnAuthorized(SessionEventArgs args)
{
string headerName = null;
HttpHeader authHeader = null;
//check in non-unique headers first
var header = args.WebSession.Response
.NonUniqueResponseHeaders
.FirstOrDefault(x =>
authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
if (!header.Equals(new KeyValuePair<string, List<HttpHeader>>()))
{
headerName = header.Key;
}
if (headerName != null)
{
authHeader = args.WebSession.Response
.NonUniqueResponseHeaders[headerName]
.Where(x => authSchemes.Any(y => x.Value.StartsWith(y, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault();
}
//check in unique headers
if (authHeader == null)
{
//check in non-unique headers first
var uHeader = args.WebSession.Response
.ResponseHeaders
.FirstOrDefault(x =>
authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
if (!uHeader.Equals(new KeyValuePair<string, HttpHeader>()))
{
headerName = uHeader.Key;
}
if (headerName != null)
{
authHeader = authSchemes.Any(x => args.WebSession.Response
.ResponseHeaders[headerName].Value.StartsWith(x, StringComparison.OrdinalIgnoreCase)) ?
args.WebSession.Response.ResponseHeaders[headerName] : null;
}
}
if (authHeader != null)
{
var scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase));
//clear any existing headers to avoid confusing bad servers
if (args.WebSession.Request.NonUniqueRequestHeaders.ContainsKey("Authorization"))
{
args.WebSession.Request.NonUniqueRequestHeaders.Remove("Authorization");
}
//initial value will match exactly any of the schemes
if (scheme != null)
{
var clientToken = WinAuthHandler.GetInitialAuthToken(args.WebSession.Request.Host, scheme, args.Id);
var auth = new HttpHeader("Authorization", string.Concat(scheme, clientToken));
//replace existing authorization header if any
if (args.WebSession.Request.RequestHeaders.ContainsKey("Authorization"))
{
args.WebSession.Request.RequestHeaders["Authorization"] = auth;
}
else
{
args.WebSession.Request.RequestHeaders.Add("Authorization", auth);
}
//don't need to send body for Authorization request
if(args.WebSession.Request.HasBody)
{
args.WebSession.Request.ContentLength = 0;
}
}
//challenge value will start with any of the scheme selected
else
{
scheme = authSchemes.FirstOrDefault(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase)
&& authHeader.Value.Length > x.Length + 1);
var serverToken = authHeader.Value.Substring(scheme.Length + 1);
var clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id);
//there will be an existing header from initial client request
args.WebSession.Request.RequestHeaders["Authorization"]
= new HttpHeader("Authorization", string.Concat(scheme, clientToken));
//send body for final auth request
if (args.WebSession.Request.HasBody)
{
args.WebSession.Request.ContentLength
= args.WebSession.Request.RequestBody.Length;
}
}
//Need to revisit this.
//Should we cache all Set-Cokiee headers from server during auth process
//and send it to client after auth?
//clear current server response
await args.ClearResponse();
//request again with updated authorization header
//and server cookies
var disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false);
return disposed;
}
return false;
}
}
}
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