Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
T
Titanium-Web-Proxy
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Titanium-Web-Proxy
Commits
2316ea2d
Commit
2316ea2d
authored
Nov 04, 2016
by
Jehonathan
Committed by
GitHub
Nov 04, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #142 from justcoding121/release
Merge Beta with Stable branch
parents
14016520
fa9a02c4
Hide whitespace changes
Inline
Side-by-side
Showing
38 changed files
with
2587 additions
and
1618 deletions
+2587
-1618
ProxyTestController.cs
.../Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs
+4
-0
PULL_REQUEST_TEMPLATE.md
PULL_REQUEST_TEMPLATE.md
+4
-0
README.md
README.md
+16
-7
CertificateManagerTests.cs
...s/Titanium.Web.Proxy.UnitTests/CertificateManagerTests.cs
+4
-3
ProxyServerTests.cs
Tests/Titanium.Web.Proxy.UnitTests/ProxyServerTests.cs
+91
-0
Titanium.Web.Proxy.UnitTests.csproj
...m.Web.Proxy.UnitTests/Titanium.Web.Proxy.UnitTests.csproj
+1
-0
CertificateHandler.cs
Titanium.Web.Proxy/CertificateHandler.cs
+11
-10
CertificateSelectionEventArgs.cs
...Web.Proxy/EventArguments/CertificateSelectionEventArgs.cs
+7
-11
SessionEventArgs.cs
Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
+447
-409
BodyNotFoundException.cs
Titanium.Web.Proxy/Exceptions/BodyNotFoundException.cs
+1
-1
ProxyAuthorizationException.cs
Titanium.Web.Proxy/Exceptions/ProxyAuthorizationException.cs
+28
-0
ProxyException.cs
Titanium.Web.Proxy/Exceptions/ProxyException.cs
+27
-0
ProxyHttpException.cs
Titanium.Web.Proxy/Exceptions/ProxyHttpException.cs
+30
-0
StreamExtensions.cs
Titanium.Web.Proxy/Extensions/StreamExtensions.cs
+45
-48
CustomBinaryReader.cs
Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs
+24
-31
Network.cs
Titanium.Web.Proxy/Helpers/Network.cs
+60
-0
SystemProxy.cs
Titanium.Web.Proxy/Helpers/SystemProxy.cs
+60
-113
Tcp.cs
Titanium.Web.Proxy/Helpers/Tcp.cs
+194
-107
HttpWebClient.cs
Titanium.Web.Proxy/Http/HttpWebClient.cs
+228
-207
Request.cs
Titanium.Web.Proxy/Http/Request.cs
+5
-5
Response.cs
Titanium.Web.Proxy/Http/Response.cs
+3
-4
OkResponse.cs
Titanium.Web.Proxy/Http/Responses/OkResponse.cs
+1
-1
RedirectResponse.cs
Titanium.Web.Proxy/Http/Responses/RedirectResponse.cs
+2
-4
EndPoint.cs
Titanium.Web.Proxy/Models/EndPoint.cs
+68
-64
ExternalProxy.cs
Titanium.Web.Proxy/Models/ExternalProxy.cs
+48
-11
CertificateMaker.cs
Titanium.Web.Proxy/Network/CertificateMaker.cs
+234
-0
CertificateManager.cs
Titanium.Web.Proxy/Network/CertificateManager.cs
+111
-202
TcpConnection.cs
Titanium.Web.Proxy/Network/TcpConnection.cs
+21
-1
TcpConnectionFactory.cs
Titanium.Web.Proxy/Network/TcpConnectionFactory.cs
+21
-13
ProxyAuthorizationHandler.cs
Titanium.Web.Proxy/ProxyAuthorizationHandler.cs
+98
-0
ProxyServer.cs
Titanium.Web.Proxy/ProxyServer.cs
+140
-18
RequestHandler.cs
Titanium.Web.Proxy/RequestHandler.cs
+188
-97
ResponseHandler.cs
Titanium.Web.Proxy/ResponseHandler.cs
+254
-244
TcpRow.cs
Titanium.Web.Proxy/Tcp/TcpRow.cs
+44
-0
TcpTable.cs
Titanium.Web.Proxy/Tcp/TcpTable.cs
+46
-0
Titanium.Web.Proxy.csproj
Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
+21
-6
Titanium.Web.Proxy.nuspec
Titanium.Web.Proxy/Titanium.Web.Proxy.nuspec
+0
-1
makecert.exe
Titanium.Web.Proxy/makecert.exe
+0
-0
No files found.
Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs
View file @
2316ea2d
...
...
@@ -13,6 +13,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
public
ProxyTestController
()
{
proxyServer
=
new
ProxyServer
();
proxyServer
.
TrustRootCertificate
=
true
;
}
public
void
StartProxy
()
...
...
@@ -115,6 +116,9 @@ namespace Titanium.Web.Proxy.Examples.Basic
//read response headers
var
responseHeaders
=
e
.
WebSession
.
Response
.
ResponseHeaders
;
// print out process id of current session
Console
.
WriteLine
(
$"PID:
{
e
.
WebSession
.
ProcessId
.
Value
}
"
);
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if
(
e
.
WebSession
.
Request
.
Method
==
"GET"
||
e
.
WebSession
.
Request
.
Method
==
"POST"
)
{
...
...
PULL_REQUEST_TEMPLATE.md
0 → 100644
View file @
2316ea2d
Doneness:
-
[
]
Build is okay - I made sure that this change is building successfully.
-
[
]
No Bugs - I made sure that this change is working properly as expected. It does'nt have any bugs that you are aware of.
-
[
]
Branching - If this is not a hotfix, I am making this request against release branch (aka beta branch)
README.md
View file @
2316ea2d
...
...
@@ -17,6 +17,8 @@ Features
*
Safely relays WebSocket requests over Http
*
Support mutual SSL authentication
*
Fully asynchronous proxy
*
Supports proxy authentication
Usage
=====
...
...
@@ -25,18 +27,22 @@ Refer the HTTP Proxy Server library in your project, look up Test project to lea
Install by nuget:
Install-Package Titanium.Web.Proxy
For beta releases on
[
release branch
](
https://github.com/justcoding121/Titanium-Web-Proxy/tree/release
)
After installing nuget package mark following files to be copied to app directory
Install-Package Titanium.Web.Proxy -Pre
*
makecert.exe
For stable releases on
[
master branch
](
https://github.com/justcoding121/Titanium-Web-Proxy/tree/master
)
Install-Package Titanium.Web.Proxy
Setup HTTP proxy:
```
csharp
var
proxyServer
=
new
ProxyServer
();
//locally trust root certificate used by this proxy
proxyServer
.
TrustRootCertificate
=
true
;
proxyServer
.
BeforeRequest
+=
OnRequest
;
proxyServer
.
BeforeResponse
+=
OnResponse
;
proxyServer
.
ServerCertificateValidationCallback
+=
OnCertificateValidation
;
...
...
@@ -69,8 +75,8 @@ Setup HTTP proxy:
};
proxyServer
.
AddEndPoint
(
transparentEndPoint
);
//proxyServer.
External
HttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.
External
HttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.
UpStream
HttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.
UpStream
HttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach
(
var
endPoint
in
proxyServer
.
ProxyEndPoints
)
Console
.
WriteLine
(
"Listening on '{0}' endpoint at Ip {1} and port: {2} "
,
...
...
@@ -176,7 +182,10 @@ Sample request and response event handlers
```
Future roadmap
============
*
Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
*
Support Server Name Indication (SNI) for transparent endpoints
*
Support HTTP 2.0
*
Support upstream AutoProxy detection
*
Support SOCKS protocol
*
Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
Tests/Titanium.Web.Proxy.UnitTests/CertificateManagerTests.cs
View file @
2316ea2d
...
...
@@ -20,7 +20,8 @@ namespace Titanium.Web.Proxy.UnitTests
{
var
tasks
=
new
List
<
Task
>();
var
mgr
=
new
CertificateManager
(
"Titanium"
,
"Titanium Root Certificate Authority"
);
var
mgr
=
new
CertificateManager
(
"Titanium"
,
"Titanium Root Certificate Authority"
,
new
Lazy
<
Action
<
Exception
>>(()
=>
(
e
=>
{
})).
Value
);
mgr
.
ClearIdleCertificates
(
1
);
...
...
@@ -33,9 +34,9 @@ namespace Titanium.Web.Proxy.UnitTests
await
Task
.
Delay
(
random
.
Next
(
0
,
10
)
*
1000
);
//get the connection
var
certificate
=
await
mgr
.
CreateCertificate
(
host
,
false
);
var
certificate
=
mgr
.
CreateCertificate
(
host
,
false
);
Assert
.
IsNotNull
(
certificate
);
Assert
.
IsNotNull
(
certificate
);
}));
...
...
Tests/Titanium.Web.Proxy.UnitTests/ProxyServerTests.cs
0 → 100644
View file @
2316ea2d
using
System
;
using
System.Net
;
using
Microsoft.VisualStudio.TestTools.UnitTesting
;
using
Titanium.Web.Proxy.Models
;
namespace
Titanium.Web.Proxy.UnitTests
{
[
TestClass
]
public
class
ProxyServerTests
{
[
TestMethod
]
public
void
GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenExceptionIsThrown
()
{
// Arrange
var
proxy
=
new
ProxyServer
();
const
int
port
=
9999
;
var
firstIpAddress
=
IPAddress
.
Parse
(
"127.0.0.1"
);
var
secondIpAddress
=
IPAddress
.
Parse
(
"127.0.0.1"
);
proxy
.
AddEndPoint
(
new
ExplicitProxyEndPoint
(
firstIpAddress
,
port
,
false
));
// Act
try
{
proxy
.
AddEndPoint
(
new
ExplicitProxyEndPoint
(
secondIpAddress
,
port
,
false
));
}
catch
(
Exception
exc
)
{
// Assert
StringAssert
.
Contains
(
exc
.
Message
,
"Cannot add another endpoint to same port"
);
return
;
}
Assert
.
Fail
(
"An exception should be thrown by now"
);
}
[
TestMethod
]
public
void
GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenTwoEndpointsExists
()
{
// Arrange
var
proxy
=
new
ProxyServer
();
const
int
port
=
9999
;
var
firstIpAddress
=
IPAddress
.
Parse
(
"127.0.0.1"
);
var
secondIpAddress
=
IPAddress
.
Parse
(
"192.168.1.1"
);
proxy
.
AddEndPoint
(
new
ExplicitProxyEndPoint
(
firstIpAddress
,
port
,
false
));
// Act
proxy
.
AddEndPoint
(
new
ExplicitProxyEndPoint
(
secondIpAddress
,
port
,
false
));
// Assert
Assert
.
AreEqual
(
2
,
proxy
.
ProxyEndPoints
.
Count
);
}
[
TestMethod
]
public
void
GivenOneEndpointIsAlreadyAddedToPort_WhenAddingNewEndpointToExistingPort_ThenExceptionIsThrown
()
{
// Arrange
var
proxy
=
new
ProxyServer
();
const
int
port
=
9999
;
proxy
.
AddEndPoint
(
new
ExplicitProxyEndPoint
(
IPAddress
.
Loopback
,
port
,
false
));
// Act
try
{
proxy
.
AddEndPoint
(
new
ExplicitProxyEndPoint
(
IPAddress
.
Loopback
,
port
,
false
));
}
catch
(
Exception
exc
)
{
// Assert
StringAssert
.
Contains
(
exc
.
Message
,
"Cannot add another endpoint to same port"
);
return
;
}
Assert
.
Fail
(
"An exception should be thrown by now"
);
}
[
TestMethod
]
public
void
GivenOneEndpointIsAlreadyAddedToZeroPort_WhenAddingNewEndpointToExistingPort_ThenTwoEndpointsExists
()
{
// Arrange
var
proxy
=
new
ProxyServer
();
const
int
port
=
0
;
proxy
.
AddEndPoint
(
new
ExplicitProxyEndPoint
(
IPAddress
.
Loopback
,
port
,
false
));
// Act
proxy
.
AddEndPoint
(
new
ExplicitProxyEndPoint
(
IPAddress
.
Loopback
,
port
,
false
));
// Assert
Assert
.
AreEqual
(
2
,
proxy
.
ProxyEndPoints
.
Count
);
}
}
}
Tests/Titanium.Web.Proxy.UnitTests/Titanium.Web.Proxy.UnitTests.csproj
View file @
2316ea2d
...
...
@@ -52,6 +52,7 @@
<ItemGroup>
<Compile
Include=
"CertificateManagerTests.cs"
/>
<Compile
Include=
"Properties\AssemblyInfo.cs"
/>
<Compile
Include=
"ProxyServerTests.cs"
/>
</ItemGroup>
<ItemGroup>
<ProjectReference
Include=
"..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj"
>
...
...
Titanium.Web.Proxy/CertificateHandler.cs
View file @
2316ea2d
...
...
@@ -60,11 +60,12 @@ namespace Titanium.Web.Proxy
/// Call back to select client certificate used for mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <param name="targetHost"></param>
/// <param name="localCertificates"></param>
/// <param name="remoteCertificate"></param>
/// <param name="acceptableIssuers"></param>
/// <returns></returns>
internal
X509Certificate
SelectClientCertificate
(
internal
X509Certificate
SelectClientCertificate
(
object
sender
,
string
targetHost
,
X509CertificateCollection
localCertificates
,
...
...
@@ -101,11 +102,11 @@ namespace Titanium.Web.Proxy
{
var
args
=
new
CertificateSelectionEventArgs
();
args
.
t
argetHost
=
targetHost
;
args
.
l
ocalCertificates
=
localCertificates
;
args
.
r
emoteCertificate
=
remoteCertificate
;
args
.
a
cceptableIssuers
=
acceptableIssuers
;
args
.
c
lientCertificate
=
clientCertificate
;
args
.
T
argetHost
=
targetHost
;
args
.
L
ocalCertificates
=
localCertificates
;
args
.
R
emoteCertificate
=
remoteCertificate
;
args
.
A
cceptableIssuers
=
acceptableIssuers
;
args
.
C
lientCertificate
=
clientCertificate
;
Delegate
[]
invocationList
=
ClientCertificateSelectionCallback
.
GetInvocationList
();
Task
[]
handlerTasks
=
new
Task
[
invocationList
.
Length
];
...
...
@@ -117,7 +118,7 @@ namespace Titanium.Web.Proxy
Task
.
WhenAll
(
handlerTasks
).
Wait
();
return
args
.
c
lientCertificate
;
return
args
.
C
lientCertificate
;
}
return
clientCertificate
;
...
...
Titanium.Web.Proxy/EventArguments/CertificateSelectionEventArgs.cs
View file @
2316ea2d
...
...
@@ -6,19 +6,15 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// An argument passed on to user for client certificate selection during mutual SSL authentication
/// </summary>
public
class
CertificateSelectionEventArgs
:
EventArgs
,
IDisposable
public
class
CertificateSelectionEventArgs
:
EventArgs
{
public
object
s
ender
{
get
;
internal
set
;
}
public
string
t
argetHost
{
get
;
internal
set
;
}
public
X509CertificateCollection
l
ocalCertificates
{
get
;
internal
set
;
}
public
X509Certificate
r
emoteCertificate
{
get
;
internal
set
;
}
public
string
[]
a
cceptableIssuers
{
get
;
internal
set
;
}
public
object
S
ender
{
get
;
internal
set
;
}
public
string
T
argetHost
{
get
;
internal
set
;
}
public
X509CertificateCollection
L
ocalCertificates
{
get
;
internal
set
;
}
public
X509Certificate
R
emoteCertificate
{
get
;
internal
set
;
}
public
string
[]
A
cceptableIssuers
{
get
;
internal
set
;
}
public
X509Certificate
c
lientCertificate
{
get
;
set
;
}
public
X509Certificate
C
lientCertificate
{
get
;
set
;
}
public
void
Dispose
()
{
throw
new
NotImplementedException
();
}
}
}
Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
View file @
2316ea2d
using
System
;
using
System.IO
;
using
System.Text
;
using
Titanium.Web.Proxy.Exceptions
;
using
Titanium.Web.Proxy.Decompression
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http.Responses
;
using
Titanium.Web.Proxy.Extensions
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Network
;
using
System.Net
;
namespace
Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// Holds info related to a single proxy session (single request/response sequence)
/// A proxy session is bounded to a single connection from client
/// A proxy session ends when client terminates connection to proxy
/// or when server terminates connection from proxy
/// </summary>
public
class
SessionEventArgs
:
EventArgs
,
IDisposable
{
/// <summary>
/// Size of Buffers used by this object
/// </summary>
private
readonly
int
bufferSize
;
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private
readonly
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
;
/// <summary>
/// Holds a reference to client
/// </summary>
internal
ProxyClient
ProxyClient
{
get
;
set
;
}
/// <summary>
/// Does this session uses SSL
/// </summary>
public
bool
IsHttps
=>
WebSession
.
Request
.
RequestUri
.
Scheme
==
Uri
.
UriSchemeHttps
;
public
IPEndPoint
ClientEndPoint
=>
(
IPEndPoint
)
ProxyClient
.
TcpClient
.
Client
.
RemoteEndPoint
;
/// <summary>
/// A web session corresponding to a single request/response sequence
/// within a proxy connection
/// </summary>
public
HttpWebClient
WebSession
{
get
;
set
;
}
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal
SessionEventArgs
(
int
bufferSize
,
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
)
{
this
.
bufferSize
=
bufferSize
;
this
.
httpResponseHandler
=
httpResponseHandler
;
ProxyClient
=
new
ProxyClient
();
WebSession
=
new
HttpWebClient
();
}
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
private
async
Task
ReadRequestBody
()
{
//GET request don't have a request body to read
if
((
WebSession
.
Request
.
Method
.
ToUpper
()
!=
"POST"
&&
WebSession
.
Request
.
Method
.
ToUpper
()
!=
"PUT"
))
{
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."
);
}
//Caching check
if
(
WebSession
.
Request
.
RequestBody
==
null
)
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header
using
(
var
requestBodyStream
=
new
MemoryStream
())
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if
(
WebSession
.
Request
.
IsChunked
)
{
await
this
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
bufferSize
,
requestBodyStream
);
}
else
{
//If not chunked then its easy just read the whole body with the content length mentioned in the request header
if
(
WebSession
.
Request
.
ContentLength
>
0
)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await
this
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStream
(
bufferSize
,
requestBodyStream
,
WebSession
.
Request
.
ContentLength
);
}
else
if
(
WebSession
.
Request
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Request
.
HttpVersion
.
Minor
==
0
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
requestBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Request
.
RequestBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Request
.
ContentEncoding
,
requestBodyStream
.
ToArray
());
}
//Now set the flag to true
//So that next time we can deliver body from cache
WebSession
.
Request
.
RequestBodyRead
=
true
;
}
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
private
async
Task
ReadResponseBody
()
{
//If not already read (not cached yet)
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
using
(
var
responseBodyStream
=
new
MemoryStream
())
{
//If chuncked the read chunk by chunk until we hit chunk end symbol
if
(
WebSession
.
Response
.
IsChunked
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStreamChunked
(
bufferSize
,
responseBodyStream
);
}
else
{
if
(
WebSession
.
Response
.
ContentLength
>
0
)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
responseBodyStream
,
WebSession
.
Response
.
ContentLength
);
}
else
if
(
WebSession
.
Response
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Response
.
HttpVersion
.
Minor
==
0
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
responseBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Response
.
ResponseBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Response
.
ContentEncoding
,
responseBodyStream
.
ToArray
());
}
//set this to true for caching
WebSession
.
Response
.
ResponseBodyRead
=
true
;
}
}
/// <summary>
/// Gets the request body as bytes
/// </summary>
/// <returns></returns>
public
async
Task
<
byte
[
]>
GetRequestBody
()
{
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
await
ReadRequestBody
();
}
return
WebSession
.
Request
.
RequestBody
;
}
/// <summary>
/// Gets the request body as string
/// </summary>
/// <returns></returns>
public
async
Task
<
string
>
GetRequestBodyAsString
()
{
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
await
ReadRequestBody
();
}
//Use the encoding specified in request to decode the byte[] data to string
return
WebSession
.
Request
.
RequestBodyString
??
(
WebSession
.
Request
.
RequestBodyString
=
WebSession
.
Request
.
Encoding
.
GetString
(
WebSession
.
Request
.
RequestBody
));
}
/// <summary>
/// Sets the request body
/// </summary>
/// <param name="body"></param>
public
async
Task
SetRequestBody
(
byte
[]
body
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
//syphon out the request body from client before setting the new body
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
await
ReadRequestBody
();
}
WebSession
.
Request
.
RequestBody
=
body
;
if
(
WebSession
.
Request
.
IsChunked
==
false
)
{
WebSession
.
Request
.
ContentLength
=
body
.
Length
;
}
else
{
WebSession
.
Request
.
ContentLength
=
-
1
;
}
}
/// <summary>
/// Sets the body with the specified string
/// </summary>
/// <param name="body"></param>
public
async
Task
SetRequestBodyString
(
string
body
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
//syphon out the request body from client before setting the new body
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
await
ReadRequestBody
();
}
await
SetRequestBody
(
WebSession
.
Request
.
Encoding
.
GetBytes
(
body
));
}
/// <summary>
/// Gets the response body as byte array
/// </summary>
/// <returns></returns>
public
async
Task
<
byte
[
]>
GetResponseBody
()
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
await
ReadResponseBody
();
return
WebSession
.
Response
.
ResponseBody
;
}
/// <summary>
/// Gets the response body as string
/// </summary>
/// <returns></returns>
public
async
Task
<
string
>
GetResponseBodyAsString
()
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
await
GetResponseBody
();
return
WebSession
.
Response
.
ResponseBodyString
??
(
WebSession
.
Response
.
ResponseBodyString
=
WebSession
.
Response
.
Encoding
.
GetString
(
WebSession
.
Response
.
ResponseBody
));
}
/// <summary>
/// Set the response body bytes
/// </summary>
/// <param name="body"></param>
public
async
Task
SetResponseBody
(
byte
[]
body
)
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
//syphon out the response body from server before setting the new body
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
await
GetResponseBody
();
}
WebSession
.
Response
.
ResponseBody
=
body
;
//If there is a content length header update it
if
(
WebSession
.
Response
.
IsChunked
==
false
)
{
WebSession
.
Response
.
ContentLength
=
body
.
Length
;
}
else
{
WebSession
.
Response
.
ContentLength
=
-
1
;
}
}
/// <summary>
/// Replace the response body with the specified string
/// </summary>
/// <param name="body"></param>
public
async
Task
SetResponseBodyString
(
string
body
)
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
//syphon out the response body from server before setting the new body
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
await
GetResponseBody
();
}
var
bodyBytes
=
WebSession
.
Response
.
Encoding
.
GetBytes
(
body
);
await
SetResponseBody
(
bodyBytes
);
}
private
async
Task
<
byte
[
]>
GetDecompressedResponseBody
(
string
encodingType
,
byte
[]
responseBodyStream
)
{
var
decompressionFactory
=
new
DecompressionFactory
();
var
decompressor
=
decompressionFactory
.
Create
(
encodingType
);
return
await
decompressor
.
Decompress
(
responseBodyStream
,
bufferSize
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
public
async
Task
Ok
(
string
html
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
if
(
html
==
null
)
{
html
=
string
.
Empty
;
}
var
result
=
Encoding
.
Default
.
GetBytes
(
html
);
await
Ok
(
result
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="body"></param>
public
async
Task
Ok
(
byte
[]
result
)
{
var
response
=
new
OkResponse
();
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
ResponseBody
=
result
;
await
Respond
(
response
);
WebSession
.
Request
.
CancelRequest
=
true
;
}
public
async
Task
Redirect
(
string
url
)
{
var
response
=
new
RedirectResponse
();
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
ResponseHeaders
.
Add
(
"Location"
,
new
Models
.
HttpHeader
(
"Location"
,
url
));
response
.
ResponseBody
=
Encoding
.
ASCII
.
GetBytes
(
string
.
Empty
);
await
Respond
(
response
);
WebSession
.
Request
.
CancelRequest
=
true
;
}
/// a generic responder method
public
async
Task
Respond
(
Response
response
)
{
WebSession
.
Request
.
RequestLocked
=
true
;
response
.
ResponseLocked
=
true
;
response
.
ResponseBodyRead
=
true
;
WebSession
.
Response
=
response
;
await
httpResponseHandler
(
this
);
}
/// <summary>
/// implement any cleanup here
/// </summary>
public
void
Dispose
()
{
}
}
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Text
;
using
Titanium.Web.Proxy.Exceptions
;
using
Titanium.Web.Proxy.Decompression
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http.Responses
;
using
Titanium.Web.Proxy.Extensions
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Network
;
using
System.Net
;
using
Titanium.Web.Proxy.Models
;
namespace
Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// Holds info related to a single proxy session (single request/response sequence)
/// A proxy session is bounded to a single connection from client
/// A proxy session ends when client terminates connection to proxy
/// or when server terminates connection from proxy
/// </summary>
public
class
SessionEventArgs
:
EventArgs
,
IDisposable
{
/// <summary>
/// Size of Buffers used by this object
/// </summary>
private
readonly
int
bufferSize
;
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private
readonly
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
;
/// <summary>
/// Holds a reference to client
/// </summary>
internal
ProxyClient
ProxyClient
{
get
;
set
;
}
//Should we send a rerequest
public
bool
ReRequest
{
get
;
set
;
}
/// <summary>
/// Does this session uses SSL
/// </summary>
public
bool
IsHttps
=>
WebSession
.
Request
.
RequestUri
.
Scheme
==
Uri
.
UriSchemeHttps
;
public
IPEndPoint
ClientEndPoint
=>
(
IPEndPoint
)
ProxyClient
.
TcpClient
.
Client
.
RemoteEndPoint
;
/// <summary>
/// A web session corresponding to a single request/response sequence
/// within a proxy connection
/// </summary>
public
HttpWebClient
WebSession
{
get
;
set
;
}
public
ExternalProxy
CustomUpStreamHttpProxyUsed
{
get
;
set
;
}
public
ExternalProxy
CustomUpStreamHttpsProxyUsed
{
get
;
set
;
}
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal
SessionEventArgs
(
int
bufferSize
,
Func
<
SessionEventArgs
,
Task
>
httpResponseHandler
)
{
this
.
bufferSize
=
bufferSize
;
this
.
httpResponseHandler
=
httpResponseHandler
;
ProxyClient
=
new
ProxyClient
();
WebSession
=
new
HttpWebClient
();
}
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
private
async
Task
ReadRequestBody
()
{
//GET request don't have a request body to read
if
((
WebSession
.
Request
.
Method
.
ToUpper
()
!=
"POST"
&&
WebSession
.
Request
.
Method
.
ToUpper
()
!=
"PUT"
))
{
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."
);
}
//Caching check
if
(
WebSession
.
Request
.
RequestBody
==
null
)
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header
using
(
var
requestBodyStream
=
new
MemoryStream
())
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if
(
WebSession
.
Request
.
IsChunked
)
{
await
this
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
bufferSize
,
requestBodyStream
);
}
else
{
//If not chunked then its easy just read the whole body with the content length mentioned in the request header
if
(
WebSession
.
Request
.
ContentLength
>
0
)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await
this
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStream
(
bufferSize
,
requestBodyStream
,
WebSession
.
Request
.
ContentLength
);
}
else
if
(
WebSession
.
Request
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Request
.
HttpVersion
.
Minor
==
0
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
requestBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Request
.
RequestBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Request
.
ContentEncoding
,
requestBodyStream
.
ToArray
());
}
//Now set the flag to true
//So that next time we can deliver body from cache
WebSession
.
Request
.
RequestBodyRead
=
true
;
}
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
private
async
Task
ReadResponseBody
()
{
//If not already read (not cached yet)
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
using
(
var
responseBodyStream
=
new
MemoryStream
())
{
//If chuncked the read chunk by chunk until we hit chunk end symbol
if
(
WebSession
.
Response
.
IsChunked
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStreamChunked
(
bufferSize
,
responseBodyStream
);
}
else
{
if
(
WebSession
.
Response
.
ContentLength
>
0
)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
responseBodyStream
,
WebSession
.
Response
.
ContentLength
);
}
else
if
((
WebSession
.
Response
.
HttpVersion
.
Major
==
1
&&
WebSession
.
Response
.
HttpVersion
.
Minor
==
0
)
||
WebSession
.
Response
.
ContentLength
==
-
1
)
{
await
WebSession
.
ServerConnection
.
StreamReader
.
CopyBytesToStream
(
bufferSize
,
responseBodyStream
,
long
.
MaxValue
);
}
}
WebSession
.
Response
.
ResponseBody
=
await
GetDecompressedResponseBody
(
WebSession
.
Response
.
ContentEncoding
,
responseBodyStream
.
ToArray
());
}
//set this to true for caching
WebSession
.
Response
.
ResponseBodyRead
=
true
;
}
}
/// <summary>
/// Gets the request body as bytes
/// </summary>
/// <returns></returns>
public
async
Task
<
byte
[
]>
GetRequestBody
()
{
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
await
ReadRequestBody
();
}
return
WebSession
.
Request
.
RequestBody
;
}
/// <summary>
/// Gets the request body as string
/// </summary>
/// <returns></returns>
public
async
Task
<
string
>
GetRequestBodyAsString
()
{
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
await
ReadRequestBody
();
}
//Use the encoding specified in request to decode the byte[] data to string
return
WebSession
.
Request
.
RequestBodyString
??
(
WebSession
.
Request
.
RequestBodyString
=
WebSession
.
Request
.
Encoding
.
GetString
(
WebSession
.
Request
.
RequestBody
));
}
/// <summary>
/// Sets the request body
/// </summary>
/// <param name="body"></param>
public
async
Task
SetRequestBody
(
byte
[]
body
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
//syphon out the request body from client before setting the new body
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
await
ReadRequestBody
();
}
WebSession
.
Request
.
RequestBody
=
body
;
if
(
WebSession
.
Request
.
IsChunked
==
false
)
{
WebSession
.
Request
.
ContentLength
=
body
.
Length
;
}
else
{
WebSession
.
Request
.
ContentLength
=
-
1
;
}
}
/// <summary>
/// Sets the body with the specified string
/// </summary>
/// <param name="body"></param>
public
async
Task
SetRequestBodyString
(
string
body
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
//syphon out the request body from client before setting the new body
if
(!
WebSession
.
Request
.
RequestBodyRead
)
{
await
ReadRequestBody
();
}
await
SetRequestBody
(
WebSession
.
Request
.
Encoding
.
GetBytes
(
body
));
}
/// <summary>
/// Gets the response body as byte array
/// </summary>
/// <returns></returns>
public
async
Task
<
byte
[
]>
GetResponseBody
()
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
await
ReadResponseBody
();
return
WebSession
.
Response
.
ResponseBody
;
}
/// <summary>
/// Gets the response body as string
/// </summary>
/// <returns></returns>
public
async
Task
<
string
>
GetResponseBodyAsString
()
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
await
GetResponseBody
();
return
WebSession
.
Response
.
ResponseBodyString
??
(
WebSession
.
Response
.
ResponseBodyString
=
WebSession
.
Response
.
Encoding
.
GetString
(
WebSession
.
Response
.
ResponseBody
));
}
/// <summary>
/// Set the response body bytes
/// </summary>
/// <param name="body"></param>
public
async
Task
SetResponseBody
(
byte
[]
body
)
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
//syphon out the response body from server before setting the new body
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
await
GetResponseBody
();
}
WebSession
.
Response
.
ResponseBody
=
body
;
//If there is a content length header update it
if
(
WebSession
.
Response
.
IsChunked
==
false
)
{
WebSession
.
Response
.
ContentLength
=
body
.
Length
;
}
else
{
WebSession
.
Response
.
ContentLength
=
-
1
;
}
}
/// <summary>
/// Replace the response body with the specified string
/// </summary>
/// <param name="body"></param>
public
async
Task
SetResponseBodyString
(
string
body
)
{
if
(!
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function before request is made to server."
);
}
//syphon out the response body from server before setting the new body
if
(
WebSession
.
Response
.
ResponseBody
==
null
)
{
await
GetResponseBody
();
}
var
bodyBytes
=
WebSession
.
Response
.
Encoding
.
GetBytes
(
body
);
await
SetResponseBody
(
bodyBytes
);
}
private
async
Task
<
byte
[
]>
GetDecompressedResponseBody
(
string
encodingType
,
byte
[]
responseBodyStream
)
{
var
decompressionFactory
=
new
DecompressionFactory
();
var
decompressor
=
decompressionFactory
.
Create
(
encodingType
);
return
await
decompressor
.
Decompress
(
responseBodyStream
,
bufferSize
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
public
async
Task
Ok
(
string
html
)
{
await
Ok
(
html
,
null
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
public
async
Task
Ok
(
string
html
,
Dictionary
<
string
,
HttpHeader
>
headers
)
{
if
(
WebSession
.
Request
.
RequestLocked
)
{
throw
new
Exception
(
"You cannot call this function after request is made to server."
);
}
if
(
html
==
null
)
{
html
=
string
.
Empty
;
}
var
result
=
Encoding
.
Default
.
GetBytes
(
html
);
await
Ok
(
result
,
headers
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
public
async
Task
Ok
(
byte
[]
result
)
{
await
Ok
(
result
,
null
);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
/// <param name="headers"></param>
public
async
Task
Ok
(
byte
[]
result
,
Dictionary
<
string
,
HttpHeader
>
headers
)
{
var
response
=
new
OkResponse
();
if
(
headers
!=
null
&&
headers
.
Count
>
0
)
{
response
.
ResponseHeaders
=
headers
;
}
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
ResponseBody
=
result
;
await
Respond
(
response
);
WebSession
.
Request
.
CancelRequest
=
true
;
}
public
async
Task
Redirect
(
string
url
)
{
var
response
=
new
RedirectResponse
();
response
.
HttpVersion
=
WebSession
.
Request
.
HttpVersion
;
response
.
ResponseHeaders
.
Add
(
"Location"
,
new
Models
.
HttpHeader
(
"Location"
,
url
));
response
.
ResponseBody
=
Encoding
.
ASCII
.
GetBytes
(
string
.
Empty
);
await
Respond
(
response
);
WebSession
.
Request
.
CancelRequest
=
true
;
}
/// a generic responder method
public
async
Task
Respond
(
Response
response
)
{
WebSession
.
Request
.
RequestLocked
=
true
;
response
.
ResponseLocked
=
true
;
response
.
ResponseBodyRead
=
true
;
WebSession
.
Response
=
response
;
await
httpResponseHandler
(
this
);
}
/// <summary>
/// implement any cleanup here
/// </summary>
public
void
Dispose
()
{
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Exceptions/BodyNotFoundException.cs
View file @
2316ea2d
...
...
@@ -5,7 +5,7 @@ namespace Titanium.Web.Proxy.Exceptions
/// <summary>
/// An expception thrown when body is unexpectedly empty
/// </summary>
public
class
BodyNotFoundException
:
Exception
public
class
BodyNotFoundException
:
Proxy
Exception
{
public
BodyNotFoundException
(
string
message
)
:
base
(
message
)
...
...
Titanium.Web.Proxy/Exceptions/ProxyAuthorizationException.cs
0 → 100644
View file @
2316ea2d
using
System
;
using
System.Collections.Generic
;
using
Titanium.Web.Proxy.Models
;
namespace
Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy authorization exception
/// </summary>
public
class
ProxyAuthorizationException
:
ProxyException
{
/// <summary>
/// Instantiate new instance
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="innerException">Inner exception associated to upstream proxy authorization</param>
/// <param name="headers">Http's headers associated</param>
public
ProxyAuthorizationException
(
string
message
,
Exception
innerException
,
IEnumerable
<
HttpHeader
>
headers
)
:
base
(
message
,
innerException
)
{
Headers
=
headers
;
}
/// <summary>
/// Headers associated with the authorization exception
/// </summary>
public
IEnumerable
<
HttpHeader
>
Headers
{
get
;
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Exceptions/ProxyException.cs
0 → 100644
View file @
2316ea2d
using
System
;
namespace
Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Base class exception associated with this proxy implementation
/// </summary>
public
abstract
class
ProxyException
:
Exception
{
/// <summary>
/// Instantiate a new instance of this exception - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Exception message</param>
protected
ProxyException
(
string
message
)
:
base
(
message
)
{
}
/// <summary>
/// Instantiate this exception - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Excception message</param>
/// <param name="innerException">Inner exception associated</param>
protected
ProxyException
(
string
message
,
Exception
innerException
)
:
base
(
message
,
innerException
)
{
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Exceptions/ProxyHttpException.cs
0 → 100644
View file @
2316ea2d
using
System
;
using
Titanium.Web.Proxy.EventArguments
;
namespace
Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy HTTP exception
/// </summary>
public
class
ProxyHttpException
:
ProxyException
{
/// <summary>
/// Instantiate new instance
/// </summary>
/// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner exception</param>
/// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs"/> associated to the exception</param>
public
ProxyHttpException
(
string
message
,
Exception
innerException
,
SessionEventArgs
sessionEventArgs
)
:
base
(
message
,
innerException
)
{
SessionEventArgs
=
sessionEventArgs
;
}
/// <summary>
/// Gets session info associated to the exception
/// </summary>
/// <remarks>
/// This object should not be edited
/// </remarks>
public
SessionEventArgs
SessionEventArgs
{
get
;
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Extensions/StreamExtensions.cs
View file @
2316ea2d
...
...
@@ -29,27 +29,20 @@ namespace Titanium.Web.Proxy.Extensions
await
input
.
CopyToAsync
(
output
);
}
/// <summary>
/// copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="streamReader"></param>
/// <param name="stream"></param>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal
static
async
Task
CopyBytesToStream
(
this
CustomBinaryReader
streamReader
,
int
bufferSize
,
Stream
stream
,
long
totalBytesToRead
)
/// <summary>
/// copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="streamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="stream"></param>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal
static
async
Task
CopyBytesToStream
(
this
CustomBinaryReader
streamReader
,
int
bufferSize
,
Stream
stream
,
long
totalBytesToRead
)
{
var
totalbytesRead
=
0
;
long
bytesToRead
;
if
(
totalBytesToRead
<
bufferSize
)
{
bytesToRead
=
totalBytesToRead
;
}
else
{
bytesToRead
=
bufferSize
;
}
long
bytesToRead
=
totalBytesToRead
<
bufferSize
?
totalBytesToRead
:
bufferSize
;
while
(
totalbytesRead
<
totalBytesToRead
)
{
...
...
@@ -72,13 +65,14 @@ namespace Titanium.Web.Proxy.Extensions
}
}
/// <summary>
/// Copies the stream chunked
/// </summary>
/// <param name="clientStreamReader"></param>
/// <param name="stream"></param>
/// <returns></returns>
internal
static
async
Task
CopyBytesToStreamChunked
(
this
CustomBinaryReader
clientStreamReader
,
int
bufferSize
,
Stream
stream
)
/// <summary>
/// Copies the stream chunked
/// </summary>
/// <param name="clientStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="stream"></param>
/// <returns></returns>
internal
static
async
Task
CopyBytesToStreamChunked
(
this
CustomBinaryReader
clientStreamReader
,
int
bufferSize
,
Stream
stream
)
{
while
(
true
)
{
...
...
@@ -117,30 +111,32 @@ namespace Titanium.Web.Proxy.Extensions
await
WriteResponseBodyChunked
(
data
,
clientStream
);
}
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="outStream"></param>
/// <param name="isChunked"></param>
/// <param name="ContentLength"></param>
/// <returns></returns>
internal
static
async
Task
WriteResponseBody
(
this
CustomBinaryReader
inStreamReader
,
int
bufferSize
,
Stream
outStream
,
bool
isChunked
,
long
ContentLength
)
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="outStream"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <returns></returns>
internal
static
async
Task
WriteResponseBody
(
this
CustomBinaryReader
inStreamReader
,
int
bufferSize
,
Stream
outStream
,
bool
isChunked
,
long
contentLength
)
{
if
(!
isChunked
)
{
//http 1.0
if
(
C
ontentLength
==
-
1
)
if
(
c
ontentLength
==
-
1
)
{
C
ontentLength
=
long
.
MaxValue
;
c
ontentLength
=
long
.
MaxValue
;
}
int
bytesToRead
=
bufferSize
;
if
(
C
ontentLength
<
bufferSize
)
if
(
c
ontentLength
<
bufferSize
)
{
bytesToRead
=
(
int
)
C
ontentLength
;
bytesToRead
=
(
int
)
c
ontentLength
;
}
var
buffer
=
new
byte
[
bufferSize
];
...
...
@@ -153,11 +149,11 @@ namespace Titanium.Web.Proxy.Extensions
await
outStream
.
WriteAsync
(
buffer
,
0
,
bytesRead
);
totalBytesRead
+=
bytesRead
;
if
(
totalBytesRead
==
C
ontentLength
)
if
(
totalBytesRead
==
c
ontentLength
)
break
;
bytesRead
=
0
;
var
remainingBytes
=
(
C
ontentLength
-
totalBytesRead
);
var
remainingBytes
=
(
c
ontentLength
-
totalBytesRead
);
bytesToRead
=
remainingBytes
>
(
long
)
bufferSize
?
bufferSize
:
(
int
)
remainingBytes
;
}
}
...
...
@@ -167,13 +163,14 @@ namespace Titanium.Web.Proxy.Extensions
}
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal
static
async
Task
WriteResponseBodyChunked
(
this
CustomBinaryReader
inStreamReader
,
int
bufferSize
,
Stream
outStream
)
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal
static
async
Task
WriteResponseBodyChunked
(
this
CustomBinaryReader
inStreamReader
,
int
bufferSize
,
Stream
outStream
)
{
while
(
true
)
{
...
...
Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs
View file @
2316ea2d
...
...
@@ -37,37 +37,30 @@ namespace Titanium.Web.Proxy.Helpers
{
using
(
var
readBuffer
=
new
MemoryStream
())
{
try
{
var
lastChar
=
default
(
char
);
var
buffer
=
new
byte
[
1
];
while
((
await
this
.
stream
.
ReadAsync
(
buffer
,
0
,
1
))
>
0
)
{
//if new line
if
(
lastChar
==
'\r'
&&
buffer
[
0
]
==
'\n'
)
{
var
result
=
readBuffer
.
ToArray
();
return
encoding
.
GetString
(
result
.
SubArray
(
0
,
result
.
Length
-
1
));
}
//end of stream
if
(
buffer
[
0
]
==
'\0'
)
{
return
encoding
.
GetString
(
readBuffer
.
ToArray
());
}
await
readBuffer
.
WriteAsync
(
buffer
,
0
,
1
);
//store last char for new line comparison
lastChar
=
(
char
)
buffer
[
0
];
}
return
encoding
.
GetString
(
readBuffer
.
ToArray
());
}
catch
(
IOException
)
{
throw
;
}
var
lastChar
=
default
(
char
);
var
buffer
=
new
byte
[
1
];
while
((
await
this
.
stream
.
ReadAsync
(
buffer
,
0
,
1
))
>
0
)
{
//if new line
if
(
lastChar
==
'\r'
&&
buffer
[
0
]
==
'\n'
)
{
var
result
=
readBuffer
.
ToArray
();
return
encoding
.
GetString
(
result
.
SubArray
(
0
,
result
.
Length
-
1
));
}
//end of stream
if
(
buffer
[
0
]
==
'\0'
)
{
return
encoding
.
GetString
(
readBuffer
.
ToArray
());
}
await
readBuffer
.
WriteAsync
(
buffer
,
0
,
1
);
//store last char for new line comparison
lastChar
=
(
char
)
buffer
[
0
];
}
return
encoding
.
GetString
(
readBuffer
.
ToArray
());
}
}
...
...
Titanium.Web.Proxy/Helpers/Network.cs
0 → 100644
View file @
2316ea2d
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Net
;
using
System.Text
;
using
System.Threading.Tasks
;
namespace
Titanium.Web.Proxy.Helpers
{
internal
class
NetworkHelper
{
private
static
int
FindProcessIdFromLocalPort
(
int
port
,
IpVersion
ipVersion
)
{
var
tcpRow
=
TcpHelper
.
GetExtendedTcpTable
(
ipVersion
).
FirstOrDefault
(
row
=>
row
.
LocalEndPoint
.
Port
==
port
);
return
tcpRow
?.
ProcessId
??
0
;
}
internal
static
int
GetProcessIdFromPort
(
int
port
,
bool
ipV6Enabled
)
{
var
processId
=
FindProcessIdFromLocalPort
(
port
,
IpVersion
.
Ipv4
);
if
(
processId
>
0
&&
!
ipV6Enabled
)
{
return
processId
;
}
return
FindProcessIdFromLocalPort
(
port
,
IpVersion
.
Ipv6
);
}
/// <summary>
/// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary>
/// <param name="address></param>
/// <returns></returns>
internal
static
bool
IsLocalIpAddress
(
IPAddress
address
)
{
try
{
// get local IP addresses
IPAddress
[]
localIPs
=
Dns
.
GetHostAddresses
(
Dns
.
GetHostName
());
// test if any host IP equals to any local IP or to localhost
// is localhost
if
(
IPAddress
.
IsLoopback
(
address
))
return
true
;
// is local address
foreach
(
IPAddress
localIP
in
localIPs
)
{
if
(
address
.
Equals
(
localIP
))
return
true
;
}
}
catch
{
}
return
false
;
}
}
}
Titanium.Web.Proxy/Helpers/SystemProxy.cs
View file @
2316ea2d
...
...
@@ -4,14 +4,20 @@ using Microsoft.Win32;
using
System.Text.RegularExpressions
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Net.Sockets
;
/// <summary>
/// Helper classes for setting system proxy settings
/// </summary>
namespace
Titanium.Web.Proxy.Helpers
{
internal
class
NativeMethods
internal
enum
ProxyProtocolType
{
Http
,
Https
,
}
internal
partial
class
NativeMethods
{
[
DllImport
(
"wininet.dll"
)]
internal
static
extern
bool
InternetSetOption
(
IntPtr
hInternet
,
int
dwOption
,
IntPtr
lpBuffer
,
...
...
@@ -26,16 +32,10 @@ namespace Titanium.Web.Proxy.Helpers
public
override
string
ToString
()
{
if
(!
IsHttps
)
{
return
"http="
+
HostName
+
":"
+
Port
;
}
else
{
return
"https="
+
HostName
+
":"
+
Port
;
}
return
$"
{(
IsHttps
?
"https"
:
"http"
)}
=
{
HostName
}
:
{
Port
}
"
;
}
}
/// <summary>
/// Manage system proxy settings
/// </summary>
...
...
@@ -44,62 +44,17 @@ namespace Titanium.Web.Proxy.Helpers
internal
const
int
InternetOptionSettingsChanged
=
39
;
internal
const
int
InternetOptionRefresh
=
37
;
internal
void
SetHttpProxy
(
string
hostname
,
int
port
)
internal
void
SetHttpProxy
(
string
hostname
,
int
port
)
{
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
if
(
reg
!=
null
)
{
prepareRegistry
(
reg
);
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
!
x
.
IsHttps
);
existingSystemProxyValues
.
Add
(
new
HttpSystemProxyValue
()
{
HostName
=
hostname
,
IsHttps
=
false
,
Port
=
port
});
reg
.
SetValue
(
"ProxyEnable"
,
1
);
reg
.
SetValue
(
"ProxyServer"
,
String
.
Join
(
";"
,
existingSystemProxyValues
.
Select
(
x
=>
x
.
ToString
()).
ToArray
()));
}
Refresh
();
SetProxy
(
hostname
,
port
,
ProxyProtocolType
.
Http
);
}
/// <summary>
/// Remove the http proxy setting from current machine
/// </summary>
internal
void
RemoveHttpProxy
()
internal
void
RemoveHttpProxy
()
{
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
if
(
reg
!=
null
)
{
if
(
reg
.
GetValue
(
"ProxyServer"
)
!=
null
)
{
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
!
x
.
IsHttps
);
if
(!(
existingSystemProxyValues
.
Count
()
==
0
))
{
reg
.
SetValue
(
"ProxyEnable"
,
1
);
reg
.
SetValue
(
"ProxyServer"
,
String
.
Join
(
";"
,
existingSystemProxyValues
.
Select
(
x
=>
x
.
ToString
()).
ToArray
()));
}
else
{
reg
.
SetValue
(
"ProxyEnable"
,
0
);
reg
.
SetValue
(
"ProxyServer"
,
string
.
Empty
);
}
}
}
Refresh
();
RemoveProxy
(
ProxyProtocolType
.
Http
);
}
/// <summary>
...
...
@@ -107,60 +62,65 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
internal
void
SetHttpsProxy
(
string
hostname
,
int
port
)
internal
void
SetHttpsProxy
(
string
hostname
,
int
port
)
{
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
(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
if
(
reg
!=
null
)
{
p
repareRegistry
(
reg
);
P
repareRegistry
(
reg
);
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
x
.
IsHttps
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
protocolType
==
ProxyProtocolType
.
Https
?
x
.
IsHttps
:
!
x
.
IsHttps
);
existingSystemProxyValues
.
Add
(
new
HttpSystemProxyValue
()
{
HostName
=
hostname
,
IsHttps
=
true
,
IsHttps
=
protocolType
==
ProxyProtocolType
.
Https
,
Port
=
port
});
reg
.
SetValue
(
"ProxyEnable"
,
1
);
reg
.
SetValue
(
"ProxyServer"
,
S
tring
.
Join
(
";"
,
existingSystemProxyValues
.
Select
(
x
=>
x
.
ToString
()).
ToArray
()));
reg
.
SetValue
(
"ProxyServer"
,
s
tring
.
Join
(
";"
,
existingSystemProxyValues
.
Select
(
x
=>
x
.
ToString
()).
ToArray
()));
}
Refresh
();
}
/// <summary>
/// Removes the https proxy setting to nothing
/// </summary>
internal
void
RemoveHttpsProxy
()
private
void
RemoveProxy
(
ProxyProtocolType
protocolType
)
{
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
if
(
reg
!=
null
)
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
if
(
reg
?.
GetValue
(
"ProxyServer"
)
!=
null
)
{
if
(
reg
.
GetValue
(
"ProxyServer"
)
!=
null
)
{
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
x
.
IsHttps
);
if
(!(
existingSystemProxyValues
.
Count
()
==
0
))
{
reg
.
SetValue
(
"ProxyEnable"
,
1
);
reg
.
SetValue
(
"ProxyServer"
,
String
.
Join
(
";"
,
existingSystemProxyValues
.
Select
(
x
=>
x
.
ToString
()).
ToArray
()));
}
else
{
reg
.
SetValue
(
"ProxyEnable"
,
0
);
reg
.
SetValue
(
"ProxyServer"
,
string
.
Empty
);
}
var
exisitingContent
=
reg
.
GetValue
(
"ProxyServer"
)
as
string
;
var
existingSystemProxyValues
=
GetSystemProxyValues
(
exisitingContent
);
existingSystemProxyValues
.
RemoveAll
(
x
=>
protocolType
==
ProxyProtocolType
.
Https
?
x
.
IsHttps
:
!
x
.
IsHttps
);
if
(
existingSystemProxyValues
.
Count
!=
0
)
{
reg
.
SetValue
(
"ProxyEnable"
,
1
);
reg
.
SetValue
(
"ProxyServer"
,
string
.
Join
(
";"
,
existingSystemProxyValues
.
Select
(
x
=>
x
.
ToString
()).
ToArray
()));
}
else
{
reg
.
SetValue
(
"ProxyEnable"
,
0
);
reg
.
SetValue
(
"ProxyServer"
,
string
.
Empty
);
}
}
...
...
@@ -170,7 +130,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// Removes all types of proxy settings (both http & https)
/// </summary>
internal
void
DisableAllProxy
()
internal
void
DisableAllProxy
()
{
var
reg
=
Registry
.
CurrentUser
.
OpenSubKey
(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
,
true
);
...
...
@@ -189,7 +149,7 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
/// <param name="prevServerValue"></param>
/// <returns></returns>
private
List
<
HttpSystemProxyValue
>
GetSystemProxyValues
(
string
prevServerValue
)
private
List
<
HttpSystemProxyValue
>
GetSystemProxyValues
(
string
prevServerValue
)
{
var
result
=
new
List
<
HttpSystemProxyValue
>();
...
...
@@ -200,16 +160,11 @@ namespace Titanium.Web.Proxy.Helpers
if
(
proxyValues
.
Length
>
0
)
{
foreach
(
var
value
in
proxyValues
)
{
var
parsedValue
=
parseProxyValue
(
value
);
if
(
parsedValue
!=
null
)
result
.
Add
(
parsedValue
);
}
result
.
AddRange
(
proxyValues
.
Select
(
ParseProxyValue
).
Where
(
parsedValue
=>
parsedValue
!=
null
));
}
else
{
var
parsedValue
=
p
arseProxyValue
(
prevServerValue
);
var
parsedValue
=
P
arseProxyValue
(
prevServerValue
);
if
(
parsedValue
!=
null
)
result
.
Add
(
parsedValue
);
}
...
...
@@ -222,29 +177,21 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private
HttpSystemProxyValue
p
arseProxyValue
(
string
value
)
private
HttpSystemProxyValue
P
arseProxyValue
(
string
value
)
{
var
tmp
=
Regex
.
Replace
(
value
,
@"\s+"
,
" "
).
Trim
().
ToLower
();
if
(
tmp
.
StartsWith
(
"http="
))
{
var
endPoint
=
tmp
.
Substring
(
5
);
return
new
HttpSystemProxyValue
()
{
HostName
=
endPoint
.
Split
(
':'
)[
0
],
Port
=
int
.
Parse
(
endPoint
.
Split
(
':'
)[
1
]),
IsHttps
=
false
};
}
else
if
(
tmp
.
StartsWith
(
"https="
))
if
(
tmp
.
StartsWith
(
"http="
)
||
tmp
.
StartsWith
(
"https="
))
{
var
endPoint
=
tmp
.
Substring
(
5
);
return
new
HttpSystemProxyValue
()
{
HostName
=
endPoint
.
Split
(
':'
)[
0
],
Port
=
int
.
Parse
(
endPoint
.
Split
(
':'
)[
1
]),
IsHttps
=
t
rue
};
IsHttps
=
t
mp
.
StartsWith
(
"https="
)
};
}
return
null
;
}
...
...
@@ -252,7 +199,7 @@ namespace Titanium.Web.Proxy.Helpers
/// Prepares the proxy server registry (create empty values if they don't exist)
/// </summary>
/// <param name="reg"></param>
private
void
p
repareRegistry
(
RegistryKey
reg
)
private
static
void
P
repareRegistry
(
RegistryKey
reg
)
{
if
(
reg
.
GetValue
(
"ProxyEnable"
)
==
null
)
{
...
...
@@ -269,7 +216,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// Refresh the settings so that the system know about a change in proxy setting
/// </summary>
private
void
Refresh
()
private
void
Refresh
()
{
NativeMethods
.
InternetSetOption
(
IntPtr
.
Zero
,
InternetOptionSettingsChanged
,
IntPtr
.
Zero
,
0
);
NativeMethods
.
InternetSetOption
(
IntPtr
.
Zero
,
InternetOptionRefresh
,
IntPtr
.
Zero
,
0
);
...
...
Titanium.Web.Proxy/Helpers/Tcp.cs
View file @
2316ea2d
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Net.Security
;
using
System.Net.Sockets
;
using
System.Security.Authentication
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network
;
namespace
Titanium.Web.Proxy.Helpers
{
internal
class
TcpHelper
{
/// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <returns></returns>
internal
static
async
Task
SendRaw
(
int
bufferSize
,
int
connectionTimeOutSeconds
,
string
remoteHostName
,
int
remotePort
,
string
httpCmd
,
Version
httpVersion
,
Dictionary
<
string
,
HttpHeader
>
requestHeaders
,
bool
isHttps
,
SslProtocols
supportedProtocols
,
RemoteCertificateValidationCallback
remoteCertificateValidationCallback
,
LocalCertificateSelectionCallback
localCertificateSelectionCallback
,
Stream
clientStream
,
TcpConnectionFactory
tcpConnectionFactory
)
{
//prepare the prefix content
StringBuilder
sb
=
null
;
if
(
httpCmd
!=
null
||
requestHeaders
!=
null
)
{
sb
=
new
StringBuilder
();
if
(
httpCmd
!=
null
)
{
sb
.
Append
(
httpCmd
);
sb
.
Append
(
Environment
.
NewLine
);
}
if
(
requestHeaders
!=
null
)
{
foreach
(
var
header
in
requestHeaders
.
Select
(
t
=>
t
.
Value
.
ToString
()))
{
sb
.
Append
(
header
);
sb
.
Append
(
Environment
.
NewLine
);
}
}
sb
.
Append
(
Environment
.
NewLine
);
}
var
tcpConnection
=
await
tcpConnectionFactory
.
CreateClient
(
bufferSize
,
connectionTimeOutSeconds
,
remoteHostName
,
remotePort
,
httpVersion
,
isHttps
,
supportedProtocols
,
remoteCertificateValidationCallback
,
localCertificateSelectionCallback
,
null
,
null
,
clientStream
);
try
{
TcpClient
tunnelClient
=
tcpConnection
.
TcpClient
;
Stream
tunnelStream
=
tcpConnection
.
Stream
;
Task
sendRelay
;
//Now async relay all server=>client & client=>server data
if
(
sb
!=
null
)
{
sendRelay
=
clientStream
.
CopyToAsync
(
sb
.
ToString
(),
tunnelStream
);
}
else
{
sendRelay
=
clientStream
.
CopyToAsync
(
string
.
Empty
,
tunnelStream
);
}
var
receiveRelay
=
tunnelStream
.
CopyToAsync
(
string
.
Empty
,
clientStream
);
await
Task
.
WhenAll
(
sendRelay
,
receiveRelay
);
}
catch
{
throw
;
}
finally
{
tcpConnection
.
Dispose
();
}
}
}
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Net.NetworkInformation
;
using
System.Net.Security
;
using
System.Runtime.InteropServices
;
using
System.Security.Authentication
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network
;
using
Titanium.Web.Proxy.Tcp
;
namespace
Titanium.Web.Proxy.Helpers
{
internal
enum
IpVersion
{
Ipv4
=
1
,
Ipv6
=
2
,
}
internal
partial
class
NativeMethods
{
internal
const
int
AfInet
=
2
;
internal
const
int
AfInet6
=
23
;
internal
enum
TcpTableType
{
BasicListener
,
BasicConnections
,
BasicAll
,
OwnerPidListener
,
OwnerPidConnections
,
OwnerPidAll
,
OwnerModuleListener
,
OwnerModuleConnections
,
OwnerModuleAll
,
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
[
StructLayout
(
LayoutKind
.
Sequential
)]
internal
struct
TcpTable
{
public
uint
length
;
public
TcpRow
row
;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
[
StructLayout
(
LayoutKind
.
Sequential
)]
internal
struct
TcpRow
{
public
TcpState
state
;
public
uint
localAddr
;
public
byte
localPort1
;
public
byte
localPort2
;
public
byte
localPort3
;
public
byte
localPort4
;
public
uint
remoteAddr
;
public
byte
remotePort1
;
public
byte
remotePort2
;
public
byte
remotePort3
;
public
byte
remotePort4
;
public
int
owningPid
;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[
DllImport
(
"iphlpapi.dll"
,
SetLastError
=
true
)]
internal
static
extern
uint
GetExtendedTcpTable
(
IntPtr
tcpTable
,
ref
int
size
,
bool
sort
,
int
ipVersion
,
int
tableClass
,
int
reserved
);
}
internal
class
TcpHelper
{
/// <summary>
/// Gets the extended TCP table.
/// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns>
internal
static
TcpTable
GetExtendedTcpTable
(
IpVersion
ipVersion
)
{
List
<
TcpRow
>
tcpRows
=
new
List
<
TcpRow
>();
IntPtr
tcpTable
=
IntPtr
.
Zero
;
int
tcpTableLength
=
0
;
var
ipVersionValue
=
ipVersion
==
IpVersion
.
Ipv4
?
NativeMethods
.
AfInet
:
NativeMethods
.
AfInet6
;
if
(
NativeMethods
.
GetExtendedTcpTable
(
tcpTable
,
ref
tcpTableLength
,
false
,
ipVersionValue
,
(
int
)
NativeMethods
.
TcpTableType
.
OwnerPidAll
,
0
)
!=
0
)
{
try
{
tcpTable
=
Marshal
.
AllocHGlobal
(
tcpTableLength
);
if
(
NativeMethods
.
GetExtendedTcpTable
(
tcpTable
,
ref
tcpTableLength
,
true
,
ipVersionValue
,
(
int
)
NativeMethods
.
TcpTableType
.
OwnerPidAll
,
0
)
==
0
)
{
NativeMethods
.
TcpTable
table
=
(
NativeMethods
.
TcpTable
)
Marshal
.
PtrToStructure
(
tcpTable
,
typeof
(
NativeMethods
.
TcpTable
));
IntPtr
rowPtr
=
(
IntPtr
)((
long
)
tcpTable
+
Marshal
.
SizeOf
(
table
.
length
));
for
(
int
i
=
0
;
i
<
table
.
length
;
++
i
)
{
tcpRows
.
Add
(
new
TcpRow
((
NativeMethods
.
TcpRow
)
Marshal
.
PtrToStructure
(
rowPtr
,
typeof
(
NativeMethods
.
TcpRow
))));
rowPtr
=
(
IntPtr
)((
long
)
rowPtr
+
Marshal
.
SizeOf
(
typeof
(
NativeMethods
.
TcpRow
)));
}
}
}
finally
{
if
(
tcpTable
!=
IntPtr
.
Zero
)
{
Marshal
.
FreeHGlobal
(
tcpTable
);
}
}
}
return
new
TcpTable
(
tcpRows
);
}
/// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <returns></returns>
internal
static
async
Task
SendRaw
(
int
bufferSize
,
int
connectionTimeOutSeconds
,
string
remoteHostName
,
int
remotePort
,
string
httpCmd
,
Version
httpVersion
,
Dictionary
<
string
,
HttpHeader
>
requestHeaders
,
bool
isHttps
,
SslProtocols
supportedProtocols
,
RemoteCertificateValidationCallback
remoteCertificateValidationCallback
,
LocalCertificateSelectionCallback
localCertificateSelectionCallback
,
Stream
clientStream
,
TcpConnectionFactory
tcpConnectionFactory
)
{
//prepare the prefix content
StringBuilder
sb
=
null
;
if
(
httpCmd
!=
null
||
requestHeaders
!=
null
)
{
sb
=
new
StringBuilder
();
if
(
httpCmd
!=
null
)
{
sb
.
Append
(
httpCmd
);
sb
.
Append
(
Environment
.
NewLine
);
}
if
(
requestHeaders
!=
null
)
{
foreach
(
var
header
in
requestHeaders
.
Select
(
t
=>
t
.
Value
.
ToString
()))
{
sb
.
Append
(
header
);
sb
.
Append
(
Environment
.
NewLine
);
}
}
sb
.
Append
(
Environment
.
NewLine
);
}
var
tcpConnection
=
await
tcpConnectionFactory
.
CreateClient
(
bufferSize
,
connectionTimeOutSeconds
,
remoteHostName
,
remotePort
,
httpVersion
,
isHttps
,
supportedProtocols
,
remoteCertificateValidationCallback
,
localCertificateSelectionCallback
,
null
,
null
,
clientStream
);
try
{
Stream
tunnelStream
=
tcpConnection
.
Stream
;
//Now async relay all server=>client & client=>server data
var
sendRelay
=
clientStream
.
CopyToAsync
(
sb
?.
ToString
()
??
string
.
Empty
,
tunnelStream
);
var
receiveRelay
=
tunnelStream
.
CopyToAsync
(
string
.
Empty
,
clientStream
);
await
Task
.
WhenAll
(
sendRelay
,
receiveRelay
);
}
finally
{
tcpConnection
.
Dispose
();
}
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Http/HttpWebClient.cs
View file @
2316ea2d
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network
;
using
Titanium.Web.Proxy.Shared
;
namespace
Titanium.Web.Proxy.Http
{
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public
class
HttpWebClient
{
/// <summary>
/// Connection to server
/// </summary>
internal
TcpConnection
ServerConnection
{
get
;
set
;
}
public
Request
Request
{
get
;
set
;
}
public
Response
Response
{
get
;
set
;
}
/// <summary>
/// Is Https?
/// </summary>
public
bool
IsHttps
{
get
{
return
this
.
Request
.
RequestUri
.
Scheme
==
Uri
.
UriSchemeHttps
;
}
}
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="Connection"></param>
internal
void
SetConnection
(
TcpConnection
Connection
)
{
ServerConnection
=
Connection
;
}
internal
HttpWebClient
()
{
this
.
Request
=
new
Request
();
this
.
Response
=
new
Response
();
}
/// <summary>
/// Prepare & send the http(s) request
/// </summary>
/// <returns></returns>
internal
async
Task
SendRequest
(
bool
enable100ContinueBehaviour
)
{
Stream
stream
=
ServerConnection
.
Stream
;
StringBuilder
requestLines
=
new
StringBuilder
();
//prepare the request & headers
requestLines
.
AppendLine
(
string
.
Join
(
" "
,
new
string
[
3
]
{
this
.
Request
.
Method
,
this
.
Request
.
RequestUri
.
PathAndQuery
,
string
.
Format
(
"HTTP/{0}.{1}"
,
this
.
Request
.
HttpVersion
.
Major
,
this
.
Request
.
HttpVersion
.
Minor
)
}));
//write request headers
foreach
(
var
headerItem
in
this
.
Request
.
RequestHeaders
)
{
var
header
=
headerItem
.
Value
;
requestLines
.
AppendLine
(
header
.
Name
+
':'
+
header
.
Value
);
}
//write non unique request headers
foreach
(
var
headerItem
in
this
.
Request
.
NonUniqueRequestHeaders
)
{
var
headers
=
headerItem
.
Value
;
foreach
(
var
header
in
headers
)
{
requestLines
.
AppendLine
(
header
.
Name
+
':'
+
header
.
Value
);
}
}
requestLines
.
AppendLine
();
string
request
=
requestLines
.
ToString
();
byte
[]
requestBytes
=
Encoding
.
ASCII
.
GetBytes
(
request
);
await
stream
.
WriteAsync
(
requestBytes
,
0
,
requestBytes
.
Length
);
await
stream
.
FlushAsync
();
if
(
enable100ContinueBehaviour
)
{
if
(
this
.
Request
.
ExpectContinue
)
{
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
var
responseStatusCode
=
httpResult
[
1
].
Trim
();
var
responseStatusDescription
=
httpResult
[
2
].
Trim
();
//find if server is willing for expect continue
if
(
responseStatusCode
.
Equals
(
"100"
)
&&
responseStatusDescription
.
ToLower
().
Equals
(
"continue"
))
{
this
.
Request
.
Is100Continue
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
else
if
(
responseStatusCode
.
Equals
(
"417"
)
&&
responseStatusDescription
.
ToLower
().
Equals
(
"expectation failed"
))
{
this
.
Request
.
ExpectationFailed
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
}
}
}
/// <summary>
/// Receive & parse the http response from server
/// </summary>
/// <returns></returns>
internal
async
Task
ReceiveResponse
()
{
//return if this is already read
if
(
this
.
Response
.
ResponseStatusCode
!=
null
)
return
;
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
if
(
string
.
IsNullOrEmpty
(
httpResult
[
0
]))
{
//Empty content in first-line, try again
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
}
var
httpVersion
=
httpResult
[
0
].
Trim
().
ToLower
();
var
version
=
new
Version
(
1
,
1
);
if
(
httpVersion
==
"http/1.0"
)
{
version
=
new
Version
(
1
,
0
);
}
this
.
Response
.
HttpVersion
=
version
;
this
.
Response
.
ResponseStatusCode
=
httpResult
[
1
].
Trim
();
this
.
Response
.
ResponseStatusDescription
=
httpResult
[
2
].
Trim
();
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if
(
this
.
Response
.
ResponseStatusCode
.
Equals
(
"100"
)
&&
this
.
Response
.
ResponseStatusDescription
.
ToLower
().
Equals
(
"continue"
))
{
//Read the next line after 100-continue
this
.
Response
.
Is100Continue
=
true
;
this
.
Response
.
ResponseStatusCode
=
null
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
//now receive response
await
ReceiveResponse
();
return
;
}
else
if
(
this
.
Response
.
ResponseStatusCode
.
Equals
(
"417"
)
&&
this
.
Response
.
ResponseStatusDescription
.
ToLower
().
Equals
(
"expectation failed"
))
{
//read next line after expectation failed response
this
.
Response
.
ExpectationFailed
=
true
;
this
.
Response
.
ResponseStatusCode
=
null
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
//now receive response
await
ReceiveResponse
();
return
;
}
//Read the Response headers
//Read the response headers in to unique and non-unique header collections
string
tmpLine
;
while
(!
string
.
IsNullOrEmpty
(
tmpLine
=
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()))
{
var
header
=
tmpLine
.
Split
(
ProxyConstants
.
ColonSplit
,
2
);
var
newHeader
=
new
HttpHeader
(
header
[
0
],
header
[
1
]);
//if header exist in non-unique header collection add it there
if
(
Response
.
NonUniqueResponseHeaders
.
ContainsKey
(
newHeader
.
Name
))
{
Response
.
NonUniqueResponseHeaders
[
newHeader
.
Name
].
Add
(
newHeader
);
}
//if header is alread in unique header collection then move both to non-unique collection
else
if
(
Response
.
ResponseHeaders
.
ContainsKey
(
newHeader
.
Name
))
{
var
existing
=
Response
.
ResponseHeaders
[
newHeader
.
Name
];
var
nonUniqueHeaders
=
new
List
<
HttpHeader
>();
nonUniqueHeaders
.
Add
(
existing
);
nonUniqueHeaders
.
Add
(
newHeader
);
Response
.
NonUniqueResponseHeaders
.
Add
(
newHeader
.
Name
,
nonUniqueHeaders
);
Response
.
ResponseHeaders
.
Remove
(
newHeader
.
Name
);
}
//add to unique header collection
else
{
Response
.
ResponseHeaders
.
Add
(
newHeader
.
Name
,
newHeader
);
}
}
}
}
}
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network
;
using
Titanium.Web.Proxy.Shared
;
namespace
Titanium.Web.Proxy.Http
{
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public
class
HttpWebClient
{
/// <summary>
/// Connection to server
/// </summary>
internal
TcpConnection
ServerConnection
{
get
;
set
;
}
public
Guid
RequestId
{
get
;
private
set
;
}
public
List
<
HttpHeader
>
ConnectHeaders
{
get
;
set
;
}
public
Request
Request
{
get
;
set
;
}
public
Response
Response
{
get
;
set
;
}
/// <summary>
/// PID of the process that is created the current session when client is running in this machine
/// If client is remote then this will return
/// </summary>
public
Lazy
<
int
>
ProcessId
{
get
;
internal
set
;
}
/// <summary>
/// Is Https?
/// </summary>
public
bool
IsHttps
=>
this
.
Request
.
RequestUri
.
Scheme
==
Uri
.
UriSchemeHttps
;
internal
HttpWebClient
()
{
this
.
RequestId
=
Guid
.
NewGuid
();
this
.
Request
=
new
Request
();
this
.
Response
=
new
Response
();
}
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="connection">Instance of <see cref="TcpConnection"/></param>
internal
void
SetConnection
(
TcpConnection
connection
)
{
connection
.
LastAccess
=
DateTime
.
Now
;
ServerConnection
=
connection
;
}
/// <summary>
/// Prepare & send the http(s) request
/// </summary>
/// <returns></returns>
internal
async
Task
SendRequest
(
bool
enable100ContinueBehaviour
)
{
Stream
stream
=
ServerConnection
.
Stream
;
StringBuilder
requestLines
=
new
StringBuilder
();
//prepare the request & headers
if
((
ServerConnection
.
UpStreamHttpProxy
!=
null
&&
ServerConnection
.
IsHttps
==
false
)
||
(
ServerConnection
.
UpStreamHttpsProxy
!=
null
&&
ServerConnection
.
IsHttps
==
true
))
{
requestLines
.
AppendLine
(
string
.
Join
(
" "
,
this
.
Request
.
Method
,
this
.
Request
.
RequestUri
.
AbsoluteUri
,
$"HTTP/
{
this
.
Request
.
HttpVersion
.
Major
}
.
{
this
.
Request
.
HttpVersion
.
Minor
}
"
));
}
else
{
requestLines
.
AppendLine
(
string
.
Join
(
" "
,
this
.
Request
.
Method
,
this
.
Request
.
RequestUri
.
PathAndQuery
,
$"HTTP/
{
this
.
Request
.
HttpVersion
.
Major
}
.
{
this
.
Request
.
HttpVersion
.
Minor
}
"
));
}
//Send Authentication to Upstream proxy if needed
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-Authorization"
+
": Basic "
+
Convert
.
ToBase64String
(
Encoding
.
UTF8
.
GetBytes
(
ServerConnection
.
UpStreamHttpProxy
.
UserName
+
":"
+
ServerConnection
.
UpStreamHttpProxy
.
Password
)));
}
//write request headers
foreach
(
var
headerItem
in
this
.
Request
.
RequestHeaders
)
{
var
header
=
headerItem
.
Value
;
if
(
headerItem
.
Key
!=
"Proxy-Authorization"
)
{
requestLines
.
AppendLine
(
header
.
Name
+
':'
+
header
.
Value
);
}
}
//write non unique request headers
foreach
(
var
headerItem
in
this
.
Request
.
NonUniqueRequestHeaders
)
{
var
headers
=
headerItem
.
Value
;
foreach
(
var
header
in
headers
)
{
if
(
headerItem
.
Key
!=
"Proxy-Authorization"
)
{
requestLines
.
AppendLine
(
header
.
Name
+
':'
+
header
.
Value
);
}
}
}
requestLines
.
AppendLine
();
string
request
=
requestLines
.
ToString
();
byte
[]
requestBytes
=
Encoding
.
ASCII
.
GetBytes
(
request
);
await
stream
.
WriteAsync
(
requestBytes
,
0
,
requestBytes
.
Length
);
await
stream
.
FlushAsync
();
if
(
enable100ContinueBehaviour
)
{
if
(
this
.
Request
.
ExpectContinue
)
{
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
var
responseStatusCode
=
httpResult
[
1
].
Trim
();
var
responseStatusDescription
=
httpResult
[
2
].
Trim
();
//find if server is willing for expect continue
if
(
responseStatusCode
.
Equals
(
"100"
)
&&
responseStatusDescription
.
ToLower
().
Equals
(
"continue"
))
{
this
.
Request
.
Is100Continue
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
else
if
(
responseStatusCode
.
Equals
(
"417"
)
&&
responseStatusDescription
.
ToLower
().
Equals
(
"expectation failed"
))
{
this
.
Request
.
ExpectationFailed
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
}
}
}
/// <summary>
/// Receive & parse the http response from server
/// </summary>
/// <returns></returns>
internal
async
Task
ReceiveResponse
()
{
//return if this is already read
if
(
this
.
Response
.
ResponseStatusCode
!=
null
)
return
;
var
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
if
(
string
.
IsNullOrEmpty
(
httpResult
[
0
]))
{
//Empty content in first-line, try again
httpResult
=
(
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()).
Split
(
ProxyConstants
.
SpaceSplit
,
3
);
}
var
httpVersion
=
httpResult
[
0
].
Trim
().
ToLower
();
var
version
=
new
Version
(
1
,
1
);
if
(
httpVersion
==
"http/1.0"
)
{
version
=
new
Version
(
1
,
0
);
}
this
.
Response
.
HttpVersion
=
version
;
this
.
Response
.
ResponseStatusCode
=
httpResult
[
1
].
Trim
();
this
.
Response
.
ResponseStatusDescription
=
httpResult
[
2
].
Trim
();
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if
(
this
.
Response
.
ResponseStatusCode
.
Equals
(
"100"
)
&&
this
.
Response
.
ResponseStatusDescription
.
ToLower
().
Equals
(
"continue"
))
{
//Read the next line after 100-continue
this
.
Response
.
Is100Continue
=
true
;
this
.
Response
.
ResponseStatusCode
=
null
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
//now receive response
await
ReceiveResponse
();
return
;
}
else
if
(
this
.
Response
.
ResponseStatusCode
.
Equals
(
"417"
)
&&
this
.
Response
.
ResponseStatusDescription
.
ToLower
().
Equals
(
"expectation failed"
))
{
//read next line after expectation failed response
this
.
Response
.
ExpectationFailed
=
true
;
this
.
Response
.
ResponseStatusCode
=
null
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
//now receive response
await
ReceiveResponse
();
return
;
}
//Read the Response headers
//Read the response headers in to unique and non-unique header collections
string
tmpLine
;
while
(!
string
.
IsNullOrEmpty
(
tmpLine
=
await
ServerConnection
.
StreamReader
.
ReadLineAsync
()))
{
var
header
=
tmpLine
.
Split
(
ProxyConstants
.
ColonSplit
,
2
);
var
newHeader
=
new
HttpHeader
(
header
[
0
],
header
[
1
]);
//if header exist in non-unique header collection add it there
if
(
Response
.
NonUniqueResponseHeaders
.
ContainsKey
(
newHeader
.
Name
))
{
Response
.
NonUniqueResponseHeaders
[
newHeader
.
Name
].
Add
(
newHeader
);
}
//if header is alread in unique header collection then move both to non-unique collection
else
if
(
Response
.
ResponseHeaders
.
ContainsKey
(
newHeader
.
Name
))
{
var
existing
=
Response
.
ResponseHeaders
[
newHeader
.
Name
];
var
nonUniqueHeaders
=
new
List
<
HttpHeader
>
{
existing
,
newHeader
};
Response
.
NonUniqueResponseHeaders
.
Add
(
newHeader
.
Name
,
nonUniqueHeaders
);
Response
.
ResponseHeaders
.
Remove
(
newHeader
.
Name
);
}
//add to unique header collection
else
{
Response
.
ResponseHeaders
.
Add
(
newHeader
.
Name
,
newHeader
);
}
}
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Http/Request.cs
View file @
2316ea2d
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Text
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Extensions
;
...
...
@@ -234,13 +233,14 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Request Url
/// </summary>
public
string
Url
{
get
{
return
RequestUri
.
OriginalString
;
}
}
public
string
Url
=>
RequestUri
.
OriginalString
;
/// <summary>
/// <summary>
/// Encoding for this request
/// </summary>
internal
Encoding
Encoding
{
get
{
return
this
.
GetEncoding
();
}
}
/// <summary>
internal
Encoding
Encoding
=>
this
.
GetEncoding
();
/// <summary>
/// Terminates the underlying Tcp Connection to client after current request
/// </summary>
internal
bool
CancelRequest
{
get
;
set
;
}
...
...
Titanium.Web.Proxy/Http/Response.cs
View file @
2316ea2d
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Text
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Extensions
;
...
...
@@ -16,9 +15,9 @@ namespace Titanium.Web.Proxy.Http
public
string
ResponseStatusCode
{
get
;
set
;
}
public
string
ResponseStatusDescription
{
get
;
set
;
}
internal
Encoding
Encoding
{
get
{
return
this
.
GetResponseCharacterEncoding
();
}
}
internal
Encoding
Encoding
=>
this
.
GetResponseCharacterEncoding
();
/// <summary>
/// <summary>
/// Content encoding for this response
/// </summary>
internal
string
ContentEncoding
...
...
@@ -205,7 +204,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Response network stream
/// </summary>
internal
Stream
ResponseStream
{
get
;
set
;
}
public
Stream
ResponseStream
{
get
;
set
;
}
/// <summary>
/// response body contenst as byte array
...
...
Titanium.Web.Proxy/Http/Responses/OkResponse.cs
View file @
2316ea2d
...
...
@@ -3,7 +3,7 @@
/// <summary>
/// 200 Ok response
/// </summary>
public
class
OkResponse
:
Response
public
sealed
class
OkResponse
:
Response
{
public
OkResponse
()
{
...
...
Titanium.Web.Proxy/Http/Responses/RedirectResponse.cs
View file @
2316ea2d
using
Titanium.Web.Proxy.Network
;
namespace
Titanium.Web.Proxy.Http.Responses
namespace
Titanium.Web.Proxy.Http.Responses
{
/// <summary>
/// Redirect response
/// </summary>
public
class
RedirectResponse
:
Response
public
sealed
class
RedirectResponse
:
Response
{
public
RedirectResponse
()
{
...
...
Titanium.Web.Proxy/Models/EndPoint.cs
View file @
2316ea2d
using
System.Collections.Generic
;
using
System.Net
;
using
System.Net.Sockets
;
namespace
Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public
abstract
class
ProxyEndPoint
{
public
ProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
{
this
.
IpAddress
=
IpAddress
;
this
.
Port
=
Port
;
this
.
EnableSsl
=
EnableSsl
;
}
public
IPAddress
IpAddress
{
get
;
internal
set
;
}
public
int
Port
{
get
;
internal
set
;
}
public
bool
EnableSsl
{
get
;
internal
set
;
}
internal
TcpListener
listener
{
get
;
set
;
}
}
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
public
class
ExplicitProxyEndPoint
:
ProxyEndPoint
{
internal
bool
IsSystemHttpProxy
{
get
;
set
;
}
internal
bool
IsSystemHttpsProxy
{
get
;
set
;
}
public
List
<
string
>
ExcludedHttpsHostNameRegex
{
get
;
set
;
}
public
ExplicitProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
:
base
(
IpAddress
,
Port
,
EnableSsl
)
{
}
}
/// <summary>
/// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding
/// </summary>
public
class
TransparentProxyEndPoint
:
ProxyEndPoint
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//This is valid only when UseServerNameIndication is set to false
public
string
GenericCertificateName
{
get
;
set
;
}
// public bool UseServerNameIndication { get; set; }
public
TransparentProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
:
base
(
IpAddress
,
Port
,
EnableSsl
)
{
this
.
GenericCertificateName
=
"localhost"
;
}
}
}
using
System.Collections.Generic
;
using
System.Net
;
using
System.Net.Sockets
;
namespace
Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public
abstract
class
ProxyEndPoint
{
public
ProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
{
this
.
IpAddress
=
IpAddress
;
this
.
Port
=
Port
;
this
.
EnableSsl
=
EnableSsl
;
}
public
IPAddress
IpAddress
{
get
;
internal
set
;
}
public
int
Port
{
get
;
internal
set
;
}
public
bool
EnableSsl
{
get
;
internal
set
;
}
public
bool
IpV6Enabled
=>
IpAddress
==
IPAddress
.
IPv6Any
||
IpAddress
==
IPAddress
.
IPv6Loopback
||
IpAddress
==
IPAddress
.
IPv6None
;
internal
TcpListener
listener
{
get
;
set
;
}
}
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
public
class
ExplicitProxyEndPoint
:
ProxyEndPoint
{
internal
bool
IsSystemHttpProxy
{
get
;
set
;
}
internal
bool
IsSystemHttpsProxy
{
get
;
set
;
}
public
List
<
string
>
ExcludedHttpsHostNameRegex
{
get
;
set
;
}
public
ExplicitProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
:
base
(
IpAddress
,
Port
,
EnableSsl
)
{
}
}
/// <summary>
/// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding
/// </summary>
public
class
TransparentProxyEndPoint
:
ProxyEndPoint
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//This is valid only when UseServerNameIndication is set to false
public
string
GenericCertificateName
{
get
;
set
;
}
// public bool UseServerNameIndication { get; set; }
public
TransparentProxyEndPoint
(
IPAddress
IpAddress
,
int
Port
,
bool
EnableSsl
)
:
base
(
IpAddress
,
Port
,
EnableSsl
)
{
this
.
GenericCertificateName
=
"localhost"
;
}
}
}
Titanium.Web.Proxy/Models/ExternalProxy.cs
View file @
2316ea2d
namespace
Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any
/// </summary>
public
class
ExternalProxy
{
public
string
HostName
{
get
;
set
;
}
public
int
Port
{
get
;
set
;
}
}
}
using
System
;
using
System.Net
;
namespace
Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any
/// </summary>
public
class
ExternalProxy
{
private
static
readonly
Lazy
<
NetworkCredential
>
DefaultCredentials
=
new
Lazy
<
NetworkCredential
>(()
=>
CredentialCache
.
DefaultNetworkCredentials
);
private
string
userName
;
private
string
password
;
public
bool
UseDefaultCredentials
{
get
;
set
;
}
public
string
UserName
{
get
{
return
UseDefaultCredentials
?
DefaultCredentials
.
Value
.
UserName
:
userName
;
}
set
{
userName
=
value
;
if
(
DefaultCredentials
.
Value
.
UserName
!=
userName
)
{
UseDefaultCredentials
=
false
;
}
}
}
public
string
Password
{
get
{
return
UseDefaultCredentials
?
DefaultCredentials
.
Value
.
Password
:
password
;
}
set
{
password
=
value
;
if
(
DefaultCredentials
.
Value
.
Password
!=
password
)
{
UseDefaultCredentials
=
false
;
}
}
}
public
string
HostName
{
get
;
set
;
}
public
int
Port
{
get
;
set
;
}
}
}
Titanium.Web.Proxy/Network/CertificateMaker.cs
0 → 100644
View file @
2316ea2d
using
System
;
using
System.Reflection
;
using
System.Security.Cryptography.X509Certificates
;
using
System.Threading
;
namespace
Titanium.Web.Proxy.Network
{
public
class
CertificateMaker
{
private
Type
typeX500DN
;
private
Type
typeX509PrivateKey
;
private
Type
typeOID
;
private
Type
typeOIDS
;
private
Type
typeKUExt
;
private
Type
typeEKUExt
;
private
Type
typeRequestCert
;
private
Type
typeX509Extensions
;
private
Type
typeBasicConstraints
;
private
Type
typeSignerCertificate
;
private
Type
typeX509Enrollment
;
private
Type
typeAlternativeName
;
private
Type
typeAlternativeNames
;
private
Type
typeAlternativeNamesExt
;
private
string
sProviderName
=
"Microsoft Enhanced Cryptographic Provider v1.0"
;
private
object
_SharedPrivateKey
;
public
CertificateMaker
()
{
this
.
typeX500DN
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CX500DistinguishedName"
,
true
);
this
.
typeX509PrivateKey
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CX509PrivateKey"
,
true
);
this
.
typeOID
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CObjectId"
,
true
);
this
.
typeOIDS
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CObjectIds.1"
,
true
);
this
.
typeEKUExt
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CX509ExtensionEnhancedKeyUsage"
);
this
.
typeKUExt
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CX509ExtensionKeyUsage"
);
this
.
typeRequestCert
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CX509CertificateRequestCertificate"
);
this
.
typeX509Extensions
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CX509Extensions"
);
this
.
typeBasicConstraints
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CX509ExtensionBasicConstraints"
);
this
.
typeSignerCertificate
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CSignerCertificate"
);
this
.
typeX509Enrollment
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CX509Enrollment"
);
this
.
typeAlternativeName
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CAlternativeName"
);
this
.
typeAlternativeNames
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CAlternativeNames"
);
this
.
typeAlternativeNamesExt
=
Type
.
GetTypeFromProgID
(
"X509Enrollment.CX509ExtensionAlternativeNames"
);
}
public
X509Certificate2
MakeCertificate
(
string
sSubjectCN
,
bool
isRoot
,
X509Certificate2
signingCert
=
null
)
{
return
this
.
MakeCertificateInternal
(
sSubjectCN
,
isRoot
,
true
,
signingCert
);
}
private
X509Certificate2
MakeCertificate
(
bool
IsRoot
,
string
SubjectCN
,
string
FullSubject
,
int
PrivateKeyLength
,
string
HashAlg
,
DateTime
ValidFrom
,
DateTime
ValidTo
,
X509Certificate2
SigningCertificate
)
{
X509Certificate2
cert
;
if
(
IsRoot
!=
(
null
==
SigningCertificate
))
{
throw
new
ArgumentException
(
"You must specify a Signing Certificate if and only if you are not creating a root."
,
"oSigningCertificate"
);
}
object
x500DN
=
Activator
.
CreateInstance
(
this
.
typeX500DN
);
object
[]
subject
=
new
object
[]
{
FullSubject
,
0
};
this
.
typeX500DN
.
InvokeMember
(
"Encode"
,
BindingFlags
.
InvokeMethod
,
null
,
x500DN
,
subject
);
object
x500DN2
=
Activator
.
CreateInstance
(
this
.
typeX500DN
);
if
(!
IsRoot
)
{
subject
[
0
]
=
SigningCertificate
.
Subject
;
}
this
.
typeX500DN
.
InvokeMember
(
"Encode"
,
BindingFlags
.
InvokeMethod
,
null
,
x500DN2
,
subject
);
object
sharedPrivateKey
=
null
;
if
(!
IsRoot
)
{
sharedPrivateKey
=
this
.
_SharedPrivateKey
;
}
if
(
sharedPrivateKey
==
null
)
{
sharedPrivateKey
=
Activator
.
CreateInstance
(
this
.
typeX509PrivateKey
);
subject
=
new
object
[]
{
this
.
sProviderName
};
this
.
typeX509PrivateKey
.
InvokeMember
(
"ProviderName"
,
BindingFlags
.
PutDispProperty
,
null
,
sharedPrivateKey
,
subject
);
subject
[
0
]
=
2
;
this
.
typeX509PrivateKey
.
InvokeMember
(
"ExportPolicy"
,
BindingFlags
.
PutDispProperty
,
null
,
sharedPrivateKey
,
subject
);
subject
=
new
object
[]
{
(
IsRoot
?
2
:
1
)
};
this
.
typeX509PrivateKey
.
InvokeMember
(
"KeySpec"
,
BindingFlags
.
PutDispProperty
,
null
,
sharedPrivateKey
,
subject
);
if
(!
IsRoot
)
{
subject
=
new
object
[]
{
176
};
this
.
typeX509PrivateKey
.
InvokeMember
(
"KeyUsage"
,
BindingFlags
.
PutDispProperty
,
null
,
sharedPrivateKey
,
subject
);
}
subject
[
0
]
=
PrivateKeyLength
;
this
.
typeX509PrivateKey
.
InvokeMember
(
"Length"
,
BindingFlags
.
PutDispProperty
,
null
,
sharedPrivateKey
,
subject
);
this
.
typeX509PrivateKey
.
InvokeMember
(
"Create"
,
BindingFlags
.
InvokeMethod
,
null
,
sharedPrivateKey
,
null
);
if
(!
IsRoot
)
{
this
.
_SharedPrivateKey
=
sharedPrivateKey
;
}
}
subject
=
new
object
[
1
];
object
obj3
=
Activator
.
CreateInstance
(
this
.
typeOID
);
subject
[
0
]
=
"1.3.6.1.5.5.7.3.1"
;
this
.
typeOID
.
InvokeMember
(
"InitializeFromValue"
,
BindingFlags
.
InvokeMethod
,
null
,
obj3
,
subject
);
object
obj4
=
Activator
.
CreateInstance
(
this
.
typeOIDS
);
subject
[
0
]
=
obj3
;
this
.
typeOIDS
.
InvokeMember
(
"Add"
,
BindingFlags
.
InvokeMethod
,
null
,
obj4
,
subject
);
object
obj5
=
Activator
.
CreateInstance
(
this
.
typeEKUExt
);
subject
[
0
]
=
obj4
;
this
.
typeEKUExt
.
InvokeMember
(
"InitializeEncode"
,
BindingFlags
.
InvokeMethod
,
null
,
obj5
,
subject
);
object
obj6
=
Activator
.
CreateInstance
(
this
.
typeRequestCert
);
subject
=
new
object
[]
{
1
,
sharedPrivateKey
,
string
.
Empty
};
this
.
typeRequestCert
.
InvokeMember
(
"InitializeFromPrivateKey"
,
BindingFlags
.
InvokeMethod
,
null
,
obj6
,
subject
);
subject
=
new
object
[]
{
x500DN
};
this
.
typeRequestCert
.
InvokeMember
(
"Subject"
,
BindingFlags
.
PutDispProperty
,
null
,
obj6
,
subject
);
subject
[
0
]
=
x500DN
;
this
.
typeRequestCert
.
InvokeMember
(
"Issuer"
,
BindingFlags
.
PutDispProperty
,
null
,
obj6
,
subject
);
subject
[
0
]
=
ValidFrom
;
this
.
typeRequestCert
.
InvokeMember
(
"NotBefore"
,
BindingFlags
.
PutDispProperty
,
null
,
obj6
,
subject
);
subject
[
0
]
=
ValidTo
;
this
.
typeRequestCert
.
InvokeMember
(
"NotAfter"
,
BindingFlags
.
PutDispProperty
,
null
,
obj6
,
subject
);
object
obj7
=
Activator
.
CreateInstance
(
this
.
typeKUExt
);
subject
[
0
]
=
176
;
this
.
typeKUExt
.
InvokeMember
(
"InitializeEncode"
,
BindingFlags
.
InvokeMethod
,
null
,
obj7
,
subject
);
object
obj8
=
this
.
typeRequestCert
.
InvokeMember
(
"X509Extensions"
,
BindingFlags
.
GetProperty
,
null
,
obj6
,
null
);
subject
=
new
object
[
1
];
if
(!
IsRoot
)
{
subject
[
0
]
=
obj7
;
this
.
typeX509Extensions
.
InvokeMember
(
"Add"
,
BindingFlags
.
InvokeMethod
,
null
,
obj8
,
subject
);
}
subject
[
0
]
=
obj5
;
this
.
typeX509Extensions
.
InvokeMember
(
"Add"
,
BindingFlags
.
InvokeMethod
,
null
,
obj8
,
subject
);
if
(!
IsRoot
)
{
object
obj12
=
Activator
.
CreateInstance
(
this
.
typeSignerCertificate
);
subject
=
new
object
[]
{
0
,
0
,
12
,
SigningCertificate
.
Thumbprint
};
this
.
typeSignerCertificate
.
InvokeMember
(
"Initialize"
,
BindingFlags
.
InvokeMethod
,
null
,
obj12
,
subject
);
subject
=
new
object
[]
{
obj12
};
this
.
typeRequestCert
.
InvokeMember
(
"SignerCertificate"
,
BindingFlags
.
PutDispProperty
,
null
,
obj6
,
subject
);
}
else
{
object
obj13
=
Activator
.
CreateInstance
(
this
.
typeBasicConstraints
);
subject
=
new
object
[]
{
"true"
,
"0"
};
this
.
typeBasicConstraints
.
InvokeMember
(
"InitializeEncode"
,
BindingFlags
.
InvokeMethod
,
null
,
obj13
,
subject
);
subject
=
new
object
[]
{
obj13
};
this
.
typeX509Extensions
.
InvokeMember
(
"Add"
,
BindingFlags
.
InvokeMethod
,
null
,
obj8
,
subject
);
}
object
obj14
=
Activator
.
CreateInstance
(
this
.
typeOID
);
subject
=
new
object
[]
{
1
,
0
,
0
,
HashAlg
};
this
.
typeOID
.
InvokeMember
(
"InitializeFromAlgorithmName"
,
BindingFlags
.
InvokeMethod
,
null
,
obj14
,
subject
);
subject
=
new
object
[]
{
obj14
};
this
.
typeRequestCert
.
InvokeMember
(
"HashAlgorithm"
,
BindingFlags
.
PutDispProperty
,
null
,
obj6
,
subject
);
this
.
typeRequestCert
.
InvokeMember
(
"Encode"
,
BindingFlags
.
InvokeMethod
,
null
,
obj6
,
null
);
object
obj15
=
Activator
.
CreateInstance
(
this
.
typeX509Enrollment
);
subject
[
0
]
=
obj6
;
this
.
typeX509Enrollment
.
InvokeMember
(
"InitializeFromRequest"
,
BindingFlags
.
InvokeMethod
,
null
,
obj15
,
subject
);
if
(
IsRoot
)
{
subject
[
0
]
=
"DO_NOT_TRUST_TitaniumProxy-CE"
;
this
.
typeX509Enrollment
.
InvokeMember
(
"CertificateFriendlyName"
,
BindingFlags
.
PutDispProperty
,
null
,
obj15
,
subject
);
}
subject
[
0
]
=
0
;
object
obj16
=
this
.
typeX509Enrollment
.
InvokeMember
(
"CreateRequest"
,
BindingFlags
.
InvokeMethod
,
null
,
obj15
,
subject
);
subject
=
new
object
[]
{
2
,
obj16
,
0
,
string
.
Empty
};
this
.
typeX509Enrollment
.
InvokeMember
(
"InstallResponse"
,
BindingFlags
.
InvokeMethod
,
null
,
obj15
,
subject
);
subject
=
new
object
[]
{
null
,
0
,
1
};
string
empty
=
string
.
Empty
;
try
{
empty
=
(
string
)
this
.
typeX509Enrollment
.
InvokeMember
(
"CreatePFX"
,
BindingFlags
.
InvokeMethod
,
null
,
obj15
,
subject
);
return
new
X509Certificate2
(
Convert
.
FromBase64String
(
empty
),
string
.
Empty
,
X509KeyStorageFlags
.
Exportable
);
}
catch
(
Exception
exception1
)
{
Exception
exception
=
exception1
;
cert
=
null
;
}
return
cert
;
}
private
X509Certificate2
MakeCertificateInternal
(
string
sSubjectCN
,
bool
isRoot
,
bool
switchToMTAIfNeeded
,
X509Certificate2
signingCert
=
null
)
{
X509Certificate2
rCert
=
null
;
if
(
switchToMTAIfNeeded
&&
Thread
.
CurrentThread
.
GetApartmentState
()
!=
ApartmentState
.
MTA
)
{
ManualResetEvent
manualResetEvent
=
new
ManualResetEvent
(
false
);
ThreadPool
.
QueueUserWorkItem
((
object
o
)
=>
{
rCert
=
this
.
MakeCertificateInternal
(
sSubjectCN
,
isRoot
,
false
,
signingCert
);
manualResetEvent
.
Set
();
});
manualResetEvent
.
WaitOne
();
manualResetEvent
.
Close
();
return
rCert
;
}
string
fullSubject
=
string
.
Format
(
"CN={0}{1}"
,
sSubjectCN
,
""
);
//Subject
string
HashAlgo
=
"SHA256"
;
//Sig Algo
int
GraceDays
=
-
366
;
//Grace Days
int
ValidDays
=
1825
;
//ValiDays
int
keyLength
=
2048
;
//KeyLength
DateTime
graceTime
=
DateTime
.
Now
.
AddDays
((
double
)
GraceDays
);
DateTime
now
=
DateTime
.
Now
;
try
{
if
(!
isRoot
)
{
rCert
=
this
.
MakeCertificate
(
false
,
sSubjectCN
,
fullSubject
,
keyLength
,
HashAlgo
,
graceTime
,
now
.
AddDays
((
double
)
ValidDays
),
signingCert
);
}
else
{
rCert
=
this
.
MakeCertificate
(
true
,
sSubjectCN
,
fullSubject
,
keyLength
,
HashAlgo
,
graceTime
,
now
.
AddDays
((
double
)
ValidDays
),
null
);
}
}
catch
(
Exception
e
)
{
throw
e
;
}
return
rCert
;
}
}
}
Titanium.Web.Proxy/Network/CertificateManager.cs
View file @
2316ea2d
using
System
;
using
System.IO
;
using
System.Diagnostics
;
using
System.Collections.Generic
;
using
System.Security.Cryptography.X509Certificates
;
using
System.Reflection
;
using
System.Threading.Tasks
;
using
System.Threading
;
using
System.Linq
;
using
System.Collections.Concurrent
;
using
System.IO
;
namespace
Titanium.Web.Proxy.Network
{
...
...
@@ -15,74 +13,86 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
internal
class
CertificateManager
:
IDisposable
{
private
const
string
CertCreateFormat
=
"-ss {0} -n \"CN={1}, O={2}\" -sky {3} -cy {4} -m 120 -a sha256 -eku 1.3.6.1.5.5.7.3.1 {5}"
;
private
CertificateMaker
certEngine
=
null
;
private
bool
clearCertificates
{
get
;
set
;
}
/// <summary>
/// Cache dictionary
/// </summary>
private
readonly
IDictionary
<
string
,
CachedCertificate
>
certificateCache
;
/// <summary>
/// A lock to manage concurrency
/// </summary>
private
SemaphoreSlim
semaphoreLock
=
new
SemaphoreSlim
(
1
);
private
Action
<
Exception
>
exceptionFunc
;
internal
string
Issuer
{
get
;
private
set
;
}
internal
string
RootCertificateName
{
get
;
private
set
;
}
internal
X509Store
MyStore
{
get
;
private
set
;
}
internal
X509Store
RootStore
{
get
;
private
set
;
}
internal
X509Certificate2
rootCertificate
{
get
;
set
;
}
internal
CertificateManager
(
string
issuer
,
string
rootCertificateName
)
internal
CertificateManager
(
string
issuer
,
string
rootCertificateName
,
Action
<
Exception
>
exceptionFunc
)
{
Issuer
=
issuer
;
RootCertificateName
=
rootCertificateName
;
this
.
exceptionFunc
=
exceptionFunc
;
MyStore
=
new
X509Store
(
StoreName
.
My
,
StoreLocation
.
CurrentUser
);
RootStore
=
new
X509Store
(
StoreName
.
Root
,
StoreLocation
.
CurrentUser
);
certEngine
=
new
CertificateMaker
();
certificateCache
=
new
Dictionary
<
string
,
CachedCertificate
>();
Issuer
=
issuer
;
RootCertificateName
=
rootCertificateName
;
certificateCache
=
new
ConcurrentDictionary
<
string
,
CachedCertificate
>();
}
/// <summary>
/// Attempts to move a self-signed certificate to the root store.
/// </summary>
/// <returns>true if succeeded, else false</returns>
internal
async
Task
<
bool
>
CreateTrustedRootCertificate
()
internal
X509Certificate2
GetRootCertificate
()
{
X509Certificate2
rootCertificate
=
await
CreateCertificate
(
RootStore
,
RootCertificateName
,
true
);
var
fileName
=
Path
.
Combine
(
System
.
IO
.
Path
.
GetDirectoryName
(
System
.
Reflection
.
Assembly
.
GetExecutingAssembly
().
Location
),
"rootCert.pfx"
);
return
rootCertificate
!=
null
;
if
(
File
.
Exists
(
fileName
))
{
try
{
return
new
X509Certificate2
(
fileName
,
string
.
Empty
,
X509KeyStorageFlags
.
Exportable
);
}
catch
(
Exception
e
)
{
exceptionFunc
(
e
);
return
null
;
}
}
return
null
;
}
/// <summary>
/// Attempts to
remove the self-signed certificate from the root store.
/// Attempts to
create a RootCertificate
/// </summary>
/// <returns>true if succeeded, else false</returns>
internal
bool
DestroyTrustedRootCertificate
()
{
return
DestroyCertificate
(
RootStore
,
RootCertificateName
,
false
);
}
internal
X509Certificate2Collection
FindCertificates
(
string
certificateSubject
)
internal
bool
CreateTrustedRootCertificate
()
{
return
FindCertificates
(
MyStore
,
certificateSubject
);
}
protected
virtual
X509Certificate2Collection
FindCertificates
(
X509Store
store
,
string
certificateSubject
)
{
X509Certificate2Collection
discoveredCertificates
=
store
.
Certificates
.
Find
(
X509FindType
.
FindBySubjectDistinguishedName
,
certificateSubject
,
false
);
return
discoveredCertificates
.
Count
>
0
?
discoveredCertificates
:
null
;
}
internal
async
Task
<
X509Certificate2
>
CreateCertificate
(
string
certificateName
,
bool
isRootCertificate
)
{
return
await
CreateCertificate
(
MyStore
,
certificateName
,
isRootCertificate
);
rootCertificate
=
GetRootCertificate
();
if
(
rootCertificate
!=
null
)
{
return
true
;
}
try
{
rootCertificate
=
CreateCertificate
(
RootCertificateName
,
true
);
}
catch
(
Exception
e
)
{
exceptionFunc
(
e
);
}
if
(
rootCertificate
!=
null
)
{
try
{
var
fileName
=
Path
.
Combine
(
System
.
IO
.
Path
.
GetDirectoryName
(
System
.
Reflection
.
Assembly
.
GetExecutingAssembly
().
Location
),
"rootCert.pfx"
);
File
.
WriteAllBytes
(
fileName
,
rootCertificate
.
Export
(
X509ContentType
.
Pkcs12
));
}
catch
(
Exception
e
)
{
exceptionFunc
(
e
);
}
}
return
rootCertificate
!=
null
;
}
/// <summary>
/// Create an SSL certificate
/// </summary>
...
...
@@ -90,10 +100,8 @@ namespace Titanium.Web.Proxy.Network
/// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param>
/// <returns></returns>
protected
async
virtual
Task
<
X509Certificate2
>
CreateCertificate
(
X509Store
store
,
string
certificateName
,
bool
isRootCertificate
)
internal
virtual
X509Certificate2
CreateCertificate
(
string
certificateName
,
bool
isRootCertificate
)
{
await
semaphoreLock
.
WaitAsync
();
try
{
if
(
certificateCache
.
ContainsKey
(
certificateName
))
...
...
@@ -102,167 +110,46 @@ namespace Titanium.Web.Proxy.Network
cached
.
LastAccess
=
DateTime
.
Now
;
return
cached
.
Certificate
;
}
}
catch
{
X509Certificate2
certificate
=
null
;
store
.
Open
(
OpenFlags
.
ReadWrite
);
string
certificateSubject
=
string
.
Format
(
"CN={0}, O={1}"
,
certificateName
,
Issuer
);
X509Certificate2Collection
certificates
;
if
(
isRootCertificate
)
}
X509Certificate2
certificate
=
null
;
lock
(
string
.
Intern
(
certificateName
))
{
if
(
certificateCache
.
ContainsKey
(
certificateName
)
==
false
)
{
certificates
=
FindCertificates
(
store
,
certificateSubject
);
if
(
certificates
!=
null
)
try
{
certificate
=
cert
ificates
[
0
]
;
certificate
=
cert
Engine
.
MakeCertificate
(
certificateName
,
isRootCertificate
,
rootCertificate
)
;
}
}
if
(
certificate
==
null
)
{
string
[]
args
=
new
[]
{
GetCertificateCreateArgs
(
store
,
certificateName
)
};
await
CreateCertificate
(
args
);
certificates
=
FindCertificates
(
store
,
certificateSubject
);
//remove it from store
if
(!
isRootCertificate
)
catch
(
Exception
e
)
{
DestroyCertificate
(
certificateNam
e
);
exceptionFunc
(
e
);
}
if
(
certificates
!=
null
)
if
(
certificate
!=
null
&&
!
certificateCache
.
ContainsKey
(
certificateName
))
{
certificate
=
certificates
[
0
]
;
certificate
Cache
.
Add
(
certificateName
,
new
CachedCertificate
()
{
Certificate
=
certificate
})
;
}
}
store
.
Close
();
if
(
certificate
!=
null
&&
!
certificateCache
.
ContainsKey
(
certificateName
))
else
{
certificateCache
.
Add
(
certificateName
,
new
CachedCertificate
()
{
Certificate
=
certificate
});
if
(
certificateCache
.
ContainsKey
(
certificateName
))
{
var
cached
=
certificateCache
[
certificateName
];
cached
.
LastAccess
=
DateTime
.
Now
;
return
cached
.
Certificate
;
}
}
return
certificate
;
}
finally
{
semaphoreLock
.
Release
();
}
}
/// <summary>
/// Create certificate using makecert.exe
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
protected
virtual
Task
<
int
>
CreateCertificate
(
string
[]
args
)
{
// there is no non-generic TaskCompletionSource
var
tcs
=
new
TaskCompletionSource
<
int
>();
var
process
=
new
Process
();
string
file
=
Path
.
Combine
(
Path
.
GetDirectoryName
(
Assembly
.
GetExecutingAssembly
().
Location
),
"makecert.exe"
);
if
(!
File
.
Exists
(
file
))
{
throw
new
Exception
(
"Unable to locate 'makecert.exe'."
);
}
process
.
StartInfo
.
Verb
=
"runas"
;
process
.
StartInfo
.
Arguments
=
args
!=
null
?
args
[
0
]
:
string
.
Empty
;
process
.
StartInfo
.
CreateNoWindow
=
true
;
process
.
StartInfo
.
UseShellExecute
=
false
;
process
.
StartInfo
.
FileName
=
file
;
process
.
EnableRaisingEvents
=
true
;
process
.
Exited
+=
(
sender
,
processArgs
)
=>
{
tcs
.
SetResult
(
process
.
ExitCode
);
process
.
Dispose
();
};
bool
started
=
process
.
Start
();
if
(!
started
)
{
//you may allow for the process to be re-used (started = false)
//but I'm not sure about the guarantees of the Exited event in such a case
throw
new
InvalidOperationException
(
"Could not start process: "
+
process
);
}
return
tcs
.
Task
;
}
/// <summary>
/// Destroy an SSL certificate from local store
/// </summary>
/// <param name="certificateName"></param>
/// <returns></returns>
internal
bool
DestroyCertificate
(
string
certificateName
)
{
return
DestroyCertificate
(
MyStore
,
certificateName
,
false
);
}
/// <summary>
/// Destroy certificate from the specified store
/// optionally also remove from proxy certificate cache
/// </summary>
/// <param name="store"></param>
/// <param name="certificateName"></param>
/// <param name="removeFromCache"></param>
/// <returns></returns>
protected
virtual
bool
DestroyCertificate
(
X509Store
store
,
string
certificateName
,
bool
removeFromCache
)
{
X509Certificate2Collection
certificates
=
null
;
store
.
Open
(
OpenFlags
.
ReadWrite
);
string
certificateSubject
=
string
.
Format
(
"CN={0}, O={1}"
,
certificateName
,
Issuer
);
certificates
=
FindCertificates
(
store
,
certificateSubject
)
;
return
certificate
;
if
(
certificates
!=
null
)
{
store
.
RemoveRange
(
certificates
);
certificates
=
FindCertificates
(
store
,
certificateSubject
);
}
store
.
Close
();
if
(
removeFromCache
&&
certificateCache
.
ContainsKey
(
certificateName
))
{
certificateCache
.
Remove
(
certificateName
);
}
return
certificates
==
null
;
}
/// <summary>
/// Create the neccessary arguments for makecert.exe to create the required certificate
/// </summary>
/// <param name="store"></param>
/// <param name="certificateName"></param>
/// <returns></returns>
protected
virtual
string
GetCertificateCreateArgs
(
X509Store
store
,
string
certificateName
)
{
bool
isRootCertificate
=
(
certificateName
==
RootCertificateName
);
string
certCreatArgs
=
string
.
Format
(
CertCreateFormat
,
store
.
Name
,
certificateName
,
Issuer
,
isRootCertificate
?
"signature"
:
"exchange"
,
isRootCertificate
?
"authority"
:
"end"
,
isRootCertificate
?
"-h 1 -r"
:
string
.
Format
(
"-pe -in \"{0}\" -is Root"
,
RootCertificateName
));
return
certCreatArgs
;
}
private
bool
clearCertificates
{
get
;
set
;
}
/// <summary>
/// Stops the certificate cache clear process
/// </summary>
...
...
@@ -279,7 +166,6 @@ namespace Titanium.Web.Proxy.Network
clearCertificates
=
true
;
while
(
clearCertificates
)
{
await
semaphoreLock
.
WaitAsync
();
try
{
...
...
@@ -292,26 +178,49 @@ namespace Titanium.Web.Proxy.Network
foreach
(
var
cache
in
outdated
)
certificateCache
.
Remove
(
cache
.
Key
);
}
finally
{
semaphoreLock
.
Release
();
finally
{
}
//after a minute come back to check for outdated certificates in cache
await
Task
.
Delay
(
1000
*
60
);
}
}
public
void
Dispos
e
()
internal
bool
TrustRootCertificat
e
()
{
if
(
MyStore
!
=
null
)
if
(
rootCertificate
=
=
null
)
{
MyStore
.
Close
()
;
return
false
;
}
try
{
X509Store
x509RootStore
=
new
X509Store
(
StoreName
.
Root
,
StoreLocation
.
CurrentUser
);
var
x509PersonalStore
=
new
X509Store
(
StoreName
.
My
,
StoreLocation
.
CurrentUser
);
x509RootStore
.
Open
(
OpenFlags
.
ReadWrite
);
x509PersonalStore
.
Open
(
OpenFlags
.
ReadWrite
);
if
(
RootStore
!=
null
)
try
{
x509RootStore
.
Add
(
rootCertificate
);
x509PersonalStore
.
Add
(
rootCertificate
);
}
finally
{
x509RootStore
.
Close
();
x509PersonalStore
.
Close
();
}
return
true
;
}
catch
{
RootStore
.
Close
()
;
return
false
;
}
}
public
void
Dispose
()
{
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Network/TcpConnection.cs
View file @
2316ea2d
...
...
@@ -2,6 +2,7 @@
using
System.IO
;
using
System.Net.Sockets
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Models
;
namespace
Titanium.Web.Proxy.Network
{
...
...
@@ -10,11 +11,20 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
public
class
TcpConnection
:
IDisposable
{
internal
ExternalProxy
UpStreamHttpProxy
{
get
;
set
;
}
internal
ExternalProxy
UpStreamHttpsProxy
{
get
;
set
;
}
internal
string
HostName
{
get
;
set
;
}
internal
int
p
ort
{
get
;
set
;
}
internal
int
P
ort
{
get
;
set
;
}
internal
bool
IsHttps
{
get
;
set
;
}
/// <summary>
/// Http version
/// </summary>
internal
Version
Version
{
get
;
set
;
}
internal
TcpClient
TcpClient
{
get
;
set
;
}
/// <summary>
...
...
@@ -27,6 +37,16 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
internal
Stream
Stream
{
get
;
set
;
}
/// <summary>
/// Last time this connection was used
/// </summary>
internal
DateTime
LastAccess
{
get
;
set
;
}
internal
TcpConnection
()
{
LastAccess
=
DateTime
.
Now
;
}
public
void
Dispose
()
{
Stream
.
Close
();
...
...
Titanium.Web.Proxy/Network/TcpConnectionFactory.cs
View file @
2316ea2d
...
...
@@ -7,6 +7,7 @@ using System.Net.Security;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Models
;
using
System.Security.Authentication
;
using
System.Linq
;
namespace
Titanium.Web.Proxy.Network
{
...
...
@@ -34,7 +35,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="clientStream"></param>
/// <returns></returns>
internal
async
Task
<
TcpConnection
>
CreateClient
(
int
bufferSize
,
int
connectionTimeOutSeconds
,
string
remoteHostName
,
int
remotePort
,
Version
httpVersion
,
string
remoteHostName
,
int
remotePort
,
Version
httpVersion
,
bool
isHttps
,
SslProtocols
supportedSslProtocols
,
RemoteCertificateValidationCallback
remoteCertificateValidationCallback
,
LocalCertificateSelectionCallback
localCertificateSelectionCallback
,
ExternalProxy
externalHttpProxy
,
ExternalProxy
externalHttpsProxy
,
...
...
@@ -48,16 +49,22 @@ namespace Titanium.Web.Proxy.Network
SslStream
sslStream
=
null
;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if
(
externalHttpsProxy
!=
null
)
if
(
externalHttpsProxy
!=
null
&&
externalHttpsProxy
.
HostName
!=
remoteHostName
)
{
client
=
new
TcpClient
(
externalHttpsProxy
.
HostName
,
externalHttpsProxy
.
Port
);
stream
=
client
.
GetStream
();
using
(
var
writer
=
new
StreamWriter
(
stream
,
Encoding
.
ASCII
,
bufferSize
,
true
))
{
await
writer
.
WriteLineAsync
(
string
.
Format
(
"CONNECT {0}:{1} {2}"
,
remoteHostName
,
remotePort
,
httpVersion
)
);
await
writer
.
WriteLineAsync
(
string
.
Format
(
"Host: {0}:{1}"
,
remoteHostName
,
remotePort
)
);
await
writer
.
WriteLineAsync
(
$"CONNECT
{
remoteHostName
}
:
{
remotePort
}
HTTP/
{
httpVersion
}
"
);
await
writer
.
WriteLineAsync
(
$"Host:
{
remoteHostName
}
:
{
remotePort
}
"
);
await
writer
.
WriteLineAsync
(
"Connection: Keep-Alive"
);
if
(!
string
.
IsNullOrEmpty
(
externalHttpsProxy
.
UserName
)
&&
externalHttpsProxy
.
Password
!=
null
)
{
await
writer
.
WriteLineAsync
(
"Proxy-Connection: keep-alive"
);
await
writer
.
WriteLineAsync
(
"Proxy-Authorization"
+
": Basic "
+
Convert
.
ToBase64String
(
System
.
Text
.
Encoding
.
UTF8
.
GetBytes
(
externalHttpsProxy
.
UserName
+
":"
+
externalHttpsProxy
.
Password
)));
}
await
writer
.
WriteLineAsync
();
await
writer
.
FlushAsync
();
writer
.
Close
();
...
...
@@ -67,7 +74,8 @@ namespace Titanium.Web.Proxy.Network
{
var
result
=
await
reader
.
ReadLineAsync
();
if
(!
result
.
ToLower
().
Contains
(
"200 connection established"
))
if
(!
new
string
[]
{
"200 OK"
,
"connection established"
}.
Any
(
s
=>
result
.
ToLower
().
Contains
(
s
.
ToLower
())))
{
throw
new
Exception
(
"Upstream proxy failed to create a secure tunnel"
);
}
...
...
@@ -92,17 +100,14 @@ namespace Titanium.Web.Proxy.Network
}
catch
{
if
(
sslStream
!=
null
)
{
sslStream
.
Dispose
();
}
sslStream
?.
Dispose
();
throw
;
}
}
else
{
if
(
externalHttpProxy
!=
null
)
if
(
externalHttpProxy
!=
null
&&
externalHttpProxy
.
HostName
!=
remoteHostName
)
{
client
=
new
TcpClient
(
externalHttpProxy
.
HostName
,
externalHttpProxy
.
Port
);
stream
=
client
.
GetStream
();
...
...
@@ -119,15 +124,18 @@ namespace Titanium.Web.Proxy.Network
stream
.
ReadTimeout
=
connectionTimeOutSeconds
*
1000
;
stream
.
WriteTimeout
=
connectionTimeOutSeconds
*
1000
;
return
new
TcpConnection
()
{
UpStreamHttpProxy
=
externalHttpProxy
,
UpStreamHttpsProxy
=
externalHttpsProxy
,
HostName
=
remoteHostName
,
p
ort
=
remotePort
,
P
ort
=
remotePort
,
IsHttps
=
isHttps
,
TcpClient
=
client
,
StreamReader
=
new
CustomBinaryReader
(
stream
),
Stream
=
stream
Stream
=
stream
,
Version
=
httpVersion
};
}
}
...
...
Titanium.Web.Proxy/ProxyAuthorizationHandler.cs
0 → 100644
View file @
2316ea2d
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Text
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Exceptions
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Models
;
namespace
Titanium.Web.Proxy
{
public
partial
class
ProxyServer
{
private
async
Task
<
bool
>
CheckAuthorization
(
StreamWriter
clientStreamWriter
,
IEnumerable
<
HttpHeader
>
Headers
)
{
if
(
AuthenticateUserFunc
==
null
)
{
return
true
;
}
var
httpHeaders
=
Headers
as
HttpHeader
[]
??
Headers
.
ToArray
();
try
{
if
(!
httpHeaders
.
Where
(
t
=>
t
.
Name
==
"Proxy-Authorization"
).
Any
())
{
await
WriteResponseStatus
(
new
Version
(
1
,
1
),
"407"
,
"Proxy Authentication Required"
,
clientStreamWriter
);
var
response
=
new
Response
();
response
.
ResponseHeaders
=
new
Dictionary
<
string
,
HttpHeader
>();
response
.
ResponseHeaders
.
Add
(
"Proxy-Authenticate"
,
new
HttpHeader
(
"Proxy-Authenticate"
,
"Basic realm=\"TitaniumProxy\""
));
response
.
ResponseHeaders
.
Add
(
"Proxy-Connection"
,
new
HttpHeader
(
"Proxy-Connection"
,
"close"
));
await
WriteResponseHeaders
(
clientStreamWriter
,
response
);
await
clientStreamWriter
.
WriteLineAsync
();
return
false
;
}
else
{
var
headerValue
=
httpHeaders
.
Where
(
t
=>
t
.
Name
==
"Proxy-Authorization"
).
FirstOrDefault
().
Value
.
Trim
();
if
(!
headerValue
.
ToLower
().
StartsWith
(
"basic"
))
{
//Return not authorized
await
WriteResponseStatus
(
new
Version
(
1
,
1
),
"407"
,
"Proxy Authentication Invalid"
,
clientStreamWriter
);
var
response
=
new
Response
();
response
.
ResponseHeaders
=
new
Dictionary
<
string
,
HttpHeader
>();
response
.
ResponseHeaders
.
Add
(
"Proxy-Authenticate"
,
new
HttpHeader
(
"Proxy-Authenticate"
,
"Basic realm=\"TitaniumProxy\""
));
response
.
ResponseHeaders
.
Add
(
"Proxy-Connection"
,
new
HttpHeader
(
"Proxy-Connection"
,
"close"
));
await
WriteResponseHeaders
(
clientStreamWriter
,
response
);
await
clientStreamWriter
.
WriteLineAsync
();
return
false
;
}
headerValue
=
headerValue
.
Substring
(
5
).
Trim
();
var
decoded
=
Encoding
.
UTF8
.
GetString
(
Convert
.
FromBase64String
(
headerValue
));
if
(
decoded
.
Contains
(
":"
)
==
false
)
{
//Return not authorized
await
WriteResponseStatus
(
new
Version
(
1
,
1
),
"407"
,
"Proxy Authentication Invalid"
,
clientStreamWriter
);
var
response
=
new
Response
();
response
.
ResponseHeaders
=
new
Dictionary
<
string
,
HttpHeader
>();
response
.
ResponseHeaders
.
Add
(
"Proxy-Authenticate"
,
new
HttpHeader
(
"Proxy-Authenticate"
,
"Basic realm=\"TitaniumProxy\""
));
response
.
ResponseHeaders
.
Add
(
"Proxy-Connection"
,
new
HttpHeader
(
"Proxy-Connection"
,
"close"
));
await
WriteResponseHeaders
(
clientStreamWriter
,
response
);
await
clientStreamWriter
.
WriteLineAsync
();
return
false
;
}
var
username
=
decoded
.
Substring
(
0
,
decoded
.
IndexOf
(
':'
));
var
password
=
decoded
.
Substring
(
decoded
.
IndexOf
(
':'
)
+
1
);
return
await
AuthenticateUserFunc
(
username
,
password
).
ConfigureAwait
(
false
);
}
}
catch
(
Exception
e
)
{
ExceptionFunc
(
new
ProxyAuthorizationException
(
"Error whilst authorizing request"
,
e
,
httpHeaders
));
//Return not authorized
await
WriteResponseStatus
(
new
Version
(
1
,
1
),
"407"
,
"Proxy Authentication Invalid"
,
clientStreamWriter
);
var
response
=
new
Response
();
response
.
ResponseHeaders
=
new
Dictionary
<
string
,
HttpHeader
>();
response
.
ResponseHeaders
.
Add
(
"Proxy-Authenticate"
,
new
HttpHeader
(
"Proxy-Authenticate"
,
"Basic realm=\"TitaniumProxy\""
));
response
.
ResponseHeaders
.
Add
(
"Proxy-Connection"
,
new
HttpHeader
(
"Proxy-Connection"
,
"close"
));
await
WriteResponseHeaders
(
clientStreamWriter
,
response
);
await
clientStreamWriter
.
WriteLineAsync
();
return
false
;
}
}
}
}
Titanium.Web.Proxy/ProxyServer.cs
View file @
2316ea2d
...
...
@@ -17,10 +17,11 @@ namespace Titanium.Web.Proxy
/// </summary>
public
partial
class
ProxyServer
:
IDisposable
{
/// <summary>
///
Does the root certificate used by this proxy is trusted by the machine
?
///
Is the root certificate used by this proxy is valid
?
/// </summary>
private
bool
cert
Trus
ted
{
get
;
set
;
}
private
bool
cert
Valida
ted
{
get
;
set
;
}
/// <summary>
/// Is the proxy currently running
...
...
@@ -32,6 +33,16 @@ namespace Titanium.Web.Proxy
/// </summary>
private
CertificateManager
certificateCacheManager
{
get
;
set
;
}
/// <summary>
/// An default exception log func
/// </summary>
private
readonly
Lazy
<
Action
<
Exception
>>
defaultExceptionFunc
=
new
Lazy
<
Action
<
Exception
>>(()
=>
(
e
=>
{
}));
/// <summary>
/// backing exception func for exposed public property
/// </summary>
private
Action
<
Exception
>
exceptionFunc
;
/// <summary>
/// A object that creates tcp connection to server
/// </summary>
...
...
@@ -56,9 +67,19 @@ namespace Titanium.Web.Proxy
/// <summary>
/// Name of the root certificate
/// If no certificate is provided then a default Root Certificate will be created and used
/// The provided root certificate has to be in the proxy exe directory with the private key
/// The root certificate file should be named as "rootCert.pfx"
/// </summary>
public
string
RootCertificateName
{
get
;
set
;
}
/// <summary>
/// Trust the RootCertificate used by this proxy server
/// Note that this do not make the client trust the certificate!
/// This would import the root certificate to the certificate store of machine that runs this proxy server
/// </summary>
public
bool
TrustRootCertificate
{
get
;
set
;
}
/// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled
...
...
@@ -88,12 +109,12 @@ namespace Titanium.Web.Proxy
/// <summary>
/// External proxy for Http
/// </summary>
public
ExternalProxy
External
HttpProxy
{
get
;
set
;
}
public
ExternalProxy
UpStream
HttpProxy
{
get
;
set
;
}
/// <summary>
/// External proxy for Http
s
/// External proxy for Http
/// </summary>
public
ExternalProxy
External
HttpsProxy
{
get
;
set
;
}
public
ExternalProxy
UpStream
HttpsProxy
{
get
;
set
;
}
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
...
...
@@ -105,6 +126,51 @@ namespace Titanium.Web.Proxy
/// </summary>
public
event
Func
<
object
,
CertificateSelectionEventArgs
,
Task
>
ClientCertificateSelectionCallback
;
/// <summary>
/// Callback for error events in proxy
/// </summary>
public
Action
<
Exception
>
ExceptionFunc
{
get
{
return
exceptionFunc
??
defaultExceptionFunc
.
Value
;
}
set
{
exceptionFunc
=
value
;
}
}
/// <summary>
/// A callback to authenticate clients
/// Parameters are username, password provided by client
/// return true for successful authentication
/// </summary>
public
Func
<
string
,
string
,
Task
<
bool
>>
AuthenticateUserFunc
{
get
;
set
;
}
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests
/// return the ExternalProxy object with valid credentials
/// </summary>
public
Func
<
SessionEventArgs
,
Task
<
ExternalProxy
>>
GetCustomUpStreamHttpProxyFunc
{
get
;
set
;
}
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTPS requests
/// return the ExternalProxy object with valid credentials
public
Func
<
SessionEventArgs
,
Task
<
ExternalProxy
>>
GetCustomUpStreamHttpsProxyFunc
{
get
;
set
;
}
/// <summary>
/// A list of IpAddress & port this proxy is listening to
/// </summary>
...
...
@@ -115,11 +181,25 @@ namespace Titanium.Web.Proxy
/// </summary>
public
SslProtocols
SupportedSslProtocols
{
get
;
set
;
}
=
SslProtocols
.
Tls
|
SslProtocols
.
Tls11
|
SslProtocols
.
Tls12
|
SslProtocols
.
Ssl3
;
/// <summary>
/// Is the proxy currently running
/// </summary>
public
bool
ProxyRunning
=>
proxyRunning
;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public
bool
ForwardToUpstreamGateway
{
get
;
set
;
}
/// <summary>
/// Constructor
/// </summary>
public
ProxyServer
()
public
ProxyServer
()
:
this
(
null
,
null
)
{
}
public
ProxyServer
(
string
rootCertificateName
,
string
rootCertificateIssuerName
)
{
RootCertificateName
=
rootCertificateName
;
RootCertificateIssuerName
=
rootCertificateIssuerName
;
//default values
ConnectionTimeOutSeconds
=
120
;
CertificateCacheTimeOutMinutes
=
60
;
...
...
@@ -131,9 +211,6 @@ namespace Titanium.Web.Proxy
RootCertificateName
=
RootCertificateName
??
"Titanium Root Certificate Authority"
;
RootCertificateIssuerName
=
RootCertificateIssuerName
??
"Titanium"
;
certificateCacheManager
=
new
CertificateManager
(
RootCertificateIssuerName
,
RootCertificateName
);
}
/// <summary>
...
...
@@ -142,7 +219,7 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param>
public
void
AddEndPoint
(
ProxyEndPoint
endPoint
)
{
if
(
ProxyEndPoints
.
Any
(
x
=>
x
.
IpAddress
==
endPoint
.
IpAddress
&&
x
.
Port
==
endPoint
.
Port
))
if
(
ProxyEndPoints
.
Any
(
x
=>
x
.
IpAddress
.
Equals
(
endPoint
.
IpAddress
)
&&
endPoint
.
Port
!=
0
&&
x
.
Port
==
endPoint
.
Port
))
{
throw
new
Exception
(
"Cannot add another endpoint to same port & ip address"
);
}
...
...
@@ -216,13 +293,14 @@ namespace Titanium.Web.Proxy
//If certificate was trusted by the machine
if
(
cert
Trus
ted
)
if
(
cert
Valida
ted
)
{
systemProxySettingsManager
.
SetHttpsProxy
(
Equals
(
endPoint
.
IpAddress
,
IPAddress
.
Any
)
|
Equals
(
endPoint
.
IpAddress
,
IPAddress
.
Loopback
)
?
"127.0.0.1"
:
endPoint
.
IpAddress
.
ToString
(),
endPoint
.
Port
);
}
endPoint
.
IsSystemHttpsProxy
=
true
;
#if !DEBUG
...
...
@@ -265,7 +343,21 @@ namespace Titanium.Web.Proxy
throw
new
Exception
(
"Proxy is already running."
);
}
certTrusted
=
certificateCacheManager
.
CreateTrustedRootCertificate
().
Result
;
certificateCacheManager
=
new
CertificateManager
(
RootCertificateIssuerName
,
RootCertificateName
,
ExceptionFunc
);
certValidated
=
certificateCacheManager
.
CreateTrustedRootCertificate
();
if
(
TrustRootCertificate
)
{
certificateCacheManager
.
TrustRootCertificate
();
}
if
(
ForwardToUpstreamGateway
&&
GetCustomUpStreamHttpProxyFunc
==
null
&&
GetCustomUpStreamHttpsProxyFunc
==
null
)
{
GetCustomUpStreamHttpProxyFunc
=
GetSystemUpStreamProxy
;
GetCustomUpStreamHttpsProxyFunc
=
GetSystemUpStreamProxy
;
}
foreach
(
var
endPoint
in
ProxyEndPoints
)
{
...
...
@@ -277,6 +369,28 @@ namespace Titanium.Web.Proxy
proxyRunning
=
true
;
}
/// <summary>
/// Gets the system up stream proxy.
/// </summary>
/// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param>
/// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns>
private
Task
<
ExternalProxy
>
GetSystemUpStreamProxy
(
SessionEventArgs
sessionEventArgs
)
{
// Use built-in WebProxy class to handle PAC/WAPD scripts.
var
systemProxyResolver
=
new
WebProxy
();
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
);
}
/// <summary>
/// Stop this proxy server
/// </summary>
...
...
@@ -304,7 +418,7 @@ namespace Titanium.Web.Proxy
ProxyEndPoints
.
Clear
();
certificateCacheManager
.
StopClearIdleCertificates
();
certificateCacheManager
?
.
StopClearIdleCertificates
();
proxyRunning
=
false
;
}
...
...
@@ -378,32 +492,40 @@ namespace Titanium.Web.Proxy
//Other errors are discarded to keep proxy running
}
if
(
tcpClient
!=
null
)
{
{
Task
.
Run
(
async
()
=>
{
try
{
{
if
(
endPoint
.
GetType
()
==
typeof
(
TransparentProxyEndPoint
))
{
await
HandleClient
(
endPoint
as
TransparentProxyEndPoint
,
tcpClient
);
}
else
{
await
HandleClient
(
endPoint
as
ExplicitProxyEndPoint
,
tcpClient
);
}
}
finally
{
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
.
Client
.
Shutdown
(
SocketShutdown
.
Both
);
tcpClient
.
Client
.
Close
();
tcpClient
.
Client
.
Dispose
();
tcpClient
.
Close
();
}
}
});
...
...
@@ -420,7 +542,7 @@ namespace Titanium.Web.Proxy
Stop
();
}
certificateCacheManager
.
Dispose
();
certificateCacheManager
?
.
Dispose
();
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/RequestHandler.cs
View file @
2316ea2d
...
...
@@ -2,10 +2,12 @@
using
System.Collections.Generic
;
using
System.IO
;
using
System.Linq
;
using
System.Net
;
using
System.Net.Security
;
using
System.Net.Sockets
;
using
System.Security.Authentication
;
using
System.Text.RegularExpressions
;
using
Titanium.Web.Proxy.Exceptions
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Network
;
...
...
@@ -23,11 +25,14 @@ namespace Titanium.Web.Proxy
/// </summary>
partial
class
ProxyServer
{
//This is called when client is aware of proxy
//So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
private
async
Task
HandleClient
(
ExplicitProxyEndPoint
endPoint
,
TcpClient
c
lient
)
private
async
Task
HandleClient
(
ExplicitProxyEndPoint
endPoint
,
TcpClient
tcpC
lient
)
{
Stream
clientStream
=
client
.
GetStream
();
Stream
clientStream
=
tcpClient
.
GetStream
();
clientStream
.
ReadTimeout
=
ConnectionTimeOutSeconds
*
1000
;
clientStream
.
WriteTimeout
=
ConnectionTimeOutSeconds
*
1000
;
...
...
@@ -77,11 +82,28 @@ namespace Titanium.Web.Proxy
var
excluded
=
endPoint
.
ExcludedHttpsHostNameRegex
!=
null
?
endPoint
.
ExcludedHttpsHostNameRegex
.
Any
(
x
=>
Regex
.
IsMatch
(
httpRemoteUri
.
Host
,
x
))
:
false
;
List
<
HttpHeader
>
connectRequestHeaders
=
null
;
//Client wants to create a secure tcp tunnel (its a HTTPS request)
if
(
httpVerb
.
ToUpper
()
==
"CONNECT"
&&
!
excluded
&&
httpRemoteUri
.
Port
!=
80
)
{
httpRemoteUri
=
new
Uri
(
"https://"
+
httpCmdSplit
[
1
]);
await
clientStreamReader
.
ReadAllLinesAsync
();
string
tmpLine
=
null
;
connectRequestHeaders
=
new
List
<
HttpHeader
>();
while
(!
string
.
IsNullOrEmpty
(
tmpLine
=
await
clientStreamReader
.
ReadLineAsync
()))
{
var
header
=
tmpLine
.
Split
(
ProxyConstants
.
ColonSplit
,
2
);
var
newHeader
=
new
HttpHeader
(
header
[
0
],
header
[
1
]);
connectRequestHeaders
.
Add
(
newHeader
);
}
if
(
await
CheckAuthorization
(
clientStreamWriter
,
connectRequestHeaders
)
==
false
)
{
Dispose
(
clientStream
,
clientStreamReader
,
clientStreamWriter
,
null
);
return
;
}
await
WriteConnectResponse
(
clientStreamWriter
,
version
);
...
...
@@ -91,7 +113,7 @@ namespace Titanium.Web.Proxy
{
sslStream
=
new
SslStream
(
clientStream
,
true
);
var
certificate
=
await
certificateCacheManager
.
CreateCertificate
(
httpRemoteUri
.
Host
,
false
);
var
certificate
=
certificateCacheManager
.
CreateCertificate
(
httpRemoteUri
.
Host
,
false
);
//Successfully managed to authenticate the client using the fake certificate
await
sslStream
.
AuthenticateAsServerAsync
(
certificate
,
false
,
SupportedSslProtocols
,
false
);
...
...
@@ -125,7 +147,6 @@ namespace Titanium.Web.Proxy
//write back successfull CONNECT response
await
WriteConnectResponse
(
clientStreamWriter
,
version
);
await
TcpHelper
.
SendRaw
(
BUFFER_SIZE
,
ConnectionTimeOutSeconds
,
httpRemoteUri
.
Host
,
httpRemoteUri
.
Port
,
httpCmd
,
version
,
null
,
false
,
SupportedSslProtocols
,
...
...
@@ -136,12 +157,11 @@ namespace Titanium.Web.Proxy
Dispose
(
clientStream
,
clientStreamReader
,
clientStreamWriter
,
null
);
return
;
}
//Now create the request
await
HandleHttpSessionRequest
(
c
lient
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
httpRemoteUri
.
Scheme
==
Uri
.
UriSchemeHttps
?
httpRemoteUri
.
Host
:
null
);
await
HandleHttpSessionRequest
(
tcpC
lient
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
httpRemoteUri
.
Scheme
==
Uri
.
UriSchemeHttps
?
httpRemoteUri
.
Host
:
null
,
endPoint
,
connectRequestHeaders
,
null
,
null
);
}
catch
catch
(
Exception
)
{
Dispose
(
clientStream
,
clientStreamReader
,
clientStreamWriter
,
null
);
}
...
...
@@ -151,6 +171,7 @@ namespace Titanium.Web.Proxy
//So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
private
async
Task
HandleClient
(
TransparentProxyEndPoint
endPoint
,
TcpClient
tcpClient
)
{
Stream
clientStream
=
tcpClient
.
GetStream
();
clientStream
.
ReadTimeout
=
ConnectionTimeOutSeconds
*
1000
;
...
...
@@ -165,7 +186,7 @@ namespace Titanium.Web.Proxy
var
sslStream
=
new
SslStream
(
clientStream
,
true
);
//implement in future once SNI supported by SSL stream, for now use the same certificate
certificate
=
await
certificateCacheManager
.
CreateCertificate
(
endPoint
.
GenericCertificateName
,
false
);
certificate
=
certificateCacheManager
.
CreateCertificate
(
endPoint
.
GenericCertificateName
,
false
);
try
{
...
...
@@ -180,10 +201,7 @@ namespace Titanium.Web.Proxy
}
catch
(
Exception
)
{
if
(
sslStream
!=
null
)
{
sslStream
.
Dispose
();
}
sslStream
.
Dispose
();
Dispose
(
sslStream
,
clientStreamReader
,
clientStreamWriter
,
null
);
return
;
...
...
@@ -193,6 +211,7 @@ namespace Titanium.Web.Proxy
else
{
clientStreamReader
=
new
CustomBinaryReader
(
clientStream
);
clientStreamWriter
=
new
StreamWriter
(
clientStream
);
}
//now read the request line
...
...
@@ -200,8 +219,134 @@ namespace Titanium.Web.Proxy
//Now create the request
await
HandleHttpSessionRequest
(
tcpClient
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
endPoint
.
EnableSsl
?
endPoint
.
GenericCertificateName
:
null
);
endPoint
.
EnableSsl
?
endPoint
.
GenericCertificateName
:
null
,
endPoint
,
null
);
}
private
async
Task
HandleHttpSessionRequestInternal
(
TcpConnection
connection
,
SessionEventArgs
args
,
ExternalProxy
customUpStreamHttpProxy
,
ExternalProxy
customUpStreamHttpsProxy
,
bool
CloseConnection
)
{
try
{
if
(
connection
==
null
)
{
if
(
args
.
WebSession
.
Request
.
RequestUri
.
Scheme
==
"http"
)
{
if
(
GetCustomUpStreamHttpProxyFunc
!=
null
)
{
customUpStreamHttpProxy
=
await
GetCustomUpStreamHttpProxyFunc
(
args
).
ConfigureAwait
(
false
);
}
}
else
{
if
(
GetCustomUpStreamHttpsProxyFunc
!=
null
)
{
customUpStreamHttpsProxy
=
await
GetCustomUpStreamHttpsProxyFunc
(
args
).
ConfigureAwait
(
false
);
}
}
args
.
CustomUpStreamHttpProxyUsed
=
customUpStreamHttpProxy
;
args
.
CustomUpStreamHttpsProxyUsed
=
customUpStreamHttpsProxy
;
connection
=
await
tcpConnectionFactory
.
CreateClient
(
BUFFER_SIZE
,
ConnectionTimeOutSeconds
,
args
.
WebSession
.
Request
.
RequestUri
.
Host
,
args
.
WebSession
.
Request
.
RequestUri
.
Port
,
args
.
WebSession
.
Request
.
HttpVersion
,
args
.
IsHttps
,
SupportedSslProtocols
,
new
RemoteCertificateValidationCallback
(
ValidateServerCertificate
),
new
LocalCertificateSelectionCallback
(
SelectClientCertificate
),
customUpStreamHttpProxy
??
UpStreamHttpProxy
,
customUpStreamHttpsProxy
??
UpStreamHttpsProxy
,
args
.
ProxyClient
.
ClientStream
);
}
args
.
WebSession
.
Request
.
RequestLocked
=
true
;
//If request was cancelled by user then dispose the client
if
(
args
.
WebSession
.
Request
.
CancelRequest
)
{
Dispose
(
args
.
ProxyClient
.
ClientStream
,
args
.
ProxyClient
.
ClientStreamReader
,
args
.
ProxyClient
.
ClientStreamWriter
,
args
);
return
;
}
//if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue
if
(
args
.
WebSession
.
Request
.
ExpectContinue
)
{
args
.
WebSession
.
SetConnection
(
connection
);
await
args
.
WebSession
.
SendRequest
(
Enable100ContinueBehaviour
);
}
//If 100 continue was the response inform that to the client
if
(
Enable100ContinueBehaviour
)
{
if
(
args
.
WebSession
.
Request
.
Is100Continue
)
{
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"100"
,
"Continue"
,
args
.
ProxyClient
.
ClientStreamWriter
);
await
args
.
ProxyClient
.
ClientStreamWriter
.
WriteLineAsync
();
}
else
if
(
args
.
WebSession
.
Request
.
ExpectationFailed
)
{
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"417"
,
"Expectation Failed"
,
args
.
ProxyClient
.
ClientStreamWriter
);
await
args
.
ProxyClient
.
ClientStreamWriter
.
WriteLineAsync
();
}
}
//If expect continue is not enabled then set the connectio and send request headers
if
(!
args
.
WebSession
.
Request
.
ExpectContinue
)
{
args
.
WebSession
.
SetConnection
(
connection
);
await
args
.
WebSession
.
SendRequest
(
Enable100ContinueBehaviour
);
}
//If request was modified by user
if
(
args
.
WebSession
.
Request
.
RequestBodyRead
)
{
if
(
args
.
WebSession
.
Request
.
ContentEncoding
!=
null
)
{
args
.
WebSession
.
Request
.
RequestBody
=
await
GetCompressedResponseBody
(
args
.
WebSession
.
Request
.
ContentEncoding
,
args
.
WebSession
.
Request
.
RequestBody
);
}
//chunked send is not supported as of now
args
.
WebSession
.
Request
.
ContentLength
=
args
.
WebSession
.
Request
.
RequestBody
.
Length
;
var
newStream
=
args
.
WebSession
.
ServerConnection
.
Stream
;
await
newStream
.
WriteAsync
(
args
.
WebSession
.
Request
.
RequestBody
,
0
,
args
.
WebSession
.
Request
.
RequestBody
.
Length
);
}
else
{
if
(!
args
.
WebSession
.
Request
.
ExpectationFailed
)
{
//If its a post/put request, then read the client html body and send it to server
if
(
args
.
WebSession
.
Request
.
Method
.
ToUpper
()
==
"POST"
||
args
.
WebSession
.
Request
.
Method
.
ToUpper
()
==
"PUT"
)
{
await
SendClientRequestBody
(
args
);
}
}
}
//If not expectation failed response was returned by server then parse response
if
(!
args
.
WebSession
.
Request
.
ExpectationFailed
)
{
await
HandleHttpSessionResponse
(
args
);
}
//if connection is closing exit
if
(
args
.
WebSession
.
Response
.
ResponseKeepAlive
==
false
)
{
Dispose
(
args
.
ProxyClient
.
ClientStream
,
args
.
ProxyClient
.
ClientStreamReader
,
args
.
ProxyClient
.
ClientStreamWriter
,
args
);
return
;
}
}
catch
(
Exception
e
)
{
ExceptionFunc
(
new
ProxyHttpException
(
"Error occured whilst handling session request (internal)"
,
e
,
args
));
Dispose
(
args
.
ProxyClient
.
ClientStream
,
args
.
ProxyClient
.
ClientStreamReader
,
args
.
ProxyClient
.
ClientStreamWriter
,
args
);
return
;
}
if
(
CloseConnection
)
{
//dispose
connection
?.
Dispose
();
}
}
/// <summary>
/// This is the core request handler method for a particular connection from client
/// </summary>
...
...
@@ -213,7 +358,7 @@ namespace Titanium.Web.Proxy
/// <param name="httpsHostName"></param>
/// <returns></returns>
private
async
Task
HandleHttpSessionRequest
(
TcpClient
client
,
string
httpCmd
,
Stream
clientStream
,
CustomBinaryReader
clientStreamReader
,
StreamWriter
clientStreamWriter
,
string
httpsHostName
)
CustomBinaryReader
clientStreamReader
,
StreamWriter
clientStreamWriter
,
string
httpsHostName
,
ProxyEndPoint
endPoint
,
List
<
HttpHeader
>
connectHeaders
,
ExternalProxy
customUpStreamHttpProxy
=
null
,
ExternalProxy
customUpStreamHttpsProxy
=
null
)
{
TcpConnection
connection
=
null
;
...
...
@@ -229,7 +374,22 @@ namespace Titanium.Web.Proxy
var
args
=
new
SessionEventArgs
(
BUFFER_SIZE
,
HandleHttpSessionResponse
);
args
.
ProxyClient
.
TcpClient
=
client
;
args
.
WebSession
.
ConnectHeaders
=
connectHeaders
;
args
.
WebSession
.
ProcessId
=
new
Lazy
<
int
>(()
=>
{
var
remoteEndPoint
=
(
IPEndPoint
)
args
.
ProxyClient
.
TcpClient
.
Client
.
RemoteEndPoint
;
//If client is localhost get the process id
if
(
NetworkHelper
.
IsLocalIpAddress
(
remoteEndPoint
.
Address
))
{
return
NetworkHelper
.
GetProcessIdFromPort
(
remoteEndPoint
.
Port
,
endPoint
.
IpV6Enabled
);
}
//can't access process Id of remote request from remote machine
return
-
1
;
});
try
{
//break up the line into three components (method, remote URL & Http Version)
...
...
@@ -249,6 +409,7 @@ namespace Titanium.Web.Proxy
}
}
//Read the request headers in to unique and non-unique header collections
string
tmpLine
;
while
(!
string
.
IsNullOrEmpty
(
tmpLine
=
await
clientStreamReader
.
ReadLineAsync
()))
...
...
@@ -294,6 +455,13 @@ namespace Titanium.Web.Proxy
args
.
ProxyClient
.
ClientStreamReader
=
clientStreamReader
;
args
.
ProxyClient
.
ClientStreamWriter
=
clientStreamWriter
;
if
(
httpsHostName
==
null
&&
(
await
CheckAuthorization
(
clientStreamWriter
,
args
.
WebSession
.
Request
.
RequestHeaders
.
Values
)
==
false
))
{
Dispose
(
clientStream
,
clientStreamReader
,
clientStreamWriter
,
args
);
break
;
}
PrepareRequestHeaders
(
args
.
WebSession
.
Request
.
RequestHeaders
,
args
.
WebSession
);
args
.
WebSession
.
Request
.
Host
=
args
.
WebSession
.
Request
.
RequestUri
.
Authority
;
...
...
@@ -325,92 +493,17 @@ namespace Titanium.Web.Proxy
}
//construct the web request that we are going to issue on behalf of the client.
if
(
connection
==
null
)
{
connection
=
await
tcpConnectionFactory
.
CreateClient
(
BUFFER_SIZE
,
ConnectionTimeOutSeconds
,
args
.
WebSession
.
Request
.
RequestUri
.
Host
,
args
.
WebSession
.
Request
.
RequestUri
.
Port
,
httpVersion
,
args
.
IsHttps
,
SupportedSslProtocols
,
new
RemoteCertificateValidationCallback
(
ValidateServerCertificate
),
new
LocalCertificateSelectionCallback
(
SelectClientCertificate
),
ExternalHttpProxy
,
ExternalHttpsProxy
,
clientStream
);
}
args
.
WebSession
.
Request
.
RequestLocked
=
true
;
await
HandleHttpSessionRequestInternal
(
connection
,
args
,
customUpStreamHttpProxy
,
customUpStreamHttpsProxy
,
false
).
ConfigureAwait
(
false
);
//If request was cancelled by user then dispose the client
if
(
args
.
WebSession
.
Request
.
CancelRequest
)
{
Dispose
(
clientStream
,
clientStreamReader
,
clientStreamWriter
,
args
);
break
;
}
//if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue
if
(
args
.
WebSession
.
Request
.
ExpectContinue
)
{
args
.
WebSession
.
SetConnection
(
connection
);
await
args
.
WebSession
.
SendRequest
(
Enable100ContinueBehaviour
);
}
//If 100 continue was the response inform that to the client
if
(
Enable100ContinueBehaviour
)
{
if
(
args
.
WebSession
.
Request
.
Is100Continue
)
{
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"100"
,
"Continue"
,
args
.
ProxyClient
.
ClientStreamWriter
);
await
args
.
ProxyClient
.
ClientStreamWriter
.
WriteLineAsync
();
}
else
if
(
args
.
WebSession
.
Request
.
ExpectationFailed
)
{
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"417"
,
"Expectation Failed"
,
args
.
ProxyClient
.
ClientStreamWriter
);
await
args
.
ProxyClient
.
ClientStreamWriter
.
WriteLineAsync
();
}
}
//If expect continue is not enabled then set the connectio and send request headers
if
(!
args
.
WebSession
.
Request
.
ExpectContinue
)
{
args
.
WebSession
.
SetConnection
(
connection
);
await
args
.
WebSession
.
SendRequest
(
Enable100ContinueBehaviour
);
}
//If request was modified by user
if
(
args
.
WebSession
.
Request
.
RequestBodyRead
)
{
if
(
args
.
WebSession
.
Request
.
ContentEncoding
!=
null
)
{
args
.
WebSession
.
Request
.
RequestBody
=
await
GetCompressedResponseBody
(
args
.
WebSession
.
Request
.
ContentEncoding
,
args
.
WebSession
.
Request
.
RequestBody
);
}
//chunked send is not supported as of now
args
.
WebSession
.
Request
.
ContentLength
=
args
.
WebSession
.
Request
.
RequestBody
.
Length
;
var
newStream
=
args
.
WebSession
.
ServerConnection
.
Stream
;
await
newStream
.
WriteAsync
(
args
.
WebSession
.
Request
.
RequestBody
,
0
,
args
.
WebSession
.
Request
.
RequestBody
.
Length
);
}
else
{
if
(!
args
.
WebSession
.
Request
.
ExpectationFailed
)
{
//If its a post/put request, then read the client html body and send it to server
if
(
httpMethod
.
ToUpper
()
==
"POST"
||
httpMethod
.
ToUpper
()
==
"PUT"
)
{
await
SendClientRequestBody
(
args
);
}
}
}
//If not expectation failed response was returned by server then parse response
if
(!
args
.
WebSession
.
Request
.
ExpectationFailed
)
{
await
HandleHttpSessionResponse
(
args
);
}
//if connection is closing exit
if
(
args
.
WebSession
.
Response
.
ResponseKeepAlive
==
false
)
{
Dispose
(
clientStream
,
clientStreamReader
,
clientStreamWriter
,
args
);
break
;
}
...
...
@@ -418,8 +511,9 @@ namespace Titanium.Web.Proxy
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
}
catch
catch
(
Exception
e
)
{
ExceptionFunc
(
new
ProxyHttpException
(
"Error occured whilst handling session request"
,
e
,
args
));
Dispose
(
clientStream
,
clientStreamReader
,
clientStreamWriter
,
args
);
break
;
}
...
...
@@ -475,7 +569,6 @@ namespace Titanium.Web.Proxy
webRequest
.
Request
.
RequestHeaders
=
requestHeaders
;
}
/// <summary>
/// This is called when the request is PUT/POST to read the body
/// </summary>
...
...
@@ -497,9 +590,7 @@ namespace Titanium.Web.Proxy
else
if
(
args
.
WebSession
.
Request
.
IsChunked
)
{
await
args
.
ProxyClient
.
ClientStreamReader
.
CopyBytesToStreamChunked
(
BUFFER_SIZE
,
postStream
);
}
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/ResponseHandler.cs
View file @
2316ea2d
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Compression
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Helpers
;
namespace
Titanium.Web.Proxy
{
/// <summary>
/// Handle the response from server
/// </summary>
partial
class
ProxyServer
{
//Called asynchronously when a request was successfully and we received the response
public
async
Task
HandleHttpSessionResponse
(
SessionEventArgs
args
)
{
//read response & headers from server
await
args
.
WebSession
.
ReceiveResponse
();
try
{
if
(!
args
.
WebSession
.
Response
.
ResponseBodyRead
)
{
args
.
WebSession
.
Response
.
ResponseStream
=
args
.
WebSession
.
ServerConnection
.
Stream
;
}
//If user requested call back then do it
if
(
BeforeResponse
!=
null
&&
!
args
.
WebSession
.
Response
.
ResponseLocked
)
{
Delegate
[]
invocationList
=
BeforeResponse
.
GetInvocationList
();
Task
[]
handlerTasks
=
new
Task
[
invocationList
.
Length
];
for
(
int
i
=
0
;
i
<
invocationList
.
Length
;
i
++)
{
handlerTasks
[
i
]
=
((
Func
<
object
,
SessionEventArgs
,
Task
>)
invocationList
[
i
])(
null
,
args
);
}
await
Task
.
WhenAll
(
handlerTasks
);
}
args
.
WebSession
.
Response
.
ResponseLocked
=
true
;
//Write back to client 100-conitinue response if that's what server returned
if
(
args
.
WebSession
.
Response
.
Is100Continue
)
{
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"100"
,
"Continue"
,
args
.
ProxyClient
.
ClientStreamWriter
);
await
args
.
ProxyClient
.
ClientStreamWriter
.
WriteLineAsync
();
}
else
if
(
args
.
WebSession
.
Response
.
ExpectationFailed
)
{
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"417"
,
"Expectation Failed"
,
args
.
ProxyClient
.
ClientStreamWriter
);
await
args
.
ProxyClient
.
ClientStreamWriter
.
WriteLineAsync
();
}
//Write back response status to client
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
args
.
WebSession
.
Response
.
ResponseStatusCode
,
args
.
WebSession
.
Response
.
ResponseStatusDescription
,
args
.
ProxyClient
.
ClientStreamWriter
);
if
(
args
.
WebSession
.
Response
.
ResponseBodyRead
)
{
var
isChunked
=
args
.
WebSession
.
Response
.
IsChunked
;
var
contentEncoding
=
args
.
WebSession
.
Response
.
ContentEncoding
;
if
(
contentEncoding
!=
null
)
{
args
.
WebSession
.
Response
.
ResponseBody
=
await
GetCompressedResponseBody
(
contentEncoding
,
args
.
WebSession
.
Response
.
ResponseBody
);
if
(
isChunked
==
false
)
{
args
.
WebSession
.
Response
.
ContentLength
=
args
.
WebSession
.
Response
.
ResponseBody
.
Length
;
}
else
{
args
.
WebSession
.
Response
.
ContentLength
=
-
1
;
}
}
await
WriteResponseHeaders
(
args
.
ProxyClient
.
ClientStreamWriter
,
args
.
WebSession
.
Response
);
await
args
.
ProxyClient
.
ClientStream
.
WriteResponseBody
(
args
.
WebSession
.
Response
.
ResponseBody
,
isChunked
);
}
else
{
await
WriteResponseHeaders
(
args
.
ProxyClient
.
ClientStreamWriter
,
args
.
WebSession
.
Response
);
//Write body only if response is chunked or content length >0
//Is none are true then check if connection:close header exist, if so write response until server or client terminates the connection
if
(
args
.
WebSession
.
Response
.
IsChunked
||
args
.
WebSession
.
Response
.
ContentLength
>
0
||
!
args
.
WebSession
.
Response
.
ResponseKeepAlive
)
{
await
args
.
WebSession
.
ServerConnection
.
StreamReader
.
WriteResponseBody
(
BUFFER_SIZE
,
args
.
ProxyClient
.
ClientStream
,
args
.
WebSession
.
Response
.
IsChunked
,
args
.
WebSession
.
Response
.
ContentLength
);
}
//write 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)
else
if
(
args
.
WebSession
.
Response
.
ResponseKeepAlive
&&
args
.
WebSession
.
Response
.
HttpVersion
.
Minor
==
0
)
{
await
args
.
WebSession
.
ServerConnection
.
StreamReader
.
WriteResponseBody
(
BUFFER_SIZE
,
args
.
ProxyClient
.
ClientStream
,
args
.
WebSession
.
Response
.
IsChunked
,
args
.
WebSession
.
Response
.
ContentLength
);
}
}
await
args
.
ProxyClient
.
ClientStream
.
FlushAsync
();
}
catch
{
Dispose
(
args
.
ProxyClient
.
ClientStream
,
args
.
ProxyClient
.
ClientStreamReader
,
args
.
ProxyClient
.
ClientStreamWriter
,
args
);
}
finally
{
args
.
Dispose
();
}
}
/// <summary>
/// get the compressed response body from give response bytes
/// </summary>
/// <param name="encodingType"></param>
/// <param name="responseBodyStream"></param>
/// <returns></returns>
private
async
Task
<
byte
[
]>
GetCompressedResponseBody
(
string
encodingType
,
byte
[]
responseBodyStream
)
{
var
compressionFactory
=
new
CompressionFactory
();
var
compressor
=
compressionFactory
.
Create
(
encodingType
);
return
await
compressor
.
Compress
(
responseBodyStream
);
}
/// <summary>
/// Write response status
/// </summary>
/// <param name="version"></param>
/// <param name="code"></param>
/// <param name="description"></param>
/// <param name="responseWriter"></param>
/// <returns></returns>
private
async
Task
WriteResponseStatus
(
Version
version
,
string
code
,
string
description
,
StreamWriter
responseWriter
)
{
await
responseWriter
.
WriteLineAsync
(
string
.
Format
(
"HTTP/{0}.{1} {2} {3}"
,
version
.
Major
,
version
.
Minor
,
code
,
description
));
}
/// <summary>
/// Write response headers to client
/// </summary>
/// <param name="responseWriter"></param>
/// <param name="headers"></param>
/// <returns></returns>
private
async
Task
WriteResponseHeaders
(
StreamWriter
responseWriter
,
Response
response
)
{
FixProxyHeaders
(
response
.
ResponseHeaders
);
foreach
(
var
header
in
response
.
ResponseHeaders
)
{
await
responseWriter
.
WriteLineAsync
(
header
.
Value
.
ToString
());
}
//write non unique request headers
foreach
(
var
headerItem
in
response
.
NonUniqueResponseHeaders
)
{
var
headers
=
headerItem
.
Value
;
foreach
(
var
header
in
headers
)
{
await
responseWriter
.
WriteLineAsync
(
header
.
ToString
());
}
}
await
responseWriter
.
WriteLineAsync
();
await
responseWriter
.
FlushAsync
();
}
/// <summary>
/// Fix proxy specific headers
/// </summary>
/// <param name="headers"></param>
private
void
FixProxyHeaders
(
Dictionary
<
string
,
HttpHeader
>
headers
)
{
//If proxy-connection close was returned inform to close the connection
var
hasProxyHeader
=
headers
.
ContainsKey
(
"proxy-connection"
);
var
hasConnectionheader
=
headers
.
ContainsKey
(
"connection"
);
if
(
hasProxyHeader
)
{
var
proxyHeader
=
headers
[
"proxy-connection"
];
if
(
hasConnectionheader
==
false
)
{
headers
.
Add
(
"connection"
,
new
HttpHeader
(
"connection"
,
proxyHeader
.
Value
));
}
else
{
var
connectionHeader
=
headers
[
"connection"
];
connectionHeader
.
Value
=
proxyHeader
.
Value
;
}
headers
.
Remove
(
"proxy-connection"
);
}
}
/// <summary>
/// Handle dispose of a client/server session
/// </summary>
/// <param name="tcpClient"></param>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="args"></param>
private
void
Dispose
(
Stream
clientStream
,
CustomBinaryReader
clientStreamReader
,
StreamWriter
clientStreamWriter
,
IDisposable
args
)
{
if
(
clientStream
!=
null
)
{
clientStream
.
Close
();
clientStream
.
Dispose
();
}
if
(
args
!=
null
)
{
args
.
Dispose
();
}
if
(
clientStreamReader
!=
null
)
{
clientStreamReader
.
Dispose
();
}
if
(
clientStreamWriter
!=
null
)
{
clientStreamWriter
.
Close
();
clientStreamWriter
.
Dispose
();
}
}
}
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Compression
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Exceptions
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Helpers
;
namespace
Titanium.Web.Proxy
{
/// <summary>
/// Handle the response from server
/// </summary>
partial
class
ProxyServer
{
//Called asynchronously when a request was successfully and we received the response
public
async
Task
HandleHttpSessionResponse
(
SessionEventArgs
args
)
{
//read response & headers from server
await
args
.
WebSession
.
ReceiveResponse
();
try
{
if
(!
args
.
WebSession
.
Response
.
ResponseBodyRead
)
{
args
.
WebSession
.
Response
.
ResponseStream
=
args
.
WebSession
.
ServerConnection
.
Stream
;
}
args
.
ReRequest
=
false
;
//If user requested call back then do it
if
(
BeforeResponse
!=
null
&&
!
args
.
WebSession
.
Response
.
ResponseLocked
)
{
Delegate
[]
invocationList
=
BeforeResponse
.
GetInvocationList
();
Task
[]
handlerTasks
=
new
Task
[
invocationList
.
Length
];
for
(
int
i
=
0
;
i
<
invocationList
.
Length
;
i
++)
{
handlerTasks
[
i
]
=
((
Func
<
object
,
SessionEventArgs
,
Task
>)
invocationList
[
i
])(
this
,
args
);
}
await
Task
.
WhenAll
(
handlerTasks
);
}
if
(
args
.
ReRequest
)
{
await
HandleHttpSessionRequestInternal
(
null
,
args
,
null
,
null
,
true
).
ConfigureAwait
(
false
);
return
;
}
args
.
WebSession
.
Response
.
ResponseLocked
=
true
;
//Write back to client 100-conitinue response if that's what server returned
if
(
args
.
WebSession
.
Response
.
Is100Continue
)
{
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"100"
,
"Continue"
,
args
.
ProxyClient
.
ClientStreamWriter
);
await
args
.
ProxyClient
.
ClientStreamWriter
.
WriteLineAsync
();
}
else
if
(
args
.
WebSession
.
Response
.
ExpectationFailed
)
{
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
"417"
,
"Expectation Failed"
,
args
.
ProxyClient
.
ClientStreamWriter
);
await
args
.
ProxyClient
.
ClientStreamWriter
.
WriteLineAsync
();
}
//Write back response status to client
await
WriteResponseStatus
(
args
.
WebSession
.
Response
.
HttpVersion
,
args
.
WebSession
.
Response
.
ResponseStatusCode
,
args
.
WebSession
.
Response
.
ResponseStatusDescription
,
args
.
ProxyClient
.
ClientStreamWriter
);
if
(
args
.
WebSession
.
Response
.
ResponseBodyRead
)
{
var
isChunked
=
args
.
WebSession
.
Response
.
IsChunked
;
var
contentEncoding
=
args
.
WebSession
.
Response
.
ContentEncoding
;
if
(
contentEncoding
!=
null
)
{
args
.
WebSession
.
Response
.
ResponseBody
=
await
GetCompressedResponseBody
(
contentEncoding
,
args
.
WebSession
.
Response
.
ResponseBody
);
if
(
isChunked
==
false
)
{
args
.
WebSession
.
Response
.
ContentLength
=
args
.
WebSession
.
Response
.
ResponseBody
.
Length
;
}
else
{
args
.
WebSession
.
Response
.
ContentLength
=
-
1
;
}
}
await
WriteResponseHeaders
(
args
.
ProxyClient
.
ClientStreamWriter
,
args
.
WebSession
.
Response
);
await
args
.
ProxyClient
.
ClientStream
.
WriteResponseBody
(
args
.
WebSession
.
Response
.
ResponseBody
,
isChunked
);
}
else
{
await
WriteResponseHeaders
(
args
.
ProxyClient
.
ClientStreamWriter
,
args
.
WebSession
.
Response
);
//Write body only if response is chunked or content length >0
//Is none are true then check if connection:close header exist, if so write response until server or client terminates the connection
if
(
args
.
WebSession
.
Response
.
IsChunked
||
args
.
WebSession
.
Response
.
ContentLength
>
0
||
!
args
.
WebSession
.
Response
.
ResponseKeepAlive
)
{
await
args
.
WebSession
.
ServerConnection
.
StreamReader
.
WriteResponseBody
(
BUFFER_SIZE
,
args
.
ProxyClient
.
ClientStream
,
args
.
WebSession
.
Response
.
IsChunked
,
args
.
WebSession
.
Response
.
ContentLength
);
}
//write 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)
else
if
(
args
.
WebSession
.
Response
.
ResponseKeepAlive
&&
args
.
WebSession
.
Response
.
HttpVersion
.
Minor
==
0
)
{
await
args
.
WebSession
.
ServerConnection
.
StreamReader
.
WriteResponseBody
(
BUFFER_SIZE
,
args
.
ProxyClient
.
ClientStream
,
args
.
WebSession
.
Response
.
IsChunked
,
args
.
WebSession
.
Response
.
ContentLength
);
}
}
await
args
.
ProxyClient
.
ClientStream
.
FlushAsync
();
}
catch
(
Exception
e
)
{
ExceptionFunc
(
new
ProxyHttpException
(
"Error occured wilst handling session response"
,
e
,
args
));
Dispose
(
args
.
ProxyClient
.
ClientStream
,
args
.
ProxyClient
.
ClientStreamReader
,
args
.
ProxyClient
.
ClientStreamWriter
,
args
);
}
finally
{
args
.
Dispose
();
}
}
/// <summary>
/// get the compressed response body from give response bytes
/// </summary>
/// <param name="encodingType"></param>
/// <param name="responseBodyStream"></param>
/// <returns></returns>
private
async
Task
<
byte
[
]>
GetCompressedResponseBody
(
string
encodingType
,
byte
[]
responseBodyStream
)
{
var
compressionFactory
=
new
CompressionFactory
();
var
compressor
=
compressionFactory
.
Create
(
encodingType
);
return
await
compressor
.
Compress
(
responseBodyStream
);
}
/// <summary>
/// Write response status
/// </summary>
/// <param name="version"></param>
/// <param name="code"></param>
/// <param name="description"></param>
/// <param name="responseWriter"></param>
/// <returns></returns>
private
async
Task
WriteResponseStatus
(
Version
version
,
string
code
,
string
description
,
StreamWriter
responseWriter
)
{
await
responseWriter
.
WriteLineAsync
(
string
.
Format
(
"HTTP/{0}.{1} {2} {3}"
,
version
.
Major
,
version
.
Minor
,
code
,
description
));
}
/// <summary>
/// Write response headers to client
/// </summary>
/// <param name="responseWriter"></param>
/// <param name="headers"></param>
/// <returns></returns>
private
async
Task
WriteResponseHeaders
(
StreamWriter
responseWriter
,
Response
response
)
{
FixProxyHeaders
(
response
.
ResponseHeaders
);
foreach
(
var
header
in
response
.
ResponseHeaders
)
{
await
responseWriter
.
WriteLineAsync
(
header
.
Value
.
ToString
());
}
//write non unique request headers
foreach
(
var
headerItem
in
response
.
NonUniqueResponseHeaders
)
{
var
headers
=
headerItem
.
Value
;
foreach
(
var
header
in
headers
)
{
await
responseWriter
.
WriteLineAsync
(
header
.
ToString
());
}
}
await
responseWriter
.
WriteLineAsync
();
await
responseWriter
.
FlushAsync
();
}
/// <summary>
/// Fix proxy specific headers
/// </summary>
/// <param name="headers"></param>
private
void
FixProxyHeaders
(
Dictionary
<
string
,
HttpHeader
>
headers
)
{
//If proxy-connection close was returned inform to close the connection
var
hasProxyHeader
=
headers
.
ContainsKey
(
"proxy-connection"
);
var
hasConnectionheader
=
headers
.
ContainsKey
(
"connection"
);
if
(
hasProxyHeader
)
{
var
proxyHeader
=
headers
[
"proxy-connection"
];
if
(
hasConnectionheader
==
false
)
{
headers
.
Add
(
"connection"
,
new
HttpHeader
(
"connection"
,
proxyHeader
.
Value
));
}
else
{
var
connectionHeader
=
headers
[
"connection"
];
connectionHeader
.
Value
=
proxyHeader
.
Value
;
}
headers
.
Remove
(
"proxy-connection"
);
}
}
/// <summary>
/// Handle dispose of a client/server session
/// </summary>
/// <param name="tcpClient"></param>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="args"></param>
private
void
Dispose
(
Stream
clientStream
,
CustomBinaryReader
clientStreamReader
,
StreamWriter
clientStreamWriter
,
IDisposable
args
)
{
if
(
clientStream
!=
null
)
{
clientStream
.
Close
();
clientStream
.
Dispose
();
}
if
(
args
!=
null
)
{
args
.
Dispose
();
}
if
(
clientStreamReader
!=
null
)
{
clientStreamReader
.
Dispose
();
}
if
(
clientStreamWriter
!=
null
)
{
clientStreamWriter
.
Close
();
clientStreamWriter
.
Dispose
();
}
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Tcp/TcpRow.cs
0 → 100644
View file @
2316ea2d
using
System.Net
;
using
Titanium.Web.Proxy.Helpers
;
namespace
Titanium.Web.Proxy.Tcp
{
/// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
internal
class
TcpRow
{
/// <summary>
/// Initializes a new instance of the <see cref="TcpRow"/> class.
/// </summary>
/// <param name="tcpRow">TcpRow struct.</param>
public
TcpRow
(
NativeMethods
.
TcpRow
tcpRow
)
{
ProcessId
=
tcpRow
.
owningPid
;
int
localPort
=
(
tcpRow
.
localPort1
<<
8
)
+
(
tcpRow
.
localPort2
)
+
(
tcpRow
.
localPort3
<<
24
)
+
(
tcpRow
.
localPort4
<<
16
);
long
localAddress
=
tcpRow
.
localAddr
;
LocalEndPoint
=
new
IPEndPoint
(
localAddress
,
localPort
);
int
remotePort
=
(
tcpRow
.
remotePort1
<<
8
)
+
(
tcpRow
.
remotePort2
)
+
(
tcpRow
.
remotePort3
<<
24
)
+
(
tcpRow
.
remotePort4
<<
16
);
long
remoteAddress
=
tcpRow
.
remoteAddr
;
RemoteEndPoint
=
new
IPEndPoint
(
remoteAddress
,
remotePort
);
}
/// <summary>
/// Gets the local end point.
/// </summary>
public
IPEndPoint
LocalEndPoint
{
get
;
private
set
;
}
/// <summary>
/// Gets the remote end point.
/// </summary>
public
IPEndPoint
RemoteEndPoint
{
get
;
private
set
;
}
/// <summary>
/// Gets the process identifier.
/// </summary>
public
int
ProcessId
{
get
;
private
set
;
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Tcp/TcpTable.cs
0 → 100644
View file @
2316ea2d
using
System.Collections
;
using
System.Collections.Generic
;
namespace
Titanium.Web.Proxy.Tcp
{
/// <summary>
/// Represents collection of TcpRows
/// </summary>
/// <seealso cref="System.Collections.Generic.IEnumerable{Titanium.Web.Proxy.Tcp.TcpRow}" />
internal
class
TcpTable
:
IEnumerable
<
TcpRow
>
{
private
readonly
IEnumerable
<
TcpRow
>
tcpRows
;
/// <summary>
/// Initializes a new instance of the <see cref="TcpTable"/> class.
/// </summary>
/// <param name="tcpRows">TcpRow collection to initialize with.</param>
public
TcpTable
(
IEnumerable
<
TcpRow
>
tcpRows
)
{
this
.
tcpRows
=
tcpRows
;
}
/// <summary>
/// Gets the TCP rows.
/// </summary>
public
IEnumerable
<
TcpRow
>
TcpRows
=>
tcpRows
;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public
IEnumerator
<
TcpRow
>
GetEnumerator
()
{
return
tcpRows
.
GetEnumerator
();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator
IEnumerable
.
GetEnumerator
()
{
return
GetEnumerator
();
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
View file @
2316ea2d
...
...
@@ -25,6 +25,7 @@
<AllowUnsafeBlocks>
true
</AllowUnsafeBlocks>
<TargetFrameworkVersion>
v4.5
</TargetFrameworkVersion>
<Prefer32Bit>
false
</Prefer32Bit>
<DocumentationFile>
bin\Debug\Titanium.Web.Proxy.XML
</DocumentationFile>
</PropertyGroup>
<PropertyGroup
Condition=
"'$(Configuration)|$(Platform)' == 'Release|AnyCPU'"
>
<PlatformTarget>
AnyCPU
</PlatformTarget>
...
...
@@ -32,6 +33,7 @@
<TargetFrameworkVersion>
v4.5
</TargetFrameworkVersion>
<DefineConstants>
NET45
</DefineConstants>
<Prefer32Bit>
false
</Prefer32Bit>
<DocumentationFile>
bin\Release\Titanium.Web.Proxy.XML
</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference
Include=
"Ionic.Zip, Version=1.9.8.0, Culture=neutral, PublicKeyToken=6583c7c814667745, processorArchitecture=MSIL"
>
...
...
@@ -64,9 +66,14 @@
<Compile
Include=
"EventArguments\CertificateSelectionEventArgs.cs"
/>
<Compile
Include=
"EventArguments\CertificateValidationEventArgs.cs"
/>
<Compile
Include=
"Extensions\ByteArrayExtensions.cs"
/>
<Compile
Include=
"Helpers\Network.cs"
/>
<Compile
Include=
"Network\CachedCertificate.cs"
/>
<Compile
Include=
"Network\CertificateMaker.cs"
/>
<Compile
Include=
"Network\ProxyClient.cs"
/>
<Compile
Include=
"Exceptions\BodyNotFoundException.cs"
/>
<Compile
Include=
"Exceptions\ProxyAuthorizationException.cs"
/>
<Compile
Include=
"Exceptions\ProxyException.cs"
/>
<Compile
Include=
"Exceptions\ProxyHttpException.cs"
/>
<Compile
Include=
"Extensions\HttpWebResponseExtensions.cs"
/>
<Compile
Include=
"Extensions\HttpWebRequestExtensions.cs"
/>
<Compile
Include=
"Network\CertificateManager.cs"
/>
...
...
@@ -82,6 +89,7 @@
<Compile
Include=
"Models\HttpHeader.cs"
/>
<Compile
Include=
"Http\HttpWebClient.cs"
/>
<Compile
Include=
"Properties\AssemblyInfo.cs"
/>
<Compile
Include=
"ProxyAuthorizationHandler.cs"
/>
<Compile
Include=
"RequestHandler.cs"
/>
<Compile
Include=
"ResponseHandler.cs"
/>
<Compile
Include=
"Helpers\CustomBinaryReader.cs"
/>
...
...
@@ -92,16 +100,23 @@
<Compile
Include=
"Http\Responses\OkResponse.cs"
/>
<Compile
Include=
"Http\Responses\RedirectResponse.cs"
/>
<Compile
Include=
"Shared\ProxyConstants.cs"
/>
<Compile
Include=
"Tcp\TcpRow.cs"
/>
<Compile
Include=
"Tcp\TcpTable.cs"
/>
</ItemGroup>
<ItemGroup
/>
<ItemGroup>
<None
Include=
"app.config"
/>
<None
Include=
"packages.config"
/>
<COMReference
Include=
"CERTENROLLLib"
>
<Guid>
{728AB348-217D-11DA-B2A4-000E7BBB2B09}
</Guid>
<VersionMajor>
1
</VersionMajor>
<VersionMinor>
0
</VersionMinor>
<Lcid>
0
</Lcid>
<WrapperTool>
tlbimp
</WrapperTool>
<Isolated>
False
</Isolated>
<EmbedInteropTypes>
True
</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<ItemGroup>
<None
Include=
"makecert.exe"
>
<CopyToOutputDirectory>
PreserveNewest
</CopyToOutputDirectory>
</None>
<None
Include=
"app.config"
/>
<None
Include=
"packages.config"
/>
</ItemGroup>
<Import
Project=
"$(MSBuildToolsPath)\Microsoft.CSharp.targets"
/>
<Import
Project=
"$(SolutionDir)\.nuget\NuGet.targets"
Condition=
"Exists('$(SolutionDir)\.nuget\NuGet.targets')"
/>
...
...
Titanium.Web.Proxy/Titanium.Web.Proxy.nuspec
View file @
2316ea2d
...
...
@@ -19,6 +19,5 @@
</metadata>
<files>
<file
src=
"bin\$configuration$\Titanium.Web.Proxy.dll"
target=
"lib\net45"
/>
<file
src=
"bin\$configuration$\makecert.exe"
target=
"content"
/>
</files>
</package>
\ No newline at end of file
Titanium.Web.Proxy/makecert.exe
deleted
100644 → 0
View file @
14016520
File deleted
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment