Commit 11563d36 authored by titanium007's avatar titanium007

Fix request body modification

parent 6827dbeb
...@@ -64,14 +64,30 @@ Sample request and response event handlers ...@@ -64,14 +64,30 @@ Sample request and response event handlers
```csharp ```csharp
//Test On Request, intecept requests
//Read browser URL send back to proxy by the injection script in OnResponse event
public void OnRequest(object sender, SessionEventArgs e) public void OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine(e.RequestURL);
if (e.RequestURL.Contains("somewebsite.com"))
if ((e.ProxyRequest.Method.ToUpper() == "POST" || e.ProxyRequest.Method.ToUpper() == "PUT") && e.ProxyRequest.ContentLength > 0)
{
var m = e.GetRequestBody().Replace("a", "b");
e.SetRequestBody(m);
}
//To cancel a request with a custom HTML content //To cancel a request with a custom HTML content
//Filter URL //Filter URL
if (e.RequestURL.Contains("somewebsite.com")) if (e.RequestURL.Contains("somewebsite.com"))
{ {
e.Ok("<!DOCTYPE html><html><body><h1>Blocked</h1><p>website blocked.</p></body></html>"); e.Ok("<!DOCTYPE html><html><body><h1>Blocked</h1><p>Website blocked.</p></body></html>");
} }
} }
public void OnResponse(object sender, SessionEventArgs e) public void OnResponse(object sender, SessionEventArgs e)
...@@ -81,11 +97,14 @@ Sample request and response event handlers ...@@ -81,11 +97,14 @@ Sample request and response event handlers
if (e.ServerResponse.ContentType.Trim().ToLower().Contains("text/html")) if (e.ServerResponse.ContentType.Trim().ToLower().Contains("text/html"))
{ {
//Get response body //Get response body
string responseHtmlBody = e.GetResponseHtmlBody(); string responseBody = e.GetResponseBody();
//Modify e.ServerResponse //Modify e.ServerResponse
responseHtmlBody = "<html><head></head><body>Response is modified!</body></html>"; Regex rex = new Regex("</body>", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline);
string modified = rex.Replace(responseBody, "<script type =\"text/javascript\">alert('Response was modified by this script!');</script></body>", 1);
//Set modifed response Html Body //Set modifed response Html Body
e.SetResponseHtmlBody(responseHtmlBody); e.SetResponseBody(modified);
} }
} }
} }
......
...@@ -24,20 +24,21 @@ namespace Titanium.Web.Proxy.Test ...@@ -24,20 +24,21 @@ namespace Titanium.Web.Proxy.Test
public void StartProxy() public void StartProxy()
{ {
ProxyServer.BeforeRequest += OnRequest; ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse; ProxyServer.BeforeResponse += OnResponse;
ProxyServer.EnableSSL = EnableSSL; ProxyServer.EnableSSL = EnableSSL;
ProxyServer.SetAsSystemProxy = SetAsSystemProxy; ProxyServer.SetAsSystemProxy = SetAsSystemProxy;
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//for example dropbox.com
ProxyServer.ExcludedHttpsHostNameRegex.Add(".dropbox.com");
ProxyServer.Start(); ProxyServer.Start();
ProxyServer.ListeningPort = ProxyServer.ListeningPort;
ListeningPort = ProxyServer.ListeningPort;
Console.WriteLine(String.Format("Proxy listening on local machine port: {0} ", ProxyServer.ListeningPort)); Console.WriteLine(String.Format("Proxy listening on local machine port: {0} ", ProxyServer.ListeningPort));
...@@ -59,13 +60,22 @@ namespace Titanium.Web.Proxy.Test ...@@ -59,13 +60,22 @@ namespace Titanium.Web.Proxy.Test
Console.WriteLine(e.RequestURL); Console.WriteLine(e.RequestURL);
if (e.RequestURL.Contains("somewebsite.com"))
if ((e.ProxyRequest.Method.ToUpper() == "POST" || e.ProxyRequest.Method.ToUpper() == "PUT") && e.ProxyRequest.ContentLength > 0)
{
var m = e.GetRequestBody().Replace("a", "b");
e.SetRequestBody(m);
}
//To cancel a request with a custom HTML content //To cancel a request with a custom HTML content
//Filter URL //Filter URL
//if (e.RequestURL.Contains("somewebsite.com")) if (e.RequestURL.Contains("somewebsite.com"))
//{ {
// e.Ok("<!DOCTYPE html><html><body><h1>Blocked</h1><p>website blocked.</p></body></html>"); e.Ok("<!DOCTYPE html><html><body><h1>Blocked</h1><p>Website blocked.</p></body></html>");
//} }
} }
...@@ -75,18 +85,21 @@ namespace Titanium.Web.Proxy.Test ...@@ -75,18 +85,21 @@ namespace Titanium.Web.Proxy.Test
{ {
//To modify a response //To modify a response
//if (e.ServerResponse.StatusCode == HttpStatusCode.OK) if (e.ServerResponse.StatusCode == HttpStatusCode.OK)
//{ {
// if (e.ServerResponse.ContentType.Trim().ToLower().Contains("text/html")) if (e.ServerResponse.ContentType.Trim().ToLower().Contains("text/html"))
// { {
// //Get response body //Get response body
// string responseHtmlBody = e.GetResponseHtmlBody(); string responseBody = e.GetResponseBody();
// //Modify e.ServerResponse
// responseHtmlBody = "<html><head></head><body>Response is modified!</body></html>"; //Modify e.ServerResponse
// //Set modifed response Html Body Regex rex = new Regex("</body>", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline);
// e.SetResponseHtmlBody(responseHtmlBody); string modified = rex.Replace(responseBody, "<script type =\"text/javascript\">alert('Response was modified by this script!');</script></body>", 1);
// }
//} //Set modifed response Html Body
e.SetResponseBody(modified);
}
}
} }
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Titanium.Web.Proxy.Exceptions
{
public class BodyNotFoundException : Exception
{
public BodyNotFoundException(string message)
:base(message)
{
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
namespace Titanium.Web.Proxy.Extensions
{
public static class HttpWebRequestExtensions
{
public static Encoding GetEncoding(this HttpWebRequest request)
{
try
{
if (request.ContentType == null) return Encoding.GetEncoding("ISO-8859-1");
var contentTypes = request.ContentType.Split(';');
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
if (encodingSplit.Length == 2 && encodingSplit[0].ToLower().Trim() == "charset")
{
return Encoding.GetEncoding(encodingSplit[1]);
}
}
}
catch { }
return Encoding.GetEncoding("ISO-8859-1");
}
}
}
...@@ -31,9 +31,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -31,9 +31,6 @@ namespace Titanium.Web.Proxy.Helpers
Task sendRelay = Task.Factory.StartNew(() => StreamHelper.CopyTo(clientStream, tunnelStream, BUFFER_SIZE)); Task sendRelay = Task.Factory.StartNew(() => StreamHelper.CopyTo(clientStream, tunnelStream, BUFFER_SIZE));
Task receiveRelay = Task.Factory.StartNew(() => StreamHelper.CopyTo(tunnelStream, clientStream, BUFFER_SIZE)); Task receiveRelay = Task.Factory.StartNew(() => StreamHelper.CopyTo(tunnelStream, clientStream, BUFFER_SIZE));
sendRelay.Start();
receiveRelay.Start();
Task.WaitAll(sendRelay, receiveRelay); Task.WaitAll(sendRelay, receiveRelay);
} }
catch catch
...@@ -118,9 +115,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -118,9 +115,6 @@ namespace Titanium.Web.Proxy.Helpers
var sendRelay = new Task(() => StreamHelper.CopyTo(sb.ToString(), clientStream, tunnelStream, BUFFER_SIZE)); var sendRelay = new Task(() => StreamHelper.CopyTo(sb.ToString(), clientStream, tunnelStream, BUFFER_SIZE));
var receiveRelay = new Task(() => StreamHelper.CopyTo(tunnelStream, clientStream, BUFFER_SIZE)); var receiveRelay = new Task(() => StreamHelper.CopyTo(tunnelStream, clientStream, BUFFER_SIZE));
sendRelay.Start();
receiveRelay.Start();
Task.WaitAll(sendRelay, receiveRelay); Task.WaitAll(sendRelay, receiveRelay);
} }
catch catch
......
...@@ -4,6 +4,7 @@ using System.IO; ...@@ -4,6 +4,7 @@ using System.IO;
using System.Net; using System.Net;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Exceptions;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
...@@ -18,25 +19,25 @@ namespace Titanium.Web.Proxy.Models ...@@ -18,25 +19,25 @@ namespace Titanium.Web.Proxy.Models
internal CustomBinaryReader ClientStreamReader { get; set; } internal CustomBinaryReader ClientStreamReader { get; set; }
internal StreamWriter ClientStreamWriter { get; set; } internal StreamWriter ClientStreamWriter { get; set; }
internal string UpgradeProtocol { get; set; } internal string HttpsHostName { get; set; }
internal Encoding Encoding { get; set; } internal string HttpsDecoratedHostName { get; set; }
internal int RequestLength { get; set; } internal int RequestContentLength { get; set; }
internal Encoding RequestEncoding { get; set; }
internal Version RequestHttpVersion { get; set; } internal Version RequestHttpVersion { get; set; }
internal bool RequestIsAlive { get; set; } internal bool RequestIsAlive { get; set; }
internal bool CancelRequest { get; set; } internal bool CancelRequest { get; set; }
internal string RequestHtmlBody { get; set; } internal string RequestBody { get; set; }
internal bool RequestWasModified { get; set; } internal bool RequestBodyRead { get; set; }
internal Stream ServerResponseStream { get; set; }
internal string ResponseHtmlBody { get; set; }
internal bool ResponseWasModified { get; set; }
internal Encoding ResponseEncoding { get; set; }
internal Stream ResponseStream { get; set; }
internal string ResponseBody { get; set; }
internal bool ResponseBodyRead { get; set; }
public int ClientPort { get; set; } public int ClientPort { get; set; }
public IPAddress ClientIpAddress { get; set; } public IPAddress ClientIpAddress { get; set; }
public string tunnelHostName { get; set; }
public string securehost { get; set; } public bool IsHttps { get; set; }
public bool IsSSLRequest { get; set; }
public string RequestURL { get; set; } public string RequestURL { get; set; }
public string RequestHostname { get; set; } public string RequestHostname { get; set; }
...@@ -48,8 +49,8 @@ namespace Titanium.Web.Proxy.Models ...@@ -48,8 +49,8 @@ namespace Titanium.Web.Proxy.Models
if (this.ProxyRequest != null) if (this.ProxyRequest != null)
this.ProxyRequest.Abort(); this.ProxyRequest.Abort();
if (this.ServerResponseStream != null) if (this.ResponseStream != null)
this.ServerResponseStream.Dispose(); this.ResponseStream.Dispose();
if (this.ServerResponse != null) if (this.ServerResponse != null)
this.ServerResponse.Close(); this.ServerResponse.Close();
...@@ -60,70 +61,66 @@ namespace Titanium.Web.Proxy.Models ...@@ -60,70 +61,66 @@ namespace Titanium.Web.Proxy.Models
{ {
BUFFER_SIZE = bufferSize; BUFFER_SIZE = bufferSize;
} }
public string GetRequestHtmlBody() public string GetRequestBody()
{ {
if (RequestHtmlBody == null) if ((ProxyRequest.Method.ToUpper() == "POST" || ProxyRequest.Method.ToUpper() == "PUT") && RequestContentLength > 0)
{ {
if (RequestBody == null)
int bytesRead;
int totalBytesRead = 0;
MemoryStream mw = new MemoryStream();
var buffer = ClientStreamReader.ReadBytes(RequestLength);
while (totalBytesRead < RequestLength && (bytesRead = buffer.Length) > 0)
{ {
totalBytesRead += bytesRead; var buffer = ClientStreamReader.ReadBytes(RequestContentLength);
mw.Write(buffer, 0, bytesRead); RequestBody = RequestEncoding.GetString(buffer);
} }
RequestBodyRead = true;
mw.Close(); return RequestBody;
RequestHtmlBody = Encoding.Default.GetString(mw.ToArray());
} }
RequestWasModified = true; else
return RequestHtmlBody; throw new BodyNotFoundException("Request don't have a body." +
"Please verify that this request is a Http POST/PUT and request content length is greater than zero before accessing the body.");
} }
public void SetRequestHtmlBody(string body) public void SetRequestBody(string body)
{ {
this.RequestHtmlBody = body; this.RequestBody = body;
RequestWasModified = true; RequestBodyRead = true;
} }
public string GetResponseHtmlBody() public string GetResponseBody()
{ {
if (ResponseHtmlBody == null) if (ResponseBody == null)
{ {
Encoding = Encoding.GetEncoding(ServerResponse.CharacterSet); if (ResponseEncoding == null) ResponseEncoding = Encoding.GetEncoding(ServerResponse.CharacterSet);
if (ResponseEncoding == null) ResponseEncoding = Encoding.Default;
if (Encoding == null) Encoding = Encoding.Default;
switch (ServerResponse.ContentEncoding) switch (ServerResponse.ContentEncoding)
{ {
case "gzip": case "gzip":
ResponseHtmlBody = CompressionHelper.DecompressGzip(ServerResponseStream, Encoding); ResponseBody = CompressionHelper.DecompressGzip(ResponseStream, ResponseEncoding);
break; break;
case "deflate": case "deflate":
ResponseHtmlBody = CompressionHelper.DecompressDeflate(ServerResponseStream, Encoding); ResponseBody = CompressionHelper.DecompressDeflate(ResponseStream, ResponseEncoding);
break; break;
case "zlib": case "zlib":
ResponseHtmlBody = CompressionHelper.DecompressZlib(ServerResponseStream, Encoding); ResponseBody = CompressionHelper.DecompressZlib(ResponseStream, ResponseEncoding);
break; break;
default: default:
ResponseHtmlBody = DecodeData(ServerResponseStream, Encoding); ResponseBody = DecodeData(ResponseStream, ResponseEncoding);
break; break;
} }
ResponseWasModified = true; ResponseBodyRead = true;
} }
return ResponseHtmlBody; return ResponseBody;
} }
public void SetResponseHtmlBody(string body) public void SetResponseBody(string body)
{ {
this.ResponseHtmlBody = body; if (ResponseEncoding == null) ResponseEncoding = Encoding.GetEncoding(ServerResponse.CharacterSet);
ResponseWasModified = true; if (ResponseEncoding == null) ResponseEncoding = Encoding.Default;
this.ResponseBody = body;
ResponseBodyRead = true;
} }
//stream reader not recomended for images //stream reader not recomended for images
private string DecodeData(Stream responseStream, Encoding e) private string DecodeData(Stream responseStream, Encoding e)
......
...@@ -31,29 +31,23 @@ namespace Titanium.Web.Proxy ...@@ -31,29 +31,23 @@ namespace Titanium.Web.Proxy
private static readonly Regex cookieSplitRegEx = new Regex(@",(?! )"); private static readonly Regex cookieSplitRegEx = new Regex(@",(?! )");
private static object certificateAccessLock = new object(); private static object certificateAccessLock = new object();
private static List<string> pinnedCertificateClients = new List<string>();
private static TcpListener listener; private static TcpListener listener;
private static Thread listenerThread; private static Thread listenerThread;
private static bool ShouldListen { get; set; } private static bool ShouldListen { get; set; }
public static List<string> ExcludedHttpsHostNameRegex = new List<string>();
public static event EventHandler<SessionEventArgs> BeforeRequest; public static event EventHandler<SessionEventArgs> BeforeRequest;
public static event EventHandler<SessionEventArgs> BeforeResponse; public static event EventHandler<SessionEventArgs> BeforeResponse;
public static IPAddress ListeningIPInterface { get; set; }
public static string RootCertificateName { get; set; } public static string RootCertificateName { get; set; }
public static bool EnableSSL { get; set; } public static bool EnableSSL { get; set; }
public static bool SetAsSystemProxy { get; set; } public static bool SetAsSystemProxy { get; set; }
public static Int32 ListeningPort public static Int32 ListeningPort { get; set; }
{ public static IPAddress ListeningIpAddress { get; set; }
get
{
return ((IPEndPoint)listener.LocalEndpoint).Port;
}
}
public static CertificateManager CertManager { get; set; } public static CertificateManager CertManager { get; set; }
...@@ -61,12 +55,14 @@ namespace Titanium.Web.Proxy ...@@ -61,12 +55,14 @@ namespace Titanium.Web.Proxy
{ {
CertManager = new CertificateManager("Titanium", CertManager = new CertificateManager("Titanium",
"Titanium Root Certificate Authority"); "Titanium Root Certificate Authority");
ListeningIpAddress = IPAddress.Any;
ListeningPort = 0;
} }
public ProxyServer() public ProxyServer()
{ {
System.Net.ServicePointManager.Expect100Continue = false; System.Net.ServicePointManager.Expect100Continue = false;
System.Net.WebRequest.DefaultWebProxy = null; System.Net.WebRequest.DefaultWebProxy = null;
System.Net.ServicePointManager.DefaultConnectionLimit = 10; System.Net.ServicePointManager.DefaultConnectionLimit = 10;
...@@ -111,13 +107,16 @@ namespace Titanium.Web.Proxy ...@@ -111,13 +107,16 @@ namespace Titanium.Web.Proxy
public static bool Start() public static bool Start()
{ {
listener = new TcpListener(IPAddress.Any, 0); listener = new TcpListener(ListeningIpAddress, ListeningPort);
listener.Start(); listener.Start();
listenerThread = new Thread(new ParameterizedThreadStart(Listen)); listenerThread = new Thread(new ParameterizedThreadStart(Listen));
listenerThread.IsBackground = true; listenerThread.IsBackground = true;
ShouldListen = true; ShouldListen = true;
listenerThread.Start(listener); listenerThread.Start(listener);
ListeningPort = ((IPEndPoint)listener.LocalEndpoint).Port;
if (SetAsSystemProxy) if (SetAsSystemProxy)
{ {
SystemProxyHelper.EnableProxyHTTP("localhost", ListeningPort); SystemProxyHelper.EnableProxyHTTP("localhost", ListeningPort);
......
This diff is collapsed.
...@@ -38,41 +38,39 @@ namespace Titanium.Web.Proxy ...@@ -38,41 +38,39 @@ namespace Titanium.Web.Proxy
if (args.ServerResponse != null) if (args.ServerResponse != null)
{ {
List<Tuple<String, String>> responseHeaders = ProcessResponse(args.ServerResponse); List<Tuple<String, String>> responseHeaders = ProcessResponse(args.ServerResponse);
args.ServerResponseStream = args.ServerResponse.GetResponseStream(); args.ResponseStream = args.ServerResponse.GetResponseStream();
bool isChunked = args.ServerResponse.GetResponseHeader("transfer-encoding") == null ? false : args.ServerResponse.GetResponseHeader("transfer-encoding").ToLower() == "chunked" ? true : false; bool isChunked = args.ServerResponse.GetResponseHeader("transfer-encoding") == null ? false : args.ServerResponse.GetResponseHeader("transfer-encoding").ToLower() == "chunked" ? true : false;
args.UpgradeProtocol = args.ServerResponse.GetResponseHeader("upgrade") == null ? null : args.ServerResponse.GetResponseHeader("upgrade");
if (BeforeResponse != null) if (BeforeResponse != null)
BeforeResponse(null, args); BeforeResponse(null, args);
if (args.ResponseWasModified) if (args.ResponseBodyRead)
{ {
byte[] data; byte[] data;
switch (args.ServerResponse.ContentEncoding) switch (args.ServerResponse.ContentEncoding)
{ {
case "gzip": case "gzip":
data = CompressionHelper.CompressGzip(args.ResponseHtmlBody, args.Encoding); data = CompressionHelper.CompressGzip(args.ResponseBody, args.ResponseEncoding);
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter); WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length); WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length);
SendData(args.ClientStream, data, isChunked); SendData(args.ClientStream, data, isChunked);
break; break;
case "deflate": case "deflate":
data = CompressionHelper.CompressDeflate(args.ResponseHtmlBody, args.Encoding); data = CompressionHelper.CompressDeflate(args.ResponseBody, args.ResponseEncoding);
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter); WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length); WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length);
SendData(args.ClientStream, data, isChunked); SendData(args.ClientStream, data, isChunked);
break; break;
case "zlib": case "zlib":
data = CompressionHelper.CompressZlib(args.ResponseHtmlBody, args.Encoding); data = CompressionHelper.CompressZlib(args.ResponseBody, args.ResponseEncoding);
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter); WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length); WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length);
SendData(args.ClientStream, data, isChunked); SendData(args.ClientStream, data, isChunked);
break; break;
default: default:
data = EncodeData(args.ResponseHtmlBody, args.Encoding); data = EncodeData(args.ResponseBody, args.ResponseEncoding);
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter); WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode, args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length); WriteResponseHeaders(args.ClientStreamWriter, responseHeaders, data.Length);
SendData(args.ClientStream, data, isChunked); SendData(args.ClientStream, data, isChunked);
...@@ -86,9 +84,9 @@ namespace Titanium.Web.Proxy ...@@ -86,9 +84,9 @@ namespace Titanium.Web.Proxy
WriteResponseHeaders(args.ClientStreamWriter, responseHeaders); WriteResponseHeaders(args.ClientStreamWriter, responseHeaders);
if (isChunked) if (isChunked)
SendChunked(args.ServerResponseStream, args.ClientStream); SendChunked(args.ResponseStream, args.ClientStream);
else else
SendNormal(args.ServerResponseStream, args.ClientStream); SendNormal(args.ResponseStream, args.ClientStream);
} }
......
...@@ -76,6 +76,8 @@ ...@@ -76,6 +76,8 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Extensions\HttpWebRequestExtensions.cs" />
<Compile Include="Helpers\CertificateManager.cs" /> <Compile Include="Helpers\CertificateManager.cs" />
<Compile Include="Helpers\Firefox.cs" /> <Compile Include="Helpers\Firefox.cs" />
<Compile Include="Helpers\SystemProxy.cs" /> <Compile Include="Helpers\SystemProxy.cs" />
......
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