Skip to content
This repository was archived by the owner on Jul 9, 2023. It is now read-only.

Commit f5a6fe9

Browse files
Merge pull request #254 from justcoding121/beta
Stable
2 parents 2f89234 + 8063d02 commit f5a6fe9

36 files changed

+1964
-395
lines changed

Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ public class ProxyTestController
1313
private readonly ProxyServer proxyServer;
1414

1515
//share requestBody outside handlers
16-
private readonly Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>();
16+
//Using a dictionary is not a good idea since it can cause memory overflow
17+
//ideally the data should be moved out of memory
18+
//private readonly Dictionary<Guid, string> requestBodyHistory
19+
// = new Dictionary<Guid, string>();
1720

1821
public ProxyTestController()
1922
{
@@ -42,6 +45,8 @@ public void StartProxy()
4245
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
4346
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
4447

48+
//proxyServer.EnableWinAuth = true;
49+
4550
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
4651
{
4752
//Exclude Https addresses you don't want to proxy
@@ -113,8 +118,7 @@ public async Task OnRequest(object sender, SessionEventArgs e)
113118
//read request headers
114119
var requestHeaders = e.WebSession.Request.RequestHeaders;
115120

116-
var method = e.WebSession.Request.Method.ToUpper();
117-
if (method == "POST" || method == "PUT" || method == "PATCH")
121+
if (e.WebSession.Request.HasBody)
118122
{
119123
//Get/Set request body bytes
120124
byte[] bodyBytes = await e.GetRequestBody();
@@ -124,7 +128,7 @@ public async Task OnRequest(object sender, SessionEventArgs e)
124128
string bodyString = await e.GetRequestBodyAsString();
125129
await e.SetRequestBodyString(bodyString);
126130

127-
requestBodyHistory[e.Id] = bodyString;
131+
//requestBodyHistory[e.Id] = bodyString;
128132
}
129133

130134
////To cancel a request with a custom HTML content
@@ -152,11 +156,11 @@ public async Task OnResponse(object sender, SessionEventArgs e)
152156
{
153157
Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
154158

155-
if (requestBodyHistory.ContainsKey(e.Id))
156-
{
157-
//access request body by looking up the shared dictionary using requestId
158-
var requestBody = requestBodyHistory[e.Id];
159-
}
159+
//if (requestBodyHistory.ContainsKey(e.Id))
160+
//{
161+
// //access request body by looking up the shared dictionary using requestId
162+
// var requestBody = requestBodyHistory[e.Id];
163+
//}
160164

161165
//read response headers
162166
var responseHeaders = e.WebSession.Response.ResponseHeaders;

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Features
1818
* Support mutual SSL authentication
1919
* Fully asynchronous proxy
2020
* Supports proxy authentication & automatic proxy detection
21+
* Kerberos/NTLM authentication over HTTP protocols for windows domain
2122

2223
Usage
2324
=====
@@ -203,7 +204,6 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
203204
```
204205
Future road map (Pull requests are welcome!)
205206
============
206-
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
207207
* Support Server Name Indication (SNI) for transparent endpoints
208208
* Support HTTP 2.0
209209
* Support SOCKS protocol

Tests/Titanium.Web.Proxy.UnitTests/Titanium.Web.Proxy.UnitTests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<DefineConstants>TRACE</DefineConstants>
3434
<ErrorReport>prompt</ErrorReport>
3535
<WarningLevel>4</WarningLevel>
36+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
3637
</PropertyGroup>
3738
<PropertyGroup>
3839
<SignAssembly>true</SignAssembly>
@@ -59,6 +60,7 @@
5960
<Compile Include="CertificateManagerTests.cs" />
6061
<Compile Include="Properties\AssemblyInfo.cs" />
6162
<Compile Include="ProxyServerTests.cs" />
63+
<Compile Include="WinAuthTests.cs" />
6264
</ItemGroup>
6365
<ItemGroup>
6466
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
using System;
3+
using Titanium.Web.Proxy.Network.WinAuth;
4+
5+
namespace Titanium.Web.Proxy.UnitTests
6+
{
7+
[TestClass]
8+
public class WinAuthTests
9+
{
10+
[TestMethod]
11+
public void Test_Acquire_Client_Token()
12+
{
13+
var token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com", "NTLM", Guid.NewGuid());
14+
Assert.IsTrue(token.Length > 1);
15+
}
16+
}
17+
}

Titanium.Web.Proxy/Decompression/DeflateDecompression.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.IO;
22
using System.IO.Compression;
33
using System.Threading.Tasks;
4+
using Titanium.Web.Proxy.Helpers;
45

56
namespace Titanium.Web.Proxy.Decompression
67
{
@@ -14,17 +15,23 @@ public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
1415
using (var stream = new MemoryStream(compressedArray))
1516
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
1617
{
17-
var buffer = new byte[bufferSize];
18-
19-
using (var output = new MemoryStream())
18+
var buffer = BufferPool.GetBuffer(bufferSize);
19+
try
2020
{
21-
int read;
22-
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
21+
using (var output = new MemoryStream())
2322
{
24-
output.Write(buffer, 0, read);
25-
}
23+
int read;
24+
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
25+
{
26+
output.Write(buffer, 0, read);
27+
}
2628

27-
return output.ToArray();
29+
return output.ToArray();
30+
}
31+
}
32+
finally
33+
{
34+
BufferPool.ReturnBuffer(buffer);
2835
}
2936
}
3037
}

Titanium.Web.Proxy/Decompression/GZipDecompression.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.IO;
22
using System.IO.Compression;
33
using System.Threading.Tasks;
4+
using Titanium.Web.Proxy.Helpers;
45

56
namespace Titanium.Web.Proxy.Decompression
67
{
@@ -13,16 +14,23 @@ public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
1314
{
1415
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
1516
{
16-
var buffer = new byte[bufferSize];
17-
using (var output = new MemoryStream())
17+
var buffer = BufferPool.GetBuffer(bufferSize);
18+
try
1819
{
19-
int read;
20-
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
20+
using (var output = new MemoryStream())
2121
{
22-
output.Write(buffer, 0, read);
23-
}
22+
int read;
23+
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
24+
{
25+
output.Write(buffer, 0, read);
26+
}
2427

25-
return output.ToArray();
28+
return output.ToArray();
29+
}
30+
}
31+
finally
32+
{
33+
BufferPool.ReturnBuffer(buffer);
2634
}
2735
}
2836
}

Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public class SessionEventArgs : EventArgs, IDisposable
3232
/// </summary>
3333
private Func<SessionEventArgs, Task> httpResponseHandler;
3434

35+
/// <summary>
36+
/// Backing field for corresponding public property
37+
/// </summary>
38+
private bool reRequest;
39+
3540
/// <summary>
3641
/// Holds a reference to client
3742
/// </summary>
@@ -43,10 +48,24 @@ public class SessionEventArgs : EventArgs, IDisposable
4348
/// </summary>
4449
public Guid Id => WebSession.RequestId;
4550

51+
4652
/// <summary>
47-
/// Should we send a rerequest
53+
/// Should we send the request again
4854
/// </summary>
49-
public bool ReRequest { get; set; }
55+
public bool ReRequest
56+
{
57+
get { return reRequest; }
58+
set
59+
{
60+
if (WebSession.Response.ResponseStatusCode == null)
61+
{
62+
throw new Exception("Response status code is null. Cannot request again a request "
63+
+ "which was never send to server.");
64+
}
65+
66+
reRequest = value;
67+
}
68+
}
5069

5170
/// <summary>
5271
/// Does this session uses SSL
@@ -92,8 +111,7 @@ internal SessionEventArgs(int bufferSize, Func<SessionEventArgs, Task> httpRespo
92111
private async Task ReadRequestBody()
93112
{
94113
//GET request don't have a request body to read
95-
var method = WebSession.Request.Method.ToUpper();
96-
if (method != "POST" && method != "PUT" && method != "PATCH")
114+
if (!WebSession.Request.HasBody)
97115
{
98116
throw new BodyNotFoundException("Request don't have a body. " +
99117
"Please verify that this request is a Http POST/PUT/PATCH and request " +
@@ -117,12 +135,12 @@ private async Task ReadRequestBody()
117135
if (WebSession.Request.ContentLength > 0)
118136
{
119137
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
120-
await ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream,
138+
await ProxyClient.ClientStreamReader.CopyBytesToStream(requestBodyStream,
121139
WebSession.Request.ContentLength);
122140
}
123141
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
124142
{
125-
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue);
143+
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue);
126144
}
127145
}
128146
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding,
@@ -135,6 +153,18 @@ await ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodySt
135153
}
136154
}
137155

156+
/// <summary>
157+
/// reinit response object
158+
/// </summary>
159+
internal async Task ClearResponse()
160+
{
161+
//siphon out the body
162+
await ReadResponseBody();
163+
WebSession.Response.Dispose();
164+
WebSession.Response = new Response();
165+
}
166+
167+
138168
/// <summary>
139169
/// Read response body as byte[] for current response
140170
/// </summary>
@@ -155,12 +185,12 @@ private async Task ReadResponseBody()
155185
if (WebSession.Response.ContentLength > 0)
156186
{
157187
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
158-
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream,
188+
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream,
159189
WebSession.Response.ContentLength);
160190
}
161191
else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0 || WebSession.Response.ContentLength == -1)
162192
{
163-
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue);
193+
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue);
164194
}
165195
}
166196

@@ -420,27 +450,29 @@ public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers)
420450
WebSession.Request.CancelRequest = true;
421451
}
422452

423-
/// <summary>
424-
/// Before request is made to server 
453+
/// <summary>
454+
/// Before request is made to server 
425455
/// Respond with the specified HTML string to client
426456
/// and ignore the request 
427-
/// </summary>
428-
/// <param name="html"></param>
429-
/// <param name="status"></param>
457+
/// </summary>
458+
/// <param name="html"></param>
459+
/// <param name="status"></param>
460+
/// <returns></returns>
430461
public async Task GenericResponse(string html, HttpStatusCode status)
431462
{
432463
await GenericResponse(html, null, status);
433464
}
434465

435466
/// <summary>
436-
/// Before request is made to server 
467+
/// Before request is made to server 
437468
/// Respond with the specified HTML string to client
438469
/// and the specified status
439470
/// and ignore the request 
440-
/// </summary>
441-
/// <param name="html"></param>
442-
/// <param name="headers"></param>
443-
/// <param name="status"></param>
471+
/// </summary>
472+
/// <param name="html"></param>
473+
/// <param name="headers"></param>
474+
/// <param name="status"></param>
475+
/// <returns></returns>
444476
public async Task GenericResponse(string html, Dictionary<string, HttpHeader> headers, HttpStatusCode status)
445477
{
446478
if (WebSession.Request.RequestLocked)
Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Text;
3+
using Titanium.Web.Proxy.Helpers;
34
using Titanium.Web.Proxy.Http;
4-
using Titanium.Web.Proxy.Shared;
55

66
namespace Titanium.Web.Proxy.Extensions
77
{
@@ -17,33 +17,7 @@ internal static class HttpWebRequestExtensions
1717
/// <returns></returns>
1818
internal static Encoding GetEncoding(this Request request)
1919
{
20-
try
21-
{
22-
//return default if not specified
23-
if (request.ContentType == null)
24-
{
25-
return Encoding.GetEncoding("ISO-8859-1");
26-
}
27-
28-
//extract the encoding by finding the charset
29-
var contentTypes = request.ContentType.Split(ProxyConstants.SemiColonSplit);
30-
foreach (var contentType in contentTypes)
31-
{
32-
var encodingSplit = contentType.Split('=');
33-
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
34-
{
35-
return Encoding.GetEncoding(encodingSplit[1]);
36-
}
37-
}
38-
}
39-
catch
40-
{
41-
//parsing errors
42-
// ignored
43-
}
44-
45-
//return default if not specified
46-
return Encoding.GetEncoding("ISO-8859-1");
20+
return HttpHelper.GetEncodingFromContentType(request.ContentType);
4721
}
4822
}
4923
}

0 commit comments

Comments
 (0)