Commit e1543300 authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #275 from justcoding121/beta

Stable
parents acad670d ac2877be
...@@ -14,10 +14,6 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -14,10 +14,6 @@ namespace Titanium.Web.Proxy.Examples.Basic
//fix console hang due to QuickEdit mode //fix console hang due to QuickEdit mode
ConsoleHelper.DisableQuickEditMode(); ConsoleHelper.DisableQuickEditMode();
//On Console exit make sure we also exit the proxy
NativeMethods.Handler = ConsoleEventCallback;
NativeMethods.SetConsoleCtrlHandler(NativeMethods.Handler, true);
//Start proxy controller //Start proxy controller
controller.StartProxy(); controller.StartProxy();
...@@ -27,34 +23,5 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -27,34 +23,5 @@ namespace Titanium.Web.Proxy.Examples.Basic
controller.Stop(); controller.Stop();
} }
private static bool ConsoleEventCallback(int eventType)
{
if (eventType != 2) return false;
try
{
controller.Stop();
}
catch
{
// ignored
}
return false;
} }
}
internal static class NativeMethods
{
// Keeps it from getting garbage collected
internal static ConsoleEventDelegate Handler;
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
// Pinvoke
internal delegate bool ConsoleEventDelegate(int eventType);
}
} }
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Examples.Basic namespace Titanium.Web.Proxy.Examples.Basic
...@@ -15,8 +17,8 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -15,8 +17,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
//share requestBody outside handlers //share requestBody outside handlers
//Using a dictionary is not a good idea since it can cause memory overflow //Using a dictionary is not a good idea since it can cause memory overflow
//ideally the data should be moved out of memory //ideally the data should be moved out of memory
//private readonly Dictionary<Guid, string> requestBodyHistory //private readonly IDictionary<Guid, string> requestBodyHistory
// = new Dictionary<Guid, string>(); // = new ConcurrentDictionary<Guid, string>();
public ProxyTestController() public ProxyTestController()
{ {
...@@ -29,6 +31,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -29,6 +31,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message); proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message);
proxyServer.TrustRootCertificate = true; proxyServer.TrustRootCertificate = true;
proxyServer.ForwardToUpstreamGateway = true;
//optionally set the Certificate Engine //optionally set the Certificate Engine
//Under Mono only BouncyCastle will be supported //Under Mono only BouncyCastle will be supported
...@@ -92,8 +95,9 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -92,8 +95,9 @@ namespace Titanium.Web.Proxy.Examples.Basic
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
//Only explicit proxies can be set as system proxy! //Only explicit proxies can be set as system proxy!
proxyServer.SetAsSystemHttpProxy(explicitEndPoint); //proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint); //proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
proxyServer.SetAsSystemProxy(explicitEndPoint, ProxyProtocolType.AllHttp);
} }
public void Stop() public void Stop()
......
...@@ -112,7 +112,8 @@ Sample request and response event handlers ...@@ -112,7 +112,8 @@ Sample request and response event handlers
```csharp ```csharp
//To access requestBody from OnResponse handler //To access requestBody from OnResponse handler
private Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>(); private IDictionary<Guid, string> requestBodyHistory
= new ConcurrentDictionary<Guid, string>();
public async Task OnRequest(object sender, SessionEventArgs e) public async Task OnRequest(object sender, SessionEventArgs e)
{ {
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers.WinHttp;
namespace Titanium.Web.Proxy.UnitTests
{
[TestClass]
public class SystemProxyTest
{
[TestMethod]
public void CompareProxyAdddressReturendByWebProxyAndWinHttpProxyResolver()
{
var proxyManager = new SystemProxyManager();
try
{
CompareUrls();
proxyManager.SetProxy("127.0.0.1", 8000, ProxyProtocolType.Http);
CompareUrls();
proxyManager.SetProxy("127.0.0.1", 8000, ProxyProtocolType.Https);
CompareUrls();
proxyManager.SetProxy("127.0.0.1", 8000, ProxyProtocolType.AllHttp);
CompareUrls();
// for this test you need to add a proxy.pac file to a local webserver
//function FindProxyForURL(url, host)
//{
// if (shExpMatch(host, "google.com"))
// {
// return "PROXY 127.0.0.1:8888";
// }
// return "DIRECT";
//}
//proxyManager.SetAutoProxyUrl("http://localhost/proxy.pac");
//CompareUrls();
proxyManager.SetProxyOverride("<-loopback>");
CompareUrls();
proxyManager.SetProxyOverride("<local>");
CompareUrls();
proxyManager.SetProxyOverride("yahoo.com");
CompareUrls();
proxyManager.SetProxyOverride("*.local");
CompareUrls();
proxyManager.SetProxyOverride("http://*.local");
CompareUrls();
proxyManager.SetProxyOverride("<-loopback>;*.local");
CompareUrls();
proxyManager.SetProxyOverride("<-loopback>;*.local;<local>");
CompareUrls();
}
finally
{
proxyManager.RestoreOriginalSettings();
}
}
private void CompareUrls()
{
var webProxy = WebRequest.GetSystemWebProxy();
var resolver = new WinHttpWebProxyFinder();
resolver.LoadFromIE();
CompareProxy(webProxy, resolver, "http://127.0.0.1");
CompareProxy(webProxy, resolver, "https://127.0.0.1");
CompareProxy(webProxy, resolver, "http://localhost");
CompareProxy(webProxy, resolver, "https://localhost");
string hostName = null;
try
{
hostName = Dns.GetHostName();
}
catch{}
if (hostName != null)
{
CompareProxy(webProxy, resolver, "http://" + hostName);
CompareProxy(webProxy, resolver, "https://" + hostName);
}
CompareProxy(webProxy, resolver, "http://google.com");
CompareProxy(webProxy, resolver, "https://google.com");
CompareProxy(webProxy, resolver, "http://bing.com");
CompareProxy(webProxy, resolver, "https://bing.com");
CompareProxy(webProxy, resolver, "http://yahoo.com");
CompareProxy(webProxy, resolver, "https://yahoo.com");
CompareProxy(webProxy, resolver, "http://test.local");
CompareProxy(webProxy, resolver, "https://test.local");
}
private void CompareProxy(IWebProxy webProxy, WinHttpWebProxyFinder resolver, string url)
{
var uri = new Uri(url);
var expectedProxyUri = webProxy.GetProxy(uri);
var proxy = resolver.GetProxy(uri);
if (expectedProxyUri == uri)
{
// no proxy
Assert.AreEqual(proxy, null);
return;
}
Assert.AreEqual(expectedProxyUri.ToString(), $"http://{proxy.HostName}:{proxy.Port}/");
}
}
}
...@@ -60,6 +60,7 @@ ...@@ -60,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="SystemProxyTest.cs" />
<Compile Include="WinAuthTests.cs" /> <Compile Include="WinAuthTests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
internal class ProxyInfo
{
public bool? AutoDetect { get; }
public string AutoConfigUrl { get; }
public int? ProxyEnable { get; }
public string ProxyServer { get; }
public string ProxyOverride { get; }
public bool BypassLoopback { get; }
public bool BypassOnLocal { get; }
public Dictionary<ProxyProtocolType, HttpSystemProxyValue> Proxies { get; }
public string[] BypassList { get; }
public ProxyInfo(bool? autoDetect, string autoConfigUrl, int? proxyEnable, string proxyServer, string proxyOverride)
{
AutoDetect = autoDetect;
AutoConfigUrl = autoConfigUrl;
ProxyEnable = proxyEnable;
ProxyServer = proxyServer;
ProxyOverride = proxyOverride;
if (proxyServer != null)
{
Proxies = GetSystemProxyValues(proxyServer).ToDictionary(x=>x.ProtocolType);
}
if (proxyOverride != null)
{
var overrides = proxyOverride.Split(';');
var overrides2 = new List<string>();
foreach (var overrideHost in overrides)
{
if (overrideHost == "<-loopback>")
{
BypassLoopback = true;
}
else if (overrideHost == "<local>")
{
BypassOnLocal = true;
}
else
{
overrides2.Add(BypassStringEscape(overrideHost));
}
}
if (overrides2.Count > 0)
{
BypassList = overrides2.ToArray();
}
Proxies = GetSystemProxyValues(proxyServer).ToDictionary(x => x.ProtocolType);
}
}
private static string BypassStringEscape(string rawString)
{
Match match = new Regex("^(?<scheme>.*://)?(?<host>[^:]*)(?<port>:[0-9]{1,5})?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Match(rawString);
string empty1;
string rawString1;
string empty2;
if (match.Success)
{
empty1 = match.Groups["scheme"].Value;
rawString1 = match.Groups["host"].Value;
empty2 = match.Groups["port"].Value;
}
else
{
empty1 = string.Empty;
rawString1 = rawString;
empty2 = string.Empty;
}
string str1 = ConvertRegexReservedChars(empty1);
string str2 = ConvertRegexReservedChars(rawString1);
string str3 = ConvertRegexReservedChars(empty2);
if (str1 == string.Empty)
str1 = "(?:.*://)?";
if (str3 == string.Empty)
str3 = "(?::[0-9]{1,5})?";
return "^" + str1 + str2 + str3 + "$";
}
private static string ConvertRegexReservedChars(string rawString)
{
if (rawString.Length == 0)
return rawString;
var stringBuilder = new StringBuilder();
foreach (char ch in rawString)
{
if ("#$()+.?[\\^{|".IndexOf(ch) != -1)
stringBuilder.Append('\\');
else if (ch == 42)
stringBuilder.Append('.');
stringBuilder.Append(ch);
}
return stringBuilder.ToString();
}
public static ProxyProtocolType? ParseProtocolType(string protocolTypeStr)
{
if (protocolTypeStr == null)
{
return null;
}
ProxyProtocolType? protocolType = null;
if (protocolTypeStr.Equals("http", StringComparison.InvariantCultureIgnoreCase))
{
protocolType = ProxyProtocolType.Http;
}
else if (protocolTypeStr.Equals("https", StringComparison.InvariantCultureIgnoreCase))
{
protocolType = ProxyProtocolType.Https;
}
return protocolType;
}
/// <summary>
/// Parse the system proxy setting values
/// </summary>
/// <param name="proxyServerValues"></param>
/// <returns></returns>
public static List<HttpSystemProxyValue> GetSystemProxyValues(string proxyServerValues)
{
var result = new List<HttpSystemProxyValue>();
if (string.IsNullOrWhiteSpace(proxyServerValues))
return result;
var proxyValues = proxyServerValues.Split(';');
if (proxyValues.Length > 0)
{
result.AddRange(proxyValues.Select(ParseProxyValue).Where(parsedValue => parsedValue != null));
}
else
{
var parsedValue = ParseProxyValue(proxyServerValues);
if (parsedValue != null)
result.Add(parsedValue);
}
return result;
}
/// <summary>
/// Parses the system proxy setting string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static HttpSystemProxyValue ParseProxyValue(string value)
{
var tmp = Regex.Replace(value, @"\s+", " ").Trim();
int equalsIndex = tmp.IndexOf("=", StringComparison.InvariantCulture);
if (equalsIndex >= 0)
{
string protocolTypeStr = tmp.Substring(0, equalsIndex);
var protocolType = ParseProtocolType(protocolTypeStr);
if (protocolType.HasValue)
{
var endPointParts = tmp.Substring(equalsIndex + 1).Split(':');
return new HttpSystemProxyValue
{
HostName = endPointParts[0],
Port = int.Parse(endPointParts[1]),
ProtocolType = protocolType.Value,
};
}
}
return null;
}
}
}
...@@ -2,16 +2,33 @@ ...@@ -2,16 +2,33 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Microsoft.Win32; using Microsoft.Win32;
// Helper classes for setting system proxy settings // Helper classes for setting system proxy settings
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal enum ProxyProtocolType [Flags]
public enum ProxyProtocolType
{ {
Http, /// <summary>
Https, /// The none
/// </summary>
None = 0,
/// <summary>
/// HTTP
/// </summary>
Http = 1,
/// <summary>
/// HTTPS
/// </summary>
Https = 2,
/// <summary>
/// Both HTTP and HTTPS
/// </summary>
AllHttp = Http | Https,
} }
internal partial class NativeMethods internal partial class NativeMethods
...@@ -19,17 +36,44 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -19,17 +36,44 @@ namespace Titanium.Web.Proxy.Helpers
[DllImport("wininet.dll")] [DllImport("wininet.dll")]
internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer,
int dwBufferLength); int dwBufferLength);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetConsoleWindow();
// Keeps it from getting garbage collected
internal static ConsoleEventDelegate Handler;
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
// Pinvoke
internal delegate bool ConsoleEventDelegate(int eventType);
} }
internal class HttpSystemProxyValue internal class HttpSystemProxyValue
{ {
internal string HostName { get; set; } internal string HostName { get; set; }
internal int Port { get; set; } internal int Port { get; set; }
internal bool IsHttps { get; set; }
internal ProxyProtocolType ProtocolType { get; set; }
public override string ToString() public override string ToString()
{ {
return $"{(IsHttps ? "https" : "http")}={HostName}:{Port}"; string protocol;
switch (ProtocolType)
{
case ProxyProtocolType.Http:
protocol = "http";
break;
case ProxyProtocolType.Https:
protocol = "https";
break;
default:
throw new Exception("Unsupported protocol type");
}
return $"{protocol}={HostName}:{Port}";
} }
} }
...@@ -38,174 +82,263 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -38,174 +82,263 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
internal class SystemProxyManager internal class SystemProxyManager
{ {
private const string regKeyInternetSettings = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
private const string regAutoConfigUrl = "AutoConfigURL";
private const string regProxyEnable = "ProxyEnable";
private const string regProxyServer = "ProxyServer";
private const string regProxyOverride = "ProxyOverride";
internal const int InternetOptionSettingsChanged = 39; internal const int InternetOptionSettingsChanged = 39;
internal const int InternetOptionRefresh = 37; internal const int InternetOptionRefresh = 37;
internal void SetHttpProxy(string hostname, int port) private ProxyInfo originalValues;
public SystemProxyManager()
{
AppDomain.CurrentDomain.ProcessExit += (o, args) => RestoreOriginalSettings();
if (Environment.UserInteractive && NativeMethods.GetConsoleWindow() != IntPtr.Zero)
{
NativeMethods.Handler = eventType =>
{
if (eventType != 2)
{ {
SetProxy(hostname, port, ProxyProtocolType.Http); return false;
} }
/// <summary> RestoreOriginalSettings();
/// Remove the http proxy setting from current machine return false;
/// </summary> };
internal void RemoveHttpProxy()
{ //On Console exit make sure we also exit the proxy
RemoveProxy(ProxyProtocolType.Http); NativeMethods.SetConsoleCtrlHandler(NativeMethods.Handler, true);
}
} }
/// <summary> /// <summary>
/// Set the HTTPS proxy server for current machine /// Set the HTTP and/or HTTPS proxy server for current machine
/// </summary> /// </summary>
/// <param name="hostname"></param> /// <param name="hostname"></param>
/// <param name="port"></param> /// <param name="port"></param>
internal void SetHttpsProxy(string hostname, int port) /// <param name="protocolType"></param>
{ internal void SetProxy(string hostname, int port, ProxyProtocolType protocolType)
SetProxy(hostname, port, ProxyProtocolType.Https);
}
/// <summary>
/// Removes the https proxy setting to nothing
/// </summary>
internal void RemoveHttpsProxy()
{
RemoveProxy(ProxyProtocolType.Https);
}
private void SetProxy(string hostname, int port, ProxyProtocolType protocolType)
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(regKeyInternetSettings, true);
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
if (reg != null) if (reg != null)
{ {
SaveOriginalProxyConfiguration(reg);
PrepareRegistry(reg); PrepareRegistry(reg);
var exisitingContent = reg.GetValue("ProxyServer") as string; var exisitingContent = reg.GetValue(regProxyServer) as string;
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent); var existingSystemProxyValues = ProxyInfo.GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => protocolType == ProxyProtocolType.Https ? x.IsHttps : !x.IsHttps); existingSystemProxyValues.RemoveAll(x => (protocolType & x.ProtocolType) != 0);
if ((protocolType & ProxyProtocolType.Http) != 0)
{
existingSystemProxyValues.Add(new HttpSystemProxyValue existingSystemProxyValues.Add(new HttpSystemProxyValue
{ {
HostName = hostname, HostName = hostname,
IsHttps = protocolType == ProxyProtocolType.Https, ProtocolType = ProxyProtocolType.Http,
Port = port Port = port
}); });
}
reg.SetValue("ProxyEnable", 1); if ((protocolType & ProxyProtocolType.Https) != 0)
reg.SetValue("ProxyServer", string.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray())); {
existingSystemProxyValues.Add(new HttpSystemProxyValue
{
HostName = hostname,
ProtocolType = ProxyProtocolType.Https,
Port = port
});
} }
reg.DeleteValue(regAutoConfigUrl, false);
reg.SetValue(regProxyEnable, 1);
reg.SetValue(regProxyServer, string.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray()));
Refresh(); Refresh();
} }
}
private void RemoveProxy(ProxyProtocolType protocolType) /// <summary>
/// Remove the HTTP and/or HTTPS proxy setting from current machine
/// </summary>
internal void RemoveProxy(ProxyProtocolType protocolType, bool saveOriginalConfig = true)
{
var reg = Registry.CurrentUser.OpenSubKey(regKeyInternetSettings, true);
if (reg != null)
{ {
var reg = Registry.CurrentUser.OpenSubKey( if (saveOriginalConfig)
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true); {
if (reg?.GetValue("ProxyServer") != null) SaveOriginalProxyConfiguration(reg);
}
if (reg.GetValue(regProxyServer) != null)
{ {
var exisitingContent = reg.GetValue("ProxyServer") as string; var exisitingContent = reg.GetValue(regProxyServer) as string;
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent); var existingSystemProxyValues = ProxyInfo.GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => protocolType == ProxyProtocolType.Https ? x.IsHttps : !x.IsHttps); existingSystemProxyValues.RemoveAll(x => (protocolType & x.ProtocolType) != 0);
if (existingSystemProxyValues.Count != 0) if (existingSystemProxyValues.Count != 0)
{ {
reg.SetValue("ProxyEnable", 1); reg.SetValue(regProxyEnable, 1);
reg.SetValue("ProxyServer", string.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray())); reg.SetValue(regProxyServer, string.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray()));
} }
else else
{ {
reg.SetValue("ProxyEnable", 0); reg.SetValue(regProxyEnable, 0);
reg.SetValue("ProxyServer", string.Empty); reg.SetValue(regProxyServer, string.Empty);
} }
} }
Refresh(); Refresh();
} }
}
/// <summary> /// <summary>
/// Removes all types of proxy settings (both http and https) /// Removes all types of proxy settings (both http and https)
/// </summary> /// </summary>
internal void DisableAllProxy() internal void DisableAllProxy()
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(regKeyInternetSettings, true);
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
if (reg != null) if (reg != null)
{ {
reg.SetValue("ProxyEnable", 0); SaveOriginalProxyConfiguration(reg);
reg.SetValue("ProxyServer", string.Empty);
reg.SetValue(regProxyEnable, 0);
reg.SetValue(regProxyServer, string.Empty);
Refresh();
} }
}
internal void SetAutoProxyUrl(string url)
{
var reg = Registry.CurrentUser.OpenSubKey(regKeyInternetSettings, true);
if (reg != null)
{
SaveOriginalProxyConfiguration(reg);
reg.SetValue(regAutoConfigUrl, url);
Refresh(); Refresh();
} }
}
/// <summary> internal void SetProxyOverride(string proxyOverride)
/// Get the current system proxy setting values {
/// </summary> var reg = Registry.CurrentUser.OpenSubKey(regKeyInternetSettings, true);
/// <param name="prevServerValue"></param>
/// <returns></returns> if (reg != null)
private List<HttpSystemProxyValue> GetSystemProxyValues(string prevServerValue)
{ {
var result = new List<HttpSystemProxyValue>(); SaveOriginalProxyConfiguration(reg);
reg.SetValue(regProxyOverride, proxyOverride);
Refresh();
}
}
if (string.IsNullOrWhiteSpace(prevServerValue)) internal void RestoreOriginalSettings()
return result; {
if (originalValues == null)
{
return;
}
var proxyValues = prevServerValue.Split(';'); var reg = Registry.CurrentUser.OpenSubKey(regKeyInternetSettings, true);
if (proxyValues.Length > 0) if (reg != null)
{
var ov = originalValues;
if (ov.AutoConfigUrl != null)
{ {
result.AddRange(proxyValues.Select(ParseProxyValue).Where(parsedValue => parsedValue != null)); reg.SetValue(regAutoConfigUrl, ov.AutoConfigUrl);
} }
else else
{ {
var parsedValue = ParseProxyValue(prevServerValue); reg.DeleteValue(regAutoConfigUrl, false);
if (parsedValue != null)
result.Add(parsedValue);
} }
return result; if (ov.ProxyEnable.HasValue)
{
reg.SetValue(regProxyEnable, ov.ProxyEnable.Value);
}
else
{
reg.DeleteValue(regProxyEnable, false);
} }
/// <summary> if (ov.ProxyServer != null)
/// Parses the system proxy setting string {
/// </summary> reg.SetValue(regProxyServer, ov.ProxyServer);
/// <param name="value"></param> }
/// <returns></returns> else
private HttpSystemProxyValue ParseProxyValue(string value)
{ {
var tmp = Regex.Replace(value, @"\s+", " ").Trim().ToLower(); reg.DeleteValue(regProxyServer, false);
}
if (tmp.StartsWith("http=") || tmp.StartsWith("https=")) if (ov.ProxyOverride != null)
{ {
var endPoint = tmp.Substring(5); reg.SetValue(regProxyOverride, ov.ProxyOverride);
return new HttpSystemProxyValue }
else
{ {
HostName = endPoint.Split(':')[0], reg.DeleteValue(regProxyOverride, false);
Port = int.Parse(endPoint.Split(':')[1]), }
IsHttps = tmp.StartsWith("https=")
}; originalValues = null;
Refresh();
}
}
internal ProxyInfo GetProxyInfoFromRegistry()
{
var reg = Registry.CurrentUser.OpenSubKey(regKeyInternetSettings, true);
if (reg != null)
{
return GetProxyInfoFromRegistry(reg);
} }
return null; return null;
} }
private ProxyInfo GetProxyInfoFromRegistry(RegistryKey reg)
{
var pi = new ProxyInfo(
null,
reg.GetValue(regAutoConfigUrl) as string,
reg.GetValue(regProxyEnable) as int?,
reg.GetValue(regProxyServer) as string,
reg.GetValue(regProxyOverride) as string);
return pi;
}
private void SaveOriginalProxyConfiguration(RegistryKey reg)
{
if (originalValues != null)
{
return;
}
originalValues = GetProxyInfoFromRegistry(reg);
}
/// <summary> /// <summary>
/// Prepares the proxy server registry (create empty values if they don't exist) /// Prepares the proxy server registry (create empty values if they don't exist)
/// </summary> /// </summary>
/// <param name="reg"></param> /// <param name="reg"></param>
private static void PrepareRegistry(RegistryKey reg) private static void PrepareRegistry(RegistryKey reg)
{ {
if (reg.GetValue("ProxyEnable") == null) if (reg.GetValue(regProxyEnable) == null)
{ {
reg.SetValue("ProxyEnable", 0); reg.SetValue(regProxyEnable, 0);
} }
if (reg.GetValue("ProxyServer") == null || reg.GetValue("ProxyEnable") as string == "0") if (reg.GetValue(regProxyServer) == null || reg.GetValue(regProxyEnable) as int? == 0)
{ {
reg.SetValue("ProxyServer", string.Empty); reg.SetValue(regProxyServer, string.Empty);
} }
} }
......
using System;
using System.Runtime.InteropServices;
// Helper classes for setting system proxy settings
namespace Titanium.Web.Proxy.Helpers.WinHttp
{
internal partial class NativeMethods
{
internal static class WinHttp
{
[DllImport("winhttp.dll", SetLastError = true)]
internal static extern bool WinHttpGetIEProxyConfigForCurrentUser(ref WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig);
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern WinHttpHandle WinHttpOpen(string userAgent, AccessType accessType, string proxyName, string proxyBypass, int dwFlags);
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool WinHttpSetTimeouts(WinHttpHandle session, int resolveTimeout, int connectTimeout, int sendTimeout, int receiveTimeout);
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool WinHttpGetProxyForUrl(WinHttpHandle session, string url, [In] ref WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions, out WINHTTP_PROXY_INFO proxyInfo);
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool WinHttpCloseHandle(IntPtr httpSession);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
{
public bool AutoDetect;
public IntPtr AutoConfigUrl;
public IntPtr Proxy;
public IntPtr ProxyBypass;
}
[Flags]
internal enum AutoProxyFlags
{
AutoDetect = 1,
AutoProxyConfigUrl = 2,
RunInProcess = 65536,
RunOutProcessOnly = 131072
}
internal enum AccessType
{
DefaultProxy = 0,
NoProxy = 1,
NamedProxy = 3
}
[Flags]
internal enum AutoDetectType
{
None = 0,
Dhcp = 1,
DnsA = 2
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct WINHTTP_AUTOPROXY_OPTIONS
{
public AutoProxyFlags Flags;
public AutoDetectType AutoDetectFlags;
[MarshalAs(UnmanagedType.LPWStr)] public string AutoConfigUrl;
private IntPtr lpvReserved;
private int dwReserved;
public bool AutoLogonIfChallenged;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct WINHTTP_PROXY_INFO
{
public AccessType AccessType;
public IntPtr Proxy;
public IntPtr ProxyBypass;
}
internal enum ErrorCodes
{
Success = 0,
OutOfHandles = 12001,
Timeout = 12002,
InternalError = 12004,
InvalidUrl = 12005,
UnrecognizedScheme = 12006,
NameNotResolved = 12007,
InvalidOption = 12009,
OptionNotSettable = 12011,
Shutdown = 12012,
LoginFailure = 12015,
OperationCancelled = 12017,
IncorrectHandleType = 12018,
IncorrectHandleState = 12019,
CannotConnect = 12029,
ConnectionError = 12030,
ResendRequest = 12032,
SecureCertDateInvalid = 12037,
SecureCertCNInvalid = 12038,
AuthCertNeeded = 12044,
SecureInvalidCA = 12045,
SecureCertRevFailed = 12057,
CannotCallBeforeOpen = 12100,
CannotCallBeforeSend = 12101,
CannotCallAfterSend = 12102,
CannotCallAfterOpen = 12103,
HeaderNotFound = 12150,
InvalidServerResponse = 12152,
InvalidHeader = 12153,
InvalidQueryRequest = 12154,
HeaderAlreadyExists = 12155,
RedirectFailed = 12156,
SecureChannelError = 12157,
BadAutoProxyScript = 12166,
UnableToDownloadScript = 12167,
SecureInvalidCert = 12169,
SecureCertRevoked = 12170,
NotInitialized = 12172,
SecureFailure = 12175,
AutoProxyServiceError = 12178,
SecureCertWrongUsage = 12179,
AudodetectionFailed = 12180,
HeaderCountExceeded = 12181,
HeaderSizeOverflow = 12182,
ChunkedEncodingHeaderSizeOverflow = 12183,
ResponseDrainOverflow = 12184,
ClientCertNoPrivateKey = 12185,
ClientCertNoAccessPrivateKey = 12186
}
}
}
}
using System;
using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Helpers.WinHttp
{
internal class WinHttpHandle : SafeHandle
{
public WinHttpHandle() : base(IntPtr.Zero, true)
{
}
protected override bool ReleaseHandle()
{
return NativeMethods.WinHttp.WinHttpCloseHandle(handle);
}
public override bool IsInvalid => handle == IntPtr.Zero;
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Helpers.WinHttp
{
internal sealed class WinHttpWebProxyFinder : IDisposable
{
private readonly WinHttpHandle session;
private bool autoDetectFailed;
private AutoWebProxyState state;
public ICredentials Credentials { get; set; }
public ProxyInfo ProxyInfo { get; internal set; }
public bool BypassLoopback { get; internal set; }
public bool BypassOnLocal { get; internal set; }
public Uri AutomaticConfigurationScript { get; internal set; }
public bool AutomaticallyDetectSettings { get; internal set; }
private WebProxy Proxy { get; set; }
public WinHttpWebProxyFinder()
{
session = NativeMethods.WinHttp.WinHttpOpen(null, NativeMethods.WinHttp.AccessType.NoProxy, null, null, 0);
if (session == null || session.IsInvalid)
{
int lastWin32Error = GetLastWin32Error();
}
else
{
int downloadTimeout = 60 * 1000;
if (NativeMethods.WinHttp.WinHttpSetTimeouts(session, downloadTimeout, downloadTimeout, downloadTimeout, downloadTimeout))
return;
int lastWin32Error = GetLastWin32Error();
}
}
public bool GetAutoProxies(Uri destination, out IList<string> proxyList)
{
proxyList = null;
if (session == null || session.IsInvalid || state == AutoWebProxyState.UnrecognizedScheme)
return false;
string proxyListString = null;
var errorCode = NativeMethods.WinHttp.ErrorCodes.AudodetectionFailed;
if (AutomaticallyDetectSettings && !autoDetectFailed)
{
errorCode = (NativeMethods.WinHttp.ErrorCodes)GetAutoProxies(destination, null, out proxyListString);
autoDetectFailed = IsErrorFatalForAutoDetect(errorCode);
if (errorCode == NativeMethods.WinHttp.ErrorCodes.UnrecognizedScheme)
{
state = AutoWebProxyState.UnrecognizedScheme;
return false;
}
}
if (AutomaticConfigurationScript != null && IsRecoverableAutoProxyError(errorCode))
errorCode = (NativeMethods.WinHttp.ErrorCodes)GetAutoProxies(destination, AutomaticConfigurationScript, out proxyListString);
state = GetStateFromErrorCode(errorCode);
if (state != AutoWebProxyState.Completed)
return false;
if (!string.IsNullOrEmpty(proxyListString))
{
proxyListString = RemoveWhitespaces(proxyListString);
proxyList = proxyListString.Split(';');
}
return true;
}
public ExternalProxy GetProxy(Uri destination)
{
IList<string> proxies;
if (GetAutoProxies(destination, out proxies))
{
if (proxies == null)
{
return null;
}
string proxy = proxies[0];
int port = 80;
if (proxy.Contains(":"))
{
var parts = proxy.Split(new[] { ':' }, 2);
proxy = parts[0];
port = int.Parse(parts[1]);
}
// TODO: Apply authorization
var systemProxy = new ExternalProxy
{
HostName = proxy,
Port = port,
};
return systemProxy;
}
if (Proxy?.IsBypassed(destination) == true)
return null;
var protocolType = ProxyInfo.ParseProtocolType(destination.Scheme);
if (protocolType.HasValue)
{
HttpSystemProxyValue value = null;
if (ProxyInfo?.Proxies?.TryGetValue(protocolType.Value, out value) == true)
{
var systemProxy = new ExternalProxy
{
HostName = value.HostName,
Port = value.Port,
};
return systemProxy;
}
}
return null;
}
public void LoadFromIE()
{
var pi = GetProxyInfo();
ProxyInfo = pi;
AutomaticallyDetectSettings = pi.AutoDetect == true;
AutomaticConfigurationScript = pi.AutoConfigUrl == null ? null : new Uri(pi.AutoConfigUrl);
BypassLoopback = pi.BypassLoopback;
BypassOnLocal = pi.BypassOnLocal;
Proxy = new WebProxy(new Uri("http://localhost"), BypassOnLocal, pi.BypassList);
}
private ProxyInfo GetProxyInfo()
{
var proxyConfig = new NativeMethods.WinHttp.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG();
RuntimeHelpers.PrepareConstrainedRegions();
try
{
ProxyInfo result;
if (NativeMethods.WinHttp.WinHttpGetIEProxyConfigForCurrentUser(ref proxyConfig))
{
result = new ProxyInfo(
proxyConfig.AutoDetect,
Marshal.PtrToStringUni(proxyConfig.AutoConfigUrl),
null,
Marshal.PtrToStringUni(proxyConfig.Proxy),
Marshal.PtrToStringUni(proxyConfig.ProxyBypass));
}
else
{
if (Marshal.GetLastWin32Error() == 8)
throw new OutOfMemoryException();
result = new ProxyInfo(true, null, null, null, null);
}
return result;
}
finally
{
Marshal.FreeHGlobal(proxyConfig.Proxy);
Marshal.FreeHGlobal(proxyConfig.ProxyBypass);
Marshal.FreeHGlobal(proxyConfig.AutoConfigUrl);
}
}
public void Reset()
{
state = AutoWebProxyState.Uninitialized;
autoDetectFailed = false;
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (!disposing || session == null || session.IsInvalid)
return;
session.Close();
}
private int GetAutoProxies(Uri destination, Uri scriptLocation, out string proxyListString)
{
int num = 0;
var autoProxyOptions = new NativeMethods.WinHttp.WINHTTP_AUTOPROXY_OPTIONS();
autoProxyOptions.AutoLogonIfChallenged = false;
if (scriptLocation == null)
{
autoProxyOptions.Flags = NativeMethods.WinHttp.AutoProxyFlags.AutoDetect;
autoProxyOptions.AutoConfigUrl = null;
autoProxyOptions.AutoDetectFlags = NativeMethods.WinHttp.AutoDetectType.Dhcp | NativeMethods.WinHttp.AutoDetectType.DnsA;
}
else
{
autoProxyOptions.Flags = NativeMethods.WinHttp.AutoProxyFlags.AutoProxyConfigUrl;
autoProxyOptions.AutoConfigUrl = scriptLocation.ToString();
autoProxyOptions.AutoDetectFlags = NativeMethods.WinHttp.AutoDetectType.None;
}
if (!WinHttpGetProxyForUrl(destination.ToString(), ref autoProxyOptions, out proxyListString))
{
num = GetLastWin32Error();
if (num == (int)NativeMethods.WinHttp.ErrorCodes.LoginFailure && Credentials != null)
{
autoProxyOptions.AutoLogonIfChallenged = true;
if (!WinHttpGetProxyForUrl(destination.ToString(), ref autoProxyOptions, out proxyListString))
num = GetLastWin32Error();
}
}
return num;
}
private bool WinHttpGetProxyForUrl(string destination, ref NativeMethods.WinHttp.WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions, out string proxyListString)
{
proxyListString = null;
bool flag;
var proxyInfo = new NativeMethods.WinHttp.WINHTTP_PROXY_INFO();
RuntimeHelpers.PrepareConstrainedRegions();
try
{
flag = NativeMethods.WinHttp.WinHttpGetProxyForUrl(session, destination, ref autoProxyOptions, out proxyInfo);
if (flag)
proxyListString = Marshal.PtrToStringUni(proxyInfo.Proxy);
}
finally
{
Marshal.FreeHGlobal(proxyInfo.Proxy);
Marshal.FreeHGlobal(proxyInfo.ProxyBypass);
}
return flag;
}
private static int GetLastWin32Error()
{
int lastWin32Error = Marshal.GetLastWin32Error();
if (lastWin32Error == 8)
throw new OutOfMemoryException();
return lastWin32Error;
}
private static bool IsRecoverableAutoProxyError(NativeMethods.WinHttp.ErrorCodes errorCode)
{
switch (errorCode)
{
case NativeMethods.WinHttp.ErrorCodes.AutoProxyServiceError:
case NativeMethods.WinHttp.ErrorCodes.AudodetectionFailed:
case NativeMethods.WinHttp.ErrorCodes.BadAutoProxyScript:
case NativeMethods.WinHttp.ErrorCodes.UnableToDownloadScript:
case NativeMethods.WinHttp.ErrorCodes.LoginFailure:
case NativeMethods.WinHttp.ErrorCodes.OperationCancelled:
case NativeMethods.WinHttp.ErrorCodes.Timeout:
case NativeMethods.WinHttp.ErrorCodes.UnrecognizedScheme:
return true;
default:
return false;
}
}
private static AutoWebProxyState GetStateFromErrorCode(NativeMethods.WinHttp.ErrorCodes errorCode)
{
if (errorCode == 0L)
return AutoWebProxyState.Completed;
switch (errorCode)
{
case NativeMethods.WinHttp.ErrorCodes.UnableToDownloadScript:
return AutoWebProxyState.DownloadFailure;
case NativeMethods.WinHttp.ErrorCodes.AutoProxyServiceError:
case NativeMethods.WinHttp.ErrorCodes.InvalidUrl:
case NativeMethods.WinHttp.ErrorCodes.BadAutoProxyScript:
return AutoWebProxyState.Completed;
case NativeMethods.WinHttp.ErrorCodes.AudodetectionFailed:
return AutoWebProxyState.DiscoveryFailure;
case NativeMethods.WinHttp.ErrorCodes.UnrecognizedScheme:
return AutoWebProxyState.UnrecognizedScheme;
default:
return AutoWebProxyState.CompilationFailure;
}
}
private static string RemoveWhitespaces(string value)
{
var stringBuilder = new StringBuilder();
foreach (char c in value)
{
if (!char.IsWhiteSpace(c))
stringBuilder.Append(c);
}
return stringBuilder.ToString();
}
private static bool IsErrorFatalForAutoDetect(NativeMethods.WinHttp.ErrorCodes errorCode)
{
switch (errorCode)
{
case NativeMethods.WinHttp.ErrorCodes.BadAutoProxyScript:
case NativeMethods.WinHttp.ErrorCodes.AutoProxyServiceError:
case NativeMethods.WinHttp.ErrorCodes.Success:
case NativeMethods.WinHttp.ErrorCodes.InvalidUrl:
return false;
default:
return true;
}
}
private enum AutoWebProxyState
{
Uninitialized,
DiscoveryFailure,
DownloadFailure,
CompilationFailure,
UnrecognizedScheme,
Completed,
}
}
}
\ No newline at end of file
...@@ -80,17 +80,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -80,17 +80,14 @@ namespace Titanium.Web.Proxy.Http
var requestLines = new StringBuilder(); var requestLines = new StringBuilder();
//prepare the request & headers //prepare the request & headers
if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false || ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps)
{
requestLines.AppendLine($"{Request.Method} {Request.RequestUri.AbsoluteUri} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
}
else
{
requestLines.AppendLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"); requestLines.AppendLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}");
}
//Send Authentication to Upstream proxy if needed //Send Authentication to Upstream proxy if needed
if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false && !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName) && ServerConnection.UpStreamHttpProxy.Password != null) if (ServerConnection.UpStreamHttpProxy != null
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName)
&& ServerConnection.UpStreamHttpProxy.Password != null)
{ {
requestLines.AppendLine("Proxy-Connection: keep-alive"); requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes( requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(
......
...@@ -33,9 +33,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -33,9 +33,11 @@ namespace Titanium.Web.Proxy.Http
public bool HasBody => Method == "POST" || Method == "PUT" || Method == "PATCH"; public bool HasBody => Method == "POST" || Method == "PUT" || Method == "PATCH";
/// <summary> /// <summary>
/// Request Http hostanem /// Http hostname header value if exists
/// Note: Changing this does NOT change host in RequestUri
/// Users can set new RequestUri separately
/// </summary> /// </summary>
internal string Host public string Host
{ {
get get
{ {
...@@ -57,9 +59,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -57,9 +59,9 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Request content encoding /// Content encoding header value
/// </summary> /// </summary>
internal string ContentEncoding public string ContentEncoding
{ {
get get
{ {
...@@ -231,7 +233,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -231,7 +233,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Encoding for this request /// Encoding for this request
/// </summary> /// </summary>
internal Encoding Encoding => this.GetEncoding(); public Encoding Encoding => this.GetEncoding();
/// <summary> /// <summary>
/// Terminates the underlying Tcp Connection to client after current request /// Terminates the underlying Tcp Connection to client after current request
...@@ -261,7 +263,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -261,7 +263,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Does this request has an upgrade to websocket header? /// Does this request has an upgrade to websocket header?
/// </summary> /// </summary>
internal bool UpgradeToWebSocket public bool UpgradeToWebSocket
{ {
get get
{ {
......
...@@ -23,12 +23,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -23,12 +23,15 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public string ResponseStatusDescription { get; set; } public string ResponseStatusDescription { get; set; }
internal Encoding Encoding => this.GetResponseCharacterEncoding(); /// <summary>
/// Encoding used in response
/// </summary>
public Encoding Encoding => this.GetResponseCharacterEncoding();
/// <summary> /// <summary>
/// Content encoding for this response /// Content encoding for this response
/// </summary> /// </summary>
internal string ContentEncoding public string ContentEncoding
{ {
get get
{ {
...@@ -41,12 +44,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -41,12 +44,15 @@ namespace Titanium.Web.Proxy.Http
} }
} }
internal Version HttpVersion { get; set; } /// <summary>
/// Http version
/// </summary>
public Version HttpVersion { get; set; }
/// <summary> /// <summary>
/// Keep the connection alive? /// Keep the connection alive?
/// </summary> /// </summary>
internal bool ResponseKeepAlive public bool ResponseKeepAlive
{ {
get get
{ {
...@@ -89,7 +95,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -89,7 +95,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Length of response body /// Length of response body
/// </summary> /// </summary>
internal long ContentLength public long ContentLength
{ {
get get
{ {
...@@ -142,7 +148,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -142,7 +148,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Response transfer-encoding is chunked? /// Response transfer-encoding is chunked?
/// </summary> /// </summary>
internal bool IsChunked public bool IsChunked
{ {
get get
{ {
...@@ -198,14 +204,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -198,14 +204,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public Dictionary<string, List<HttpHeader>> NonUniqueResponseHeaders { get; set; } public Dictionary<string, List<HttpHeader>> NonUniqueResponseHeaders { get; set; }
/// <summary>
/// Response network stream
/// </summary>
public Stream ResponseStream { get; set; }
/// <summary> /// <summary>
/// response body contenst as byte array /// response body content as byte array
/// </summary> /// </summary>
internal byte[] ResponseBody { get; set; } internal byte[] ResponseBody { get; set; }
......
...@@ -9,6 +9,7 @@ using System.Threading; ...@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers.WinHttp;
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;
...@@ -36,6 +37,8 @@ namespace Titanium.Web.Proxy ...@@ -36,6 +37,8 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private Action<Exception> exceptionFunc; private Action<Exception> exceptionFunc;
private WinHttpWebProxyFinder systemProxyResolver;
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
...@@ -61,7 +64,6 @@ namespace Titanium.Web.Proxy ...@@ -61,7 +64,6 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private SystemProxyManager systemProxySettingsManager { get; } private SystemProxyManager systemProxySettingsManager { get; }
/// <summary> /// <summary>
/// Set firefox to use default system proxy /// Set firefox to use default system proxy
/// </summary> /// </summary>
...@@ -287,8 +289,10 @@ namespace Titanium.Web.Proxy ...@@ -287,8 +289,10 @@ namespace Titanium.Web.Proxy
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(); tcpConnectionFactory = new TcpConnectionFactory();
if (!RunTime.IsRunningOnMono)
{
systemProxySettingsManager = new SystemProxyManager(); systemProxySettingsManager = new SystemProxyManager();
}
CertificateManager = new CertificateManager(ExceptionFunc); CertificateManager = new CertificateManager(ExceptionFunc);
if (rootCertificateName != null) if (rootCertificateName != null)
...@@ -348,32 +352,25 @@ namespace Titanium.Web.Proxy ...@@ -348,32 +352,25 @@ 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) SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
{
throw new Exception("Mono Runtime do not support system proxy settings.");
} }
ValidateEndPointAsSystemProxy(endPoint);
//clear any settings previously added /// <summary>
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false); /// Set the given explicit end point as the default proxy server for current machine
/// </summary>
systemProxySettingsManager.SetHttpProxy( /// <param name="endPoint"></param>
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), endPoint.Port); public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{
endPoint.IsSystemHttpProxy = true; SetAsSystemProxy(endPoint, ProxyProtocolType.Https);
firefoxProxySettingsManager.UseSystemProxy();
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTP Proxy", endPoint.IpAddress, endPoint.Port);
} }
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) /// <param name="protocolType"></param>
public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType)
{ {
if (RunTime.IsRunningOnMono) if (RunTime.IsRunningOnMono)
{ {
...@@ -382,33 +379,73 @@ namespace Titanium.Web.Proxy ...@@ -382,33 +379,73 @@ namespace Titanium.Web.Proxy
ValidateEndPointAsSystemProxy(endPoint); ValidateEndPointAsSystemProxy(endPoint);
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
if (isHttps)
{
if (!endPoint.EnableSsl) if (!endPoint.EnableSsl)
{ {
throw new Exception("Endpoint do not support Https connections"); throw new Exception("Endpoint do not support Https connections");
} }
//clear any settings previously added
ProxyEndPoints.OfType<ExplicitProxyEndPoint>()
.ToList()
.ForEach(x => x.IsSystemHttpsProxy = false);
EnsureRootCertificate(); EnsureRootCertificate();
//If certificate was trusted by the machine //If certificate was trusted by the machine
if (CertificateManager.CertValidated) if (!CertificateManager.CertValidated)
{ {
systemProxySettingsManager.SetHttpsProxy( protocolType = protocolType & ~ProxyProtocolType.Https;
isHttps = false;
}
}
//clear any settings previously added
if (isHttp)
{
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false);
}
if (isHttps)
{
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false);
}
systemProxySettingsManager.SetProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Any) |
Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(),
endPoint.Port); endPoint.Port,
} protocolType);
if (isHttp)
{
endPoint.IsSystemHttpsProxy = true;
}
if (isHttps)
{
endPoint.IsSystemHttpsProxy = true; endPoint.IsSystemHttpsProxy = true;
}
firefoxProxySettingsManager.UseSystemProxy(); firefoxProxySettingsManager.UseSystemProxy();
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", endPoint.IpAddress, endPoint.Port); string proxyType = null;
switch (protocolType)
{
case ProxyProtocolType.Http:
proxyType = "HTTP";
break;
case ProxyProtocolType.Https:
proxyType = "HTTPS";
break;
case ProxyProtocolType.AllHttp:
proxyType = "HTTP and HTTPS";
break;
}
if (protocolType != ProxyProtocolType.None)
{
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress, endPoint.Port, proxyType);
}
} }
/// <summary> /// <summary>
...@@ -416,25 +453,28 @@ namespace Titanium.Web.Proxy ...@@ -416,25 +453,28 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public void DisableSystemHttpProxy() public void DisableSystemHttpProxy()
{ {
if (RunTime.IsRunningOnMono) DisableSystemProxy(ProxyProtocolType.Http);
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.RemoveHttpProxy();
} }
/// <summary> /// <summary>
/// Remove any HTTPS proxy setting for current machine /// Remove any HTTPS proxy setting for current machine
/// </summary> /// </summary>
public void DisableSystemHttpsProxy() public void DisableSystemHttpsProxy()
{
DisableSystemProxy(ProxyProtocolType.Https);
}
/// <summary>
/// Remove the specified proxy settings for current machine
/// </summary>
public void DisableSystemProxy(ProxyProtocolType protocolType)
{ {
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.");
} }
systemProxySettingsManager.RemoveHttpsProxy(); systemProxySettingsManager.RemoveProxy(protocolType);
} }
/// <summary> /// <summary>
...@@ -460,9 +500,38 @@ namespace Titanium.Web.Proxy ...@@ -460,9 +500,38 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
} }
if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null //clear any system proxy settings which is pointing to our own endpoint
&& GetCustomUpStreamHttpsProxyFunc == null) //due to non gracious proxy shutdown before
if (systemProxySettingsManager != null)
{
var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry();
if (proxyInfo.Proxies != null)
{ {
var protocolToRemove = ProxyProtocolType.None;
foreach (var proxy in proxyInfo.Proxies.Values)
{
if (proxy.HostName == "127.0.0.1" && ProxyEndPoints.Any(x => x.Port == proxy.Port))
{
protocolToRemove |= proxy.ProtocolType;
}
}
if (protocolToRemove != ProxyProtocolType.None)
{
//do not restore to any of listening address when we quit
systemProxySettingsManager.RemoveProxy(protocolToRemove, false);
}
}
}
if (ForwardToUpstreamGateway
&& GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null
&& systemProxySettingsManager != null)
{
// Use WinHttp to handle PAC/WAPD scripts.
systemProxyResolver = new WinHttpWebProxyFinder();
systemProxyResolver.LoadFromIE();
GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy; GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy;
GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy; GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
} }
...@@ -494,11 +563,14 @@ namespace Titanium.Web.Proxy ...@@ -494,11 +563,14 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is not running."); throw new Exception("Proxy is not running.");
} }
if (!RunTime.IsRunningOnMono)
{
var setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy); var setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy);
if (setAsSystemProxy) if (setAsSystemProxy)
{ {
systemProxySettingsManager.DisableAllProxy(); systemProxySettingsManager.RestoreOriginalSettings();
}
} }
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
...@@ -565,19 +637,8 @@ namespace Titanium.Web.Proxy ...@@ -565,19 +637,8 @@ namespace Titanium.Web.Proxy
/// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns> /// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns>
private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgs sessionEventArgs) private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgs sessionEventArgs)
{ {
// Use built-in WebProxy class to handle PAC/WAPD scripts. var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
var systemProxyResolver = new WebProxy(); return Task.FromResult(proxy);
var systemProxyUri = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
// TODO: Apply authorization
var systemProxy = new ExternalProxy
{
HostName = systemProxyUri.Host,
Port = systemProxyUri.Port
};
return Task.FromResult(systemProxy);
} }
......
...@@ -6,6 +6,7 @@ using System.Net; ...@@ -6,6 +6,7 @@ using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
...@@ -236,12 +237,12 @@ namespace Titanium.Web.Proxy ...@@ -236,12 +237,12 @@ namespace Titanium.Web.Proxy
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="httpsHostName"></param> /// <param name="httpsConnectHostname"></param>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
/// <param name="connectHeaders"></param> /// <param name="connectHeaders"></param>
/// <returns></returns> /// <returns></returns>
private async Task<bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private async Task<bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsConnectHostname,
ProxyEndPoint endPoint, List<HttpHeader> connectHeaders) ProxyEndPoint endPoint, List<HttpHeader> connectHeaders)
{ {
bool disposed = false; bool disposed = false;
...@@ -299,9 +300,9 @@ namespace Titanium.Web.Proxy ...@@ -299,9 +300,9 @@ namespace Titanium.Web.Proxy
//Read the request headers in to unique and non-unique header collections //Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.NonUniqueRequestHeaders, args.WebSession.Request.RequestHeaders); await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.NonUniqueRequestHeaders, args.WebSession.Request.RequestHeaders);
var httpRemoteUri = new Uri(httpsHostName == null var httpRemoteUri = new Uri(httpsConnectHostname == null
? httpCmdSplit[1] ? httpCmdSplit[1]
: string.Concat("https://", args.WebSession.Request.Host ?? httpsHostName, httpCmdSplit[1])); : string.Concat("https://", args.WebSession.Request.Host ?? httpsConnectHostname, httpCmdSplit[1]));
args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.RequestUri = httpRemoteUri;
...@@ -311,7 +312,8 @@ namespace Titanium.Web.Proxy ...@@ -311,7 +312,8 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter; args.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (httpsHostName == null && //proxy authorization check
if (httpsConnectHostname == null &&
await CheckAuthorization(clientStreamWriter, await CheckAuthorization(clientStreamWriter,
args.WebSession.Request.RequestHeaders.Values) == false) args.WebSession.Request.RequestHeaders.Values) == false)
{ {
...@@ -338,6 +340,12 @@ namespace Titanium.Web.Proxy ...@@ -338,6 +340,12 @@ namespace Titanium.Web.Proxy
await BeforeRequest.InvokeParallelAsync(this, args); await BeforeRequest.InvokeParallelAsync(this, args);
} }
if (args.WebSession.Request.CancelRequest)
{
args.Dispose();
break;
}
//if upgrading to websocket then relay the requet without reading the contents //if upgrading to websocket then relay the requet without reading the contents
if (args.WebSession.Request.UpgradeToWebSocket) if (args.WebSession.Request.UpgradeToWebSocket)
{ {
...@@ -354,6 +362,14 @@ namespace Titanium.Web.Proxy ...@@ -354,6 +362,14 @@ namespace Titanium.Web.Proxy
{ {
connection = await GetServerConnection(args); connection = await GetServerConnection(args);
} }
//create a new connection if hostname changes
else if (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host,
StringComparison.OrdinalIgnoreCase))
{
connection.Dispose();
Interlocked.Decrement(ref serverConnectionCount);
connection = await GetServerConnection(args);
}
//construct the web request that we are going to issue on behalf of the client. //construct the web request that we are going to issue on behalf of the client.
disposed = await HandleHttpSessionRequestInternal(connection, args, false); disposed = await HandleHttpSessionRequestInternal(connection, args, false);
...@@ -365,12 +381,6 @@ namespace Titanium.Web.Proxy ...@@ -365,12 +381,6 @@ namespace Titanium.Web.Proxy
break; break;
} }
if (args.WebSession.Request.CancelRequest)
{
args.Dispose();
break;
}
//if connection is closing exit //if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false) if (args.WebSession.Response.ResponseKeepAlive == false)
{ {
...@@ -415,12 +425,6 @@ namespace Titanium.Web.Proxy ...@@ -415,12 +425,6 @@ namespace Titanium.Web.Proxy
{ {
args.WebSession.Request.RequestLocked = true; args.WebSession.Request.RequestLocked = true;
//If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest)
{
return true;
}
//if expect continue is enabled then send the headers first //if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue //and see if server would return 100 conitinue
if (args.WebSession.Request.ExpectContinue) if (args.WebSession.Request.ExpectContinue)
......
...@@ -31,11 +31,6 @@ namespace Titanium.Web.Proxy ...@@ -31,11 +31,6 @@ namespace Titanium.Web.Proxy
//read response & headers from server //read response & headers from server
await args.WebSession.ReceiveResponse(); await args.WebSession.ReceiveResponse();
if (!args.WebSession.Response.ResponseBodyRead)
{
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
}
//check for windows authentication //check for windows authentication
if(EnableWinAuth if(EnableWinAuth
&& !RunTime.IsRunningOnMono && !RunTime.IsRunningOnMono
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Net" /> <Reference Include="System.Net" />
<Reference Include="System.configuration" /> <Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
...@@ -79,8 +79,12 @@ ...@@ -79,8 +79,12 @@
<Compile Include="Extensions\StringExtensions.cs" /> <Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Helpers\BufferPool.cs" /> <Compile Include="Helpers\BufferPool.cs" />
<Compile Include="Helpers\CustomBufferedStream.cs" /> <Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\ProxyInfo.cs" />
<Compile Include="Helpers\WinHttp\NativeMethods.WinHttp.cs" />
<Compile Include="Helpers\Network.cs" /> <Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\RunTime.cs" /> <Compile Include="Helpers\RunTime.cs" />
<Compile Include="Helpers\WinHttp\WinHttpHandle.cs" />
<Compile Include="Helpers\WinHttp\WinHttpWebProxyFinder.cs" />
<Compile Include="Http\HeaderParser.cs" /> <Compile Include="Http\HeaderParser.cs" />
<Compile Include="Http\Responses\GenericResponse.cs" /> <Compile Include="Http\Responses\GenericResponse.cs" />
<Compile Include="Network\CachedCertificate.cs" /> <Compile Include="Network\CachedCertificate.cs" />
...@@ -129,7 +133,7 @@ ...@@ -129,7 +133,7 @@
<Compile Include="WinAuthHandler.cs" /> <Compile Include="WinAuthHandler.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<COMReference Include="CERTENROLLLib"> <COMReference Include="CERTENROLLLib" Condition="'$(OS)' != 'Unix'">
<Guid>{728AB348-217D-11DA-B2A4-000E7BBB2B09}</Guid> <Guid>{728AB348-217D-11DA-B2A4-000E7BBB2B09}</Guid>
<VersionMajor>1</VersionMajor> <VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor> <VersionMinor>0</VersionMinor>
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
<title>Titanium web proxy</title> <title>Titanium web proxy</title>
<authors>Titanium</authors> <authors>Titanium</authors>
<owners>Titanium</owners> <owners>Titanium</owners>
<licenseUrl>https://github.com/titanium007/Titanium/blob/master/LICENSE</licenseUrl> <licenseUrl>https://github.com/justcoding121/Titanium-Web-Proxy/blob/develop/LICENSE</licenseUrl>
<projectUrl>https://github.com/titanium007/Titanium/</projectUrl> <projectUrl>https://github.com/justcoding121/Titanium-Web-Proxy</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Titanium is a light weight web proxy.</description> <description>Titanium is a light weight web proxy.</description>
<releaseNotes></releaseNotes> <releaseNotes></releaseNotes>
......
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