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
44d042ae
Commit
44d042ae
authored
Oct 28, 2016
by
justcoding121
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add request Id; refactor process id as Lazy<int>
parent
1acc1ad8
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
468 additions
and
412 deletions
+468
-412
ProxyTestController.cs
.../Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs
+1
-1
CertificateHandler.cs
Titanium.Web.Proxy/CertificateHandler.cs
+11
-10
CertificateSelectionEventArgs.cs
...Web.Proxy/EventArguments/CertificateSelectionEventArgs.cs
+7
-11
Network.cs
Titanium.Web.Proxy/Helpers/Network.cs
+60
-0
HttpWebClient.cs
Titanium.Web.Proxy/Http/HttpWebClient.cs
+15
-9
ProxyAuthorizationHandler.cs
Titanium.Web.Proxy/ProxyAuthorizationHandler.cs
+96
-96
RequestHandler.cs
Titanium.Web.Proxy/RequestHandler.cs
+24
-32
ResponseHandler.cs
Titanium.Web.Proxy/ResponseHandler.cs
+253
-253
Titanium.Web.Proxy.csproj
Titanium.Web.Proxy/Titanium.Web.Proxy.csproj
+1
-0
No files found.
Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs
View file @
44d042ae
...
...
@@ -117,7 +117,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
var
responseHeaders
=
e
.
WebSession
.
Response
.
ResponseHeaders
;
// print out process id of current session
Console
.
WriteLine
(
$"PID:
{
e
.
WebSession
.
ProcessId
}
"
);
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"
)
...
...
Titanium.Web.Proxy/CertificateHandler.cs
View file @
44d042ae
...
...
@@ -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 @
44d042ae
...
...
@@ -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/Helpers/Network.cs
0 → 100644
View file @
44d042ae
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/Http/HttpWebClient.cs
View file @
44d042ae
...
...
@@ -14,28 +14,39 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public
class
HttpWebClient
{
private
int
processId
;
/// <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
/// 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
int
ProcessId
{
get
;
internal
set
;
}
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>
...
...
@@ -45,12 +56,7 @@ namespace Titanium.Web.Proxy.Http
connection
.
LastAccess
=
DateTime
.
Now
;
ServerConnection
=
connection
;
}
internal
HttpWebClient
()
{
this
.
Request
=
new
Request
();
this
.
Response
=
new
Response
();
}
/// <summary>
/// Prepare & send the http(s) request
...
...
Titanium.Web.Proxy/ProxyAuthorizationHandler.cs
View file @
44d042ae
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
;
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
;
}
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/RequestHandler.cs
View file @
44d042ae
...
...
@@ -25,32 +25,13 @@ namespace Titanium.Web.Proxy
/// </summary>
partial
class
ProxyServer
{
private
int
FindProcessIdFromLocalPort
(
int
port
,
IpVersion
ipVersion
)
{
var
tcpRow
=
TcpHelper
.
GetExtendedTcpTable
(
ipVersion
).
FirstOrDefault
(
row
=>
row
.
LocalEndPoint
.
Port
==
port
);
return
tcpRow
?.
ProcessId
??
0
;
}
private
int
GetProcessIdFromPort
(
int
port
,
bool
ipV6Enabled
)
{
var
processId
=
FindProcessIdFromLocalPort
(
port
,
IpVersion
.
Ipv4
);
if
(
processId
>
0
&&
!
ipV6Enabled
)
{
return
processId
;
}
return
FindProcessIdFromLocalPort
(
port
,
IpVersion
.
Ipv6
);
}
//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
tcpClient
)
{
var
processId
=
GetProcessIdFromPort
(((
IPEndPoint
)
tcpClient
.
Client
.
RemoteEndPoint
).
Port
,
endPoint
.
IpV6Enabled
);
Stream
clientStream
=
tcpClient
.
GetStream
();
clientStream
.
ReadTimeout
=
ConnectionTimeOutSeconds
*
1000
;
...
...
@@ -177,10 +158,10 @@ namespace Titanium.Web.Proxy
return
;
}
//Now create the request
await
HandleHttpSessionRequest
(
tcpClient
,
processId
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
httpRemoteUri
.
Scheme
==
Uri
.
UriSchemeHttps
?
httpRemoteUri
.
Host
:
null
,
connectRequestHeaders
,
null
,
null
);
await
HandleHttpSessionRequest
(
tcpClient
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
httpRemoteUri
.
Scheme
==
Uri
.
UriSchemeHttps
?
httpRemoteUri
.
Host
:
null
,
endPoint
,
connectRequestHeaders
,
null
,
null
);
}
catch
(
Exception
ex
)
catch
(
Exception
)
{
Dispose
(
clientStream
,
clientStreamReader
,
clientStreamWriter
,
null
);
}
...
...
@@ -190,7 +171,6 @@ 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
)
{
var
processId
=
GetProcessIdFromPort
(((
IPEndPoint
)
tcpClient
.
Client
.
RemoteEndPoint
).
Port
,
endPoint
.
IpV6Enabled
);
Stream
clientStream
=
tcpClient
.
GetStream
();
...
...
@@ -238,8 +218,8 @@ namespace Titanium.Web.Proxy
var
httpCmd
=
await
clientStreamReader
.
ReadLineAsync
();
//Now create the request
await
HandleHttpSessionRequest
(
tcpClient
,
processId
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
endPoint
.
EnableSsl
?
endPoint
.
GenericCertificateName
:
null
,
null
);
await
HandleHttpSessionRequest
(
tcpClient
,
httpCmd
,
clientStream
,
clientStreamReader
,
clientStreamWriter
,
endPoint
.
EnableSsl
?
endPoint
.
GenericCertificateName
:
null
,
endPoint
,
null
);
}
private
async
Task
HandleHttpSessionRequestInternal
(
TcpConnection
connection
,
SessionEventArgs
args
,
ExternalProxy
customUpStreamHttpProxy
,
ExternalProxy
customUpStreamHttpsProxy
,
bool
CloseConnection
)
...
...
@@ -371,15 +351,14 @@ namespace Titanium.Web.Proxy
/// This is the core request handler method for a particular connection from client
/// </summary>
/// <param name="client"></param>
/// <param name="processId"></param>
/// <param name="httpCmd"></param>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="httpsHostName"></param>
/// <returns></returns>
private
async
Task
HandleHttpSessionRequest
(
TcpClient
client
,
int
processId
,
string
httpCmd
,
Stream
clientStream
,
CustomBinaryReader
clientStreamReader
,
StreamWriter
clientStreamWriter
,
string
httpsHostName
,
List
<
HttpHeader
>
connectHeaders
,
ExternalProxy
customUpStreamHttpProxy
=
null
,
ExternalProxy
customUpStreamHttpsProxy
=
null
)
private
async
Task
HandleHttpSessionRequest
(
TcpClient
client
,
string
httpCmd
,
Stream
clientStream
,
CustomBinaryReader
clientStreamReader
,
StreamWriter
clientStreamWriter
,
string
httpsHostName
,
ProxyEndPoint
endPoint
,
List
<
HttpHeader
>
connectHeaders
,
ExternalProxy
customUpStreamHttpProxy
=
null
,
ExternalProxy
customUpStreamHttpsProxy
=
null
)
{
TcpConnection
connection
=
null
;
...
...
@@ -396,8 +375,21 @@ namespace Titanium.Web.Proxy
var
args
=
new
SessionEventArgs
(
BUFFER_SIZE
,
HandleHttpSessionResponse
);
args
.
ProxyClient
.
TcpClient
=
client
;
args
.
WebSession
.
ConnectHeaders
=
connectHeaders
;
args
.
WebSession
.
ProcessId
=
processId
;
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)
...
...
Titanium.Web.Proxy/ResponseHandler.cs
View file @
44d042ae
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
)
{
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
();
}
}
}
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/Titanium.Web.Proxy.csproj
View file @
44d042ae
...
...
@@ -66,6 +66,7 @@
<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"
/>
...
...
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