Unverified Commit 80dd44e5 authored by honfika's avatar honfika Committed by GitHub

Merge pull request #353 from justcoding121/develop

beta|develop
parents 7a44e456 5fe25998
......@@ -143,7 +143,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
}
//intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
private async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url);
......@@ -151,6 +151,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
//read request headers
requestHeaderHistory[e.Id] = e.WebSession.Request.Headers;
////This sample shows how to get the multipart form data headers
//if (e.WebSession.Request.Host == "mail.yahoo.com" && e.WebSession.Request.IsMultipartFormData)
//{
// e.MultipartRequestPartSent += MultipartRequestPartSent;
//}
if (e.WebSession.Request.HasBody)
{
//Get/Set request body bytes
......@@ -185,7 +191,17 @@ namespace Titanium.Web.Proxy.Examples.Basic
}
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
private void MultipartRequestPartSent(object sender, MultipartRequestPartSentEventArgs e)
{
var session = (SessionEventArgs)sender;
Console.WriteLine("Multipart form data headers:");
foreach (var header in e.Headers)
{
Console.WriteLine(header);
}
}
private async Task OnResponse(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
......
......@@ -11,7 +11,6 @@ using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Examples.Wpf
{
......@@ -63,13 +62,14 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public MainWindow()
{
proxyServer = new ProxyServer();
//proxyServer.CertificateEngine = CertificateEngine.BouncyCastle;
//proxyServer.CertificateEngine = CertificateEngine.DefaultWindows;
proxyServer.TrustRootCertificate = true;
proxyServer.CertificateManager.TrustRootCertificateAsAdministrator();
proxyServer.ForwardToUpstreamGateway = true;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
ExcludedHttpsHostNameRegex = new[] { "ssllabs.com" },
//IncludedHttpsHostNameRegex = new string[0],
};
......@@ -134,10 +134,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem item = null;
await Dispatcher.InvokeAsync(() =>
{
if (sessionDictionary.TryGetValue(e.WebSession, out var item2))
if (sessionDictionary.TryGetValue(e.WebSession, out item))
{
item2.Update();
item = item2;
item.Update();
}
});
......@@ -147,6 +146,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{
e.WebSession.Response.KeepBody = true;
await e.GetResponseBody();
await Dispatcher.InvokeAsync(() =>
{
item.Update();
});
}
}
}
......
......@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private string protocol;
private string host;
private string url;
private long bodySize;
private long? bodySize;
private string process;
private long receivedDataCount;
private long sentDataCount;
......@@ -48,7 +48,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
set => SetField(ref url, value);
}
public long BodySize
public long? BodySize
{
get => bodySize;
set => SetField(ref bodySize, value);
......@@ -76,8 +76,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
protected void SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null)
{
field = value;
OnPropertyChanged(propertyName);
if (!Equals(field, value))
{
field = value;
OnPropertyChanged(propertyName);
}
}
[NotifyPropertyChangedInvocator]
......@@ -105,7 +108,24 @@ namespace Titanium.Web.Proxy.Examples.Wpf
Url = request.RequestUri.AbsolutePath;
}
BodySize = response?.ContentLength ?? -1;
if (!IsTunnelConnect)
{
long responseSize = -1;
if (response != null)
{
if (response.ContentLength != -1)
{
responseSize = response.ContentLength;
}
else if (response.IsBodyRead && response.Body != null)
{
responseSize = response.Body.Length;
}
}
BodySize = responseSize;
}
Process = GetProcessDescription(WebSession.ProcessId.Value);
}
......
......@@ -51,8 +51,8 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="StreamExtended, Version=1.0.110.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.110-beta\lib\net45\StreamExtended.dll</HintPath>
<Reference Include="StreamExtended, Version=1.0.132.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.132-beta\lib\net45\StreamExtended.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="StreamExtended" version="1.0.110-beta" targetFramework="net45" />
<package id="StreamExtended" version="1.0.132-beta" targetFramework="net45" />
</packages>
\ No newline at end of file
using System;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.EventArguments
{
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
}
\ No newline at end of file
using Titanium.Web.Proxy.Http;
using System;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments
......@@ -7,8 +8,8 @@ namespace Titanium.Web.Proxy.EventArguments
{
public bool IsHttpsConnect { get; set; }
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest)
: base(bufferSize, endPoint, null)
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, Action<Exception> exceptionFunc)
: base(bufferSize, endPoint, null, exceptionFunc)
{
WebSession.Request = connectRequest;
}
......
using System;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Extensions
{
internal static class FuncExtensions
{
public static void InvokeParallel<T>(this Func<object, T, Task> callback, object sender, T args)
public static void InvokeParallel<T>(this AsyncEventHandler<T> callback, object sender, T args)
{
var invocationList = callback.GetInvocationList();
var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, T, Task>)invocationList[i])(sender, args);
handlerTasks[i] = ((AsyncEventHandler<T>)invocationList[i])(sender, args);
}
Task.WhenAll(handlerTasks).Wait();
}
public static async Task InvokeParallelAsync<T>(this Func<object, T, Task> callback, object sender, T args, Action<Exception> exceptionFunc)
public static async Task InvokeParallelAsync<T>(this AsyncEventHandler<T> callback, object sender, T args, Action<Exception> exceptionFunc)
{
var invocationList = callback.GetInvocationList();
var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = InvokeAsync((Func<object, T, Task>)invocationList[i], sender, args, exceptionFunc);
handlerTasks[i] = InternalInvokeAsync((AsyncEventHandler<T>)invocationList[i], sender, args, exceptionFunc);
}
await Task.WhenAll(handlerTasks);
}
private static async Task InvokeAsync<T>(Func<object, T, Task> callback, object sender, T args, Action<Exception> exceptionFunc)
public static async Task InvokeAsync<T>(this AsyncEventHandler<T> callback, object sender, T args, Action<Exception> exceptionFunc)
{
var invocationList = callback.GetInvocationList();
for (int i = 0; i < invocationList.Length; i++)
{
await InternalInvokeAsync((AsyncEventHandler<T>)invocationList[i], sender, args, exceptionFunc);
}
}
private static async Task InternalInvokeAsync<T>(AsyncEventHandler<T> callback, object sender, T args, Action<Exception> exceptionFunc)
{
try
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StreamExtended;
namespace Titanium.Web.Proxy.Extensions
{
static class SslExtensions
{
public static string GetServerName(this ClientHelloInfo clientHelloInfo)
{
if (clientHelloInfo.Extensions != null && clientHelloInfo.Extensions.TryGetValue("server_name", out var serverNameExtension))
{
return serverNameExtension.Data;
}
return null;
}
}
}
using System;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using StreamExtended.Helpers;
namespace Titanium.Web.Proxy.Extensions
{
......@@ -18,83 +18,59 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="output"></param>
/// <param name="onCopy"></param>
/// <param name="bufferSize"></param>
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize)
internal static Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize)
{
byte[] buffer = new byte[bufferSize];
while (true)
{
int num = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
int bytesRead;
if ((bytesRead = num) != 0)
{
await output.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
onCopy?.Invoke(buffer, 0, bytesRead);
}
else
{
break;
}
}
return CopyToAsync(input, output, onCopy, bufferSize, CancellationToken.None);
}
/// <summary>
/// copies the specified bytes to the stream from the input stream
/// Copy streams asynchronously
/// </summary>
/// <param name="streamReader"></param>
/// <param name="stream"></param>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead)
/// <param name="input"></param>
/// <param name="output"></param>
/// <param name="onCopy"></param>
/// <param name="bufferSize"></param>
/// <param name="cancellationToken"></param>
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize, CancellationToken cancellationToken)
{
var buffer = streamReader.Buffer;
long remainingBytes = totalBytesToRead;
while (remainingBytes > 0)
byte[] buffer = BufferPool.GetBuffer(bufferSize);
try
{
int bytesToRead = buffer.Length;
if (remainingBytes < bytesToRead)
{
bytesToRead = (int)remainingBytes;
}
int bytesRead = await streamReader.ReadBytesAsync(buffer, bytesToRead);
if (bytesRead == 0)
while (!cancellationToken.IsCancellationRequested)
{
break;
// cancellation is not working on Socket ReadAsync
// https://github.com/dotnet/corefx/issues/15033
int num = await input.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).WithCancellation(cancellationToken);
int bytesRead;
if ((bytesRead = num) != 0 && !cancellationToken.IsCancellationRequested)
{
await output.WriteAsync(buffer, 0, bytesRead, CancellationToken.None);
onCopy?.Invoke(buffer, 0, bytesRead);
}
else
{
break;
}
}
remainingBytes -= bytesRead;
await stream.WriteAsync(buffer, 0, bytesRead);
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
/// <summary>
/// Copies the stream chunked
/// </summary>
/// <param name="clientStreamReader"></param>
/// <param name="stream"></param>
/// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
private static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
while (true)
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
string chuchkHead = await clientStreamReader.ReadLineAsync();
int chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
await CopyBytesToStream(clientStreamReader, stream, chunkSize);
//chunk trail
await clientStreamReader.ReadLineAsync();
}
else
if (task != await Task.WhenAny(task, tcs.Task))
{
await clientStreamReader.ReadLineAsync();
break;
return default(T);
}
}
return await task;
}
}
}
using Titanium.Web.Proxy.Helpers;
using System;
using System.Net.Sockets;
using System.Reflection;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Extensions
{
internal static class TcpExtensions
{
private static readonly Func<Socket, bool> socketCleanedUpGetter;
static TcpExtensions()
{
var property = typeof(Socket).GetProperty("CleanedUp", BindingFlags.NonPublic | BindingFlags.Instance);
if (property != null)
{
var method = property.GetMethod;
if (method != null && method.ReturnType == typeof(bool))
{
socketCleanedUpGetter = (Func<Socket, bool>)Delegate.CreateDelegate(typeof(Func<Socket, bool>), method);
}
}
}
/// <summary>
/// Gets the local port from a native TCP row object.
......@@ -24,5 +41,30 @@ namespace Titanium.Web.Proxy.Extensions
{
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
}
internal static void CloseSocket(this TcpClient tcpClient)
{
if (tcpClient == null)
{
return;
}
try
{
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
if (socketCleanedUpGetter == null || !socketCleanedUpGetter(tcpClient.Client))
{
tcpClient.LingerState = new LingerOption(true, 0);
}
tcpClient.Close();
}
catch
{
}
}
}
}
using System;
using System.Text;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
......@@ -28,7 +29,7 @@ namespace Titanium.Web.Proxy.Helpers
foreach (string parameter in parameters)
{
var split = parameter.Split(ProxyConstants.EqualSplit, 2);
if (split.Length == 2 && split[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
if (split.Length == 2 && split[0].Trim().Equals(KnownHeaders.ContentTypeCharset, StringComparison.CurrentCultureIgnoreCase))
{
string value = split[1];
if (value.Equals("x-user-defined", StringComparison.OrdinalIgnoreCase))
......@@ -65,7 +66,7 @@ namespace Titanium.Web.Proxy.Helpers
foreach (string parameter in parameters)
{
var split = parameter.Split(ProxyConstants.EqualSplit, 2);
if (split.Length == 2 && split[0].Trim().Equals("boundary", StringComparison.CurrentCultureIgnoreCase))
if (split.Length == 2 && split[0].Trim().Equals(KnownHeaders.ContentTypeBoundary, StringComparison.CurrentCultureIgnoreCase))
{
string value = split[1];
if (value.Length > 2 && value[0] == '"' && value[value.Length - 1] == '"')
......@@ -97,6 +98,13 @@ namespace Titanium.Web.Proxy.Helpers
if (hostname.Split(ProxyConstants.DotSplit).Length > 2)
{
int idx = hostname.IndexOf(ProxyConstants.DotSplit);
//issue #352
if(hostname.Substring(0, idx).Contains("-"))
{
return hostname;
}
string rootDomain = hostname.Substring(idx + 1);
return "*." + rootDomain;
}
......
......@@ -4,8 +4,7 @@ namespace Titanium.Web.Proxy.Helpers
{
sealed class HttpRequestWriter : HttpWriter
{
public HttpRequestWriter(Stream stream, int bufferSize)
: base(stream, bufferSize, true)
public HttpRequestWriter(Stream stream, int bufferSize) : base(stream, bufferSize)
{
}
}
......
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
sealed class HttpResponseWriter : HttpWriter
{
public HttpResponseWriter(Stream stream, int bufferSize)
: base(stream, bufferSize, true)
public HttpResponseWriter(Stream stream, int bufferSize) : base(stream, bufferSize)
{
}
......@@ -39,102 +33,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns>
public Task WriteResponseStatusAsync(Version version, int code, string description)
{
return WriteLineAsync($"HTTP/{version.Major}.{version.Minor} {code} {description}");
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal async Task WriteResponseBodyAsync(byte[] data, bool isChunked)
{
if (!isChunked)
{
await BaseStream.WriteAsync(data, 0, data.Length);
}
else
{
await WriteResponseBodyChunkedAsync(data);
}
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="inStreamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <returns></returns>
internal async Task WriteResponseBodyAsync(int bufferSize, CustomBinaryReader inStreamReader, bool isChunked,
long contentLength)
{
if (!isChunked)
{
//http 1.0
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
await inStreamReader.CopyBytesToStream(BaseStream, contentLength);
}
else
{
await WriteResponseBodyChunkedAsync(inStreamReader);
}
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <returns></returns>
internal async Task WriteResponseBodyChunkedAsync(CustomBinaryReader inStreamReader)
{
while (true)
{
string chunkHead = await inStreamReader.ReadLineAsync();
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
await BaseStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await inStreamReader.CopyBytesToStream(BaseStream, chunkSize);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await inStreamReader.ReadLineAsync();
}
else
{
await inStreamReader.ReadLineAsync();
await BaseStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
break;
}
}
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
internal async Task WriteResponseBodyChunkedAsync(byte[] data)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await BaseStream.WriteAsync(chunkHead, 0, chunkHead.Length);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await BaseStream.WriteAsync(data, 0, data.Length);
await BaseStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await BaseStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
return WriteLineAsync(Response.CreateResponseLine(version, code, description));
}
}
}
using System.IO;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
abstract class HttpWriter : StreamWriter
internal class HttpWriter : CustomBinaryWriter
{
protected HttpWriter(Stream stream, int bufferSize, bool leaveOpen)
: base(stream, Encoding.ASCII, bufferSize, leaveOpen)
public int BufferSize { get; }
private readonly char[] charBuffer;
private static readonly byte[] newLine = ProxyConstants.NewLine;
private static readonly Encoder encoder = Encoding.ASCII.GetEncoder();
internal HttpWriter(Stream stream, int bufferSize) : base(stream)
{
NewLine = ProxyConstants.NewLine;
BufferSize = bufferSize;
// ASCII encoder max byte count is char count + 1
charBuffer = new char[BufferSize - 1];
}
public void WriteHeaders(HeaderCollection headers, bool flush = true)
public Task WriteLineAsync()
{
if (headers != null)
return WriteAsync(newLine);
}
public async Task WriteAsync(string value)
{
int charCount = value.Length;
value.CopyTo(0, charBuffer, 0, charCount);
if (charCount < BufferSize)
{
foreach (var header in headers)
var buffer = BufferPool.GetBuffer(BufferSize);
try
{
header.WriteToStream(this);
int idx = encoder.GetBytes(charBuffer, 0, charCount, buffer, 0, true);
await WriteAsync(buffer, 0, idx);
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
WriteLine();
if (flush)
else
{
Flush();
var buffer = new byte[charCount + 1];
int idx = encoder.GetBytes(charBuffer, 0, charCount, buffer, 0, true);
await WriteAsync(buffer, 0, idx);
}
}
public async Task WriteLineAsync(string value)
{
await WriteAsync(value);
await WriteLineAsync();
}
/// <summary>
/// Write the headers to client
/// </summary>
......@@ -50,8 +84,161 @@ namespace Titanium.Web.Proxy.Helpers
await WriteLineAsync();
if (flush)
{
// flush the stream
await FlushAsync();
}
}
public async Task WriteAsync(byte[] data, bool flush = false)
{
await WriteAsync(data, 0, data.Length);
if (flush)
{
// flush the stream
await FlushAsync();
}
}
public async Task WriteAsync(byte[] data, int offset, int count, bool flush)
{
await WriteAsync(data, offset, count);
if (flush)
{
// flush the stream
await FlushAsync();
}
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal Task WriteBodyAsync(byte[] data, bool isChunked)
{
if (isChunked)
{
return WriteBodyChunkedAsync(data);
}
return WriteAsync(data);
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="streamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <param name="removeChunkedEncoding"></param>
/// <returns></returns>
internal Task CopyBodyAsync(CustomBinaryReader streamReader, bool isChunked, long contentLength, bool removeChunkedEncoding)
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (isChunked)
{
//Need to revist, find any potential bugs
//send the body bytes to server in chunks
return CopyBodyChunkedAsync(streamReader, removeChunkedEncoding);
}
//http 1.0
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
//If not chunked then its easy just read the amount of bytes mentioned in content length header
return CopyBytesFromStream(streamReader, contentLength);
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private async Task WriteBodyChunkedAsync(byte[] data)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await WriteAsync(chunkHead);
await WriteLineAsync();
await WriteAsync(data);
await WriteLineAsync();
await WriteLineAsync("0");
await WriteLineAsync();
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="reader"></param>
/// <param name="removeChunkedEncoding"></param>
/// <returns></returns>
private async Task CopyBodyChunkedAsync(CustomBinaryReader reader, bool removeChunkedEncoding)
{
while (true)
{
string chunkHead = await reader.ReadLineAsync();
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (!removeChunkedEncoding)
{
await WriteLineAsync(chunkHead);
}
if (chunkSize != 0)
{
await CopyBytesFromStream(reader, chunkSize);
}
if (!removeChunkedEncoding)
{
await WriteLineAsync();
}
//chunk trail
await reader.ReadLineAsync();
if (chunkSize == 0)
{
break;
}
}
}
/// <summary>
/// Copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="reader"></param>
/// <param name="count"></param>
/// <returns></returns>
private async Task CopyBytesFromStream(CustomBinaryReader reader, long count)
{
var buffer = reader.Buffer;
long remainingBytes = count;
while (remainingBytes > 0)
{
int bytesToRead = buffer.Length;
if (remainingBytes < bytesToRead)
{
bytesToRead = (int)remainingBytes;
}
int bytesRead = await reader.ReadBytesAsync(buffer, bytesToRead);
if (bytesRead == 0)
{
break;
}
remainingBytes -= bytesRead;
await WriteAsync(buffer, 0, bytesRead);
}
}
}
}
......@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network.Tcp;
......@@ -111,16 +112,21 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
/// <param name="bufferSize"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <returns></returns>
internal static async Task SendRaw(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive)
{
var cts = new CancellationTokenSource();
//Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(serverStream, onDataSend, bufferSize);
var sendRelay = clientStream.CopyToAsync(serverStream, onDataSend, bufferSize, cts.Token);
var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive, bufferSize, cts.Token);
var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive, bufferSize);
await Task.WhenAny(sendRelay, receiveRelay);
cts.Cancel();
await Task.WhenAll(sendRelay, receiveRelay);
}
......
......@@ -22,7 +22,7 @@ namespace Titanium.Web.Proxy.Http
StatusDescription = "Connection Established"
};
response.Headers.AddHeader("Timestamp", DateTime.Now.ToString());
response.Headers.AddHeader(KnownHeaders.Timestamp, DateTime.Now.ToString());
return response;
}
}
......
......@@ -63,6 +63,21 @@ namespace Titanium.Web.Proxy.Http
return null;
}
public HttpHeader GetFirstHeader(string name)
{
if (Headers.TryGetValue(name, out var header))
{
return header;
}
if (NonUniqueHeaders.TryGetValue(name, out var headers))
{
return headers.FirstOrDefault();
}
return null;
}
/// <summary>
/// Returns all headers
/// </summary>
......@@ -235,7 +250,7 @@ namespace Titanium.Web.Proxy.Http
return null;
}
internal string SetOrAddHeaderValue(string headerName, string value)
internal void SetOrAddHeaderValue(string headerName, string value)
{
if (Headers.TryGetValue(headerName, out var header))
{
......@@ -245,8 +260,6 @@ namespace Titanium.Web.Proxy.Http
{
Headers.Add(headerName, new HttpHeader(headerName, value));
}
return null;
}
/// <summary>
......@@ -255,12 +268,12 @@ namespace Titanium.Web.Proxy.Http
internal void FixProxyHeaders()
{
//If proxy-connection close was returned inform to close the connection
string proxyHeader = GetHeaderValueOrNull("proxy-connection");
RemoveHeader("proxy-connection");
string proxyHeader = GetHeaderValueOrNull(KnownHeaders.ProxyConnection);
RemoveHeader(KnownHeaders.ProxyConnection);
if (proxyHeader != null)
{
SetOrAddHeaderValue("connection", proxyHeader);
SetOrAddHeaderValue(KnownHeaders.Connection, proxyHeader);
}
}
......
......@@ -2,7 +2,6 @@ using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
......@@ -74,61 +73,45 @@ namespace Titanium.Web.Proxy.Http
connection.LastAccess = DateTime.Now;
ServerConnection = connection;
}
/// <summary>
/// Prepare and send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour)
{
var stream = ServerConnection.Stream;
var upstreamProxy = ServerConnection.UpStreamProxy;
byte[] requestBytes;
using (var ms = new MemoryStream())
using (var writer = new HttpRequestWriter(ms, bufferSize))
{
var upstreamProxy = ServerConnection.UpStreamProxy;
bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false;
bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false;
var writer = ServerConnection.StreamWriter;
//prepare the request & headers
if (useUpstreamProxy)
{
writer.WriteLine($"{Request.Method} {Request.OriginalUrl} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
}
else
{
writer.WriteLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
}
//prepare the request & headers
await writer.WriteLineAsync(Request.CreateRequestLine(Request.Method,
useUpstreamProxy ? Request.OriginalUrl : Request.RequestUri.PathAndQuery,
Request.HttpVersion));
//Send Authentication to Upstream proxy if needed
if (upstreamProxy != null
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null)
{
HttpHeader.ProxyConnectionKeepAlive.WriteToStream(writer);
HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStream(writer);
}
//Send Authentication to Upstream proxy if needed
if (upstreamProxy != null
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null)
{
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStreamAsync(writer);
}
//write request headers
foreach (var header in Request.Headers)
//write request headers
foreach (var header in Request.Headers)
{
if (header.Name != KnownHeaders.ProxyAuthorization)
{
if (header.Name != "Proxy-Authorization")
{
header.WriteToStream(writer);
}
await header.WriteToStreamAsync(writer);
}
writer.WriteLine();
writer.Flush();
requestBytes = ms.ToArray();
}
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync();
await writer.WriteLineAsync();
if (enable100ContinueBehaviour)
{
......@@ -191,6 +174,7 @@ namespace Titanium.Web.Proxy.Http
Response.Is100Continue = true;
Response.StatusCode = 0;
await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse();
return;
......@@ -203,6 +187,7 @@ namespace Titanium.Web.Proxy.Http
Response.ExpectationFailed = true;
Response.StatusCode = 0;
await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse();
return;
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Http
{
public static class KnownHeaders
{
// Both
public const string Connection = "connection";
public const string ConnectionClose = "close";
public const string ConnectionKeepAlive = "keep-alive";
public const string ContentLength = "content-length";
public const string ContentType = "content-type";
public const string ContentTypeCharset = "charset";
public const string ContentTypeBoundary = "boundary";
public const string Upgrade = "upgrade";
public const string UpgradeWebsocket = "websocket";
// Request headers
public const string AcceptEncoding = "accept-encoding";
public const string Authorization = "Authorization";
public const string Expect = "expect";
public const string Expect100Continue = "100-continue";
public const string Host = "host";
public const string ProxyAuthorization = "Proxy-Authorization";
public const string ProxyConnection = "Proxy-Connection";
public const string ProxyConnectionClose = "close";
// Response headers
public const string ContentEncoding = "content-encoding";
public const string Location = "Location";
public const string ProxyAuthenticate = "Proxy-Authenticate";
public const string TransferEncoding = "transfer-encoding";
public const string TransferEncodingChunked = "chunked";
// ???
public const string Timestamp = "Timestamp";
}
}
......@@ -67,14 +67,14 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public string Host
{
get => Headers.GetHeaderValueOrNull("host");
set => Headers.SetOrAddHeaderValue("host", value);
get => Headers.GetHeaderValueOrNull(KnownHeaders.Host);
set => Headers.SetOrAddHeaderValue(KnownHeaders.Host, value);
}
/// <summary>
/// Content encoding header value
/// </summary>
public string ContentEncoding => Headers.GetHeaderValueOrNull("content-encoding");
public string ContentEncoding => Headers.GetHeaderValueOrNull(KnownHeaders.ContentEncoding);
/// <summary>
/// Request content-length
......@@ -83,7 +83,7 @@ namespace Titanium.Web.Proxy.Http
{
get
{
string headerValue = Headers.GetHeaderValueOrNull("content-length");
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.ContentLength);
if (headerValue == null)
{
......@@ -102,12 +102,12 @@ namespace Titanium.Web.Proxy.Http
{
if (value >= 0)
{
Headers.SetOrAddHeaderValue("content-length", value.ToString());
Headers.SetOrAddHeaderValue(KnownHeaders.ContentLength, value.ToString());
IsChunked = false;
}
else
{
Headers.RemoveHeader("content-length");
Headers.RemoveHeader(KnownHeaders.ContentLength);
}
}
}
......@@ -117,8 +117,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public string ContentType
{
get => Headers.GetHeaderValueOrNull("content-type");
set => Headers.SetOrAddHeaderValue("content-type", value);
get => Headers.GetHeaderValueOrNull(KnownHeaders.ContentType);
set => Headers.SetOrAddHeaderValue(KnownHeaders.ContentType, value);
}
/// <summary>
......@@ -128,19 +128,19 @@ namespace Titanium.Web.Proxy.Http
{
get
{
string headerValue = Headers.GetHeaderValueOrNull("transfer-encoding");
return headerValue != null && headerValue.ContainsIgnoreCase("chunked");
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.TransferEncoding);
return headerValue != null && headerValue.ContainsIgnoreCase(KnownHeaders.TransferEncodingChunked);
}
set
{
if (value)
{
Headers.SetOrAddHeaderValue("transfer-encoding", "chunked");
Headers.SetOrAddHeaderValue(KnownHeaders.TransferEncoding, KnownHeaders.TransferEncodingChunked);
ContentLength = -1;
}
else
{
Headers.RemoveHeader("transfer-encoding");
Headers.RemoveHeader(KnownHeaders.TransferEncoding);
}
}
}
......@@ -152,8 +152,8 @@ namespace Titanium.Web.Proxy.Http
{
get
{
string headerValue = Headers.GetHeaderValueOrNull("expect");
return headerValue != null && headerValue.Equals("100-continue");
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.Expect);
return headerValue != null && headerValue.Equals(KnownHeaders.Expect100Continue);
}
}
......@@ -241,14 +241,14 @@ namespace Titanium.Web.Proxy.Http
{
get
{
string headerValue = Headers.GetHeaderValueOrNull("upgrade");
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.Upgrade);
if (headerValue == null)
{
return false;
}
return headerValue.Equals("websocket", StringComparison.CurrentCultureIgnoreCase);
return headerValue.Equals(KnownHeaders.UpgradeWebsocket, StringComparison.CurrentCultureIgnoreCase);
}
}
......@@ -275,7 +275,7 @@ namespace Titanium.Web.Proxy.Http
get
{
var sb = new StringBuilder();
sb.AppendLine($"{Method} {OriginalUrl} HTTP/{HttpVersion.Major}.{HttpVersion.Minor}");
sb.AppendLine(CreateRequestLine(Method, OriginalUrl, HttpVersion));
foreach (var header in Headers)
{
sb.AppendLine(header.ToString());
......@@ -286,6 +286,11 @@ namespace Titanium.Web.Proxy.Http
}
}
internal static string CreateRequestLine(string httpMethod, string httpUrl, Version version)
{
return $"{httpMethod} {httpUrl} HTTP/{version.Major}.{version.Minor}";
}
internal static void ParseRequestLine(string httpCmd, out string httpMethod, out string httpUrl, out Version version)
{
//break up the line into three components (method, remote URL & Http Version)
......
......@@ -42,12 +42,12 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Content encoding for this response
/// </summary>
public string ContentEncoding => Headers.GetHeaderValueOrNull("content-encoding")?.Trim();
public string ContentEncoding => Headers.GetHeaderValueOrNull(KnownHeaders.ContentEncoding)?.Trim();
/// <summary>
/// Http version
/// </summary>
public Version HttpVersion { get; set; }
public Version HttpVersion { get; set; } = HttpHeader.VersionUnknown;
/// <summary>
/// Keeps the response body data after the session is finished
......@@ -61,16 +61,24 @@ namespace Titanium.Web.Proxy.Http
{
get
{
long contentLength = ContentLength;
//If content length is set to 0 the response has no body
if (contentLength == 0)
{
return false;
}
//Has body only if response is chunked or content length >0
//If none are true then check if connection:close header exist, if so write response until server or client terminates the connection
if (IsChunked || ContentLength > 0 || !KeepAlive)
if (IsChunked || contentLength > 0 || !KeepAlive)
{
return true;
}
//has response if connection:keep-alive header exist and when version is http/1.0
//Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream)
if (KeepAlive && HttpVersion.Minor == 0)
if (KeepAlive && HttpVersion == HttpHeader.Version10)
{
return true;
}
......@@ -86,11 +94,11 @@ namespace Titanium.Web.Proxy.Http
{
get
{
string headerValue = Headers.GetHeaderValueOrNull("connection");
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.Connection);
if (headerValue != null)
{
if (headerValue.ContainsIgnoreCase("close"))
if (headerValue.ContainsIgnoreCase(KnownHeaders.ConnectionClose))
{
return false;
}
......@@ -103,7 +111,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Content type of this response
/// </summary>
public string ContentType => Headers.GetHeaderValueOrNull("content-type");
public string ContentType => Headers.GetHeaderValueOrNull(KnownHeaders.ContentType);
/// <summary>
/// Length of response body
......@@ -112,15 +120,14 @@ namespace Titanium.Web.Proxy.Http
{
get
{
string headerValue = Headers.GetHeaderValueOrNull("content-length");
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.ContentLength);
if (headerValue == null)
{
return -1;
}
long.TryParse(headerValue, out long contentLen);
if (contentLen >= 0)
if (long.TryParse(headerValue, out long contentLen))
{
return contentLen;
}
......@@ -131,12 +138,12 @@ namespace Titanium.Web.Proxy.Http
{
if (value >= 0)
{
Headers.SetOrAddHeaderValue("content-length", value.ToString());
Headers.SetOrAddHeaderValue(KnownHeaders.ContentLength, value.ToString());
IsChunked = false;
}
else
{
Headers.RemoveHeader("content-length");
Headers.RemoveHeader(KnownHeaders.ContentLength);
}
}
}
......@@ -148,19 +155,19 @@ namespace Titanium.Web.Proxy.Http
{
get
{
string headerValue = Headers.GetHeaderValueOrNull("transfer-encoding");
return headerValue != null && headerValue.ContainsIgnoreCase("chunked");
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.TransferEncoding);
return headerValue != null && headerValue.ContainsIgnoreCase(KnownHeaders.TransferEncodingChunked);
}
set
{
if (value)
{
Headers.SetOrAddHeaderValue("transfer-encoding", "chunked");
Headers.SetOrAddHeaderValue(KnownHeaders.TransferEncoding, KnownHeaders.TransferEncodingChunked);
ContentLength = -1;
}
else
{
Headers.RemoveHeader("transfer-encoding");
Headers.RemoveHeader(KnownHeaders.TransferEncoding);
}
}
}
......@@ -228,7 +235,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Gets the resposne status.
/// </summary>
public string Status => $"HTTP/{HttpVersion?.Major}.{HttpVersion?.Minor} {StatusCode} {StatusDescription}";
public string Status => CreateResponseLine(HttpVersion, StatusCode, StatusDescription);
/// <summary>
/// Gets the header text.
......@@ -249,6 +256,11 @@ namespace Titanium.Web.Proxy.Http
}
}
internal static string CreateResponseLine(Version version, int statusCode, string statusDescription)
{
return $"HTTP/{version.Major}.{version.Minor} {statusCode} {statusDescription}";
}
internal static void ParseResponseLine(string httpStatus, out Version version, out int statusCode, out string statusDescription)
{
var httpResult = httpStatus.Split(ProxyConstants.SpaceSplit, 3);
......
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Models
{
......@@ -10,6 +11,8 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class HttpHeader
{
internal static readonly Version VersionUnknown = new Version(0, 0);
internal static readonly Version Version10 = new Version(1, 0);
internal static readonly Version Version11 = new Version(1, 1);
......@@ -54,19 +57,12 @@ namespace Titanium.Web.Proxy.Models
internal static HttpHeader GetProxyAuthorizationHeader(string userName, string password)
{
var result = new HttpHeader("Proxy-Authorization",
var result = new HttpHeader(KnownHeaders.ProxyAuthorization,
"Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{userName}:{password}")));
return result;
}
internal void WriteToStream(StreamWriter writer)
{
writer.Write(Name);
writer.Write(": ");
writer.WriteLine(Value);
}
internal async Task WriteToStreamAsync(StreamWriter writer)
internal async Task WriteToStreamAsync(HttpWriter writer)
{
await writer.WriteAsync(Name);
await writer.WriteAsync(": ");
......
#if DEBUG
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
namespace Titanium.Web.Proxy.Network
{
class DebugCustomBufferedStream : CustomBufferedStream
{
private const string basePath = @".";
private static int counter;
public int Counter { get; }
private readonly FileStream fileStreamSent;
private readonly FileStream fileStreamReceived;
public DebugCustomBufferedStream(Stream baseStream, int bufferSize) : base(baseStream, bufferSize)
{
Counter = Interlocked.Increment(ref counter);
fileStreamSent = new FileStream(Path.Combine(basePath, $"{Counter}_sent.dat"), FileMode.Create);
fileStreamReceived = new FileStream(Path.Combine(basePath, $"{Counter}_received.dat"), FileMode.Create);
}
protected override void OnDataSent(byte[] buffer, int offset, int count)
{
fileStreamSent.Write(buffer, offset, count);
}
protected override void OnDataReceived(byte[] buffer, int offset, int count)
{
fileStreamReceived.Write(buffer, offset, count);
}
public override void Flush()
{
fileStreamSent.Flush(true);
fileStreamReceived.Flush(true);
base.Flush();
}
}
}
#endif
\ No newline at end of file
......@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy.Network
internal TcpClient TcpClient { get; set; }
/// <summary>
/// holds the stream to client
/// Holds the stream to client
/// </summary>
internal CustomBufferedStream ClientStream { get; set; }
......@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy.Network
internal CustomBinaryReader ClientStreamReader { get; set; }
/// <summary>
/// used to write line by line to client
/// Used to write line by line to client
/// </summary>
internal HttpResponseWriter ClientStreamWriter { get; set; }
}
......
......@@ -2,6 +2,8 @@
using System;
using System.Net;
using System.Net.Sockets;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp
......@@ -11,6 +13,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
internal class TcpConnection : IDisposable
{
private ProxyServer proxyServer { get; }
internal ExternalProxy UpStreamProxy { get; set; }
internal string HostName { get; set; }
......@@ -34,10 +38,15 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal TcpClient TcpClient { private get; set; }
/// <summary>
/// used to read lines from server
/// Used to read lines from server
/// </summary>
internal CustomBinaryReader StreamReader { get; set; }
/// <summary>
/// Used to write lines to server
/// </summary>
internal HttpRequestWriter StreamWriter { get; set; }
/// <summary>
/// Server stream
/// </summary>
......@@ -48,9 +57,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
internal DateTime LastAccess { get; set; }
internal TcpConnection()
internal TcpConnection(ProxyServer proxyServer)
{
LastAccess = DateTime.Now;
this.proxyServer = proxyServer;
this.proxyServer.UpdateServerConnectionCount(true);
}
/// <summary>
......@@ -58,25 +69,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
public void Dispose()
{
StreamReader?.Dispose();
Stream?.Dispose();
StreamReader?.Dispose();
TcpClient.CloseSocket();
try
{
if (TcpClient != null)
{
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
TcpClient.LingerState = new LingerOption(true, 0);
TcpClient.Close();
}
}
catch
{
}
proxyServer.UpdateServerConnectionCount(false);
}
}
}
......@@ -8,6 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp
......@@ -68,24 +69,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (useUpstreamProxy && (isConnect || isHttps))
{
using (var writer = new HttpRequestWriter(stream, server.BufferSize))
{
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
}
var writer = new HttpRequestWriter(stream, server.BufferSize);
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync($"{KnownHeaders.Connection}: {KnownHeaders.ConnectionKeepAlive}");
await writer.WriteLineAsync();
await writer.FlushAsync();
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync(KnownHeaders.ProxyAuthorization + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
}
await writer.WriteLineAsync();
using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{
string result = await reader.ReadLineAsync();
......@@ -118,9 +116,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
throw;
}
server.UpdateServerConnectionCount(true);
return new TcpConnection
return new TcpConnection(server)
{
UpStreamProxy = externalProxy,
UpStreamEndPoint = upStreamEndPoint,
......@@ -130,6 +126,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
UseUpstreamProxy = useUpstreamProxy,
TcpClient = client,
StreamReader = new CustomBinaryReader(stream, server.BufferSize),
StreamWriter = new HttpRequestWriter(stream, server.BufferSize),
Stream = stream,
Version = httpVersion
};
......
......@@ -8,6 +8,7 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy
{
......@@ -20,37 +21,36 @@ namespace Titanium.Web.Proxy
return true;
}
var httpHeaders = session.WebSession.Request.Headers.ToArray();
var httpHeaders = session.WebSession.Request.Headers;
try
{
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization");
var header = httpHeaders.GetFirstHeader(KnownHeaders.ProxyAuthorization);
if (header == null)
{
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required");
return false;
}
string headerValue = header.Value.Trim();
if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase))
var headerValueParts = header.Value.Split(ProxyConstants.SpaceSplit);
if (headerValueParts.Length != 2 || !headerValueParts[0].Equals("basic", StringComparison.CurrentCultureIgnoreCase))
{
//Return not authorized
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
headerValue = headerValue.Substring(5).Trim();
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
if (decoded.Contains(":") == false)
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValueParts[1]));
int colonIndex = decoded.IndexOf(':');
if (colonIndex == -1)
{
//Return not authorized
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
string username = decoded.Substring(0, decoded.IndexOf(':'));
string password = decoded.Substring(decoded.IndexOf(':') + 1);
string username = decoded.Substring(0, colonIndex);
string password = decoded.Substring(colonIndex + 1);
return await AuthenticateUserFunc(username, password);
}
catch (Exception e)
......@@ -72,8 +72,8 @@ namespace Titanium.Web.Proxy
StatusDescription = description
};
response.Headers.AddHeader("Proxy-Authenticate", $"Basic realm=\"{ProxyRealm}\"");
response.Headers.AddHeader("Proxy-Connection", "close");
response.Headers.AddHeader(KnownHeaders.ProxyAuthenticate, $"Basic realm=\"{ProxyRealm}\"");
response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ProxyConnectionClose);
await clientStreamWriter.WriteResponseAsync(response);
return response;
......
......@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers.WinHttp;
using Titanium.Web.Proxy.Models;
......@@ -164,22 +165,22 @@ namespace Titanium.Web.Proxy
/// <summary>
/// Intercept request to server
/// </summary>
public event Func<object, SessionEventArgs, Task> BeforeRequest;
public event AsyncEventHandler<SessionEventArgs> BeforeRequest;
/// <summary>
/// Intercept response from server
/// </summary>
public event Func<object, SessionEventArgs, Task> BeforeResponse;
public event AsyncEventHandler<SessionEventArgs> BeforeResponse;
/// <summary>
/// Intercept tunnel connect reques
/// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectRequest;
public event AsyncEventHandler<TunnelConnectSessionEventArgs> TunnelConnectRequest;
/// <summary>
/// Intercept tunnel connect response
/// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectResponse;
public event AsyncEventHandler<TunnelConnectSessionEventArgs> TunnelConnectResponse;
/// <summary>
/// Occurs when client connection count changed.
......@@ -229,12 +230,12 @@ namespace Titanium.Web.Proxy
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary>
public event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback;
public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback;
/// <summary>
/// Callback tooverride client certificate during SSL mutual authentication
/// </summary>
public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback;
public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback;
/// <summary>
/// Callback for error events in proxy
......@@ -612,16 +613,14 @@ namespace Titanium.Web.Proxy
/// <param name="serverConnection"></param>
private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection)
{
clientStream?.Dispose();
clientStreamReader?.Dispose();
clientStreamWriter?.Dispose();
clientStream?.Dispose();
if (serverConnection != null)
{
serverConnection.Dispose();
serverConnection = null;
UpdateServerConnectionCount(false);
}
}
......@@ -766,22 +765,7 @@ namespace Titanium.Web.Proxy
finally
{
UpdateClientConnectionCount(false);
try
{
if (tcpClient != null)
{
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Close();
}
}
catch
{
}
tcpClient.CloseSocket();
}
}
......
This diff is collapsed.
......@@ -43,7 +43,7 @@ namespace Titanium.Web.Proxy
//If user requested call back then do it
if (BeforeResponse != null && !response.ResponseLocked)
{
await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc);
await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
}
//if user requested to send request again
......@@ -96,7 +96,7 @@ namespace Titanium.Web.Proxy
}
await clientStreamWriter.WriteHeadersAsync(response.Headers);
await clientStreamWriter.WriteResponseBodyAsync(response.Body, isChunked);
await clientStreamWriter.WriteBodyAsync(response.Body, isChunked);
}
else
{
......@@ -105,12 +105,9 @@ namespace Titanium.Web.Proxy
//Write body if exists
if (response.HasBody)
{
await clientStreamWriter.WriteResponseBodyAsync(BufferSize, args.WebSession.ServerConnection.StreamReader,
response.IsChunked, response.ContentLength);
await args.CopyResponseBodyAsync(clientStreamWriter, false);
}
}
await clientStreamWriter.FlushAsync();
}
catch (Exception e)
{
......
using System.Text;
namespace Titanium.Web.Proxy.Shared
namespace Titanium.Web.Proxy.Shared
{
/// <summary>
/// Literals shared by Proxy Server
......@@ -14,10 +12,6 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly char[] EqualSplit = { '=' };
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine);
internal static readonly byte[] ChunkEnd = Encoding.ASCII.GetBytes(0.ToString("x2") + NewLine + NewLine);
internal const string NewLine = "\r\n";
internal static readonly byte[] NewLine = {(byte)'\r', (byte)'\n' };
}
}
......@@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.3" />
<PackageReference Include="StreamExtended" Version="1.0.110-beta" />
<PackageReference Include="StreamExtended" Version="1.0.132-beta" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
......@@ -21,7 +21,7 @@
<Version>4.4.0</Version>
</PackageReference>
<PackageReference Include="System.Security.Principal.Windows">
<Version>4.4.0</Version>
<Version>4.4.1</Version>
</PackageReference>
</ItemGroup>
......
......@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.WinAuth;
......@@ -84,9 +85,9 @@ namespace Titanium.Web.Proxy
string scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase));
//clear any existing headers to avoid confusing bad servers
if (args.WebSession.Request.Headers.NonUniqueHeaders.ContainsKey("Authorization"))
if (args.WebSession.Request.Headers.NonUniqueHeaders.ContainsKey(KnownHeaders.Authorization))
{
args.WebSession.Request.Headers.NonUniqueHeaders.Remove("Authorization");
args.WebSession.Request.Headers.NonUniqueHeaders.Remove(KnownHeaders.Authorization);
}
//initial value will match exactly any of the schemes
......@@ -94,16 +95,16 @@ namespace Titanium.Web.Proxy
{
string clientToken = WinAuthHandler.GetInitialAuthToken(args.WebSession.Request.Host, scheme, args.Id);
var auth = new HttpHeader("Authorization", string.Concat(scheme, clientToken));
var auth = new HttpHeader(KnownHeaders.Authorization, string.Concat(scheme, clientToken));
//replace existing authorization header if any
if (args.WebSession.Request.Headers.Headers.ContainsKey("Authorization"))
if (args.WebSession.Request.Headers.Headers.ContainsKey(KnownHeaders.Authorization))
{
args.WebSession.Request.Headers.Headers["Authorization"] = auth;
args.WebSession.Request.Headers.Headers[KnownHeaders.Authorization] = auth;
}
else
{
args.WebSession.Request.Headers.Headers.Add("Authorization", auth);
args.WebSession.Request.Headers.Headers.Add(KnownHeaders.Authorization, auth);
}
//don't need to send body for Authorization request
......@@ -122,7 +123,7 @@ namespace Titanium.Web.Proxy
string clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id);
//there will be an existing header from initial client request
args.WebSession.Request.Headers.Headers["Authorization"] = new HttpHeader("Authorization", string.Concat(scheme, clientToken));
args.WebSession.Request.Headers.Headers[KnownHeaders.Authorization] = new HttpHeader(KnownHeaders.Authorization, string.Concat(scheme, clientToken));
//send body for final auth request
if (args.WebSession.Request.HasBody)
......
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